-
Notifications
You must be signed in to change notification settings - Fork 106
Expand file tree
/
Copy pathwrite_dir.rs
More file actions
144 lines (130 loc) · 4.54 KB
/
write_dir.rs
File metadata and controls
144 lines (130 loc) · 4.54 KB
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
//! Example to write a zip dir
//!
//! ```sh
//! cargo run --example write_dir src/ dest.zip xz
//! ```
use clap::{Parser, ValueEnum};
use walkdir::WalkDir;
use zip::{result::ZipError, write::SimpleFileOptions};
use std::fs::File;
use std::path::{Path, PathBuf};
#[derive(Parser)]
#[command(about, long_about = None)]
struct Args {
// Source directory
source: PathBuf,
// Destination zipfile
destination: PathBuf,
// Compression method
#[arg(value_enum)]
compression_method: CompressionMethod,
}
#[derive(Clone, ValueEnum)]
enum CompressionMethod {
Stored,
Deflated,
Bzip2,
Xz,
Zstd,
}
/// Used to test with --no-default-features or a specific features
/// ```sh
/// cargo run --no-default-features --example write_dir src/ dest.zip xz
/// # should error because no xz
///
/// cargo run --features xz --example write_dir src/ dest.zip xz
/// # should work
/// ```
macro_rules! is_feature {
($feature:literal, $compression:expr) => {{
#[cfg(feature = $feature)]
{
Ok($compression)
}
#[cfg(not(feature = $feature))]
{
Err(format!("The `{}` feature is not enabled", $feature).into())
}
}};
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let src_dir = &args.source;
let dest_file = &args.destination;
let method: Result<zip::CompressionMethod, Box<dyn std::error::Error>> =
match args.compression_method {
CompressionMethod::Stored => Ok(zip::CompressionMethod::Stored),
// _deflate-any and _bzip2_any are enabled by their respective implementations
CompressionMethod::Deflated => {
is_feature!("_deflate-any", zip::CompressionMethod::Deflated)
}
CompressionMethod::Bzip2 => is_feature!("_bzip2_any", zip::CompressionMethod::Bzip2),
CompressionMethod::Xz => is_feature!("xz", zip::CompressionMethod::Xz),
CompressionMethod::Zstd => is_feature!("zstd", zip::CompressionMethod::Zstd),
};
let method = method?;
zip_dir(src_dir, dest_file, method)?;
println!("done: {src_dir:?} written to {dest_file:?}");
Ok(())
}
fn zip_dir(
src_dir: &Path,
dest_file: &Path,
method: zip::CompressionMethod,
) -> Result<(), Box<dyn std::error::Error>> {
if !Path::new(src_dir).is_dir() {
return Err(ZipError::FileNotFound.into());
}
if dest_file.exists() {
return Err(format!("File {} already exists", dest_file.display()).into());
}
let file = File::create(dest_file)?;
let walkdir = WalkDir::new(src_dir);
let mut zip = zip::ZipWriter::new(file);
let options = SimpleFileOptions::default()
.compression_method(method)
.unix_permissions(0o755);
// SECURITY NOTE: Any error after this point may leave a partial or corrupt
// zip file.
// This can lead to data integrity issues or race conditions (e.g., TOCTOU)
// if other processes access the incomplete file. A robust application
// should mitigate this, for example by writing to a temporary file and
// renaming it on success to ensure atomicity.
for entry_result in walkdir.into_iter() {
let entry = match entry_result {
Ok(entry) => entry,
Err(e) => {
return Err(format!("Error while traversing directory {src_dir:?}: {e}").into());
}
};
let path = entry.path();
let path_stripped = path.strip_prefix(src_dir)?;
let path_as_string = path_stripped
.to_str()
.map(str::to_owned)
.ok_or_else(|| format!("{:?} is a Non UTF-8 Path", path_stripped.display()))?;
// Write file or directory explicitly
// Some unzip tools unzip files with directory paths correctly, some do not!
if path.is_file() {
println!(
"adding file {:?} as {:?} ...",
path.display(),
path_stripped.display()
);
zip.start_file(path_as_string, options)?;
let mut f = File::open(path)?;
std::io::copy(&mut f, &mut zip)?;
} else if !path_stripped.as_os_str().is_empty() {
// Only if not root! Avoids path spec / warning
// and mapname conversion failed error on unzip
println!(
"adding dir '{}' as '{}' ...",
path.display(),
path_stripped.display()
);
zip.add_directory(path_as_string, options)?;
}
}
zip.finish()?;
Ok(())
}