diff --git a/cmd/rigtest/rigtest.go b/cmd/rigtest/rigtest.go index 4c4d31b5..099172f8 100644 --- a/cmd/rigtest/rigtest.go +++ b/cmd/rigtest/rigtest.go @@ -290,10 +290,23 @@ func main() { require.True(t, bytes.Equal(readSha.Sum(nil), shasum.Sum(nil)), "sha256 mismatch after io.copy from remote to local after seek") require.NoError(t, destf.Close(), "close after seek + read") - require.NoError(t, fsys.Delete(fn), "remove file") + require.NoError(t, fsys.Remove(fn), "remove file") _, err = destf.Stat() require.ErrorIs(t, err, fs.ErrNotExist, "file still exists") + // fsys dirops + require.NoError(t, fsys.MkDirAll("tmpdir/nested", 0644), "make nested dir") + _, err = fsys.Stat("tmpdir") + require.NoError(t, err, "tmpdir was not created") + _, err = fsys.Stat("tmpdir/nested") + require.NoError(t, err, "tmpdir/nested was not created") + + require.NoError(t, fsys.RemoveAll("tmpdir"), "remove recursive") + _, err = fsys.Stat("tmpdir/nested") + require.ErrorIs(t, err, fs.ErrNotExist, "nested dir still exists") + _, err = fsys.Stat("tmpdir") + require.ErrorIs(t, err, fs.ErrNotExist, "dir still exists") + require.NoError(t, h.Configurer.MkDir(h, "tmp/testdir/subdir"), "make dir") require.NoError(t, h.Configurer.WriteFile(h, "tmp/testdir/subdir/testfile1", "test", "0644"), "write file") require.NoError(t, h.Configurer.WriteFile(h, "tmp/testdir/testfile2", "test", "0644"), "write file") diff --git a/pkg/rigfs/posixfsys.go b/pkg/rigfs/posixfsys.go index 473503ac..4d29d4ca 100644 --- a/pkg/rigfs/posixfsys.go +++ b/pkg/rigfs/posixfsys.go @@ -8,6 +8,7 @@ import ( "io" "io/fs" "math/big" + "os" "strings" "time" @@ -375,10 +376,40 @@ func (fsys *PosixFsys) ReadDir(name string) ([]fs.DirEntry, error) { return entries, nil } -// Delete removes the named file or (empty) directory. -func (fsys *PosixFsys) Delete(name string) error { +// Remove deletes the named file or (empty) directory. +func (fsys *PosixFsys) Remove(name string) error { if err := fsys.conn.Exec(fmt.Sprintf("rm -f %s", shellescape.Quote(name)), fsys.opts...); err != nil { return fmt.Errorf("%w: delete %s: %w", ErrCommandFailed, name, err) } return nil } + +// RemoveAll removes path and any children it contains. +func (fsys *PosixFsys) RemoveAll(name string) error { + if err := fsys.conn.Exec(fmt.Sprintf("rm -rf %s", shellescape.Quote(name)), fsys.opts...); err != nil { + return fmt.Errorf("%w: remove all %s: %w", ErrCommandFailed, name, err) + } + return nil +} + +// MkDirAll creates a new directory structure with the specified name and permission bits. +// If the directory already exists, MkDirAll does nothing and returns nil. +func (fsys *PosixFsys) MkDirAll(name string, perm FileMode) error { + dir := shellescape.Quote(name) + if existing, err := fsys.Stat(name); err == nil { + if existing.IsDir() { + return nil + } + return fmt.Errorf("%w: mkdir %s: %w", ErrCommandFailed, name, fs.ErrExist) + } + + if err := fsys.conn.Exec(fmt.Sprintf("mkdir -p %s", dir), fsys.opts...); err != nil { + return fmt.Errorf("%w: mkdir %s: %w", ErrCommandFailed, name, err) + } + + if err := fsys.conn.Exec(fmt.Sprintf("chmod %#o %s", os.FileMode(perm).Perm(), dir), fsys.opts...); err != nil { + return fmt.Errorf("%w: chmod (mkdir) %s: %w", ErrCommandFailed, name, err) + } + + return nil +} diff --git a/pkg/rigfs/types.go b/pkg/rigfs/types.go index 711f1950..b8b4328a 100644 --- a/pkg/rigfs/types.go +++ b/pkg/rigfs/types.go @@ -38,7 +38,9 @@ type Fsys interface { OpenFile(string, FileMode, FileMode) (File, error) Sha256(string) (string, error) Stat(string) (fs.FileInfo, error) - Delete(string) error + Remove(string) error + RemoveAll(string) error + MkDirAll(string, FileMode) error } // FileMode is used to set the type of allowed operations when opening remote files diff --git a/pkg/rigfs/winfsys.go b/pkg/rigfs/winfsys.go index a1db5b02..3dd53364 100644 --- a/pkg/rigfs/winfsys.go +++ b/pkg/rigfs/winfsys.go @@ -360,8 +360,8 @@ func (fsys *WinFsys) Open(name string) (fs.File, error) { return f, nil } -// OpenFile opens the named remote file with the specified FileMode. perm is ignored on Windows. -func (fsys *WinFsys) OpenFile(name string, mode FileMode, perm FileMode) (File, error) { +// OpenFile opens the named remote file with the specified FileMode. Permission bits are ignored on Windows. +func (fsys *WinFsys) OpenFile(name string, mode FileMode, _ FileMode) (File, error) { var modeStr string switch mode { case ModeRead: @@ -378,7 +378,7 @@ func (fsys *WinFsys) OpenFile(name string, mode FileMode, perm FileMode) (File, return nil, &fs.PathError{Op: "open", Path: name, Err: fmt.Errorf("%w: invalid mode: %d", ErrRcpCommandFailed, mode)} } - log.Debugf("opening remote file %s (mode %s)", name, modeStr, perm) + log.Debugf("opening remote file %s (mode %s)", name, modeStr) _, err := fsys.rcp.command(fmt.Sprintf("o %s %s", modeStr, filepath.FromSlash(name))) if err != nil { return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} @@ -427,10 +427,49 @@ func (fsys *WinFsys) ReadDir(name string) ([]fs.DirEntry, error) { return entries, nil } -// Delete removes the named file or (empty) directory. -func (fsys *WinFsys) Delete(name string) error { +// Remove deletes the named file or (empty) directory. +func (fsys *WinFsys) Remove(name string) error { + if existing, err := fsys.Stat(name); err == nil && existing.IsDir() { + return fsys.removeDir(name) + } + if err := fsys.conn.Exec(fmt.Sprintf("del %s", ps.DoubleQuote(filepath.FromSlash(name)))); err != nil { - return fmt.Errorf("%w: delete %s: %w", ErrCommandFailed, name, err) + return fmt.Errorf("%w: remove %s: %w", ErrCommandFailed, name, err) + } + return nil +} + +// RemoveAll deletes the named file or directory and all its child items +func (fsys *WinFsys) RemoveAll(name string) error { + if existing, err := fsys.Stat(name); err == nil && existing.IsDir() { + return fsys.removeDirAll(name) + } + + if err := fsys.conn.Exec(fmt.Sprintf("del %s", ps.DoubleQuote(filepath.FromSlash(name)))); err != nil { + return fmt.Errorf("%w: remove all %s: %w", ErrCommandFailed, name, err) + } + return nil +} + +func (fsys *WinFsys) removeDir(name string) error { + if err := fsys.conn.Exec(fmt.Sprintf("rmdir /q %s", ps.DoubleQuote(filepath.FromSlash(name)))); err != nil { + return fmt.Errorf("%w: rmdir %s: %w", ErrCommandFailed, name, err) + } + return nil +} + +func (fsys *WinFsys) removeDirAll(name string) error { + if err := fsys.conn.Exec(fmt.Sprintf("rmdir /s /q %s", ps.DoubleQuote(filepath.FromSlash(name)))); err != nil { + return fmt.Errorf("%w: rmdir %s: %w", ErrCommandFailed, name, err) } return nil } + +// MkDirAll creates a directory named path, along with any necessary parents. The permission bits perm are ignored on Windows. +func (fsys *WinFsys) MkDirAll(path string, _ FileMode) error { + if err := fsys.conn.Exec(fmt.Sprintf("mkdir -p %s", ps.DoubleQuote(filepath.FromSlash(path)))); err != nil { + return fmt.Errorf("%w: mkdir %s: %w", ErrCommandFailed, path, err) + } + + return nil +}