Skip to content

Commit fa3f798

Browse files
authored
fix(bootstrap): use append_path_with_name for tar paths exceeding 100 bytes (#721)
Replace manual Header::set_path() + append() with builder.append_path_with_name() which emits GNU LongName extensions for paths exceeding the 100-byte POSIX tar name field limit. Fixes #705 Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
1 parent 0ec5da8 commit fa3f798

File tree

1 file changed

+36
-30
lines changed

1 file changed

+36
-30
lines changed

crates/openshell-bootstrap/src/build.rs

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
//! to import the image into the gateway's containerd runtime.
99
1010
use std::collections::HashMap;
11-
use std::io::Read;
1211
use std::path::Path;
1312

1413
use bollard::Docker;
@@ -176,36 +175,10 @@ fn walk_and_add(
176175
if path.is_dir() {
177176
walk_and_add(root, &path, ignore_patterns, builder)?;
178177
} else {
179-
let mut file = std::fs::File::open(&path)
180-
.into_diagnostic()
181-
.wrap_err_with(|| format!("failed to open file: {}", path.display()))?;
182-
let metadata = file
183-
.metadata()
184-
.into_diagnostic()
185-
.wrap_err_with(|| format!("failed to read metadata: {}", path.display()))?;
186-
187-
let mut header = tar::Header::new_gnu();
188-
header.set_size(metadata.len());
189-
header.set_mode(0o644);
190-
#[cfg(unix)]
191-
{
192-
use std::os::unix::fs::PermissionsExt;
193-
header.set_mode(metadata.permissions().mode());
194-
}
195-
header
196-
.set_path(&relative_normalized)
197-
.into_diagnostic()
198-
.wrap_err_with(|| format!("failed to set tar entry path: {relative_normalized}"))?;
199-
header.set_cksum();
200-
201-
#[allow(clippy::cast_possible_truncation)]
202-
let mut contents = Vec::with_capacity(metadata.len() as usize);
203-
file.read_to_end(&mut contents)
204-
.into_diagnostic()
205-
.wrap_err_with(|| format!("failed to read file: {}", path.display()))?;
206-
178+
// Use append_path_with_name which handles GNU LongName extensions
179+
// for paths exceeding 100 bytes (the POSIX tar name field limit).
207180
builder
208-
.append(&header, contents.as_slice())
181+
.append_path_with_name(&path, &relative_normalized)
209182
.into_diagnostic()
210183
.wrap_err_with(|| format!("failed to add file to tar: {relative_normalized}"))?;
211184
}
@@ -433,6 +406,39 @@ mod tests {
433406
assert!(entries.iter().any(|e| e.contains("important.log")));
434407
}
435408

409+
#[test]
410+
fn test_long_path_exceeding_100_bytes() {
411+
let dir = tempfile::tempdir().unwrap();
412+
let dir_path = dir.path();
413+
414+
// Build a nested path that exceeds 100 bytes when relative to root.
415+
let deep_dir = dir_path.join(
416+
"a/deeply/nested/directory/path/that/exceeds/one/hundred/bytes/total/from/the/build/context/root",
417+
);
418+
fs::create_dir_all(&deep_dir).unwrap();
419+
fs::write(deep_dir.join("file.txt"), "deep content\n").unwrap();
420+
fs::write(dir_path.join("Dockerfile"), "FROM ubuntu:24.04\n").unwrap();
421+
422+
let tar_bytes = create_build_context_tar(dir_path).unwrap();
423+
let mut archive = tar::Archive::new(tar_bytes.as_slice());
424+
let entries: Vec<String> = archive
425+
.entries()
426+
.unwrap()
427+
.filter_map(std::result::Result::ok)
428+
.map(|e| e.path().unwrap().to_string_lossy().to_string())
429+
.collect();
430+
431+
let long_entry = entries.iter().find(|e| e.contains("file.txt"));
432+
assert!(
433+
long_entry.is_some(),
434+
"tar should contain deeply nested file; entries: {entries:?}"
435+
);
436+
assert!(
437+
long_entry.unwrap().len() > 100,
438+
"path should exceed 100 bytes to exercise GNU LongName handling"
439+
);
440+
}
441+
436442
#[test]
437443
fn test_simple_glob_match() {
438444
assert!(simple_glob_match("*.txt", "hello.txt"));

0 commit comments

Comments
 (0)