github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/dos/filesystem.go (about) 1 package dos 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/fs" 8 "os" 9 "path/filepath" 10 "strconv" 11 "syscall" //nolint:depguard // "unix" don't work on windows 12 ) 13 14 // File represents a file in the filesystem. The os.File struct implements this interface. 15 type File interface { 16 io.Closer 17 io.Reader 18 io.ReaderAt 19 io.Seeker 20 io.Writer 21 io.WriterAt 22 23 Name() string 24 Readdir(count int) ([]fs.FileInfo, error) 25 Readdirnames(n int) ([]string, error) 26 Stat() (fs.FileInfo, error) 27 Sync() error 28 Truncate(size int64) error 29 WriteString(s string) (ret int, err error) 30 ReadDir(count int) ([]fs.DirEntry, error) 31 } 32 33 type OwnedFile interface { 34 Chown(uid, gid int) error 35 } 36 37 // FileSystem is an interface that implements functions in the os package. 38 type FileSystem interface { 39 Abs(name string) (string, error) 40 Chdir(name string) error 41 Create(name string) (File, error) 42 Getwd() (string, error) 43 Mkdir(name string, perm fs.FileMode) error 44 MkdirAll(name string, perm fs.FileMode) error 45 Open(name string) (File, error) 46 OpenFile(name string, flag int, perm fs.FileMode) (File, error) 47 ReadDir(name string) ([]fs.DirEntry, error) 48 ReadFile(name string) ([]byte, error) 49 RealPath(name string) (string, error) 50 Remove(name string) error 51 RemoveAll(name string) error 52 Rename(oldName, newName string) error 53 Stat(name string) (fs.FileInfo, error) 54 Symlink(oldName, newName string) error 55 WriteFile(name string, data []byte, perm fs.FileMode) error 56 } 57 58 type osFs struct { 59 tpUID int 60 tpGID int 61 } 62 63 func (*osFs) Abs(name string) (string, error) { 64 return filepath.Abs(name) 65 } 66 67 func (*osFs) Chdir(name string) error { 68 return os.Chdir(name) 69 } 70 71 func (fs *osFs) Create(name string) (File, error) { 72 f, err := os.Create(name) 73 if err != nil { 74 // It's important to return a File nil here, not a File that represents an *os.File nil. 75 return nil, err 76 } 77 return fs.chownFile(f) 78 } 79 80 func (*osFs) Getwd() (string, error) { 81 return os.Getwd() 82 } 83 84 func (fs *osFs) Mkdir(name string, perm fs.FileMode) error { 85 return fs.chown(os.Mkdir(name, perm), name) 86 } 87 88 // MkdirAll is a slightly modified version the same function in of Go 1.19.3's os/path.go. 89 func (fs *osFs) MkdirAll(path string, perm fs.FileMode) error { 90 // Fast path: if we can tell whether path is a directory or file, stop with success or error. 91 dir, err := os.Stat(path) 92 if err == nil { 93 if dir.IsDir() { 94 return nil 95 } 96 return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} //nolint:forbidigo // we want the same error 97 } 98 99 // Slow path: make sure parent exists and then call Mkdir for path. 100 i := len(path) 101 for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator. 102 i-- 103 } 104 105 j := i 106 for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element. 107 j-- 108 } 109 110 if j > 1 { 111 // Create parent. 112 if err = fs.MkdirAll(path[:j-1], perm); err != nil { 113 return err 114 } 115 } 116 117 // Parent now exists; invoke Mkdir and use its result. 118 err = fs.Mkdir(path, perm) 119 if err != nil { 120 // Handle arguments like "foo/." by 121 // double-checking that directory doesn't exist. 122 dir, err1 := os.Lstat(path) 123 if err1 == nil && dir.IsDir() { 124 return nil 125 } 126 return err 127 } 128 return nil 129 } 130 131 func (*osFs) Open(name string) (File, error) { 132 f, err := os.Open(name) 133 if err != nil { 134 // It's important to return a File nil here, not a File that represents an *os.File nil. 135 return nil, err 136 } 137 return f, nil 138 } 139 140 func (fs *osFs) OpenFile(name string, flag int, perm fs.FileMode) (File, error) { 141 if fs.mustChown() { 142 if (flag & os.O_CREATE) == os.O_CREATE { 143 if _, err := os.Stat(name); os.IsNotExist(err) { 144 f, err := os.OpenFile(name, flag, perm) 145 if err != nil { 146 return nil, err 147 } 148 return fs.chownFile(f) 149 } 150 } 151 } 152 f, err := os.OpenFile(name, flag, perm) 153 if err != nil { 154 // It's important to return a File nil here, not a File that represents an *os.File nil. 155 return nil, err 156 } 157 return f, nil 158 } 159 160 func (*osFs) ReadDir(name string) ([]fs.DirEntry, error) { 161 return os.ReadDir(name) 162 } 163 164 func (*osFs) ReadFile(name string) ([]byte, error) { 165 return os.ReadFile(name) 166 } 167 168 func (*osFs) RealPath(name string) (string, error) { 169 return filepath.Abs(name) 170 } 171 172 func (*osFs) Remove(name string) error { 173 return os.Remove(name) 174 } 175 176 func (*osFs) RemoveAll(name string) error { 177 return os.RemoveAll(name) 178 } 179 180 func (*osFs) Rename(oldName, newName string) error { 181 return os.Rename(oldName, newName) 182 } 183 184 func (*osFs) Stat(name string) (fs.FileInfo, error) { 185 return os.Stat(name) 186 } 187 188 func (*osFs) Symlink(oldName, newName string) error { 189 return os.Symlink(oldName, newName) 190 } 191 192 func (fs *osFs) WriteFile(name string, data []byte, perm fs.FileMode) error { 193 if fs.mustChown() { 194 if _, err := os.Stat(name); os.IsNotExist(err) { 195 return fs.chown(os.WriteFile(name, data, perm), name) 196 } 197 } 198 return os.WriteFile(name, data, perm) 199 } 200 201 func (fs *osFs) mustChown() bool { 202 return fs.tpUID > 0 || fs.tpGID > 0 203 } 204 205 func (fs *osFs) chown(err error, name string) error { 206 if err == nil && fs.mustChown() { 207 err = os.Chown(name, fs.tpUID, fs.tpGID) 208 } 209 return err 210 } 211 212 func (fs *osFs) chownFile(f File) (File, error) { 213 if fs.mustChown() { 214 var err error 215 if of, ok := f.(OwnedFile); ok { 216 err = of.Chown(fs.tpUID, fs.tpGID) 217 } else { 218 err = fmt.Errorf("chown is not supported by %T", f) 219 } 220 if err != nil { 221 _ = f.Close() 222 _ = fs.Remove(f.Name()) 223 return nil, err 224 } 225 } 226 return f, nil 227 } 228 229 type fsKey struct{} 230 231 // WithFS assigns the FileSystem to be used by subsequent file system related dos functions. 232 func WithFS(ctx context.Context, fs FileSystem) context.Context { 233 return context.WithValue(ctx, fsKey{}, fs) 234 } 235 236 func getFS(ctx context.Context) FileSystem { 237 if f, ok := ctx.Value(fsKey{}).(FileSystem); ok { 238 return f 239 } 240 of := newOS(ctx) 241 return &of 242 } 243 244 func newOS(ctx context.Context) osFs { 245 of := osFs{} 246 if env, ok := LookupEnv(ctx, "TELEPRESENCE_UID"); ok { 247 of.tpUID, _ = strconv.Atoi(env) 248 } 249 if env, ok := LookupEnv(ctx, "TELEPRESENCE_GID"); ok { 250 of.tpGID, _ = strconv.Atoi(env) 251 } 252 return of 253 } 254 255 // Abs is like filepath.Abs but delegates to the context's FS. 256 func Abs(ctx context.Context, name string) (string, error) { 257 return getFS(ctx).Abs(name) 258 } 259 260 // Chdir is like os.Chdir but delegates to the context's FS. 261 func Chdir(ctx context.Context, path string) error { 262 return getFS(ctx).Chdir(path) 263 } 264 265 // Create is like os.Create but delegates to the context's FS. 266 func Create(ctx context.Context, name string) (File, error) { 267 return getFS(ctx).Create(name) 268 } 269 270 // Getwd is like os.Getwd but delegates to the context's FS. 271 func Getwd(ctx context.Context) (string, error) { 272 return getFS(ctx).Getwd() 273 } 274 275 // Mkdir is like os.Mkdir but delegates to the context's FS. 276 func Mkdir(ctx context.Context, name string, perm fs.FileMode) error { 277 return getFS(ctx).Mkdir(name, perm) 278 } 279 280 // MkdirAll is like os.MkdirAll but delegates to the context's FS. 281 func MkdirAll(ctx context.Context, name string, perm fs.FileMode) error { 282 return getFS(ctx).MkdirAll(name, perm) 283 } 284 285 // Open is like os.Open but delegates to the context's FS. 286 func Open(ctx context.Context, name string) (File, error) { 287 return getFS(ctx).Open(name) 288 } 289 290 // OpenFile is like os.OpenFile but delegates to the context's FS. 291 func OpenFile(ctx context.Context, name string, flag int, perm fs.FileMode) (File, error) { 292 return getFS(ctx).OpenFile(name, flag, perm) 293 } 294 295 // ReadDir is like os.ReadDir but delegates to the context's FS. 296 func ReadDir(ctx context.Context, name string) ([]fs.DirEntry, error) { 297 return getFS(ctx).ReadDir(name) 298 } 299 300 // ReadFile is like os.ReadFile but delegates to the context's FS. 301 func ReadFile(ctx context.Context, name string) ([]byte, error) { // MODIFIED 302 return getFS(ctx).ReadFile(name) 303 } 304 305 // RealPath returns the real path in the underlying os filesystem or 306 // an error if there's no os filesystem. 307 func RealPath(ctx context.Context, name string) (string, error) { 308 return getFS(ctx).RealPath(name) 309 } 310 311 // Remove is like os.Remove but delegates to the context's FS. 312 func Remove(ctx context.Context, name string) error { 313 return getFS(ctx).Remove(name) 314 } 315 316 // RemoveAll is like os.RemoveAll but delegates to the context's FS. 317 func RemoveAll(ctx context.Context, name string) error { 318 return getFS(ctx).RemoveAll(name) 319 } 320 321 // Rename is like os.Rename but delegates to the context's FS. 322 func Rename(ctx context.Context, oldName, newName string) error { 323 return getFS(ctx).Rename(oldName, newName) 324 } 325 326 func WriteFile(ctx context.Context, name string, data []byte, perm fs.FileMode) error { 327 return getFS(ctx).WriteFile(name, data, perm) 328 } 329 330 // Stat is like os.Stat but delegates to the context's FS. 331 func Stat(ctx context.Context, name string) (fs.FileInfo, error) { 332 return getFS(ctx).Stat(name) 333 } 334 335 // Symlink is like os.Symlink but delegates to the context's FS. 336 func Symlink(ctx context.Context, oldName, newName string) error { 337 return getFS(ctx).Symlink(oldName, newName) 338 }