Skip to content
Merged
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
25 changes: 17 additions & 8 deletions src/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use parking_lot::{Mutex, RwLock};

use crate::error::{BranchError, Result};
use crate::inode::ROOT_INO;
use crate::storage;

pub struct Branch {
pub name: String,
Expand Down Expand Up @@ -104,7 +105,7 @@ impl Branch {
}

pub fn has_delta(&self, rel_path: &str) -> bool {
self.delta_path(rel_path).exists()
self.delta_path(rel_path).symlink_metadata().is_ok()
}
}

Expand Down Expand Up @@ -419,7 +420,7 @@ impl BranchManager {
}

let base = self.base_path.join(rel_path.trim_start_matches('/'));
if base.exists() {
if base.symlink_metadata().is_ok() {
Ok(Some(base))
} else {
Ok(None)
Expand Down Expand Up @@ -476,8 +477,12 @@ impl BranchManager {
// Apply tombstones as deletions
for path in &child_tombstones {
let full_path = self.base_path.join(path.trim_start_matches('/'));
if full_path.exists() {
if full_path.is_dir() {
if full_path.symlink_metadata().is_ok() {
if full_path
.symlink_metadata()
.map(|m| m.file_type().is_dir())
.unwrap_or(false)
{
fs::remove_dir_all(&full_path)?;
} else {
fs::remove_file(&full_path)?;
Expand All @@ -493,10 +498,10 @@ impl BranchManager {
if let Some(parent_dir) = dest.parent() {
let _ = fs::create_dir_all(parent_dir);
}
if let Ok(meta) = src_path.metadata() {
if let Ok(meta) = src_path.symlink_metadata() {
total_bytes += meta.len();
}
let _ = fs::copy(src_path, &dest);
let _ = storage::copy_entry(src_path, &dest);
num_files += 1;
})?;

Expand Down Expand Up @@ -557,7 +562,7 @@ impl BranchManager {
if let Some(parent_dir) = dest.parent() {
let _ = fs::create_dir_all(parent_dir);
}
let _ = fs::copy(src_path, &dest);
let _ = storage::copy_entry(src_path, &dest);
copied_paths.push(rel_path.to_string());
})?;

Expand Down Expand Up @@ -661,7 +666,11 @@ impl BranchManager {
format!("{}/{}", prefix, name)
};

if path.is_dir() {
let is_dir = path
.symlink_metadata()
.map(|m| m.file_type().is_dir())
.unwrap_or(false);
if is_dir {
self.walk_files(&path, &rel_path, f)?;
} else {
f(&rel_path, &path);
Expand Down
144 changes: 141 additions & 3 deletions src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,10 @@ impl Filesystem for BranchFs {
return;
}
};
let is_dir = resolved.is_dir();
let is_dir = resolved
.symlink_metadata()
.map(|m| m.is_dir())
.unwrap_or(false);
let ino = self.inodes.get_or_create(&path, is_dir);
match self.make_attr(ino, &resolved) {
Some(attr) => reply.entry(&TTL, &attr, 0),
Expand Down Expand Up @@ -428,7 +431,10 @@ impl Filesystem for BranchFs {
};

let inode_path = format!("/@{}{}", branch, child_rel);
let is_dir = resolved.is_dir();
let is_dir = resolved
.symlink_metadata()
.map(|m| m.is_dir())
.unwrap_or(false);
let ino = self.inodes.get_or_create(&inode_path, is_dir);
match self.make_attr(ino, &resolved) {
Some(attr) => reply.entry(&TTL, &attr, 0),
Expand All @@ -449,7 +455,10 @@ impl Filesystem for BranchFs {
return;
}
};
let is_dir = resolved.is_dir();
let is_dir = resolved
.symlink_metadata()
.map(|m| m.is_dir())
.unwrap_or(false);
let ino = self.inodes.get_or_create(&path, is_dir);
match self.make_attr(ino, &resolved) {
Some(attr) => reply.entry(&TTL, &attr, 0),
Expand Down Expand Up @@ -1679,4 +1688,133 @@ impl Filesystem for BranchFs {
}
}
}

fn readlink(&mut self, _req: &Request, ino: u64, reply: ReplyData) {
let resolved = match self.classify_ino(ino) {
Some(PathContext::BranchPath(branch, rel_path)) => {
if !self.manager.is_branch_valid(&branch) {
reply.error(libc::ENOENT);
return;
}
match self.resolve_for_branch(&branch, &rel_path) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
}
}
Some(PathContext::RootPath(ref rp)) => {
if self.is_stale() {
reply.error(libc::ESTALE);
return;
}
match self.resolve(rp) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
}
}
_ => {
reply.error(libc::EINVAL);
return;
}
};

match std::fs::read_link(&resolved) {
Ok(target) => reply.data(target.as_os_str().as_encoded_bytes()),
Err(_) => reply.error(libc::EINVAL),
}
}

fn symlink(
&mut self,
_req: &Request,
parent: u64,
link_name: &OsStr,
target: &Path,
reply: ReplyEntry,
) {
let parent_path = match self.inodes.get_path(parent) {
Some(p) => p,
None => {
reply.error(libc::ENOENT);
return;
}
};

let name_str = link_name.to_string_lossy();

let branch_ctx = match classify_path(&parent_path) {
PathContext::BranchDir(b) => Some((b, "/".to_string())),
PathContext::BranchPath(b, rel) => Some((b, rel)),
_ => None,
};

if let Some((branch, parent_rel)) = branch_ctx {
if !self.manager.is_branch_valid(&branch) {
reply.error(libc::ENOENT);
return;
}
let rel_path = if parent_rel == "/" {
format!("/{}", name_str)
} else {
format!("{}/{}", parent_rel, name_str)
};
let delta = self.get_delta_path_for_branch(&branch, &rel_path);
if storage::ensure_parent_dirs(&delta).is_err() {
reply.error(libc::EIO);
return;
}
match std::os::unix::fs::symlink(target, &delta) {
Ok(()) => {
let inode_path = format!("/@{}{}", branch, rel_path);
let ino = self.inodes.get_or_create(&inode_path, false);
match self.make_attr(ino, &delta) {
Some(attr) => reply.entry(&TTL, &attr, 0),
None => reply.error(libc::EIO),
}
}
Err(_) => reply.error(libc::EIO),
}
} else {
match classify_path(&parent_path) {
PathContext::BranchCtl(_) | PathContext::RootCtl => {
reply.error(libc::EPERM);
}
PathContext::RootPath(rp) => {
let path = if rp == "/" {
format!("/{}", name_str)
} else {
format!("{}/{}", rp, name_str)
};
let delta = self.get_delta_path(&path);
if storage::ensure_parent_dirs(&delta).is_err() {
reply.error(libc::EIO);
return;
}
match std::os::unix::fs::symlink(target, &delta) {
Ok(()) => {
if self.is_stale() {
let _ = std::fs::remove_file(&delta);
reply.error(libc::ESTALE);
return;
}
let ino = self.inodes.get_or_create(&path, false);
match self.make_attr(ino, &delta) {
Some(attr) => reply.entry(&TTL, &attr, 0),
None => reply.error(libc::EIO),
}
}
Err(_) => reply.error(libc::EIO),
}
}
_ => {
reply.error(libc::ENOENT);
}
}
}
}
}
29 changes: 20 additions & 9 deletions src/fs_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@ impl BranchFs {
) -> std::io::Result<std::path::PathBuf> {
let delta = self.get_delta_path_for_branch(branch, rel_path);

if !delta.exists() {
if delta.symlink_metadata().is_err() {
if let Some(src) = self.resolve_for_branch(branch, rel_path) {
if src.exists() && src.is_file() {
storage::copy_file(&src, &delta)
.map_err(|e| std::io::Error::other(e.to_string()))?;
if let Ok(meta) = src.symlink_metadata() {
if meta.file_type().is_symlink() || meta.file_type().is_file() {
storage::copy_entry(&src, &delta)
.map_err(|e| std::io::Error::other(e.to_string()))?;
}
}
}
}
Expand All @@ -65,7 +67,7 @@ impl BranchFs {
}

pub(crate) fn make_attr(&self, ino: u64, path: &Path) -> Option<FileAttr> {
let meta = std::fs::metadata(path).ok()?;
let meta = std::fs::symlink_metadata(path).ok()?;
let kind = if meta.is_dir() {
FileType::Directory
} else if meta.is_symlink() {
Expand Down Expand Up @@ -172,9 +174,13 @@ impl BranchFs {
format!("{}/{}", rel_path, name)
};
let inode_path = format!("{}{}", inode_prefix, child_rel);
let is_dir = entry.path().is_dir();
let ft = entry.file_type();
let is_symlink = ft.as_ref().map(|t| t.is_symlink()).unwrap_or(false);
let is_dir = !is_symlink && ft.as_ref().map(|t| t.is_dir()).unwrap_or(false);
let child_ino = self.inodes.get_or_create(&inode_path, is_dir);
let kind = if is_dir {
let kind = if is_symlink {
FileType::Symlink
} else if is_dir {
FileType::Directory
} else {
FileType::RegularFile
Expand All @@ -197,9 +203,14 @@ impl BranchFs {
format!("{}/{}", rel_path, name)
};
let inode_path = format!("{}{}", inode_prefix, child_rel);
let is_dir = entry.path().is_dir();
let ft = entry.file_type();
let is_symlink = ft.as_ref().map(|t| t.is_symlink()).unwrap_or(false);
let is_dir =
!is_symlink && ft.as_ref().map(|t| t.is_dir()).unwrap_or(false);
let child_ino = self.inodes.get_or_create(&inode_path, is_dir);
let kind = if is_dir {
let kind = if is_symlink {
FileType::Symlink
} else if is_dir {
FileType::Directory
} else {
FileType::RegularFile
Expand Down
16 changes: 16 additions & 0 deletions src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ pub fn copy_file(src: &Path, dst: &Path) -> Result<()> {
Ok(())
}

/// Symlink-aware copy: if `src` is a symlink, recreate the symlink at `dst`;
/// otherwise fall back to a regular file copy.
pub fn copy_entry(src: &Path, dst: &Path) -> Result<()> {
ensure_parent_dirs(dst)?;
let meta = src.symlink_metadata()?;
if meta.file_type().is_symlink() {
let target = fs::read_link(src)?;
// Remove any pre-existing entry at dst so symlink() won't fail
let _ = fs::remove_file(dst);
std::os::unix::fs::symlink(&target, dst)?;
} else {
fs::copy(src, dst)?;
}
Ok(())
}

pub fn read_file(path: &Path) -> Result<Vec<u8>> {
let mut file = File::open(path)?;
let mut buf = Vec::new();
Expand Down
Loading