Skip to content

Commit

Permalink
fix(snapshots): Partly fix #544 by supporting setuid/setgid/sticky bi…
Browse files Browse the repository at this point in the history
…ts when snapshotting, restoring and fuse-mounting. (#2597)

Also modified an end-to-end test to also check that these extra mode flags work when snapshotting+restoring.
Manually tested fuse-mount.

Co-authored-by: Luca Citi <lciti@ieee.org>
  • Loading branch information
lciti and Luca Citi committed Dec 7, 2022
1 parent 0e09e1a commit 37d64af
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 12 deletions.
3 changes: 3 additions & 0 deletions fs/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import (
"github.com/pkg/errors"
)

// ModBits is a bitmask representing the mode flags supported.
const ModBits = os.ModePerm | os.ModeSetgid | os.ModeSetuid | os.ModeSticky

// ErrUnknown is returned by ErrorEntry.ErrorInfo() to indicate that type of an entry is unknown.
var ErrUnknown = errors.Errorf("unknown or unsupported entry type")

Expand Down
20 changes: 19 additions & 1 deletion internal/fusemount/fusefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,26 @@ type fuseNode struct {
entry fs.Entry
}

func goModeToUnixMode(mode os.FileMode) uint32 {
unixmode := uint32(mode.Perm())

if mode&os.ModeSetuid != 0 {
unixmode |= 0o4000
}

if mode&os.ModeSetgid != 0 {
unixmode |= 0o2000
}

if mode&os.ModeSticky != 0 {
unixmode |= 0o1000
}

return unixmode
}

func populateAttributes(a *fuse.Attr, e fs.Entry) {
a.Mode = uint32(e.Mode()) & uint32(os.ModePerm)
a.Mode = goModeToUnixMode(e.Mode())
a.Size = uint64(e.Size())
a.Mtime = uint64(e.ModTime().Unix())
a.Ctime = a.Mtime
Expand Down
5 changes: 2 additions & 3 deletions snapshot/restore/local_fs_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
)

const (
modBits = os.ModePerm | os.ModeSetgid | os.ModeSetuid | os.ModeSticky
outputDirMode = 0o700 // default mode to create directories in before setting their ACLs
maxTimeDeltaToConsiderFileTheSame = 2 * time.Second
)
Expand Down Expand Up @@ -273,7 +272,7 @@ func (o *FilesystemOutput) setAttributes(targetPath string, e fs.Entry, modclear

// Set file permissions from e
if o.shouldUpdatePermissions(le, e, modclear) {
if err = o.maybeIgnorePermissionError(osChmod(targetPath, (e.Mode()&modBits)&^modclear)); err != nil {
if err = o.maybeIgnorePermissionError(osChmod(targetPath, (e.Mode()&fs.ModBits)&^modclear)); err != nil {
return errors.Wrap(err, "could not change permissions on "+targetPath)
}
}
Expand Down Expand Up @@ -317,7 +316,7 @@ func (o *FilesystemOutput) shouldUpdatePermissions(local, remote fs.Entry, modcl
return false
}

return ((local.Mode() & modBits) &^ modclear) != (remote.Mode() & modBits)
return ((local.Mode() & fs.ModBits) &^ modclear) != (remote.Mode() & fs.ModBits)
}

func (o *FilesystemOutput) shouldUpdateTimes(local, remote fs.Entry) bool {
Expand Down
2 changes: 1 addition & 1 deletion snapshot/snapshotfs/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ func newDirEntry(md fs.Entry, fname string, oid object.ID) (*snapshot.DirEntry,
return &snapshot.DirEntry{
Name: fname,
Type: entryType,
Permissions: snapshot.Permissions(md.Mode() & os.ModePerm),
Permissions: snapshot.Permissions(md.Mode() & fs.ModBits),
FileSize: md.Size(),
ModTime: fs.UTCTimestampFromTime(md.ModTime()),
UserID: md.Owner().UserID,
Expand Down
15 changes: 8 additions & 7 deletions tests/end_to_end_test/restore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ func TestSnapshotRestore(t *testing.T) {

// restoring single file
e.RunAndExpectSuccess(t, "snapshot", "restore", rootID+"/single-file", filepath.Join(restoreByOIDFile, "output-file"))
verifyFileMode(t, filepath.Join(restoreByOIDFile, "output-file"), os.FileMode(0o651))
verifyFileMode(t, filepath.Join(restoreByOIDFile, "output-file"), overriddenFilePermissions)

// Restore last snapshot using the snapshot ID
e.RunAndExpectSuccess(t, "snapshot", "restore", snapID, restoreDir)
Expand Down Expand Up @@ -461,21 +461,22 @@ func TestRestoreSnapshotOfSingleFile(t *testing.T) {
verifyFileMode(t, filepath.Join(restoreDir, "restored-2"), os.FileMode(0o653))

if runtime.GOOS != windowsOSName {
overriddenFilePermissions := 0o654 | os.ModeSetuid

// change source file permissions and create one more snapshot
// at this poing we will have multiple snapshot manifests with one root but different attributes.
os.Chmod(sourceFile, 0o654)
os.Chmod(sourceFile, overriddenFilePermissions)
e.RunAndExpectSuccess(t, "snapshot", "create", sourceFile)

// when restoring by root Kopia needs to pick which manifest to apply since they are conflicting
// We're passing --consistent-attributes which causes it to fail, since otherwise we'd restore arbitrary
// top-level object permissions.
e.RunAndExpectFailure(t, "snapshot", "restore", rootID, "--consistent-attributes", filepath.Join(restoreDir, "restored-3"))
}

// Without the flag kopia picks the attributes from the latest snapshot.
e.RunAndExpectSuccess(t, "snapshot", "restore", rootID, filepath.Join(restoreDir, "restored-3"))

verifyFileMode(t, filepath.Join(restoreDir, "restored-3"), os.FileMode(0o654))
// Without the flag kopia picks the attributes from the latest snapshot.
e.RunAndExpectSuccess(t, "snapshot", "restore", rootID, filepath.Join(restoreDir, "restored-3"))
verifyFileMode(t, filepath.Join(restoreDir, "restored-3"), overriddenFilePermissions)
}

// restoring using snapshot ID is unambiguous and always produces file with 0o653
e.RunAndExpectSuccess(t, "snapshot", "restore", snapID, filepath.Join(restoreDir, "restored-4"))
Expand Down

0 comments on commit 37d64af

Please sign in to comment.