Make rustic compile on windows

This commit is contained in:
Alexander Weiss 2022-06-02 00:09:29 +02:00
parent d6e59cf8bf
commit 5aef696b0d
6 changed files with 251 additions and 111 deletions

View File

@ -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"

View File

@ -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::<Utc>::from(t).with_timezone(&Local));
let atime = if with_atime {
m.accessed()
.ok()
.map(|t| DateTime::<Utc>::from(t).with_timezone(&Local))
} else {
// TODO: Use None here?
mtime
};
let ctime = m
.created()
.ok()
.map(|t| DateTime::<Utc>::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::<Utc>::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::<Utc>::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 (<https://pkg.go.dev/io/fs#ModeType>)
/// 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 (<https://pkg.go.dev/io/fs#ModeType>)
/// 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 (<https://pkg.go.dev/io/fs#ModeType>) 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 (<https://pkg.go.dev/io/fs#ModeType>) 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
}
}

View File

@ -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<Path>, _meta: &Metadata) -> Result<()> {
Ok(())
}
#[cfg(not(windows))]
pub fn set_user_group(&self, item: impl AsRef<Path>, 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<Path>, _meta: &Metadata) -> Result<()> {
Ok(())
}
#[cfg(not(windows))]
pub fn set_uid_gid(&self, item: impl AsRef<Path>, 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<Path>, _meta: &Metadata) -> Result<()> {
Ok(())
}
#[cfg(not(windows))]
pub fn set_permission(&self, item: impl AsRef<Path>, 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<Path>, _node: &Node) -> Result<()> {
Ok(())
}
#[cfg(not(windows))]
pub fn create_special(&self, item: impl AsRef<Path>, node: &Node) -> Result<()> {
let filename = self.path(item);
@ -377,11 +411,12 @@ impl LocalDestination {
pub fn write_at(&self, item: impl AsRef<Path>, 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(())
}
}

View File

@ -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<OsString> {
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<OsString> {
let mut chars = s.chars();
@ -228,6 +246,7 @@ pub fn unescape_filename(s: &str) -> Result<OsString> {
Ok(OsStr::from_bytes(&u).to_os_string())
}
#[cfg(not(windows))]
#[inline]
// Iterator#take cannot be used because it consumes the iterator
fn take<I: Iterator<Item = char>>(iterator: &mut I, n: usize) -> String {
@ -238,6 +257,7 @@ fn take<I: Iterator<Item = char>>(iterator: &mut I, n: usize) -> String {
s
}
#[cfg(not(windows))]
#[cfg(test)]
mod tests {
use super::*;

View File

@ -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::<Utc>::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!");

View File

@ -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);