github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/fileutil/fs_case.go (about) 1 package fileutil 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 10 nanoid "github.com/matoous/go-nanoid/v2" 11 ) 12 13 // caseSensitiveNamePrefix is a prefix used for testing case sensitivity. 14 // It contains both lowercase and uppercase characters. 15 const caseSensitiveNamePrefix = ".cAsE." 16 17 // IsCaseInsensitiveLocation returns true if dirPath is a directory on a 18 // case-insensitive filesystem. dirPath must be writable. If it fails to 19 // delete the file, it uses warnFunc to emit a warning message. 20 func IsCaseInsensitiveLocation(fs FS, dirPath string, warnFunc func(string)) (bool, error) { 21 // Random element in the filename to ensure uniqueness: the filename 22 // will not be in use from anything else. 23 id, err := nanoid.New() 24 if err != nil { 25 return false, fmt.Errorf("generate random name: %w", err) 26 } 27 path := filepath.Join(dirPath, caseSensitiveNamePrefix+id) 28 err = fs.Touch(path) 29 if err != nil { 30 return false, fmt.Errorf("touch %s: %w", path, err) 31 } 32 defer func() { 33 err := fs.Remove(path) 34 if err != nil { 35 warnFunc(fmt.Sprintf("Garbage file %s remains: %s; make sure to delete this file", path, err)) 36 } 37 }() 38 39 lowercasePrefix := strings.ToLower(caseSensitiveNamePrefix) 40 lowercasePath := filepath.Join(dirPath, lowercasePrefix+id) 41 // If lowercasePath exists, fs is case-insensitive. Random id 42 // ensures that it can be no other file! 43 return fs.Exists(lowercasePath) 44 } 45 46 // FS is a tiny filesystem abstraction. The standard io/fs does not support 47 // any write operations, see https://github.com/golang/go/issues/45757. 48 type FS interface { 49 // Touch creates a file at path. 50 Touch(path string) error 51 // Exists returns true if there is a file at path. It follows 52 // symbolic links. 53 Exists(path string) (bool, error) 54 // Remove deletes the file at path. 55 Remove(path string) error 56 } 57 58 // OSFS is the filesystem of the OS. 59 type OSFS struct{} 60 61 func NewOSFS() FS { 62 return OSFS{} 63 } 64 65 func (OSFS) Touch(path string) error { 66 file, err := os.Create(path) 67 if err != nil { 68 return err 69 } 70 return file.Close() 71 } 72 73 func (OSFS) Exists(path string) (bool, error) { 74 _, err := os.Stat(path) 75 if errors.Is(err, os.ErrNotExist) { 76 return false, nil 77 } 78 if err != nil { 79 return false, err 80 } 81 return true, nil 82 } 83 84 func (OSFS) Remove(path string) error { 85 return os.Remove(path) 86 }