Skip chown when restore runs as non-root; warn at end

chown(2) requires root on every Unix-ish kernel. Restoring 39k files
as a non-root user produces 39k EPERM syscalls plus 39k matching debug
log lines, all for an operation that can't possibly succeed. Skip the
syscall entirely when euid != 0, and emit one warning at the end of the
restore so the user knows the on-disk UID/GID will reflect the running
user rather than the original owner.
This commit is contained in:
2026-06-28 07:48:28 +02:00
parent d492f34e8d
commit 1a97a80a81

View File

@@ -126,6 +126,10 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error {
v.UI.Duration(result.Duration), v.UI.Duration(result.Duration),
) )
if os.Geteuid() != 0 {
v.UI.Warning("Restore did not preserve file ownership: chown(2) requires root. Re-run as root (e.g. with sudo) if you need original UID/GID preserved.")
}
if result.FilesFailed > 0 { if result.FilesFailed > 0 {
v.UI.Warning("%d file(s) failed to restore:", result.FilesFailed) v.UI.Warning("%d file(s) failed to restore:", result.FilesFailed)
for _, path := range result.FailedFiles { for _, path := range result.FailedFiles {
@@ -247,6 +251,7 @@ func (v *Vaultik) restoreAllFiles(
blobCache: blobCache, blobCache: blobCache,
sweeper: sweeper, sweeper: sweeper,
result: result, result: result,
runningAsRoot: os.Geteuid() == 0,
} }
// Periodic progress output, matching the snapshot create cadence. // Periodic progress output, matching the snapshot create cadence.
@@ -536,6 +541,13 @@ type restoreSession struct {
blobCache *blobDiskCache blobCache *blobDiskCache
sweeper *restoreSweeper sweeper *restoreSweeper
result *RestoreResult result *RestoreResult
// runningAsRoot gates chown(2). On every Unix-ish kernel, only
// root can chown a file to an arbitrary UID/GID — non-root chown
// always fails with EPERM. Attempting it anyway produces N
// guaranteed-failed syscalls + N noisy debug lines, so we skip
// the call entirely as non-root and emit one warning at the end
// of the restore explaining that ownership was not preserved.
runningAsRoot bool
} }
// restoreFile dispatches to the right per-kind restorer. // restoreFile dispatches to the right per-kind restorer.
@@ -580,11 +592,13 @@ func (s *restoreSession) restoreDirectory(file *database.File, targetPath string
if err := s.v.Fs.Chmod(targetPath, os.FileMode(file.Mode)); err != nil { if err := s.v.Fs.Chmod(targetPath, os.FileMode(file.Mode)); err != nil {
log.Debug("Failed to set directory permissions", "path", targetPath, "error", err) log.Debug("Failed to set directory permissions", "path", targetPath, "error", err)
} }
if s.runningAsRoot {
if _, ok := s.v.Fs.(*afero.OsFs); ok { if _, ok := s.v.Fs.(*afero.OsFs); ok {
if err := os.Chown(targetPath, int(file.UID), int(file.GID)); err != nil { if err := os.Chown(targetPath, int(file.UID), int(file.GID)); err != nil {
log.Debug("Failed to set directory ownership", "path", targetPath, "error", err) log.Debug("Failed to set directory ownership", "path", targetPath, "error", err)
} }
} }
}
if err := s.v.Fs.Chtimes(targetPath, file.MTime, file.MTime); err != nil { if err := s.v.Fs.Chtimes(targetPath, file.MTime, file.MTime); err != nil {
log.Debug("Failed to set directory mtime", "path", targetPath, "error", err) log.Debug("Failed to set directory mtime", "path", targetPath, "error", err)
} }
@@ -671,11 +685,13 @@ func (s *restoreSession) restoreRegularFile(file *database.File, targetPath stri
if err := s.v.Fs.Chmod(targetPath, os.FileMode(file.Mode)); err != nil { if err := s.v.Fs.Chmod(targetPath, os.FileMode(file.Mode)); err != nil {
log.Debug("Failed to set file permissions", "path", targetPath, "error", err) log.Debug("Failed to set file permissions", "path", targetPath, "error", err)
} }
if s.runningAsRoot {
if _, ok := s.v.Fs.(*afero.OsFs); ok { if _, ok := s.v.Fs.(*afero.OsFs); ok {
if err := os.Chown(targetPath, int(file.UID), int(file.GID)); err != nil { if err := os.Chown(targetPath, int(file.UID), int(file.GID)); err != nil {
log.Debug("Failed to set file ownership", "path", targetPath, "error", err) log.Debug("Failed to set file ownership", "path", targetPath, "error", err)
} }
} }
}
if err := s.v.Fs.Chtimes(targetPath, file.MTime, file.MTime); err != nil { if err := s.v.Fs.Chtimes(targetPath, file.MTime, file.MTime); err != nil {
log.Debug("Failed to set file mtime", "path", targetPath, "error", err) log.Debug("Failed to set file mtime", "path", targetPath, "error", err)
} }