Skip to content

Commit d97c74b

Browse files
committed
Restore dir/symlink ownership if possible
Fixes #228
1 parent 5da7eaf commit d97c74b

File tree

3 files changed

+27
-19
lines changed

3 files changed

+27
-19
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Fixed: Restore now sets Unix user/group ownership on symlinks and directories. Previously, only file ownership was restored. (Setting file ownership typically requires restoring as root.)
6+
57
- Performance: Also keep a cache of the existence of blocks that have not yet been read.
68

79
- Changed: The format and keys written by `--metrics-json` has changed.

src/owner/unix.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,15 @@
1515
//! Unix implementation of file ownership.
1616
1717
use std::io;
18-
use std::os::unix::fs::MetadataExt;
18+
use std::os::unix::fs::{lchown, MetadataExt};
1919
use std::sync::Mutex;
2020
use std::{fs, path::Path};
2121

2222
use lazy_static::lazy_static;
23-
use nix::errno::Errno;
24-
use nix::unistd;
2523
use uzers::{Groups, Users, UsersCache};
2624

2725
use super::Owner;
28-
use crate::{Error, Result};
26+
use crate::Result;
2927

3028
lazy_static! {
3129
static ref USERS_CACHE: Mutex<UsersCache> = Mutex::new(UsersCache::new());
@@ -44,34 +42,29 @@ impl From<&fs::Metadata> for Owner {
4442
}
4543
}
4644

47-
#[mutants::skip] // TODO: Test that at least groups are restored!
45+
#[mutants::skip] // TODO: Difficult to test as non-root but we could at least test that at least groups are restored!
4846
pub(crate) fn set_owner(owner: &Owner, path: &Path) -> Result<()> {
4947
let users_cache = USERS_CACHE.lock().unwrap();
5048
let uid_opt = owner
5149
.user
5250
.as_ref()
5351
.and_then(|user| users_cache.get_user_by_name(&user))
54-
.map(|user| user.uid())
55-
.map(unistd::Uid::from_raw);
52+
.map(|user| user.uid());
5653
let gid_opt = owner
5754
.group
5855
.as_ref()
5956
.and_then(|group| users_cache.get_group_by_name(&group))
60-
.map(|group| group.gid())
61-
.map(unistd::Gid::from_raw);
57+
.map(|group| group.gid());
6258
drop(users_cache);
6359
// TODO: use `std::os::unix::fs::chown(path, uid, gid)?;` once stable
64-
match unistd::chown(path, uid_opt, gid_opt) {
60+
match lchown(path, uid_opt, gid_opt) {
6561
Ok(()) => Ok(()),
66-
Err(Errno::EPERM) => {
62+
Err(err) if err.kind() == io::ErrorKind::PermissionDenied => {
6763
// If the restore is not run as root (or with special capabilities)
6864
// then we probably can't set ownership, and there's no point
6965
// complaining
7066
Ok(())
7167
}
72-
Err(errno) => Err(Error::SetOwner {
73-
path: path.to_path_buf(),
74-
source: io::Error::from_raw_os_error(errno as i32),
75-
}),
68+
Err(err) => Err(err.into()),
7669
}
7770
}

src/restore.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ pub fn restore(
111111
path,
112112
unix_mode: entry.unix_mode(),
113113
mtime: entry.mtime(),
114+
owner: entry.owner().clone(),
114115
})
115116
}
116117
Kind::File => {
@@ -163,6 +164,7 @@ struct DirDeferral {
163164
path: PathBuf,
164165
unix_mode: UnixMode,
165166
mtime: OffsetDateTime,
167+
owner: Owner,
166168
}
167169

168170
fn apply_deferrals(deferrals: &[DirDeferral]) -> Result<RestoreStats> {
@@ -171,8 +173,13 @@ fn apply_deferrals(deferrals: &[DirDeferral]) -> Result<RestoreStats> {
171173
path,
172174
unix_mode,
173175
mtime,
176+
owner,
174177
} in deferrals
175178
{
179+
if let Err(err) = owner.set_owner(path) {
180+
error!(?path, ?err, "Error restoring ownership");
181+
stats.errors += 1;
182+
}
176183
if let Err(err) = unix_mode.set_permissions(path) {
177184
error!(?path, ?err, "Failed to set directory permissions");
178185
stats.errors += 1;
@@ -254,7 +261,8 @@ fn restore_file(
254261
}
255262

256263
#[cfg(unix)]
257-
fn restore_symlink(path: &Path, entry: &IndexEntry) -> Result<()> {
264+
fn restore_symlink(path: &Path, entry: &IndexEntry) -> Result<RestoreStats> {
265+
let mut stats = RestoreStats::default();
258266
use std::os::unix::fs as unix_fs;
259267
if let Some(ref target) = entry.symlink_target() {
260268
if let Err(source) = unix_fs::symlink(target, path) {
@@ -263,6 +271,10 @@ fn restore_symlink(path: &Path, entry: &IndexEntry) -> Result<()> {
263271
source,
264272
});
265273
}
274+
if let Err(err) = &entry.owner().set_owner(path) {
275+
error!(?path, ?err, "Error restoring ownership");
276+
stats.errors += 1;
277+
}
266278
let mtime = entry.mtime().to_file_time();
267279
if let Err(source) = set_symlink_file_times(path, mtime, mtime) {
268280
return Err(Error::RestoreModificationTime {
@@ -272,15 +284,16 @@ fn restore_symlink(path: &Path, entry: &IndexEntry) -> Result<()> {
272284
}
273285
} else {
274286
error!(apath = ?entry.apath(), "No target in symlink entry");
287+
stats.errors += 1;
275288
}
276-
Ok(())
289+
Ok(stats)
277290
}
278291

279292
#[cfg(not(unix))]
280293
#[mutants::skip]
281-
fn restore_symlink(_restore_path: &Path, entry: &IndexEntry) -> Result<()> {
294+
fn restore_symlink(_restore_path: &Path, entry: &IndexEntry) -> Result<RestoreStats> {
282295
// TODO: Add a test with a canned index containing a symlink, and expect
283296
// it cannot be restored on Windows and can be on Unix.
284297
warn!("Can't restore symlinks on non-Unix: {}", entry.apath());
285-
Ok(())
298+
Ok(RestoreStats::default())
286299
}

0 commit comments

Comments
 (0)