1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
//! Cross-platform implementations of fs methods that don't do anything
//! special. Fallback intended for use when a platform does not provide
//! enhanced functionality.

use std::{io, fs, path, ffi};
use std::cmp::PartialEq;
use crate::{tar, tape, spanning};
use crate::tuning::Configuration;

/// Supertrait that represents all the things a good archive sink needs to be.
/// 
/// TODO: The **moment** Rust gets the ability to handle multiple traits in a
/// single trait object, delete this arbitrary supertrait immediately.
/// 
/// TODO: wait no now this supertrait does downcasts because Box won't
pub trait ArchivalSink<I>: Send + io::Write + spanning::RecoverableWrite<I> {
    fn downcast_seek(&mut self) -> Option<&mut dyn io::Seek> {
        None
    }

    fn downcast_tapedevice(&mut self) -> Option<&mut dyn tape::TapeDevice> {
        None
    }
}

impl<I> ArchivalSink<I> for fs::File {
    fn downcast_seek(&mut self) -> Option<&mut dyn io::Seek> {
        Some(self)
    }
}

/// Open a sink object for writing an archive (aka "tape").
///
/// # Parameters
///
/// This function accepts the name of an output device and a blocking factor.
/// The output device's name must be interpreted within the operating system's
/// usual namespace for files and devices.
///
/// ## Blocking
///
/// Certain kinds of output devices are *record-oriented* and can be written to
/// in units of records. Notably, this includes tape devices. If blocking is
/// requested, then all writes will be buffered into records of this size, such
/// that the given data consists of a number of fixed records. (This includes
/// padding with nulls at the end of the file.) If the given device is not a
/// record-oriented device, or blocking is not requested, then a normal writer
/// will be constructed.
///
/// If your device supports records of variable length, requesting a blocking
/// factor of None will cause each write to the device to create a new record
/// of the given size.
///
/// # Returns
///
/// If the path given in outfile names a valid object of some kind that can be
/// written to, it will be opened and returned. Otherwise yields an error.
///
/// open_sink is permitted to return writers that write to any source,
/// including but not limited to:
///
///  - Files
///  - Standard output
///  - Magnetic tape drives
///  - Serial ports
///  - Nothing (e.g. /dev/null)
///
/// The only restriction is that such devices must exist within a platform
/// specified namespace and that outfile must name such a device within that
/// namespace. It is not permitted to implement nonstandard paths for accessing
/// other kinds of devices not normally exposed through a device or file
/// namespace, except in the case where the platform implements separate and
/// disjoint namespaces for each.
///
/// Due to the wide variety of sink devices, this function only returns
/// `io::Write`. For more specific access, consider using another function to
/// obtain a more suitable boxed trait object.
///
/// # Platform considerations
///
/// This is the portable version of the function. It supports writes to files
/// only. Platform-specific sink functions may support opening other kinds of
/// writers.
#[allow(unused_variables)]
pub fn open_sink<P: AsRef<path::Path>, I>(outfile: P, tuning: &Configuration, limit: Option<u64>) -> io::Result<Box<ArchivalSink<I>>> where ffi::OsString: From<P>, P: Clone, I: 'static + Send + Clone + PartialEq {
    let file = fs::File::create(outfile.as_ref())?;

    if let Some(limit) = limit {
        Ok(Box::new(spanning::LimitingWriter::wrap(file, limit)))
    } else {
        Ok(Box::new(file))
    }
}

/// Open an object for total control of a tape device.
///
/// # Parameters
///
/// This function accepts the name of an output device corresponding to a tape
/// device. The namespace exposed by `open_tape` must be identical to that of
/// `open_sink`, at least in the case where such names within the space
/// correspond to tape devices.
///
/// (e.g. `open_sink("/dev/nst0")` must match `open_tape("/dev/nst0")`, but
/// `open_tape("/dev/sda0")` is allowed to error.)
///
/// # Returns
///
/// If the path given in outfile names a valid tape device, a boxed
/// `tape::TapeDevice` will be returned by which you can control the tape.
///
/// It is implementation-defined whether it is allowed to open a tape device
/// twice. To avoid having to do that, `tape::TapeDevice` conveniently inherits
/// `io::Write`.
///
/// # Platform considerations
///
/// This is the portable version of the function. Since portable tape access
/// isn't a thing that makes sense, this function only returns errors.
pub fn open_tape<P: AsRef<path::Path>>(_tapedev: P) -> io::Result<Box<tape::TapeDevice>> where ffi::OsString: From<P>, P: Clone {
    Err(io::Error::new(io::ErrorKind::Other, "Magnetic tape control is not implemented for this operating system."))
}

