go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/system/filesystem/filepath.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package filesystem 16 17 import ( 18 "context" 19 "fmt" 20 "os" 21 "path/filepath" 22 "runtime" 23 "strings" 24 "syscall" 25 26 "go.chromium.org/luci/common/errors" 27 "go.chromium.org/luci/common/logging" 28 ) 29 30 func setReadOnly(path string, fi os.FileInfo, readOnly bool) error { 31 mode := fi.Mode() 32 if mode&os.ModeSymlink != 0 { 33 // Skip symlink. 34 return nil 35 } 36 37 if readOnly { 38 mode &= syscall.S_IRUSR | syscall.S_IXUSR 39 } else { 40 mode |= syscall.S_IRUSR | syscall.S_IWUSR 41 if runtime.GOOS != "windows" && mode.IsDir() { 42 mode |= syscall.S_IXUSR 43 } 44 } 45 return os.Chmod(path, mode) 46 } 47 48 // SetReadOnly sets or resets the write bit on a file or directory. 49 // Zaps out access to 'group' and 'others'. 50 func SetReadOnly(path string, readOnly bool) error { 51 fi, err := os.Lstat(path) 52 if err != nil { 53 return fmt.Errorf("failed to get lstat for %s: %v", path, err) 54 } 55 return setReadOnly(path, fi, readOnly) 56 } 57 58 // MakeTreeReadOnly makes all the files in the directories read only. 59 // Also makes the directories read only, only if it makes sense on the platform. 60 // This means no file can be created or deleted. 61 func MakeTreeReadOnly(ctx context.Context, root string) error { 62 logging.Debugf(ctx, "MakeTreeReadOnly(%s)", root) 63 return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 64 if err != nil { 65 return err 66 } 67 68 if runtime.GOOS != "windows" || !info.IsDir() { 69 return setReadOnly(path, info, true) 70 } 71 return nil 72 }) 73 } 74 75 // MakeTreeFilesReadOnly makes all the files in the directories read only but 76 // not the directories themselves. 77 // This means files can be created or deleted. 78 func MakeTreeFilesReadOnly(ctx context.Context, root string) error { 79 logging.Debugf(ctx, "MakeTreeFilesReadOnly(%s)", root) 80 return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 81 if err != nil { 82 return err 83 } 84 85 if !info.IsDir() { 86 return setReadOnly(path, info, true) 87 } else if runtime.GOOS != "windows" { 88 return setReadOnly(path, info, false) 89 } 90 return nil 91 }) 92 } 93 94 // MakeTreeWritable makes all the files in the directories writeable. 95 // Also makes the directories writeable, only if it makes sense on the platform. 96 func MakeTreeWritable(ctx context.Context, root string) error { 97 logging.Debugf(ctx, "MakeTreeReadOnly(%s)", root) 98 return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 99 if err != nil { 100 return err 101 } 102 103 if runtime.GOOS != "windows" || !info.IsDir() { 104 return setReadOnly(path, info, false) 105 } 106 return nil 107 }) 108 } 109 110 // ResolveSymlink recursively resolves simlink and returns absolute path that is 111 // not symlink with stat. 112 func ResolveSymlink(path string) (string, os.FileInfo, error) { 113 var stat os.FileInfo 114 for { 115 var err error 116 stat, err = os.Lstat(path) 117 if err != nil { 118 return "", stat, errors.Annotate(err, "failed to call Lstat(%s)", path).Err() 119 } 120 if (stat.Mode() & os.ModeSymlink) == 0 { 121 break 122 } 123 124 link, err := os.Readlink(path) 125 if err != nil { 126 return "", stat, errors.Annotate(err, "failed to call Readlink(%s)", path).Err() 127 } 128 129 if filepath.IsAbs(link) { 130 path = link 131 } else { 132 path = filepath.Join(filepath.Dir(path), link) 133 } 134 } 135 136 return path, stat, nil 137 } 138 139 // GetFilenameNoExt returns the base file name without the extension. 140 func GetFilenameNoExt(path string) string { 141 name := filepath.Base(path) 142 if i := strings.LastIndexByte(name, '.'); i != -1 { 143 name = name[:i] 144 } 145 return name 146 }