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
11 changes: 11 additions & 0 deletions core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions core/services/fs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ opendal-core = { path = "../../core", version = "0.55.0", default-features = fal
] }
serde = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["fs", "rt-multi-thread"] }

[target.'cfg(unix)'.dependencies]
xattr = "1"
2 changes: 2 additions & 0 deletions core/services/fs/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ impl Builder for FsBuilder {
write_can_append: true,
write_can_multi: true,
write_with_if_not_exists: true,
#[cfg(unix)]
write_with_user_metadata: true,

create_dir: true,
delete: true,
Expand Down
73 changes: 72 additions & 1 deletion core/services/fs/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.

use std::collections::HashMap;
use std::io::SeekFrom;
use std::path::Path;
use std::path::PathBuf;
Expand Down Expand Up @@ -79,11 +80,21 @@ impl FsCore {
} else {
EntryMode::Unknown
};
let m = Metadata::new(mode)
let mut m = Metadata::new(mode)
.with_content_length(meta.len())
.with_last_modified(Timestamp::try_from(
meta.modified().map_err(new_std_io_error)?,
)?);

// Read user metadata from xattr on Unix systems
#[cfg(unix)]
{
let user_metadata = Self::get_user_metadata(&p)?;
if !user_metadata.is_empty() {
m = m.with_user_metadata(user_metadata);
}
}

Ok(m)
}

Expand Down Expand Up @@ -205,4 +216,64 @@ impl FsCore {
.map_err(new_std_io_error)?;
Ok(())
}

/// Get user metadata from extended attributes on Unix systems.
///
/// Reads xattr in the "user." namespace and strips the prefix, allowing
/// interoperability with other applications that set user xattr.
#[cfg(unix)]
pub fn get_user_metadata(path: &Path) -> Result<HashMap<String, String>> {
let mut user_metadata = HashMap::new();

let attrs = match xattr::list(path) {
Ok(attrs) => attrs,
Err(e) => {
// xattr may not be supported on some filesystems, ignore the error
if e.kind() == std::io::ErrorKind::Unsupported {
return Ok(user_metadata);
}
// Also ignore "operation not supported" errors (ENOTSUP/EOPNOTSUPP)
// which may occur on filesystems that don't support xattr
if let Some(os_error) = e.raw_os_error() {
// ENOTSUP (95) and EOPNOTSUPP (95) on Linux
if os_error == 95 {
return Ok(user_metadata);
}
}
return Err(new_std_io_error(e));
}
};

for attr in attrs {
let attr_name = attr.to_string_lossy();
// Only read xattr in the "user." namespace and strip the prefix
if let Some(key) = attr_name.strip_prefix(XATTR_USER_PREFIX) {
if let Ok(Some(value)) = xattr::get(path, &attr) {
if let Ok(v) = String::from_utf8(value) {
user_metadata.insert(key.to_string(), v);
}
}
}
}

Ok(user_metadata)
}

/// Set user metadata as extended attributes on Unix systems.
///
/// Keys are automatically prefixed with "user." to comply with Linux xattr
/// naming requirements. This allows interoperability with other applications.
#[cfg(unix)]
pub fn set_user_metadata(path: &Path, user_metadata: &HashMap<String, String>) -> Result<()> {
for (key, value) in user_metadata {
let attr_name = format!("{XATTR_USER_PREFIX}{key}");
xattr::set(path, &attr_name, value.as_bytes()).map_err(new_std_io_error)?;
}
Ok(())
}
}

/// Prefix for user xattr namespace on Linux.
/// Using "user." as the standard namespace for user-defined attributes.
#[cfg(unix)]
const XATTR_USER_PREFIX: &str = "user.";
24 changes: 24 additions & 0 deletions core/services/fs/src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.

use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
Expand All @@ -34,12 +35,19 @@ pub struct FsWriter {
/// The temp_path is used to specify whether we should move to target_path after the file has been closed.
temp_path: Option<PathBuf>,
f: tokio::fs::File,
/// User metadata to be written to xattr on Unix systems.
#[cfg(unix)]
user_metadata: Option<HashMap<String, String>>,
}

impl FsWriter {
pub async fn create(core: Arc<FsCore>, path: &str, op: OpWrite) -> Result<Self> {
let target_path = core.ensure_write_abs_path(&core.root, path).await?;

// Store user metadata for later use on Unix systems.
#[cfg(unix)]
let user_metadata = op.user_metadata().cloned();

// Quick path while atomic_write_dir is not set.
if core.atomic_write_dir.is_none() {
let target_file = core.fs_write(&target_path, &op).await?;
Expand All @@ -48,6 +56,8 @@ impl FsWriter {
target_path,
temp_path: None,
f: target_file,
#[cfg(unix)]
user_metadata,
});
}

Expand Down Expand Up @@ -75,6 +85,8 @@ impl FsWriter {
target_path,
temp_path,
f,
#[cfg(unix)]
user_metadata,
})
}
}
Expand Down Expand Up @@ -104,6 +116,12 @@ impl oio::Write for FsWriter {
.map_err(new_std_io_error)?;
}

// Write user metadata to xattr on Unix systems.
#[cfg(unix)]
if let Some(ref user_metadata) = self.user_metadata {
FsCore::set_user_metadata(&self.target_path, user_metadata)?;
}

let file_meta = self.f.metadata().await.map_err(new_std_io_error)?;
let meta = Metadata::new(EntryMode::FILE)
.with_content_length(file_meta.len())
Expand Down Expand Up @@ -173,6 +191,12 @@ impl oio::PositionWrite for FsWriter {
.map_err(new_std_io_error)?;
}

// Write user metadata to xattr on Unix systems.
#[cfg(unix)]
if let Some(ref user_metadata) = self.user_metadata {
FsCore::set_user_metadata(&self.target_path, user_metadata)?;
}

let file_meta = f.metadata().map_err(new_std_io_error)?;
let mode = if file_meta.is_file() {
EntryMode::FILE
Expand Down
Loading