/// Given a directory entry, produce valid Unix mode bits for it.
///
/// # Parameters
///
/// This function accepts one parameter, the metadata to be converted into mode
/// bits.
///
/// # Returns
///
/// If no errors occured, yields a valid Unix mode bit.
///
/// # Platform considerations
/// 
/// This is the portable version of the function. It creates a plausible set of
/// mode bits for platforms that either don't provide filesystem security, or
/// provide different security notions than what Unix supports.
///
/// Operating systems with security metadata of a different format may attempt
/// to emulate Unix mode bits. Such emulation is acceptable so long as the
/// permissions faithfully reflect actual read and write permissions granted to
/// one filesystem user, one filesystem group, and all other users on the
/// system. The security principals chosen for mode bit emulation may be
/// arbitrarily selected, subject to the following restrictions:
///
///  - The given user has ownership rights over the given file, e.g., has
///    permission to grant or revoke permissions to others.
///  - The given group is a security principal with those given permissions.
///  - The other bits faithfully represent the permissions afforded to every
///    user on the system, or failing that, the least privileged user on the
///    system.
/// 
/// TODO: Make a Windows (NT?) version of this that queries the Security API to
/// produce plausible mode bits.
pub fn get_unix_mode(metadata: &fs::Metadata) -> io::Result<u32> {
    if !metadata.is_dir() {
        if metadata.permissions().readonly() {
            Ok(0o444)
        } else {
            Ok(0o644)
        }
    } else {
        if metadata.permissions().readonly() {
            Ok(0o555)
        } else {
            Ok(0o755)
        }
    }
}

/// Given some metadata, produce a valid tar file type for it.
/// 
/// # Parameters
///
/// This function accepts one parameter, the metadata to be converted into mode
/// bits.
///
/// # Returns
///
/// If no errors occured, yields a valid filetype as defined by the abstract
/// filetype enum within the `tar` module.
///
/// # Platform considerations
///
/// This is the portable version of the function. It will always indicate a
/// directory, a file, or a symbolic link. It may error if the platform
/// implementation of `fs::Metadata` indicates none of the given file types
/// apply; however, this is a violation of Rust's specifications.
pub fn get_file_type(metadata: &fs::Metadata) -> io::Result<tar::header::TarFileType> {
    if metadata.file_type().is_dir() {
        Ok(tar::header::TarFileType::Directory)
    } else if metadata.file_type().is_file() {
        Ok(tar::header::TarFileType::FileStream)
    } else if metadata.file_type().is_symlink() {
        Ok(tar::header::TarFileType::SymbolicLink)
    } else {
        Err(io::Error::new(io::ErrorKind::InvalidInput, "Metadata did not yield any valid file type for tarball"))
    }
}

/// Determine the UNIX owner ID and name for a given file.
/// 
/// # Parameters
/// 
/// This function accepts two parameters, the metadata to be read and the path
/// which generated the metadata.
/// 
/// # Returns
/// 
/// If no errors occured, yields a tuple of the user's ID and name.
///
/// # Platform considerations
///
/// This is the portable version of the function. It will always indicate that
/// all files are owned by root.
pub fn get_unix_owner(_metadata: &fs::Metadata, _path: &path::Path) -> io::Result<(u32, String)> {
    Ok((0, "root".to_string()))
}

/// Determine the UNIX group ID and name for a given file.
/// 
/// # Parameters
/// 
/// This function accepts two parameters, the metadata to be read and the path
/// which generated the metadata.
/// 
/// # Returns
/// 
/// If no errors occured, yields a tuple of the group's ID and name.
///
/// # Platform considerations
///
/// This is the portable version of the function. It will always indicate that
/// all files are owned by the root group. (Some systems call this 'wheel'.)
pub fn get_unix_group(_metadata: &fs::Metadata, _path: &path::Path) -> io::Result<(u32, String)> {
    Ok((0, "root".to_string()))
}