From 9231800f3befdd4d7bce4934aab318a014a3a06f Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Tue, 20 Jun 2017 12:38:16 +0200 Subject: [PATCH] Added support for block/char devices --- CHANGELOG.md | 1 + docs/excludes.default | 1 - docs/repository_readme.md | 11 +++++++- src/cli/mod.rs | 4 +++ src/mount.rs | 4 ++- src/repository/metadata.rs | 52 +++++++++++++++++++++++++++++++++----- src/repository/tarfile.rs | 14 +++++++++- 7 files changed, 76 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc57007..a653681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project follows [semantic versioning](http://semver.org). ### UNRELEASED * [added] Added `copy` subcommand * [added] Added support for xattrs in fuse mount +* [added] Added support for block/char devices * [modified] Also documenting common flags in subcommands * [modified] Using repository aliases (**conversion needed**) * [modified] Remote path must be absolute diff --git a/docs/excludes.default b/docs/excludes.default index 8b3263f..ad9a7f8 100644 --- a/docs/excludes.default +++ b/docs/excludes.default @@ -1,6 +1,5 @@ # Mounted locations and pseudo filesystems /cdrom -/dev lost+found /mnt /sys diff --git a/docs/repository_readme.md b/docs/repository_readme.md index 46af9d6..8953d51 100644 --- a/docs/repository_readme.md +++ b/docs/repository_readme.md @@ -306,11 +306,15 @@ The `FileType` describes the type of an inode. - `Directory` means a directory that does not contain data but might have children - `Symlink` means a symlink that points to a target +- `BlockDevice` means a block device +- `CharDevice` mean a character device FileType { File => 0, Directory => 1, - Symlink => 2 + Symlink => 2, + BlockDevice => 3, + CharDevice => 4 } @@ -432,6 +436,9 @@ as well as the whole subtree (including all children recursively). `cum_size` is the sum of all inode data sizes plus 1000 bytes for each inode (for encoded metadata). `cum_dirs` and `cum_files` is the count of directories and non-directories (symlinks and regular files). +The `xattrs` contains a mapping of all extended attributes of the inode. And +`device` contains a tuple with the major and minor device id if the inode is a +block or character device. Inode { name: string => 0, @@ -447,6 +454,8 @@ non-directories (symlinks and regular files). cum_size: int => 12, cum_dirs: int => 13, cum_files: int => 14 + xattrs: {string => bytes}? => 15, + device: (int, int)? => 16 } This structure is encoded with the following field default values: diff --git a/src/cli/mod.rs b/src/cli/mod.rs index d6a852d..592b1ff 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -153,6 +153,10 @@ pub fn format_inode_one_line(inode: &Inode) -> String { FileType::Directory => format!("{:25}\t{} entries", format!("{}/", inode.name), inode.children.as_ref().map(|c| c.len()).unwrap_or(0)), FileType::File => format!("{:25}\t{:>10}\t{}", inode.name, to_file_size(inode.size), Local.timestamp(inode.timestamp, 0).to_rfc2822()), FileType::Symlink => format!("{:25}\t -> {}", inode.name, inode.symlink_target.as_ref().map(|s| s as &str).unwrap_or("?")), + FileType::BlockDevice | FileType::CharDevice => { + let device = inode.device.unwrap_or((0, 0)); + format!("{:25}\t{:12}\t{}:{}", inode.name, inode.file_type, device.0, device.1) + } } } diff --git a/src/mount.rs b/src/mount.rs index 1883797..f016164 100644 --- a/src/mount.rs +++ b/src/mount.rs @@ -69,6 +69,8 @@ fn convert_file_type(kind: FileType) -> fuse::FileType { FileType::Directory => fuse::FileType::Directory, FileType::File => fuse::FileType::RegularFile, FileType::Symlink => fuse::FileType::Symlink, + FileType::BlockDevice => fuse::FileType::BlockDevice, + FileType::CharDevice => fuse::FileType::CharDevice } } @@ -112,7 +114,7 @@ impl FuseInode { nlink: 1, uid: uid, gid: gid, - rdev: 0, + rdev: self.inode.device.map_or(0, |(major, minor)| (major << 8) + minor), flags: 0 } } diff --git a/src/repository/metadata.rs b/src/repository/metadata.rs index e18013c..c85560a 100644 --- a/src/repository/metadata.rs +++ b/src/repository/metadata.rs @@ -2,14 +2,17 @@ use ::prelude::*; use filetime::{self, FileTime}; use xattr; +use libc; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use std::fs::{self, File, Permissions}; use std::os::linux::fs::MetadataExt; -use std::os::unix::fs::{PermissionsExt, symlink}; +use std::os::unix::fs::{FileTypeExt, PermissionsExt, MetadataExt as UnixMetadataExt, symlink}; use std::io::{self, Read, Write}; +use std::os::unix::ffi::OsStrExt; use std::fmt; +use std::ffi; quick_error!{ @@ -63,19 +66,25 @@ quick_error!{ pub enum FileType { File, Directory, - Symlink + Symlink, + BlockDevice, + CharDevice } serde_impl!(FileType(u8) { File => 0, Directory => 1, - Symlink => 2 + Symlink => 2, + BlockDevice => 3, + CharDevice => 4 }); impl fmt::Display for FileType { fn fmt(&self, format: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { FileType::File => write!(format, "file"), FileType::Directory => write!(format, "directory"), - FileType::Symlink => write!(format, "symlink") + FileType::Symlink => write!(format, "symlink"), + FileType::BlockDevice => write!(format, "block device"), + FileType::CharDevice => write!(format, "char device") } } } @@ -109,7 +118,8 @@ pub struct Inode { pub cum_size: u64, pub cum_dirs: usize, pub cum_files: usize, - pub xattrs: BTreeMap + pub xattrs: BTreeMap, + pub device: Option<(u32, u32)> } impl Default for Inode { fn default() -> Self { @@ -127,7 +137,8 @@ impl Default for Inode { cum_size: 0, cum_dirs: 0, cum_files: 0, - xattrs: BTreeMap::new() + xattrs: BTreeMap::new(), + device: None } } } @@ -145,7 +156,8 @@ serde_impl!(Inode(u8?) { cum_size: u64 => 12, cum_dirs: usize => 13, cum_files: usize => 14, - xattrs: BTreeMap => 15 + xattrs: BTreeMap => 15, + device: Option<(u32, u32)> => 16 }); @@ -165,12 +177,22 @@ impl Inode { FileType::Directory } else if meta.file_type().is_symlink() { FileType::Symlink + } else if meta.file_type().is_block_device() { + FileType::BlockDevice + } else if meta.file_type().is_char_device() { + FileType::CharDevice } else { return Err(InodeError::UnsupportedFiletype(path.to_owned())); }; if meta.file_type().is_symlink() { inode.symlink_target = Some(try!(fs::read_link(path).map_err(|e| InodeError::ReadLinkTarget(e, path.to_owned()))).to_string_lossy().to_string()); } + if meta.file_type().is_block_device() || meta.file_type().is_char_device() { + let rdev = meta.rdev(); + let major = (rdev >> 8) as u32; + let minor = (rdev & 0xff) as u32; + inode.device = Some((major, minor)); + } inode.mode = meta.permissions().mode(); inode.user = meta.st_uid(); inode.group = meta.st_gid(); @@ -202,6 +224,22 @@ impl Inode { } else { return Err(InodeError::Integrity("Symlink without target")) } + }, + FileType::BlockDevice | FileType::CharDevice => { + let name = try!(ffi::CString::new(full_path.as_os_str().as_bytes()).map_err(|_| InodeError::Integrity("Name contains nulls"))); + let mode = self.mode | match self.file_type { + FileType::BlockDevice => libc::S_IFBLK, + FileType::CharDevice => libc::S_IFCHR, + _ => unreachable!() + }; + let device = if let Some((major, minor)) = self.device { + unsafe { libc::makedev(major, minor) } + } else { + return Err(InodeError::Integrity("Device without id")) + }; + if unsafe { libc::mknod(name.as_ptr(), mode, device) } != 0 { + return Err(InodeError::Create(io::Error::last_os_error(), full_path.clone())); + } } } let time = FileTime::from_seconds_since_1970(self.timestamp as u64, 0); diff --git a/src/repository/tarfile.rs b/src/repository/tarfile.rs index 1569d17..0ca8f56 100644 --- a/src/repository/tarfile.rs +++ b/src/repository/tarfile.rs @@ -85,6 +85,8 @@ fn inode_from_entry(entry: &mut tar::Entry) -> Result FileType::File, tar::EntryType::Symlink => FileType::Symlink, tar::EntryType::Directory => FileType::Directory, + tar::EntryType::Block => FileType::BlockDevice, + tar::EntryType::Char => FileType::CharDevice, _ => return Err(InodeError::UnsupportedFiletype(path.to_path_buf()).into()) }; Inode { @@ -96,6 +98,10 @@ fn inode_from_entry(entry: &mut tar::Entry) -> Result Some((try!(header.device_major()).unwrap_or(0), try!(header.device_minor()).unwrap_or(0))), + _ => None + }, ..Default::default() } }; @@ -305,6 +311,10 @@ impl Repository { try!(header.set_link_name(target)); } } + if let Some((major, minor)) = inode.device { + try!(header.set_device_major(major)); + try!(header.set_device_minor(minor)); + } header.set_mode(inode.mode); header.set_uid(inode.user); if let Some(name) = backup.user_names.get(&inode.user) { @@ -318,7 +328,9 @@ impl Repository { header.set_entry_type(match inode.file_type { FileType::File => tar::EntryType::Regular, FileType::Symlink => tar::EntryType::Symlink, - FileType::Directory => tar::EntryType::Directory + FileType::Directory => tar::EntryType::Directory, + FileType::BlockDevice => tar::EntryType::Block, + FileType::CharDevice => tar::EntryType::Char }); header.set_cksum(); match inode.data {