From 6dd0d7f0313630c53ebe0e6d41430f842b14b5b3 Mon Sep 17 00:00:00 2001 From: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> Date: Fri, 21 Jun 2024 20:22:38 +0900 Subject: [PATCH] Init a selinux project (#2800) * selinux_init Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * use_unimplemented Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * add_explanation Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * update Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * use_path_instead_of_str Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * use_thiserror Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * use_struct Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> --------- Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> --- Cargo.toml | 2 +- experiment/selinux/Cargo.lock | 102 ++++++ experiment/selinux/Cargo.toml | 16 + experiment/selinux/README.md | 7 + experiment/selinux/src/lib.rs | 2 + experiment/selinux/src/selinux.rs | 469 ++++++++++++++++++++++++++ experiment/selinux/src/xattr/mod.rs | 3 + experiment/selinux/src/xattr/xattr.rs | 65 ++++ 8 files changed, 665 insertions(+), 1 deletion(-) create mode 100644 experiment/selinux/Cargo.lock create mode 100644 experiment/selinux/Cargo.toml create mode 100644 experiment/selinux/README.md create mode 100644 experiment/selinux/src/lib.rs create mode 100644 experiment/selinux/src/selinux.rs create mode 100644 experiment/selinux/src/xattr/mod.rs create mode 100644 experiment/selinux/src/xattr/xattr.rs diff --git a/Cargo.toml b/Cargo.toml index c2484a0a..525babf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "2" members = ["crates/*", "tests/contest/*", "tools/*"] -exclude = ["experiment/seccomp"] +exclude = ["experiment/seccomp", "experiment/selinux"] [profile.release] lto = true diff --git a/experiment/selinux/Cargo.lock b/experiment/selinux/Cargo.lock new file mode 100644 index 00000000..d99af8ce --- /dev/null +++ b/experiment/selinux/Cargo.lock @@ -0,0 +1,102 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "proc-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "selinux" +version = "0.1.0" +dependencies = [ + "nix", + "thiserror", +] + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/experiment/selinux/Cargo.toml b/experiment/selinux/Cargo.toml new file mode 100644 index 00000000..2ad3a859 --- /dev/null +++ b/experiment/selinux/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "selinux" +version = "0.1.0" +description = "Library for selinux" +license-file = "../../LICENSE" +repository = "https://github.com/containers/youki" +homepage = "https://containers.github.io/youki" +readme = "README.md" +authors = ["youki team"] +edition = "2021" +autoexamples = true +keywords = ["youki", "container", "selinux"] + +[dependencies] +nix = { version = "0.29.0", features = ["process", "fs"] } +thiserror = "1.0.61" diff --git a/experiment/selinux/README.md b/experiment/selinux/README.md new file mode 100644 index 00000000..f16b4841 --- /dev/null +++ b/experiment/selinux/README.md @@ -0,0 +1,7 @@ +This is an experimental project in order to create selinux library in Rust. +Ref: https://github.com/containers/youki/issues/2718. +Reimplementation of (selinux)[https://github.com/opencontainers/selinux] in Rust. +Also selinux depends on xattr, but nix doesn't cover xattr function. +Therefore, this PR will implement xattr in Rust. + +Please import and use this project. diff --git a/experiment/selinux/src/lib.rs b/experiment/selinux/src/lib.rs new file mode 100644 index 00000000..e283b8c5 --- /dev/null +++ b/experiment/selinux/src/lib.rs @@ -0,0 +1,2 @@ +pub mod selinux; +pub mod xattr; diff --git a/experiment/selinux/src/selinux.rs b/experiment/selinux/src/selinux.rs new file mode 100644 index 00000000..b0d86ff2 --- /dev/null +++ b/experiment/selinux/src/selinux.rs @@ -0,0 +1,469 @@ +use crate::xattr::*; +use nix::unistd::gettid; +use nix::sys::statfs; +use nix::errno::Errno; +use std::path::{Path, PathBuf}; +use std::fs::File; +use std::io::Read; +use std::os::fd::{AsFd, AsRawFd}; +use std::sync::atomic::{AtomicBool, Ordering}; + +const XATTR_NAME_SELINUX: &str = "security.selinux"; +const ERR_EMPTY_PATH: &str = "empty path"; + +#[derive(Debug, thiserror::Error)] +pub enum SELinuxError { + #[error("Failed to set file label for SELinux: {0}")] + SetFileLabel(String), + #[error("Failed to lset file label for SELinux: {0}")] + LSetFileLabel(String), + #[error("Failed to get file label for SELinux: {0}")] + FileLabel(String), + #[error("Failed to get lfile label for SELinux: {0}")] + LFileLabel(String), + #[error("Failed to call is_proc_handle for SELinux: {0}")] + IsProcHandle(String), + #[error("Failed to call read_con_fd for SELinux: {0}")] + ReadConFd(String), + #[error("Failed to call read_con for SELinux: {0}")] + ReadCon(String), +} + +pub struct SELinux { + have_thread_self: AtomicBool, + init_done: AtomicBool, +} + +impl Default for SELinux { + fn default() -> Self { + SELinux::new() + } +} + +impl SELinux { + pub fn new() -> Self { + SELinux { + have_thread_self: AtomicBool::new(false), + init_done: AtomicBool::new(false), + } + } + + // function similar with setDisabled in go-selinux repo. + // set_disabled disables SELinux support for the package. + pub fn set_disabled() { + unimplemented!("not implemented yet") + } + + // function similar with getEnabled in go-selinux repo. + // get_enabled returns whether SELinux is enabled or not. + pub fn get_enabled() -> bool { + unimplemented!("not implemented yet") + } + + // function similar with classIndex in go-selinux repo. + // classIndex returns the int index for an object class in the loaded policy, + // or -1 and an error. + pub fn class_index(class: &str) -> Result { + unimplemented!("not implemented yet") + } + + // function similar with setFileLabel in go-selinux repo. + // set_file_label sets the SELinux label for this path, following symlinks, or returns an error. + pub fn set_file_label(fpath: &Path, label: &str) -> Result<(), SELinuxError> { + if !fpath.exists() { + return Err(SELinuxError::SetFileLabel(ERR_EMPTY_PATH.to_string())); + } + + loop { + match set_xattr(fpath, XATTR_NAME_SELINUX, label.as_bytes(), 0) { + Ok(_) => break, + // TODO: This line will be fixed after implementing set_xattr. + Err(EINTR) => continue, + Err(e) => { + return Err(SELinuxError::SetFileLabel( + format!("set_xattr failed: {}", e), + )); + } + } + } + Ok(()) + } + + // function similar with lSetFileLabel in go-selinux repo. + // lset_file_label sets the SELinux label for this path, not following symlinks, + // or returns an error. + pub fn lset_file_label(fpath: &Path, label: &str) -> Result<(), SELinuxError> { + if !fpath.exists() { + return Err(SELinuxError::LSetFileLabel(ERR_EMPTY_PATH.to_string())); + } + + loop { + match lset_xattr(fpath, XATTR_NAME_SELINUX, label.as_bytes(), 0) { + Ok(_) => break, + // TODO: This line will be fixed after implementing lset_xattr. + Err(EINTR) => continue, + Err(e) => { + return Err(SELinuxError::LSetFileLabel(format!("lset_xattr failed: {}", e))); + } + } + } + Ok(()) + } + + // function similar with fileLabel in go-selinux repo. + // fileLabel returns the SELinux label for this path, following symlinks, + // or returns an error. + pub fn file_label(fpath: &Path) -> Result { + if !fpath.exists() { + return Err(SELinuxError::FileLabel(ERR_EMPTY_PATH.to_string())); + } + get_xattr(fpath, XATTR_NAME_SELINUX) + .map_err(|e| SELinuxError::FileLabel(e.to_string())) + } + + // function similar with lFileLabel in go-selinux repo. + // lfile_label returns the SELinux label for this path, not following symlinks, + // or returns an error. + pub fn lfile_label(fpath: &Path) -> Result { + if !fpath.exists() { + return Err(SELinuxError::LFileLabel(ERR_EMPTY_PATH.to_string())); + } + lget_xattr(fpath, XATTR_NAME_SELINUX) + .map_err(|e| SELinuxError::LFileLabel(e.to_string())) + } + + // function similar with setFSCreateLabel in go-selinux repo. + // set_fscreate_label sets the default label the kernel which the kernel is using + // for file system objects. + pub fn set_fscreate_label(&self, label: &str) -> Result<(), SELinuxError> { + return Self::write_con(self.attr_path("fscreate").as_path(), label); + } + + // function similar with fsCreateLabel in go-selinux repo. + // fscreate_label returns the default label the kernel which the kernel is using + // for file system objects created by this task. "" indicates default. + pub fn fscreate_label(&self) -> Result { + return Self::read_con(self.attr_path("fscreate").as_path()); + } + + // function similar with currentLabel in go-selinux repo. + // current_label returns the SELinux label of the current process thread, or an error. + pub fn current_label(&self) -> Result { + return Self::read_con(self.attr_path("current").as_path()); + } + + // function similar with pidLabel in go-selinux repo. + // pid_label returns the SELinux label of the given pid, or an error. + pub fn pid_label(pid: i64) -> Result { + let file_name = &format!("/proc/{}/attr/current", pid); + let label = Path::new(file_name); + Self::read_con(label) + } + + // function similar with execLabel in go-selinux repo. + // exec_label returns the SELinux label that the kernel will use for any programs + // that are executed by the current process thread, or an error. + pub fn exec_label(&self) -> Result { + return Self::read_con(self.attr_path("exec").as_path()); + } + + // function similar with SetExecLabel in go-selinux repo. + // set_exec_label sets the SELinux label that the kernel will use for any programs + // that are executed by the current process thread, or an error. + pub fn set_exec_label(label: &str) { + unimplemented!("not implemented yet") + } + + // function similar with SetTaskLabel in go-selinux repo. + // set_task_label sets the SELinux label for the current thread, or an error. + // This requires the dyntransition permission. + pub fn set_task_label(label: &str) { + unimplemented!("not implemented yet") + } + + // function similar with SetSocketLabel in go-selinux repo. + // set_socket_label takes a process label and tells the kernel to assign the + // label to the next socket that gets created. + pub fn set_socket_label(label: &str) { + unimplemented!("not implemented yet") + } + + // function similar with SocketLabel in go-selinux repo. + // socket_label retrieves the current socket label setting. + pub fn socket_label() { + unimplemented!("not implemented yet") + } + + // function similar with peerLabel in go-selinux repo. + // peer_label retrieves the label of the client on the other side of a socket. + pub fn peer_label() { + unimplemented!("not implemented yet") + } + + // function similar with setKeyLabel in go-selinux repo. + // set_key_label takes a process label and tells the kernel to assign the + // label to the next kernel keyring that gets created. + pub fn set_key_label(label: &str) -> Result<(), SELinuxError> { + match Self::write_con(Path::new("/proc/self/attr/keycreate"), label) { + Ok(v) => Ok(v), + // TODO: This line will be fixed after implementing write_con. + Err(e) => Err(e), + } + } + + // function similar with KeyLabel in go-selinux repo. + // key_label retrieves the current kernel keyring label setting + pub fn key_label() { + unimplemented!("not implemented yet") + } + + // function similar with clearLabels in go-selinux repo. + // clear_labels clears all reserved labels. + pub fn clear_labels() { + unimplemented!("not implemented yet") + } + + // function similar with reserveLabel in go-selinux repo. + // reserve_label reserves the MLS/MCS level component of the specified label + pub fn reserve_label(label: &str) { + unimplemented!("not implemented yet") + } + + // function similar with roFileLabel in go-selinux repo. + // ro_file_label returns the specified SELinux readonly file label + pub fn ro_file_label() { + unimplemented!("not implemented yet") + } + + // function similar with kvmContainerLabels in go-selinux repo. + // kvm_container_labels returns the default processLabel and mountLabel to be used + // for kvm containers by the calling process. + pub fn kvm_container_labels() { + unimplemented!("not implemented yet") + } + + // function similar with initContainerLabels in go-selinux repo. + // init_container_labels returns the default processLabel and file labels to be + // used for containers running an init system like systemd by the calling process. + pub fn init_container_labels() { + unimplemented!("not implemented yet") + } + + // function similar with containerLabels in go-selinux repo. + // container_labels returns an allocated processLabel and fileLabel to be used for + // container labeling by the calling process. + pub fn container_labels() { + unimplemented!("not implemented yet") + } + + // function similar with PrivContainerMountLabel in go-selinux repo. + // priv_container_mount_label returns mount label for privileged containers. + pub fn priv_container_mount_label() { + unimplemented!("not implemented yet") + } + + // function similar with FormatMountLabel in go-selinux repo. + // format_mount_label returns a string to be used by the mount command. + // Using the SELinux `context` mount option. + // Changing labels of files on mount points with this option can never be changed. + // format_mount_label returns a string to be used by the mount command. + // The format of this string will be used to alter the labeling of the mountpoint. + // The string returned is suitable to be used as the options field of the mount command. + // If you need to have additional mount point options, you can pass them in as + // the first parameter. The second parameter is the label that you wish to apply + // to all content in the mount point. + pub fn format_mount_label(src: &str, mount_label: &str) -> String { + Self::format_mount_label_by_type(src, mount_label, "context") + } + + // function similar with FormatMountLabelByType in go-selinux repo. + // format_mount_label_by_type returns a string to be used by the mount command. + // Allow caller to specify the mount options. For example using the SELinux + // `fscontext` mount option would allow certain container processes to change + // labels of files created on the mount points, where as `context` option does not. + pub fn format_mount_label_by_type(src: &str, mount_label: &str, context_type: &str) -> String { + let mut formatted_src = src.to_owned(); + + if !mount_label.is_empty() { + if formatted_src.is_empty() { + formatted_src = format!("{}=\"{}\"", context_type, mount_label); + } else { + formatted_src = format!("{},{}=\"{}\"", formatted_src, context_type, mount_label); + } + } + formatted_src + } + + // function similar with writeCon in go-selinux repo. + pub fn write_con(fpath: &Path, val: &str) -> Result<(), SELinuxError> { + unimplemented!("not implemented yet"); + } + + // function similar with isProcHandle in go-selinux repo. + pub fn is_proc_handle(file: &File) -> Result<(), SELinuxError> { + loop { + match statfs::fstatfs(file.as_fd()) { + Ok(stat) if stat.filesystem_type() == statfs::PROC_SUPER_MAGIC => break, + Ok(_) => { + return Err(SELinuxError::IsProcHandle(format!("file {} is not on procfs", file.as_raw_fd()) + )); + }, + Err(Errno::EINTR) => continue, + Err(err) => { + return Err(SELinuxError::IsProcHandle(format!("fstatfs failed: {}", err))) + } + } + } + Ok(()) + } + + // function similar with readConFd in go-selinux repo. + pub fn read_con_fd(file: &mut File) -> Result { + let mut data = String::new(); + file.read_to_string(&mut data) + .map_err(|e| SELinuxError::ReadConFd(e.to_string()))?; + + // Remove null bytes on the end of a file. + let trimmed_data = data.trim_end_matches(char::from(0)); + Ok(trimmed_data.to_string()) + } + + // function similar with readCon in go-selinux repo. + pub fn read_con(fpath: &Path) -> Result { + if fpath.as_os_str().is_empty() { + return Err(SELinuxError::ReadCon(ERR_EMPTY_PATH.to_string())); + } + let mut in_file = File::open(fpath) + .map_err(|e| SELinuxError::ReadCon(format!("failed to open file: {}", e)))?; + + Self::is_proc_handle(&in_file)?; + Self::read_con_fd(&mut in_file) + } + + // function similar with attrPath in go-selinux repo. + // attr_path determines the correct file path for accessing SELinux + // attributes of a process or thread in a Linux environment. + pub fn attr_path(&self, attr: &str) -> PathBuf { + // Linux >= 3.17 provides this + const THREAD_SELF_PREFIX: &str = "/proc/thread-self/attr"; + // Avoiding code conflicts and ensuring thread-safe execution once only. + if !self.init_done.load(Ordering::SeqCst) { + let path = PathBuf::from(THREAD_SELF_PREFIX); + let is_dir = path.is_dir(); + self.have_thread_self.store(is_dir, Ordering::SeqCst); + self.init_done.store(true, Ordering::SeqCst); + } + if self.have_thread_self.load(Ordering::SeqCst) { + return PathBuf::from(&format!("{}/{}", THREAD_SELF_PREFIX, attr)); + } + + PathBuf::from(&format!("/proc/self/task/{}/attr/{}", gettid(), attr)) + } +} + +#[cfg(test)] +mod tests { + use crate::selinux::*; + use std::fs::{self, File}; + use std::path::Path; + use std::io::Write; + + fn create_temp_file(content: &[u8], file_name: &str) { + let path = Path::new(file_name); + let mut file = File::create(&path).expect("Failed to create file"); + file.write_all(content).expect("Failed to write to file"); + file.sync_all().expect("Failed to sync file"); + } + + #[test] + fn test_format_mount_label() { + let src_array = ["", "src", "src"]; + let mount_label_array = ["foobar", "foobar", ""]; + let expected_array = ["context=\"foobar\"", "src,context=\"foobar\"", "src"]; + for (i, src) in src_array.iter().enumerate() { + let mount_label = mount_label_array[i]; + let expected = expected_array[i]; + assert_eq!( + SELinux::format_mount_label(src, mount_label), + expected + ); + } + } + + #[test] + fn test_format_mount_label_by_type() { + let src_array = ["", "src", "src"]; + let mount_label_array = ["foobar", "foobar", ""]; + let context_array = ["fscontext", "fscontext", "rootcontext"]; + let expected_array = ["fscontext=\"foobar\"", "src,fscontext=\"foobar\"", "src"]; + for (i, src) in src_array.iter().enumerate() { + let mount_label = mount_label_array[i]; + let context = context_array[i]; + let expected = expected_array[i]; + assert_eq!( + SELinux::format_mount_label_by_type(src, mount_label, context), + expected + ); + } + } + + #[test] + fn test_read_con_fd() { + let content_array: Vec<&[u8]> = vec![b"Hello, world\0", b"Hello, world\0\0\0", b"Hello,\0world"]; + let expected_array = ["Hello, world", "Hello, world", "Hello,\0world"]; + let file_name = "test.txt"; + for (i, content) in content_array.iter().enumerate() { + let expected = expected_array[i]; + create_temp_file(content, file_name); + // Need to open again to get read permission. + let mut file = File::open(file_name).expect("Failed to open file"); + let result = SELinux::read_con_fd(&mut file).expect("Failed to read file"); + assert_eq!(result, expected); + fs::remove_file(file_name).expect("Failed to remove test file"); + } + } + + #[test] + fn test_attr_path() { + let selinux = SELinux::new(); + // Test with "/proc/thread-self/attr" path (Linux >= 3.17) + let attr = "bar"; + let expected_name = &format!("/proc/thread-self/attr/{}", attr); + let expected_path = Path::new(expected_name); + let actual_path = selinux.attr_path(attr); + assert_eq!(expected_path, actual_path); + + // Test with not having "/proc/thread-self/attr" path by setting HAVE_THREAD_SELF as false + selinux.init_done.store(true, Ordering::SeqCst); + selinux.have_thread_self.store(false, Ordering::SeqCst); + let thread_id = gettid(); + let expected_name = &format!("/proc/self/task/{}/attr/{}", thread_id, attr); + let expected_path = Path::new(expected_name); + let actual_path = selinux.attr_path(attr); + assert_eq!(expected_path, actual_path); + } + + #[test] + fn test_is_proc_handle() { + let filename_array = ["/proc/self/status", "/tmp/testfile"]; + let expected_array = [true, false]; + + for (i, filename) in filename_array.iter().enumerate() { + let expected_ok = expected_array[i]; + let path = Path::new(filename); + let file = match File::open(path) { + Ok(file) => file, + Err(_) => { + create_temp_file(b"", filename); + File::open(path).expect("failed to open file") + } + }; + let result = SELinux::is_proc_handle(&file); + if expected_ok { + assert!(result.is_ok(), "Expected Ok, but got Err: {:?}", result); + } else { + assert!(result.is_err(), "Expected Err, but got Ok"); + } + } + } +} diff --git a/experiment/selinux/src/xattr/mod.rs b/experiment/selinux/src/xattr/mod.rs new file mode 100644 index 00000000..df4efc21 --- /dev/null +++ b/experiment/selinux/src/xattr/mod.rs @@ -0,0 +1,3 @@ +mod xattr; + +pub use xattr::*; diff --git a/experiment/selinux/src/xattr/xattr.rs b/experiment/selinux/src/xattr/xattr.rs new file mode 100644 index 00000000..b8baaaff --- /dev/null +++ b/experiment/selinux/src/xattr/xattr.rs @@ -0,0 +1,65 @@ +use std::path::Path; + +#[derive(Debug, thiserror::Error)] +pub enum XattrError { + #[error("Failed to set_xattr: {0}")] + SetXattr(String), + #[error("Failed to lset_xattr: {0}")] + LSetXattr(String), + #[error("Failed to get_xattr: {0}")] + GetXattr(String), + #[error("Failed to call lget_xattr: {0}")] + LGetXattr(String), +} + +// function similar with setxattr in golang.org/x/sys/unix repo. +// set_xattr sets extended attributes on a file specified by its path. +pub fn set_xattr(fpath: &Path, attr: &str, data: &[u8], flags: i64) -> Result<(), XattrError> { + unimplemented!("not implemented yet") +} + +// function similar with lsetxattr in golang.org/x/sys/unix repo. +// lset_xattr sets extended attributes on a symbolic link. +pub fn lset_xattr(fpath: &Path, attr: &str, data: &[u8], flags: i64) -> Result<(), XattrError> { + unimplemented!("not implemented yet") +} + +// function similar with getattr in go-selinux repo. +// get_xattr returns the value of an extended attribute attr set for path. +pub fn get_xattr(fpath: &Path, attr: &str) -> Result { + unimplemented!("not implemented yet") + /* + match label { + Ok(mut v) => { + if (!v.is_empty()) && (v.chars().last() == Some('\x00')) { + v = (&v[0..v.len() - 1]).to_string(); + } + return Ok(v); + }, + Err(e) => return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("get_xattr failed: {}", e), + )) + } + */ +} + +// function similar with lgetxattr in go-selinux repo. +// lget_xattr returns the value of an extended attribute attr set for path. +pub fn lget_xattr(fpath: &Path, attr: &str) -> Result { + unimplemented!("not implemented yet") + /* + match label { + Ok(mut v) => { + if (!v.is_empty()) && (v.chars().last() == Some('\x00')) { + v = (&v[0..v.len() - 1]).to_string(); + } + return Ok(v); + }, + Err(e) => return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("lget_xattr failed: {}", e), + )) + } + */ +}