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  }