From 5aef696b0d23daafcac5e7db4e96870e9582c2be Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Thu, 2 Jun 2022 00:09:29 +0200 Subject: [PATCH] Make rustic compile on windows --- Cargo.toml | 4 +- src/backend/ignore.rs | 273 ++++++++++++++++++++++++++-------------- src/backend/local.rs | 49 ++++++-- src/backend/node.rs | 24 +++- src/commands/restore.rs | 11 +- src/main.rs | 1 + 6 files changed, 251 insertions(+), 111 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4bd507..4d5030b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,12 +83,14 @@ indicatif = "0.17" path-dedot = "3" gethostname = "0.4" humantime = "2" -users = "0.11" itertools = "0.10" simplelog = "0.12" comfy-table = "6.1.4" libc="0.2" +[target.'cfg(not(windows))'.dependencies] +users = "0.11" + [dev-dependencies] rstest = "0.16" quickcheck = "1" diff --git a/src/backend/ignore.rs b/src/backend/ignore.rs index 12af56e..05addab 100644 --- a/src/backend/ignore.rs +++ b/src/backend/ignore.rs @@ -1,16 +1,20 @@ use std::fs::{read_link, File}; +#[cfg(not(windows))] use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::path::{Path, PathBuf}; use anyhow::Result; use bytesize::ByteSize; -use chrono::{Local, TimeZone, Utc}; +#[cfg(not(windows))] +use chrono::TimeZone; +use chrono::{DateTime, Local, Utc}; use clap::Parser; use ignore::{overrides::OverrideBuilder, DirEntry, Walk, WalkBuilder}; use log::*; use merge::Merge; use serde::Deserialize; use serde_with::{serde_as, DisplayFromStr}; +#[cfg(not(windows))] use users::{Groups, Users, UsersCache}; use super::{node::Metadata, node::NodeType, Node, ReadSource}; @@ -20,6 +24,7 @@ pub struct LocalSource { walker: Walk, with_atime: bool, ignore_devid: bool, + #[cfg(not(windows))] cache: UsersCache, } @@ -141,6 +146,7 @@ impl LocalSource { walker, with_atime: opts.with_atime, ignore_devid: opts.ignore_devid, + #[cfg(not(windows))] cache: UsersCache::new(), }) } @@ -175,10 +181,82 @@ impl Iterator for LocalSource { } item => item, } - .map(|e| map_entry(e?, self.with_atime, self.ignore_devid, &self.cache)) + .map(|e| { + map_entry( + e?, + self.with_atime, + self.ignore_devid, + #[cfg(not(windows))] + &self.cache, + ) + }) } } +#[cfg(windows)] +fn map_entry(entry: DirEntry, with_atime: bool, _ignore_devid: bool) -> Result<(PathBuf, Node)> { + let name = entry.file_name(); + let m = entry.metadata()?; + + // TODO: Set them to suitable values + let uid = None; + let gid = None; + let user = None; + let group = None; + + let size = if m.is_dir() { 0 } else { m.len() }; + let mode = None; + let inode = 0; + let device_id = 0; + let links = 0; + + let mtime = m + .modified() + .ok() + .map(|t| DateTime::::from(t).with_timezone(&Local)); + let atime = if with_atime { + m.accessed() + .ok() + .map(|t| DateTime::::from(t).with_timezone(&Local)) + } else { + // TODO: Use None here? + mtime + }; + let ctime = m + .created() + .ok() + .map(|t| DateTime::::from(t).with_timezone(&Local)); + + let meta = Metadata { + size, + mtime, + atime, + ctime, + mode, + uid, + gid, + user, + group, + inode, + device_id, + links, + }; + + let node = if m.is_dir() { + Node::new_node(name, NodeType::Dir, meta) + } else if m.is_symlink() { + let target = read_link(entry.path())?; + let node_type = NodeType::Symlink { + linktarget: target.to_str().expect("no unicode").to_string(), + }; + Node::new_node(name, node_type, meta) + } else { + Node::new_node(name, NodeType::File, meta) + }; + Ok((entry.path().to_path_buf(), node)) +} + +#[cfg(not(windows))] // map_entry: turn entry into (Path, Node) fn map_entry( entry: DirEntry, @@ -194,18 +272,19 @@ fn map_entry( let user = cache .get_user_by_uid(uid) .map(|u| u.name().to_str().unwrap().to_string()); + let group = cache .get_group_by_gid(gid) .map(|g| g.name().to_str().unwrap().to_string()); - let mtime = Utc - .timestamp_opt(m.mtime(), m.mtime_nsec().try_into()?) - .single() - .map(|dt| dt.with_timezone(&Local)); + let mtime = m + .modified() + .ok() + .map(|t| DateTime::::from(t).with_timezone(&Local)); let atime = if with_atime { - Utc.timestamp_opt(m.atime(), m.atime_nsec().try_into()?) - .single() - .map(|dt| dt.with_timezone(&Local)) + m.accessed() + .ok() + .map(|t| DateTime::::from(t).with_timezone(&Local)) } else { // TODO: Use None here? mtime @@ -214,8 +293,9 @@ fn map_entry( .timestamp_opt(m.ctime(), m.ctime_nsec().try_into()?) .single() .map(|dt| dt.with_timezone(&Local)); + let size = if m.is_dir() { 0 } else { m.len() }; - let mode = map_mode_to_go(m.mode()); + let mode = mapper::map_mode_to_go(m.mode()); let inode = m.ino(); let device_id = if ignore_devid { 0 } else { m.dev() }; let links = if m.is_dir() { 0 } else { m.nlink() }; @@ -260,97 +340,100 @@ fn map_entry( Ok((entry.path().to_path_buf(), node)) } -const MODE_PERM: u32 = 0o777; // permission bits +#[cfg(not(windows))] +pub mod mapper { + const MODE_PERM: u32 = 0o777; // permission bits -// consts from https://pkg.go.dev/io/fs#ModeType -const GO_MODE_DIR: u32 = 0b10000000000000000000000000000000; -const GO_MODE_SYMLINK: u32 = 0b00001000000000000000000000000000; -const GO_MODE_DEVICE: u32 = 0b00000100000000000000000000000000; -const GO_MODE_FIFO: u32 = 0b00000010000000000000000000000000; -const GO_MODE_SOCKET: u32 = 0b00000001000000000000000000000000; -const GO_MODE_SETUID: u32 = 0b00000000100000000000000000000000; -const GO_MODE_SETGID: u32 = 0b00000000010000000000000000000000; -const GO_MODE_CHARDEV: u32 = 0b00000000001000000000000000000000; -const GO_MODE_STICKY: u32 = 0b00000000000100000000000000000000; -const GO_MODE_IRREG: u32 = 0b00000000000010000000000000000000; + // consts from https://pkg.go.dev/io/fs#ModeType + const GO_MODE_DIR: u32 = 0b10000000000000000000000000000000; + const GO_MODE_SYMLINK: u32 = 0b00001000000000000000000000000000; + const GO_MODE_DEVICE: u32 = 0b00000100000000000000000000000000; + const GO_MODE_FIFO: u32 = 0b00000010000000000000000000000000; + const GO_MODE_SOCKET: u32 = 0b00000001000000000000000000000000; + const GO_MODE_SETUID: u32 = 0b00000000100000000000000000000000; + const GO_MODE_SETGID: u32 = 0b00000000010000000000000000000000; + const GO_MODE_CHARDEV: u32 = 0b00000000001000000000000000000000; + const GO_MODE_STICKY: u32 = 0b00000000000100000000000000000000; + const GO_MODE_IRREG: u32 = 0b00000000000010000000000000000000; -// consts from man page inode(7) -const S_IFFORMAT: u32 = 0o170000; // File mask -const S_IFSOCK: u32 = 0o140000; // socket -const S_IFLNK: u32 = 0o120000; // symbolic link -const S_IFREG: u32 = 0o100000; // regular file -const S_IFBLK: u32 = 0o060000; // block device -const S_IFDIR: u32 = 0o040000; // directory -const S_IFCHR: u32 = 0o020000; // character device -const S_IFIFO: u32 = 0o010000; // FIFO + // consts from man page inode(7) + const S_IFFORMAT: u32 = 0o170000; // File mask + const S_IFSOCK: u32 = 0o140000; // socket + const S_IFLNK: u32 = 0o120000; // symbolic link + const S_IFREG: u32 = 0o100000; // regular file + const S_IFBLK: u32 = 0o060000; // block device + const S_IFDIR: u32 = 0o040000; // directory + const S_IFCHR: u32 = 0o020000; // character device + const S_IFIFO: u32 = 0o010000; // FIFO -const S_ISUID: u32 = 0o4000; // set-user-ID bit (see execve(2)) -const S_ISGID: u32 = 0o2000; // set-group-ID bit (see below) -const S_ISVTX: u32 = 0o1000; // sticky bit (see below) + const S_ISUID: u32 = 0o4000; // set-user-ID bit (see execve(2)) + const S_ISGID: u32 = 0o2000; // set-group-ID bit (see below) + const S_ISVTX: u32 = 0o1000; // sticky bit (see below) -/// map `st_mode` from POSIX (`inode(7)`) to golang's definition () -/// Note, that it only sets the bits `os.ModePerm | os.ModeType | os.ModeSetuid | os.ModeSetgid | os.ModeSticky` -/// to stay compatible with the restic implementation -fn map_mode_to_go(mode: u32) -> u32 { - let mut go_mode = mode & MODE_PERM; + /// map `st_mode` from POSIX (`inode(7)`) to golang's definition () + /// Note, that it only sets the bits `os.ModePerm | os.ModeType | os.ModeSetuid | os.ModeSetgid | os.ModeSticky` + /// to stay compatible with the restic implementation + pub fn map_mode_to_go(mode: u32) -> u32 { + let mut go_mode = mode & MODE_PERM; - match mode & S_IFFORMAT { - S_IFSOCK => go_mode |= GO_MODE_SOCKET, - S_IFLNK => go_mode |= GO_MODE_SYMLINK, - S_IFBLK => go_mode |= GO_MODE_DEVICE, - S_IFDIR => go_mode |= GO_MODE_DIR, - S_IFCHR => go_mode |= GO_MODE_CHARDEV & GO_MODE_DEVICE, // no idea why go sets both for char devices... - S_IFIFO => go_mode |= GO_MODE_FIFO, - // note that POSIX specifies regular files, whereas golang specifies irregular files - S_IFREG => {} - _ => go_mode |= GO_MODE_IRREG, - }; + match mode & S_IFFORMAT { + S_IFSOCK => go_mode |= GO_MODE_SOCKET, + S_IFLNK => go_mode |= GO_MODE_SYMLINK, + S_IFBLK => go_mode |= GO_MODE_DEVICE, + S_IFDIR => go_mode |= GO_MODE_DIR, + S_IFCHR => go_mode |= GO_MODE_CHARDEV & GO_MODE_DEVICE, // no idea why go sets both for char devices... + S_IFIFO => go_mode |= GO_MODE_FIFO, + // note that POSIX specifies regular files, whereas golang specifies irregular files + S_IFREG => {} + _ => go_mode |= GO_MODE_IRREG, + }; - if mode & S_ISUID > 0 { - go_mode |= GO_MODE_SETUID; - } - if mode & S_ISGID > 0 { - go_mode |= GO_MODE_SETGID; - } - if mode & S_ISVTX > 0 { - go_mode |= GO_MODE_STICKY; + if mode & S_ISUID > 0 { + go_mode |= GO_MODE_SETUID; + } + if mode & S_ISGID > 0 { + go_mode |= GO_MODE_SETGID; + } + if mode & S_ISVTX > 0 { + go_mode |= GO_MODE_STICKY; + } + + go_mode } - go_mode -} - -/// map golangs mode definition () to `st_mode` from POSIX (`inode(7)`) -/// This is the inverse function to [`map_mode_to_go`] -pub fn map_mode_from_go(go_mode: u32) -> u32 { - let mut mode = go_mode & MODE_PERM; - - if go_mode & GO_MODE_SOCKET > 0 { - mode |= S_IFSOCK; - } else if go_mode & GO_MODE_SYMLINK > 0 { - mode |= S_IFLNK; - } else if go_mode & GO_MODE_DEVICE > 0 && go_mode & GO_MODE_CHARDEV == 0 { - mode |= S_IFBLK; - } else if go_mode & GO_MODE_DIR > 0 { - mode |= S_IFDIR; - } else if go_mode & (GO_MODE_CHARDEV | GO_MODE_DEVICE) > 0 { - mode |= S_IFCHR; - } else if go_mode & GO_MODE_FIFO > 0 { - mode |= S_IFIFO; - } else if go_mode & GO_MODE_IRREG > 0 { - // note that POSIX specifies regular files, whereas golang specifies irregular files - } else { - mode |= S_IFREG; - } - - if go_mode & GO_MODE_SETUID > 0 { - mode |= S_ISUID; - } - if go_mode & GO_MODE_SETGID > 0 { - mode |= S_ISGID; - } - if go_mode & GO_MODE_STICKY > 0 { - mode |= S_ISVTX; - } - - mode + /// map golangs mode definition () to `st_mode` from POSIX (`inode(7)`) + /// This is the inverse function to [`map_mode_to_go`] + pub fn map_mode_from_go(go_mode: u32) -> u32 { + let mut mode = go_mode & MODE_PERM; + + if go_mode & GO_MODE_SOCKET > 0 { + mode |= S_IFSOCK; + } else if go_mode & GO_MODE_SYMLINK > 0 { + mode |= S_IFLNK; + } else if go_mode & GO_MODE_DEVICE > 0 && go_mode & GO_MODE_CHARDEV == 0 { + mode |= S_IFBLK; + } else if go_mode & GO_MODE_DIR > 0 { + mode |= S_IFDIR; + } else if go_mode & (GO_MODE_CHARDEV | GO_MODE_DEVICE) > 0 { + mode |= S_IFCHR; + } else if go_mode & GO_MODE_FIFO > 0 { + mode |= S_IFIFO; + } else if go_mode & GO_MODE_IRREG > 0 { + // note that POSIX specifies regular files, whereas golang specifies irregular files + } else { + mode |= S_IFREG; + } + + if go_mode & GO_MODE_SETUID > 0 { + mode |= S_ISUID; + } + if go_mode & GO_MODE_SETGID > 0 { + mode |= S_ISGID; + } + if go_mode & GO_MODE_STICKY > 0 { + mode |= S_ISVTX; + } + + mode + } } diff --git a/src/backend/local.rs b/src/backend/local.rs index 2cf9a97..ee20ad3 100644 --- a/src/backend/local.rs +++ b/src/backend/local.rs @@ -1,6 +1,7 @@ use std::fs::{self, File, OpenOptions}; use std::io::{Read, Seek, SeekFrom, Write}; -use std::os::unix::fs::{symlink, FileExt, PermissionsExt}; +#[cfg(not(windows))] +use std::os::unix::fs::{symlink, PermissionsExt}; use std::path::{Path, PathBuf}; use std::process::Command; @@ -9,15 +10,20 @@ use anyhow::{bail, Result}; use bytes::Bytes; use filetime::{set_file_atime, set_file_mtime, FileTime}; use log::*; +#[cfg(not(windows))] use nix::sys::stat::{mknod, Mode, SFlag}; -use nix::unistd::chown; -use nix::unistd::{Gid, Group, Uid, User}; +#[cfg(not(windows))] +use nix::unistd::{chown, Gid, Group, Uid, User}; use walkdir::WalkDir; use crate::repository::parse_command; -use super::node::{Metadata, Node, NodeType}; -use super::{map_mode_from_go, FileType, Id, ReadBackend, WriteBackend, ALL_FILE_TYPES}; +#[cfg(not(windows))] +use super::mapper::map_mode_from_go; +#[cfg(not(windows))] +use super::node::NodeType; +use super::node::{Metadata, Node}; +use super::{FileType, Id, ReadBackend, WriteBackend, ALL_FILE_TYPES}; #[derive(Clone)] pub struct LocalBackend { @@ -255,6 +261,13 @@ impl LocalDestination { Ok(()) } + #[cfg(windows)] + // TODO + pub fn set_user_group(&self, _item: impl AsRef, _meta: &Metadata) -> Result<()> { + Ok(()) + } + + #[cfg(not(windows))] pub fn set_user_group(&self, item: impl AsRef, meta: &Metadata) -> Result<()> { let filename = self.path(item); @@ -277,6 +290,13 @@ impl LocalDestination { Ok(()) } + #[cfg(windows)] + // TODO + pub fn set_uid_gid(&self, _item: impl AsRef, _meta: &Metadata) -> Result<()> { + Ok(()) + } + + #[cfg(not(windows))] pub fn set_uid_gid(&self, item: impl AsRef, meta: &Metadata) -> Result<()> { let filename = self.path(item); @@ -287,6 +307,13 @@ impl LocalDestination { Ok(()) } + #[cfg(windows)] + // TODO + pub fn set_permission(&self, _item: impl AsRef, _meta: &Metadata) -> Result<()> { + Ok(()) + } + + #[cfg(not(windows))] pub fn set_permission(&self, item: impl AsRef, meta: &Metadata) -> Result<()> { let filename = self.path(item); @@ -308,6 +335,13 @@ impl LocalDestination { Ok(()) } + #[cfg(windows)] + // TODO + pub fn create_special(&self, _item: impl AsRef, _node: &Node) -> Result<()> { + Ok(()) + } + + #[cfg(not(windows))] pub fn create_special(&self, item: impl AsRef, node: &Node) -> Result<()> { let filename = self.path(item); @@ -377,11 +411,12 @@ impl LocalDestination { pub fn write_at(&self, item: impl AsRef, offset: u64, data: &[u8]) -> Result<()> { let filename = self.path(item); - let file = fs::OpenOptions::new() + let mut file = fs::OpenOptions::new() .create(true) .write(true) .open(filename)?; - file.write_all_at(data, offset)?; + file.seek(SeekFrom::Start(offset))?; + file.write_all(data)?; Ok(()) } } diff --git a/src/backend/node.rs b/src/backend/node.rs index 05c5250..f148161 100644 --- a/src/backend/node.rs +++ b/src/backend/node.rs @@ -1,9 +1,14 @@ use std::ffi::{OsStr, OsString}; -use std::fmt::{Debug, Write}; +use std::fmt::Debug; +#[cfg(not(windows))] +use std::fmt::Write; +#[cfg(not(windows))] use std::os::unix::ffi::OsStrExt; use std::str::FromStr; -use anyhow::{anyhow, bail, Result}; +use anyhow::Result; +#[cfg(not(windows))] +use anyhow::{anyhow, bail}; use chrono::{DateTime, Local}; use derive_getters::Getters; use derive_more::{Constructor, IsVariant}; @@ -116,6 +121,18 @@ impl Node { } } +// TODO(Windows): This is not able to handle non-unicode filenames and +// doesn't treat filenames which need and escape (like `\`, `"`, ...) correctly +#[cfg(windows)] +pub fn escape_filename(name: &OsStr) -> String { + name.to_string_lossy().to_string() +} +#[cfg(windows)] +pub fn unescape_filename(s: &str) -> Result { + Ok(OsString::from_str(s)?) +} + +#[cfg(not(windows))] // This escapes the filename in a way that *should* be compatible to golangs // stconv.Quote, see https://pkg.go.dev/strconv#Quote // However, so far there was no specification what Quote really does, so this @@ -168,6 +185,7 @@ pub fn escape_filename(name: &OsStr) -> String { s } +#[cfg(not(windows))] // inspired by the enquote crate pub fn unescape_filename(s: &str) -> Result { let mut chars = s.chars(); @@ -228,6 +246,7 @@ pub fn unescape_filename(s: &str) -> Result { Ok(OsStr::from_bytes(&u).to_os_string()) } +#[cfg(not(windows))] #[inline] // Iterator#take cannot be used because it consumes the iterator fn take>(iterator: &mut I, n: usize) -> String { @@ -238,6 +257,7 @@ fn take>(iterator: &mut I, n: usize) -> String { s } +#[cfg(not(windows))] #[cfg(test)] mod tests { use super::*; diff --git a/src/commands/restore.rs b/src/commands/restore.rs index 1904b7f..98f1651 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -2,11 +2,10 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::io::Read; use std::num::NonZeroU32; -use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; use anyhow::{anyhow, bail, Context, Result}; -use chrono::{Local, TimeZone, Utc}; +use chrono::{DateTime, Local, Utc}; use clap::{AppSettings, Parser}; use derive_getters::Dissolve; use ignore::{DirEntry, WalkBuilder}; @@ -524,10 +523,10 @@ impl FileInfos { if !ignore_mtime { if let Some(meta) = open_file.as_ref().map(|f| f.metadata()).transpose()? { // TODO: This is the same logic as in backend/ignore.rs => consollidate! - let mtime = Utc - .timestamp_opt(meta.mtime(), meta.mtime_nsec().try_into()?) - .single() - .map(|dt| dt.with_timezone(&Local)); + let mtime = meta + .modified() + .ok() + .map(|t| DateTime::::from(t).with_timezone(&Local)); if meta.len() == file_meta.size && mtime == file_meta.mtime { // File exists with fitting mtime => we suspect this file is ok! debug!("file {name:?} exists with suitable size and mtime, accepting it!"); diff --git a/src/main.rs b/src/main.rs index af8d799..79c8f24 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,6 +52,7 @@ mod cdc; fn main() -> Result<()> { // this is a workaround until unix_sigpipe (https://github.com/rust-lang/rust/issues/97889) is available. // See also https://github.com/rust-lang/rust/issues/46016 + #[cfg(not(windows))] #[allow(unsafe_code)] unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL);