Added support for block/char devices

This commit is contained in:
Dennis Schwerdel 2017-06-20 12:38:16 +02:00
parent 8d45176146
commit 9231800f3b
7 changed files with 76 additions and 11 deletions

View File

@ -6,6 +6,7 @@ This project follows [semantic versioning](http://semver.org).
### UNRELEASED ### UNRELEASED
* [added] Added `copy` subcommand * [added] Added `copy` subcommand
* [added] Added support for xattrs in fuse mount * [added] Added support for xattrs in fuse mount
* [added] Added support for block/char devices
* [modified] Also documenting common flags in subcommands * [modified] Also documenting common flags in subcommands
* [modified] Using repository aliases (**conversion needed**) * [modified] Using repository aliases (**conversion needed**)
* [modified] Remote path must be absolute * [modified] Remote path must be absolute

View File

@ -1,6 +1,5 @@
# Mounted locations and pseudo filesystems # Mounted locations and pseudo filesystems
/cdrom /cdrom
/dev
lost+found lost+found
/mnt /mnt
/sys /sys

View File

@ -306,11 +306,15 @@ The `FileType` describes the type of an inode.
- `Directory` means a directory that does not contain data but might have - `Directory` means a directory that does not contain data but might have
children children
- `Symlink` means a symlink that points to a target - `Symlink` means a symlink that points to a target
- `BlockDevice` means a block device
- `CharDevice` mean a character device
FileType { FileType {
File => 0, File => 0,
Directory => 1, 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 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 metadata). `cum_dirs` and `cum_files` is the count of directories and
non-directories (symlinks and regular files). 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 { Inode {
name: string => 0, name: string => 0,
@ -447,6 +454,8 @@ non-directories (symlinks and regular files).
cum_size: int => 12, cum_size: int => 12,
cum_dirs: int => 13, cum_dirs: int => 13,
cum_files: int => 14 cum_files: int => 14
xattrs: {string => bytes}? => 15,
device: (int, int)? => 16
} }
This structure is encoded with the following field default values: This structure is encoded with the following field default values:

View File

@ -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::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::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::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)
}
} }
} }

View File

@ -69,6 +69,8 @@ fn convert_file_type(kind: FileType) -> fuse::FileType {
FileType::Directory => fuse::FileType::Directory, FileType::Directory => fuse::FileType::Directory,
FileType::File => fuse::FileType::RegularFile, FileType::File => fuse::FileType::RegularFile,
FileType::Symlink => fuse::FileType::Symlink, FileType::Symlink => fuse::FileType::Symlink,
FileType::BlockDevice => fuse::FileType::BlockDevice,
FileType::CharDevice => fuse::FileType::CharDevice
} }
} }
@ -112,7 +114,7 @@ impl FuseInode {
nlink: 1, nlink: 1,
uid: uid, uid: uid,
gid: gid, gid: gid,
rdev: 0, rdev: self.inode.device.map_or(0, |(major, minor)| (major << 8) + minor),
flags: 0 flags: 0
} }
} }

View File

