github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/internal/fastwalk/fastwalk_darwin.go (about)

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build darwin && cgo
     6  // +build darwin,cgo
     7  
     8  package fastwalk
     9  
    10  /*
    11  #include <dirent.h>
    12  
    13  // fastwalk_readdir_r wraps readdir_r so that we don't have to pass a dirent**
    14  // result pointer which triggers CGO's "Go pointer to Go pointer" check unless
    15  // we allocat the result dirent* with malloc.
    16  //
    17  // fastwalk_readdir_r returns 0 on success, -1 upon reaching the end of the
    18  // directory, or a positive error number to indicate failure.
    19  static int fastwalk_readdir_r(DIR *fd, struct dirent *entry) {
    20  	struct dirent *result;
    21  	int ret = readdir_r(fd, entry, &result);
    22  	if (ret == 0 && result == NULL) {
    23  		ret = -1; // EOF
    24  	}
    25  	return ret;
    26  }
    27  */
    28  import "C"
    29  
    30  import (
    31  	"os"
    32  	"syscall"
    33  	"unsafe"
    34  )
    35  
    36  func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error {
    37  	fd, err := openDir(dirName)
    38  	if err != nil {
    39  		return &os.PathError{Op: "opendir", Path: dirName, Err: err}
    40  	}
    41  	defer C.closedir(fd)
    42  
    43  	skipFiles := false
    44  	var dirent syscall.Dirent
    45  	for {
    46  		ret := int(C.fastwalk_readdir_r(fd, (*C.struct_dirent)(unsafe.Pointer(&dirent))))
    47  		if ret != 0 {
    48  			if ret == -1 {
    49  				break // EOF
    50  			}
    51  			if ret == int(syscall.EINTR) {
    52  				continue
    53  			}
    54  			return &os.PathError{Op: "readdir", Path: dirName, Err: syscall.Errno(ret)}
    55  		}
    56  		if dirent.Ino == 0 {
    57  			continue
    58  		}
    59  		typ := dtToType(dirent.Type)
    60  		if skipFiles && typ.IsRegular() {
    61  			continue
    62  		}
    63  		name := (*[len(syscall.Dirent{}.Name)]byte)(unsafe.Pointer(&dirent.Name))[:]
    64  		name = name[:dirent.Namlen]
    65  		for i, c := range name {
    66  			if c == 0 {
    67  				name = name[:i]
    68  				break
    69  			}
    70  		}
    71  		// Check for useless names before allocating a string.
    72  		if string(name) == "." || string(name) == ".." {
    73  			continue
    74  		}
    75  		if err := fn(dirName, string(name), typ); err != nil {
    76  			if err != ErrSkipFiles {
    77  				return err
    78  			}
    79  			skipFiles = true
    80  		}
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  func dtToType(typ uint8) os.FileMode {
    87  	switch typ {
    88  	case syscall.DT_BLK:
    89  		return os.ModeDevice
    90  	case syscall.DT_CHR:
    91  		return os.ModeDevice | os.ModeCharDevice
    92  	case syscall.DT_DIR:
    93  		return os.ModeDir
    94  	case syscall.DT_FIFO:
    95  		return os.ModeNamedPipe
    96  	case syscall.DT_LNK:
    97  		return os.ModeSymlink
    98  	case syscall.DT_REG:
    99  		return 0
   100  	case syscall.DT_SOCK:
   101  		return os.ModeSocket
   102  	}
   103  	return ^os.FileMode(0)
   104  }
   105  
   106  // openDir wraps opendir(3) and handles any EINTR errors. The returned *DIR
   107  // needs to be closed with closedir(3).
   108  func openDir(path string) (*C.DIR, error) {
   109  	name, err := syscall.BytePtrFromString(path)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	for {
   114  		fd, err := C.opendir((*C.char)(unsafe.Pointer(name)))
   115  		if err != syscall.EINTR {
   116  			return fd, err
   117  		}
   118  	}
   119  }