Skip to content

Support large paths on windows #112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/combiner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
use anyhow::{bail, Context, Result};
use std::io::{Read, Write};
use std::path::Path;
use std::path::PathBuf;
use tar::Archive;

actor! {
Expand Down Expand Up @@ -34,10 +35,10 @@ actor! {
non_installed_overlay: String = "",

/// The directory to do temporary work.
work_dir: String = "./workdir",
work_dir: PathBuf = "./workdir",

/// The location to put the final image and tarball.
output_dir: String = "./dist",
output_dir: PathBuf = "./dist",

/// The formats used to compress the tarball
compression_formats: CompressionFormats = CompressionFormats::default(),
Expand All @@ -49,7 +50,7 @@ impl Combiner {
pub fn run(self) -> Result<()> {
create_dir_all(&self.work_dir)?;

let package_dir = Path::new(&self.work_dir).join(&self.package_name);
let package_dir = self.work_dir.join(&self.package_name);
if package_dir.exists() {
remove_dir_all(&package_dir)?;
}
Expand All @@ -73,14 +74,14 @@ impl Combiner {
.with_context(|| {
format!(
"unable to extract '{}' into '{}'",
&input_tarball, self.work_dir
&input_tarball, self.work_dir.display()
)
})?;

let pkg_name =
input_tarball.trim_end_matches(&format!(".tar.{}", compression.extension()));
let pkg_name = Path::new(pkg_name).file_name().unwrap();
let pkg_dir = Path::new(&self.work_dir).join(&pkg_name);
let pkg_dir = self.work_dir.join(&pkg_name);

// Verify the version number.
let mut version = String::new();
Expand Down Expand Up @@ -137,10 +138,10 @@ impl Combiner {

// Make the tarballs.
create_dir_all(&self.output_dir)?;
let output = Path::new(&self.output_dir).join(&self.package_name);
let output = self.output_dir.join(&self.package_name);
let mut tarballer = Tarballer::default();
tarballer
.work_dir(self.work_dir)
.work_dir(LongPath::new(self.work_dir))
.input(self.package_name)
.output(path_to_str(&output)?.into())
.compression_formats(self.compression_formats.clone());
Expand Down
17 changes: 9 additions & 8 deletions src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ actor! {
bulk_dirs: String = "",

/// The directory containing the installation medium
image_dir: String = "./install_image",
image_dir: LongPath = "./install_image",

/// The directory to do temporary work
work_dir: String = "./workdir",
work_dir: LongPath = "./workdir",

/// The location to put the final image and tarball
output_dir: String = "./dist",
output_dir: LongPath = "./dist",

/// The formats used to compress the tarball
compression_formats: CompressionFormats = CompressionFormats::default(),
Expand All @@ -50,9 +50,9 @@ actor! {
impl Generator {
/// Generates the actual installer tarball
pub fn run(self) -> Result<()> {
create_dir_all(&self.work_dir)?;
create_dir_all(&*self.work_dir)?;

let package_dir = Path::new(&self.work_dir).join(&self.package_name);
let package_dir = self.work_dir.join(&self.package_name);
if package_dir.exists() {
remove_dir_all(&package_dir)?;
}
Expand All @@ -78,7 +78,8 @@ impl Generator {

// Copy the overlay
if !self.non_installed_overlay.is_empty() {
copy_recursive(self.non_installed_overlay.as_ref(), &package_dir)?;
copy_recursive(self.non_installed_overlay.as_ref(), &package_dir)
.context("failed to copy overlay")?;
}

// Generate the install script
Expand All @@ -93,8 +94,8 @@ impl Generator {
scripter.run()?;

// Make the tarballs
create_dir_all(&self.output_dir)?;
let output = Path::new(&self.output_dir).join(&self.package_name);
create_dir_all(&*self.output_dir)?;
let output = self.output_dir.join(&self.package_name);
let mut tarballer = Tarballer::default();
tarballer
.work_dir(self.work_dir)
Expand Down
24 changes: 6 additions & 18 deletions src/tarballer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ actor! {
output: String = "./dist",

/// The folder in which the input is to be found.
work_dir: String = "./workdir",
work_dir: LongPath = "./workdir",

/// The formats used to compress the tarball.
compression_formats: CompressionFormats = CompressionFormats::default(),
Expand Down Expand Up @@ -50,19 +50,16 @@ impl Tarballer {
let buf = BufWriter::with_capacity(1024 * 1024, encoder);
let mut builder = Builder::new(buf);

let pool = rayon::ThreadPoolBuilder::new()
.num_threads(2)
.build()
.unwrap();
let pool = rayon::ThreadPoolBuilder::new().num_threads(2).build().unwrap();
pool.install(move || {
for path in dirs {
let src = Path::new(&self.work_dir).join(&path);
let src = self.work_dir.join(&path);
builder
.append_dir(&path, &src)
.with_context(|| format!("failed to tar dir '{}'", src.display()))?;
}
for path in files {
let src = Path::new(&self.work_dir).join(&path);
let src = self.work_dir.join(&path);
append_path(&mut builder, &src, &path)
.with_context(|| format!("failed to tar file '{}'", src.display()))?;
}
Expand Down Expand Up @@ -106,20 +103,11 @@ fn append_path<W: Write>(builder: &mut Builder<W>, src: &Path, path: &String) ->
}

/// Returns all `(directories, files)` under the source path.
fn get_recursive_paths<P, Q>(root: P, name: Q) -> Result<(Vec<String>, Vec<String>)>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let root = root.as_ref();
fn get_recursive_paths(root: &Path, name: impl AsRef<Path>) -> Result<(Vec<String>, Vec<String>)> {
let name = name.as_ref();

if !name.is_relative() && !name.starts_with(root) {
bail!(
"input '{}' is not in work dir '{}'",
name.display(),
root.display()
);
bail!("input '{}' is not in work dir '{}'", name.display(), root.display());
}

let mut dirs = vec![];
Expand Down
116 changes: 110 additions & 6 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use anyhow::{format_err, Context, Result};
use std::ffi::OsString;
use std::fs;
use std::path::Path;
use std::ops::Deref;
use std::path::{Component, Path, PathBuf, Prefix};
use walkdir::WalkDir;

// Needed to set the script mode to executable.
Expand All @@ -15,8 +17,7 @@ use std::os::windows::fs::symlink_file;

/// Converts a `&Path` to a UTF-8 `&str`.
pub fn path_to_str(path: &Path) -> Result<&str> {
path.to_str()
.ok_or_else(|| format_err!("path is not valid UTF-8 '{}'", path.display()))
path.to_str().ok_or_else(|| format_err!("path is not valid UTF-8 '{}'", path.display()))
}

/// Wraps `fs::copy` with a nicer error message.
Expand All @@ -28,9 +29,16 @@ pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<u64> {
} else {
let amt = fs::copy(&from, &to).with_context(|| {
format!(
"failed to copy '{}' to '{}'",
"failed to copy '{}' ({}) to '{}' ({}, parent {})",
from.as_ref().display(),
to.as_ref().display()
if from.as_ref().exists() { "exists" } else { "doesn't exist" },
to.as_ref().display(),
if to.as_ref().exists() { "exists" } else { "doesn't exist" },
if to.as_ref().parent().unwrap_or_else(|| Path::new("")).exists() {
"exists"
} else {
"doesn't exist"
},
)
})?;
Ok(amt)
Expand Down Expand Up @@ -97,7 +105,20 @@ pub fn remove_file<P: AsRef<Path>>(path: P) -> Result<()> {
/// Copies the `src` directory recursively to `dst`. Both are assumed to exist
/// when this function is called.
pub fn copy_recursive(src: &Path, dst: &Path) -> Result<()> {
copy_with_callback(src, dst, |_, _| Ok(()))
copy_with_callback(src, dst, |_, _| Ok(())).with_context(|| {
format!(
"failed to recursively copy '{}' ({}) to '{}' ({}, parent {})",
src.display(),
if src.exists() { "exists" } else { "doesn't exist" },
dst.display(),
if dst.exists() { "exists" } else { "doesn't exist" },
if dst.parent().unwrap_or_else(|| Path::new("")).exists() {
"exists"
} else {
"doesn't exist"
},
)
})
}

/// Copies the `src` directory recursively to `dst`. Both are assumed to exist
Expand All @@ -122,6 +143,89 @@ where
Ok(())
}

fn normalize_rest(path: PathBuf) -> PathBuf {
let mut new_components = vec![];
for component in path.components().skip(1) {
match component {
Component::Prefix(_) => unreachable!(),
Component::RootDir => new_components.clear(),
Component::CurDir => {}
Component::ParentDir => {
new_components.pop();
}
Component::Normal(component) => new_components.push(component),
}
}
new_components.into_iter().collect()
}

#[derive(Debug)]
pub struct LongPath(PathBuf);

impl LongPath {
pub fn new(path: PathBuf) -> Self {
let path = if cfg!(windows) {
// Convert paths to verbatim paths to ensure that paths longer than 255 characters work
match dbg!(path.components().next().unwrap()) {
Component::Prefix(prefix_component) => {
match prefix_component.kind() {
Prefix::Verbatim(_)
| Prefix::VerbatimUNC(_, _)
| Prefix::VerbatimDisk(_) => {
// Already a verbatim path.
path
}

Prefix::DeviceNS(dev) => {
let mut base = OsString::from("\\\\?\\");
base.push(dev);
Path::new(&base).join(normalize_rest(path))
}
Prefix::UNC(host, share) => {
let mut base = OsString::from("\\\\?\\UNC\\");
base.push(host);
base.push("\\");
base.push(share);
Path::new(&base).join(normalize_rest(path))
}
Prefix::Disk(_disk) => {
let mut base = OsString::from("\\\\?\\");
base.push(prefix_component.as_os_str());
Path::new(&base).join(normalize_rest(path))
}
}
}

Component::RootDir
| Component::CurDir
| Component::ParentDir
| Component::Normal(_) => {
return LongPath::new(dbg!(
std::env::current_dir().expect("failed to get current dir").join(&path)
));
}
}
} else {
path
};
LongPath(dbg!(path))
}
}

impl Into<LongPath> for &str {
fn into(self) -> LongPath {
LongPath::new(self.into())
}
}

impl Deref for LongPath {
type Target = Path;

fn deref(&self) -> &Path {
&self.0
}
}

/// Creates an "actor" with default values and setters for all fields.
macro_rules! actor {
($( #[ $attr:meta ] )+ pub struct $name:ident {
Expand Down