@ -2,14 +2,17 @@ use ::prelude::*;
use filetime::{self, FileTime}; use filetime::{self, FileTime};
use xattr; use xattr;
use libc;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::fs::{self, File, Permissions}; use std::fs::{self, File, Permissions};
use std::os::linux::fs::MetadataExt; 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::io::{self, Read, Write};
use std::os::unix::ffi::OsStrExt;
use std::fmt; use std::fmt;
use std::ffi;
quick_error!{ quick_error!{
@ -63,19 +66,25 @@ quick_error!{
pub enum FileType { pub enum FileType {
File, File,
Directory, Directory,
Symlink Symlink,
BlockDevice,
CharDevice
} }
serde_impl!(FileType(u8) { serde_impl!(FileType(u8) {
File => 0, File => 0,
Directory => 1, Directory => 1,
Symlink => 2 Symlink => 2,
BlockDevice => 3,
CharDevice => 4
}); });
impl fmt::Display for FileType { impl fmt::Display for FileType {
fn fmt(&self, format: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, format: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self { match *self {
FileType::File => write!(format, "file"), FileType::File => write!(format, "file"),
FileType::Directory => write!(format, "directory"), 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_size: u64,
pub cum_dirs: usize, pub cum_dirs: usize,
pub cum_files: usize, pub cum_files: usize,
pub xattrs: BTreeMap<String, msgpack::Bytes> pub xattrs: BTreeMap<String, msgpack::Bytes>,
pub device: Option<(u32, u32)>
} }
impl Default for Inode { impl Default for Inode {
fn default() -> Self { fn default() -> Self {
@ -127,7 +137,8 @@ impl Default for Inode {
cum_size: 0, cum_size: 0,
cum_dirs: 0, cum_dirs: 0,
cum_files: 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_size: u64 => 12,
cum_dirs: usize => 13, cum_dirs: usize => 13,
cum_files: usize => 14, cum_files: usize => 14,
xattrs: BTreeMap<String, msgpack::Bytes> => 15 xattrs: BTreeMap<String, msgpack::Bytes> => 15,
device: Option<(u32, u32)> => 16
}); });
@ -165,12 +177,22 @@ impl Inode {
FileType::Directory FileType::Directory
} else if meta.file_type().is_symlink() { } else if meta.file_type().is_symlink() {
FileType::Symlink FileType::Symlink
} else if meta.file_type().is_block_device() {
FileType::BlockDevice
} else if meta.file_type().is_char_device() {
FileType::CharDevice
} else { } else {
return Err(InodeError::UnsupportedFiletype(path.to_owned())); return Err(InodeError::UnsupportedFiletype(path.to_owned()));
}; };
if meta.file_type().is_symlink() { 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()); 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.mode = meta.permissions().mode();
inode.user = meta.st_uid(); inode.user = meta.st_uid();
inode.group = meta.st_gid(); inode.group = meta.st_gid();
@ -202,6 +224,22 @@ impl Inode {
} else { } else {
return Err(InodeError::Integrity("Symlink without target")) 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); let time = FileTime::from_seconds_since_1970(self.timestamp as u64, 0);

View File

@ -85,6 +85,8 @@ fn inode_from_entry<R: Read>(entry: &mut tar::Entry<R>) -> Result<Inode, Reposit
tar::EntryType::Regular | tar::EntryType::Link | tar::EntryType::Continuous => FileType::File, tar::EntryType::Regular | tar::EntryType::Link | tar::EntryType::Continuous => FileType::File,
tar::EntryType::Symlink => FileType::Symlink, tar::EntryType::Symlink => FileType::Symlink,
tar::EntryType::Directory => FileType::Directory, tar::EntryType::Directory => FileType::Directory,
tar::EntryType::Block => FileType::BlockDevice,
tar::EntryType::Char => FileType::CharDevice,
_ => return Err(InodeError::UnsupportedFiletype(path.to_path_buf()).into()) _ => return Err(InodeError::UnsupportedFiletype(path.to_path_buf()).into())
}; };
Inode { Inode {
@ -96,6 +98,10 @@ fn inode_from_entry<R: Read>(entry: &mut tar::Entry<R>) -> Result<Inode, Reposit
user: try!(header.uid()), user: try!(header.uid()),
group: try!(header.gid()), group: try!(header.gid()),
timestamp: try!(header.mtime()) as i64, timestamp: try!(header.mtime()) as i64,
device: match file_type {
FileType::BlockDevice | FileType::CharDevice => Some((try!(header.device_major()).unwrap_or(0), try!(header.device_minor()).unwrap_or(0))),
_ => None
},
..Default::default() ..Default::default()
} }
}; };
@ -305,6 +311,10 @@ impl Repository {
try!(header.set_link_name(target)); 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_mode(inode.mode);
header.set_uid(inode.user); header.set_uid(inode.user);
if let Some(name) = backup.user_names.get(&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 { header.set_entry_type(match inode.file_type {
FileType::File => tar::EntryType::Regular, FileType::File => tar::EntryType::Regular,
FileType::Symlink => tar::EntryType::Symlink, 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(); header.set_cksum();
match inode.data { match inode.data {