github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/os_unix.go (about) 1 //go:build (linux && !appengine) || darwin || freebsd || netbsd || openbsd 2 // +build linux,!appengine darwin freebsd netbsd openbsd 3 4 // Copyright (c) 2015-2021 MinIO, Inc. 5 // 6 // This file is part of MinIO Object Storage stack 7 // 8 // This program is free software: you can redistribute it and/or modify 9 // it under the terms of the GNU Affero General Public License as published by 10 // the Free Software Foundation, either version 3 of the License, or 11 // (at your option) any later version. 12 // 13 // This program is distributed in the hope that it will be useful 14 // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 // GNU Affero General Public License for more details. 17 // 18 // You should have received a copy of the GNU Affero General Public License 19 // along with this program. If not, see <http://www.gnu.org/licenses/>. 20 21 package cmd 22 23 import ( 24 "bytes" 25 "fmt" 26 "os" 27 "strings" 28 "sync" 29 "syscall" 30 "unsafe" 31 32 "golang.org/x/sys/unix" 33 ) 34 35 func access(name string) error { 36 if err := unix.Access(name, unix.F_OK); err != nil { 37 return &os.PathError{Op: "lstat", Path: name, Err: err} 38 } 39 return nil 40 } 41 42 // openFileWithFD return 'fd' based file descriptor 43 func openFileWithFD(name string, flag int, perm os.FileMode) (fd int, err error) { 44 switch flag & writeMode { 45 case writeMode: 46 defer updateOSMetrics(osMetricOpenFileWFd, name)(err) 47 default: 48 defer updateOSMetrics(osMetricOpenFileRFd, name)(err) 49 } 50 var e error 51 fd, e = syscall.Open(name, flag|syscall.O_CLOEXEC, uint32(perm)) 52 if e != nil { 53 return -1, &os.PathError{Op: "open", Path: name, Err: e} 54 } 55 return fd, nil 56 } 57 58 // Forked from Golang but chooses to avoid performing lookup 59 // 60 // osMkdirAll creates a directory named path, 61 // along with any necessary parents, and returns nil, 62 // or else returns an error. 63 // The permission bits perm (before umask) are used for all 64 // directories that MkdirAll creates. 65 // If path is already a directory, MkdirAll does nothing 66 // and returns nil. 67 func osMkdirAll(dirPath string, perm os.FileMode, baseDir string) error { 68 if baseDir != "" { 69 if strings.HasPrefix(baseDir, dirPath) { 70 return nil 71 } 72 } 73 74 // Slow path: make sure parent exists and then call Mkdir for path. 75 i := len(dirPath) 76 for i > 0 && os.IsPathSeparator(dirPath[i-1]) { // Skip trailing path separator. 77 i-- 78 } 79 80 j := i 81 for j > 0 && !os.IsPathSeparator(dirPath[j-1]) { // Scan backward over element. 82 j-- 83 } 84 85 if j > 1 { 86 // Create parent. 87 if err := osMkdirAll(dirPath[:j-1], perm, baseDir); err != nil { 88 return err 89 } 90 } 91 92 // Parent now exists; invoke Mkdir and use its result. 93 if err := Mkdir(dirPath, perm); err != nil { 94 if osIsExist(err) { 95 return nil 96 } 97 return err 98 } 99 100 return nil 101 } 102 103 // The buffer must be at least a block long. 104 // refer https://github.com/golang/go/issues/24015 105 const blockSize = 8 << 10 // 8192 106 107 // By default at least 128 entries in single getdents call (1MiB buffer) 108 var ( 109 direntPool = sync.Pool{ 110 New: func() interface{} { 111 buf := make([]byte, blockSize*128) 112 return &buf 113 }, 114 } 115 116 direntNamePool = sync.Pool{ 117 New: func() interface{} { 118 buf := make([]byte, blockSize) 119 return &buf 120 }, 121 } 122 ) 123 124 // unexpectedFileMode is a sentinel (and bogus) os.FileMode 125 // value used to represent a syscall.DT_UNKNOWN Dirent.Type. 126 const unexpectedFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice 127 128 func parseDirEnt(buf []byte) (consumed int, name []byte, typ os.FileMode, err error) { 129 // golang.org/issue/15653 130 dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0])) 131 if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v { 132 return consumed, nil, typ, fmt.Errorf("buf size of %d smaller than dirent header size %d", len(buf), v) 133 } 134 if len(buf) < int(dirent.Reclen) { 135 return consumed, nil, typ, fmt.Errorf("buf size %d < record length %d", len(buf), dirent.Reclen) 136 } 137 consumed = int(dirent.Reclen) 138 if direntInode(dirent) == 0 { // File absent in directory. 139 return 140 } 141 switch dirent.Type { 142 case syscall.DT_REG: 143 typ = 0 144 case syscall.DT_DIR: 145 typ = os.ModeDir 146 case syscall.DT_LNK: 147 typ = os.ModeSymlink 148 default: 149 // Skip all other file types. Revisit if/when this code needs 150 // to handle such files, MinIO is only interested in 151 // files and directories. 152 typ = unexpectedFileMode 153 } 154 155 nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0])) 156 nameLen, err := direntNamlen(dirent) 157 if err != nil { 158 return consumed, nil, typ, err 159 } 160 161 return consumed, nameBuf[:nameLen], typ, nil 162 } 163 164 // readDirFn applies the fn() function on each entries at dirPath, doesn't recurse into 165 // the directory itself, if the dirPath doesn't exist this function doesn't return 166 // an error. 167 func readDirFn(dirPath string, fn func(name string, typ os.FileMode) error) error { 168 fd, err := openFileWithFD(dirPath, readMode, 0o666) 169 if err != nil { 170 if osErrToFileErr(err) == errFileNotFound { 171 return nil 172 } 173 if !osIsPermission(err) { 174 return osErrToFileErr(err) 175 } 176 // There may be permission error when dirPath 177 // is at the root of the disk mount that may 178 // not have the permissions to avoid 'noatime' 179 fd, err = openFileWithFD(dirPath, os.O_RDONLY, 0o666) 180 if err != nil { 181 if osErrToFileErr(err) == errFileNotFound { 182 return nil 183 } 184 return osErrToFileErr(err) 185 } 186 187 } 188 defer syscall.Close(fd) 189 190 bufp := direntPool.Get().(*[]byte) 191 defer direntPool.Put(bufp) 192 buf := *bufp 193 194 boff := 0 // starting read position in buf 195 nbuf := 0 // end valid data in buf 196 197 for { 198 if boff >= nbuf { 199 boff = 0 200 stop := globalOSMetrics.time(osMetricReadDirent) 201 nbuf, err = syscall.ReadDirent(fd, buf) 202 stop() 203 if err != nil { 204 if isSysErrNotDir(err) { 205 return nil 206 } 207 err = osErrToFileErr(err) 208 if err == errFileNotFound { 209 return nil 210 } 211 return err 212 } 213 if nbuf <= 0 { 214 break // EOF 215 } 216 } 217 consumed, name, typ, err := parseDirEnt(buf[boff:nbuf]) 218 if err != nil { 219 return err 220 } 221 boff += consumed 222 if len(name) == 0 || bytes.Equal(name, []byte{'.'}) || bytes.Equal(name, []byte{'.', '.'}) { 223 continue 224 } 225 226 // Fallback for filesystems (like old XFS) that don't 227 // support Dirent.Type and have DT_UNKNOWN (0) there 228 // instead. 229 if typ == unexpectedFileMode || typ&os.ModeSymlink == os.ModeSymlink { 230 fi, err := Stat(pathJoin(dirPath, string(name))) 231 if err != nil { 232 // It got deleted in the meantime, not found 233 // or returns too many symlinks ignore this 234 // file/directory. 235 if osIsNotExist(err) || isSysErrPathNotFound(err) || 236 isSysErrTooManySymlinks(err) { 237 continue 238 } 239 return err 240 } 241 242 // Ignore symlinked directories. 243 if typ&os.ModeSymlink == os.ModeSymlink && fi.IsDir() { 244 continue 245 } 246 247 typ = fi.Mode() & os.ModeType 248 } 249 if err = fn(string(name), typ); err == errDoneForNow { 250 // fn() requested to return by caller. 251 return nil 252 } 253 } 254 255 return err 256 } 257 258 // Return count entries at the directory dirPath and all entries 259 // if count is set to -1 260 func readDirWithOpts(dirPath string, opts readDirOpts) (entries []string, err error) { 261 fd, err := openFileWithFD(dirPath, readMode, 0o666) 262 if err != nil { 263 if !osIsPermission(err) { 264 return nil, osErrToFileErr(err) 265 } 266 // There may be permission error when dirPath 267 // is at the root of the disk mount that may 268 // not have the permissions to avoid 'noatime' 269 fd, err = openFileWithFD(dirPath, os.O_RDONLY, 0o666) 270 if err != nil { 271 return nil, osErrToFileErr(err) 272 } 273 } 274 defer syscall.Close(fd) 275 276 bufp := direntPool.Get().(*[]byte) 277 defer direntPool.Put(bufp) 278 buf := *bufp 279 280 nameTmp := direntNamePool.Get().(*[]byte) 281 defer direntNamePool.Put(nameTmp) 282 tmp := *nameTmp 283 284 boff := 0 // starting read position in buf 285 nbuf := 0 // end valid data in buf 286 287 count := opts.count 288 289 for count != 0 { 290 if boff >= nbuf { 291 boff = 0 292 stop := globalOSMetrics.time(osMetricReadDirent) 293 nbuf, err = syscall.ReadDirent(fd, buf) 294 stop() 295 if err != nil { 296 if isSysErrNotDir(err) { 297 return nil, errFileNotFound 298 } 299 return nil, osErrToFileErr(err) 300 } 301 if nbuf <= 0 { 302 break 303 } 304 } 305 consumed, name, typ, err := parseDirEnt(buf[boff:nbuf]) 306 if err != nil { 307 return nil, err 308 } 309 boff += consumed 310 if len(name) == 0 || bytes.Equal(name, []byte{'.'}) || bytes.Equal(name, []byte{'.', '.'}) { 311 continue 312 } 313 314 // Fallback for filesystems (like old XFS) that don't 315 // support Dirent.Type and have DT_UNKNOWN (0) there 316 // instead. 317 if typ == unexpectedFileMode || typ&os.ModeSymlink == os.ModeSymlink { 318 fi, err := Stat(pathJoin(dirPath, string(name))) 319 if err != nil { 320 // It got deleted in the meantime, not found 321 // or returns too many symlinks ignore this 322 // file/directory. 323 if osIsNotExist(err) || isSysErrPathNotFound(err) || 324 isSysErrTooManySymlinks(err) { 325 continue 326 } 327 return nil, err 328 } 329 330 // Ignore symlinked directories. 331 if !opts.followDirSymlink && typ&os.ModeSymlink == os.ModeSymlink && fi.IsDir() { 332 continue 333 } 334 335 typ = fi.Mode() & os.ModeType 336 } 337 338 var nameStr string 339 if typ.IsRegular() { 340 nameStr = string(name) 341 } else if typ.IsDir() { 342 // Use temp buffer to append a slash to avoid string concat. 343 tmp = tmp[:len(name)+1] 344 copy(tmp, name) 345 tmp[len(tmp)-1] = '/' // SlashSeparator 346 nameStr = string(tmp) 347 } 348 349 count-- 350 entries = append(entries, nameStr) 351 } 352 353 return 354 } 355 356 func globalSync() { 357 defer globalOSMetrics.time(osMetricSync)() 358 syscall.Sync() 359 }