1#![cfg(any(windows, target_os = "linux"))]
9#![expect(clippy::field_reassign_with_default)] mod path;
12#[cfg(unix)]
13mod unix;
14#[cfg(windows)]
15mod windows;
16
17use std::collections::HashMap;
18use std::ffi::OsString;
19use std::path::Path;
20
21#[cfg(unix)]
22use unix as sys;
23#[cfg(windows)]
24use windows as sys;
25
26pub use path::PathBufExt;
27pub use path::PathExt;
28
29pub struct LxVolume {
60 inner: sys::LxVolume,
61}
62
63impl LxVolume {
66 pub fn new(root_path: impl AsRef<Path>) -> lx::Result<Self> {
68 Self::new_with_options(root_path, &LxVolumeOptions::new())
69 }
70
71 pub fn supports_stable_file_id(&self) -> bool {
87 self.inner.supports_stable_file_id()
88 }
89
90 pub fn lstat(&self, path: impl AsRef<Path>) -> lx::Result<lx::Stat> {
92 self.inner.lstat(path.as_ref()).map(|x| x.into())
93 }
94
95 pub fn statx(&self, path: impl AsRef<Path>) -> lx::Result<lx::StatEx> {
97 self.inner.lstat(path.as_ref())
98 }
99
100 pub fn set_attr(&self, path: impl AsRef<Path>, attr: SetAttributes) -> lx::Result<()> {
127 self.inner.set_attr(path.as_ref(), attr)
128 }
129
130 pub fn set_attr_stat(
144 &self,
145 path: impl AsRef<Path>,
146 attr: SetAttributes,
147 ) -> lx::Result<lx::Stat> {
148 self.inner.set_attr_stat(path.as_ref(), attr)
149 }
150
151 pub fn truncate(
163 &self,
164 path: impl AsRef<Path>,
165 size: lx::off_t,
166 thread_uid: lx::uid_t,
167 ) -> lx::Result<()> {
168 let mut attr = SetAttributes::default();
169 attr.size = Some(size);
170 attr.thread_uid = thread_uid;
171 self.set_attr(path, attr)
172 }
173
174 pub fn chmod(&self, path: impl AsRef<Path>, mode: lx::mode_t) -> lx::Result<()> {
187 let mut attr = SetAttributes::default();
188 attr.mode = Some(mode);
189 self.set_attr(path, attr)
190 }
191
192 pub fn chown(
199 &self,
200 path: impl AsRef<Path>,
201 uid: Option<lx::uid_t>,
202 gid: Option<lx::gid_t>,
203 ) -> lx::Result<()> {
204 let mut attr = SetAttributes::default();
205 attr.uid = uid;
206 attr.gid = gid;
207 self.set_attr(path, attr)
208 }
209
210 pub fn set_times(
214 &self,
215 path: impl AsRef<Path>,
216 atime: SetTime,
217 mtime: SetTime,
218 ) -> lx::Result<()> {
219 let mut attr = SetAttributes::default();
220 attr.atime = atime;
221 attr.mtime = mtime;
222 attr.ctime = SetTime::Now;
223 self.set_attr(path, attr)
224 }
225
226 pub fn open(
236 &self,
237 path: impl AsRef<Path>,
238 flags: i32,
239 options: Option<LxCreateOptions>,
240 ) -> lx::Result<LxFile> {
241 Ok(LxFile {
242 inner: self.inner.open(path.as_ref(), flags, options)?,
243 })
244 }
245
246 pub fn mkdir(&self, path: impl AsRef<Path>, options: LxCreateOptions) -> lx::Result<()> {
248 self.inner.mkdir(path.as_ref(), options)
249 }
250
251 pub fn mkdir_stat(
263 &self,
264 path: impl AsRef<Path>,
265 options: LxCreateOptions,
266 ) -> lx::Result<lx::Stat> {
267 self.inner.mkdir_stat(path.as_ref(), options)
268 }
269
270 pub fn symlink(
279 &self,
280 path: impl AsRef<Path>,
281 target: impl AsRef<lx::LxStr>,
282 options: LxCreateOptions,
283 ) -> lx::Result<()> {
284 self.inner.symlink(path.as_ref(), target.as_ref(), options)
285 }
286
287 pub fn symlink_stat(
301 &self,
302 path: impl AsRef<Path>,
303 target: impl AsRef<lx::LxStr>,
304 options: LxCreateOptions,
305 ) -> lx::Result<lx::Stat> {
306 self.inner
307 .symlink_stat(path.as_ref(), target.as_ref(), options)
308 }
309
310 pub fn read_link(&self, path: impl AsRef<Path>) -> lx::Result<lx::LxString> {
317 self.inner.read_link(path.as_ref())
318 }
319
320 pub fn unlink(&self, path: impl AsRef<Path>, flags: i32) -> lx::Result<()> {
329 self.inner.unlink(path.as_ref(), flags)
330 }
331
332 pub fn mknod(
338 &self,
339 path: impl AsRef<Path>,
340 options: LxCreateOptions,
341 device_id: lx::dev_t,
342 ) -> lx::Result<()> {
343 self.inner.mknod(path.as_ref(), options, device_id)
344 }
345
346 pub fn mknod_stat(
361 &self,
362 path: impl AsRef<Path>,
363 options: LxCreateOptions,
364 device_id: lx::dev_t,
365 ) -> lx::Result<lx::Stat> {
366 self.inner.mknod_stat(path.as_ref(), options, device_id)
367 }
368
369 pub fn rename(
378 &self,
379 path: impl AsRef<Path>,
380 new_path: impl AsRef<Path>,
381 flags: u32,
382 ) -> lx::Result<()> {
383 self.inner.rename(path.as_ref(), new_path.as_ref(), flags)
384 }
385
386 pub fn link(&self, path: impl AsRef<Path>, new_path: impl AsRef<Path>) -> lx::Result<()> {
388 self.inner.link(path.as_ref(), new_path.as_ref())
389 }
390
391 pub fn link_stat(
403 &self,
404 path: impl AsRef<Path>,
405 new_path: impl AsRef<Path>,
406 ) -> lx::Result<lx::Stat> {
407 self.inner.link_stat(path.as_ref(), new_path.as_ref())
408 }
409
410 pub fn stat_fs(&self, path: impl AsRef<Path>) -> lx::Result<lx::StatFs> {
420 self.inner.stat_fs(path.as_ref())
421 }
422
423 pub fn set_xattr(
442 &self,
443 path: impl AsRef<Path>,
444 name: impl AsRef<lx::LxStr>,
445 value: &[u8],
446 flags: i32,
447 ) -> lx::Result<()> {
448 self.inner
449 .set_xattr(path.as_ref(), name.as_ref(), value, flags)
450 }
451
452 pub fn get_xattr(
468 &self,
469 path: impl AsRef<Path>,
470 name: impl AsRef<lx::LxStr>,
471 value: Option<&mut [u8]>,
472 ) -> lx::Result<usize> {
473 self.inner.get_xattr(path.as_ref(), name.as_ref(), value)
474 }
475
476 pub fn list_xattr(&self, path: impl AsRef<Path>, list: Option<&mut [u8]>) -> lx::Result<usize> {
494 self.inner.list_xattr(path.as_ref(), list)
495 }
496
497 pub fn remove_xattr(
511 &self,
512 path: impl AsRef<Path>,
513 name: impl AsRef<lx::LxStr>,
514 ) -> lx::Result<()> {
515 self.inner.remove_xattr(path.as_ref(), name.as_ref())
516 }
517
518 fn new_with_options(
520 root_path: impl AsRef<Path>,
521 options: &LxVolumeOptions,
522 ) -> lx::Result<Self> {
523 Ok(Self {
524 inner: sys::LxVolume::new(root_path.as_ref(), options)?,
525 })
526 }
527}
528
529pub struct LxFile {
533 inner: sys::LxFile,
534}
535
536impl LxFile {
537 pub fn fstat(&self) -> lx::Result<lx::StatEx> {
539 self.inner.fstat()
540 }
541
542 pub fn set_attr(&self, attr: SetAttributes) -> lx::Result<()> {
566 self.inner.set_attr(attr)
567 }
568
569 pub fn truncate(&self, size: lx::off_t, thread_uid: lx::uid_t) -> lx::Result<()> {
581 let mut attr = SetAttributes::default();
582 attr.size = Some(size);
583 attr.thread_uid = thread_uid;
584 self.set_attr(attr)
585 }
586
587 pub fn chmod(&self, mode: lx::mode_t) -> lx::Result<()> {
595 let mut attr = SetAttributes::default();
596 attr.mode = Some(mode);
597 self.set_attr(attr)
598 }
599
600 pub fn chown(&self, uid: Option<lx::uid_t>, gid: Option<lx::gid_t>) -> lx::Result<()> {
607 let mut attr = SetAttributes::default();
608 attr.uid = uid;
609 attr.gid = gid;
610 self.set_attr(attr)
611 }
612
613 pub fn set_times(&self, atime: SetTime, mtime: SetTime) -> lx::Result<()> {
617 let mut attr = SetAttributes::default();
618 attr.atime = atime;
619 attr.mtime = mtime;
620 attr.ctime = SetTime::Now;
621 self.set_attr(attr)
622 }
623
624 pub fn pread(&self, buffer: &mut [u8], offset: lx::off_t) -> lx::Result<usize> {
630 self.inner.pread(buffer, offset)
631 }
632
633 pub fn pwrite(
648 &self,
649 buffer: &[u8],
650 offset: lx::off_t,
651 thread_uid: lx::uid_t,
652 ) -> lx::Result<usize> {
653 self.inner.pwrite(buffer, offset, thread_uid)
654 }
655
656 pub fn read_dir<F>(&mut self, offset: lx::off_t, callback: F) -> lx::Result<()>
671 where
672 F: FnMut(lx::DirEntry) -> lx::Result<bool>,
673 {
674 self.inner.read_dir(offset, callback)
675 }
676
677 pub fn fsync(&self, data_only: bool) -> lx::Result<()> {
681 self.inner.fsync(data_only)
682 }
683}
684
685#[derive(Clone)]
692pub struct LxVolumeOptions {
693 uid: Option<lx::uid_t>,
694 gid: Option<lx::uid_t>,
695 mode: Option<u32>,
696 default_uid: lx::uid_t,
697 default_gid: lx::gid_t,
698 umask: u32,
699 fmask: u32,
700 dmask: u32,
701 metadata: bool,
702 create_case_sensitive_dirs: bool,
703 sandbox: bool,
704 sandbox_disallowed_extensions: Vec<OsString>,
705 symlink_root: String,
706 override_xattrs: HashMap<String, Vec<u8>>,
707 readonly: bool,
708}
709
710impl LxVolumeOptions {
711 pub fn new() -> Self {
713 Self {
714 uid: None,
715 gid: None,
716 mode: None,
717 default_uid: 0,
718 default_gid: 0,
719 umask: u32::MAX,
720 fmask: u32::MAX,
721 dmask: u32::MAX,
722 metadata: false,
723 create_case_sensitive_dirs: false,
724 sandbox: false,
725 sandbox_disallowed_extensions: Vec::new(),
726 symlink_root: "".to_string(),
727 override_xattrs: HashMap::new(),
728 readonly: false,
729 }
730 }
731
732 pub fn from_option_string(option_string: &str) -> Self {
735 let mut options = Self::new();
736 for next in option_string.split(';') {
737 if next.is_empty() {
738 continue;
739 }
740 let (keyword, value) = match next.split_once('=') {
741 Some((k, v)) => (k, Some(v)),
742 None => (next, None),
743 };
744 match keyword {
745 "metadata" => {
746 if value.is_none() {
747 options.metadata(true);
748 } else {
749 tracing::warn!(value, "'metadata' option does not support value");
750 }
751 }
752 "case" => {
753 if let Some(value) = value {
754 if value == "dir" {
755 options.create_case_sensitive_dirs(true);
756 } else if value == "off" {
757 options.create_case_sensitive_dirs(false);
758 } else {
759 tracing::warn!(value, "Unrecognized 'case' option");
760 }
761 } else {
762 tracing::warn!("'case' option requires value");
763 }
764 }
765 "uid" => {
766 if let Some(value) = value {
767 if let Ok(uid) = value.parse::<u32>() {
768 options.uid(uid);
769 } else {
770 tracing::warn!(value, "Unrecognized value for 'uid'");
771 }
772 } else {
773 tracing::warn!("'uid' option requires value");
774 }
775 }
776 "gid" => {
777 if let Some(value) = value {
778 if let Ok(gid) = value.parse::<u32>() {
779 options.gid(gid);
780 } else {
781 tracing::warn!(value, "Unrecognized value for 'gid'");
782 }
783 } else {
784 tracing::warn!("'gid' option requires value");
785 }
786 }
787 "mode" => {
788 if let Some(value) = value {
789 if let Ok(mode) = value.parse::<u32>() {
790 if (mode & !0o777) == 0 {
791 options.mode(mode);
792 } else {
793 tracing::warn!(value, "Invalid 'mode' value");
794 }
795 } else {
796 tracing::warn!(value, "Unrecognized value for 'mode'");
797 }
798 } else {
799 tracing::warn!("'mode' option requires value");
800 }
801 }
802 "default_uid" => {
803 if let Some(value) = value {
804 if let Ok(uid) = value.parse::<u32>() {
805 options.default_uid(uid);
806 } else {
807 tracing::warn!(value, "Unrecognized value for 'uid'");
808 }
809 } else {
810 tracing::warn!("'default_uid' option requires value");
811 }
812 }
813 "default_gid" => {
814 if let Some(value) = value {
815 if let Ok(gid) = value.parse::<u32>() {
816 options.default_gid(gid);
817 } else {
818 tracing::warn!(value, "Unrecognized value for 'gid'");
819 }
820 } else {
821 tracing::warn!("'default_gid' option requires value");
822 }
823 }
824 "umask" => {
825 if let Some(value) = value {
826 if let Ok(umask) = value.parse::<u32>() {
827 if (umask & !0o777) == 0 {
828 options.umask(umask);
829 } else {
830 tracing::warn!(value, "Invalid 'umask' value");
831 }
832 } else {
833 tracing::warn!(value, "Unrecognized value for 'umask'");
834 }
835 } else {
836 tracing::warn!("'umask' option requires value");
837 }
838 }
839 "dmask" => {
840 if let Some(value) = value {
841 if let Ok(dmask) = value.parse::<u32>() {
842 if (dmask & !0o777) == 0 {
843 options.dmask(dmask);
844 } else {
845 tracing::warn!(value, "Invalid 'dmask' value");
846 }
847 } else {
848 tracing::warn!(value, "Unrecognized value for 'dmask'");
849 }
850 } else {
851 tracing::warn!("'dmask' option requires value");
852 }
853 }
854 "fmask" => {
855 if let Some(value) = value {
856 if let Ok(fmask) = value.parse::<u32>() {
857 if (fmask & !0o777) == 0 {
858 options.fmask(fmask);
859 } else {
860 tracing::warn!(value, "Invalid 'fmask' value");
861 }
862 } else {
863 tracing::warn!(value, "Unrecognized value for 'fmask'");
864 }
865 } else {
866 tracing::warn!("'fmask' option requires value");
867 }
868 }
869 "symlinkroot" => {
870 if let Some(value) = value {
871 options.symlink_root(value);
872 } else {
873 tracing::warn!("'symlinkroot' option requires value");
874 }
875 }
876 "xattr" => {
877 if let Some(value) = value {
878 let (xattr_key, xattr_val) = match value.split_once('=') {
879 Some(v) => v,
880 None => (value, ""),
881 };
882 options.override_xattr(xattr_key, xattr_val.as_bytes());
883 } else {
884 tracing::warn!("'xattr' option requires value");
885 }
886 }
887 "sandbox" => {
888 if value.is_none() {
889 options.sandbox(true);
890 } else {
891 tracing::warn!(value, "'sandbox' options does not support value");
892 }
893 }
894 "sandbox_disallowed_extensions" => {
895 if let Some(value) = value {
896 let extensions: Vec<&str> = value.split(',').collect();
897 options.sandbox_disallowed_extensions(extensions);
898 } else {
899 tracing::warn!("'sandbox_disallowed_extensions' option requires value");
900 }
901 }
902 "ro" => {
903 if value.is_none() {
904 options.readonly(true);
905 } else {
906 tracing::warn!(value, "'ro' option does not support value");
907 }
908 }
909 _ => tracing::warn!(option = %next, keyword, "Unrecognized mount option"),
910 }
911 }
912
913 options
914 }
915
916 pub fn new_volume(&self, root_path: impl AsRef<Path>) -> lx::Result<LxVolume> {
918 LxVolume::new_with_options(root_path, self)
919 }
920
921 pub fn uid(&mut self, uid: lx::uid_t) -> &mut Self {
923 self.uid = Some(uid);
924 self
925 }
926
927 pub fn gid(&mut self, gid: lx::gid_t) -> &mut Self {
929 self.gid = Some(gid);
930 self
931 }
932
933 pub fn mode(&mut self, mode: u32) -> &mut Self {
935 self.mode = Some(mode & 0o777);
936 self
937 }
938
939 pub fn default_uid(&mut self, uid: lx::uid_t) -> &mut Self {
941 self.default_uid = uid;
942 self
943 }
944
945 pub fn default_gid(&mut self, gid: lx::gid_t) -> &mut Self {
947 self.default_gid = gid;
948 self
949 }
950
951 pub fn umask(&mut self, umask: u32) -> &mut Self {
954 self.umask = !(umask & 0o7777);
955 self
956 }
957
958 pub fn fmask(&mut self, fmask: u32) -> &mut Self {
960 self.fmask = !(fmask & 0o7777);
961 self
962 }
963
964 pub fn dmask(&mut self, dmask: u32) -> &mut Self {
966 self.dmask = !(dmask & 0o7777);
967 self
968 }
969
970 pub fn metadata(&mut self, metadata: bool) -> &mut Self {
975 self.metadata = metadata;
976 self
977 }
978
979 pub fn sandbox(&mut self, enabled: bool) -> &mut Self {
983 self.sandbox = enabled;
984 self
985 }
986
987 pub fn sandbox_disallowed_extensions(&mut self, disallowed_extensions: Vec<&str>) -> &mut Self {
991 let mut disallowed_extensions = disallowed_extensions
992 .into_iter()
993 .map(OsString::from)
994 .map(|ext| ext.to_ascii_lowercase())
995 .collect();
996 self.sandbox_disallowed_extensions
997 .append(&mut disallowed_extensions);
998 self
999 }
1000
1001 pub fn create_case_sensitive_dirs(&mut self, create_case_sensitive_dirs: bool) -> &mut Self {
1009 self.create_case_sensitive_dirs = create_case_sensitive_dirs;
1010 self
1011 }
1012
1013 pub fn symlink_root(&mut self, symlink_root: &str) -> &mut Self {
1017 self.symlink_root = symlink_root.to_string();
1018 self
1019 }
1020
1021 pub fn override_xattr(&mut self, name: &str, val: &[u8]) -> &mut Self {
1028 let mut val_data = Vec::with_capacity(val.len());
1029 val_data.extend_from_slice(val);
1030 self.override_xattrs.insert(name.to_string(), val_data);
1031 self
1032 }
1033
1034 pub fn readonly(&mut self, readonly: bool) -> &mut Self {
1040 self.readonly = readonly;
1041 self
1042 }
1043
1044 pub fn is_readonly(&self) -> bool {
1046 self.readonly
1047 }
1048}
1049
1050impl Default for LxVolumeOptions {
1051 fn default() -> Self {
1052 Self::new()
1053 }
1054}
1055
1056#[derive(Default)]
1061pub struct LxCreateOptions {
1062 mode: lx::mode_t,
1063 #[cfg_attr(not(windows), expect(dead_code))]
1064 uid: lx::uid_t,
1065 #[cfg_attr(not(windows), expect(dead_code))]
1066 gid: lx::gid_t,
1067}
1068
1069impl LxCreateOptions {
1070 pub fn new(mode: lx::mode_t, uid: lx::uid_t, gid: lx::gid_t) -> Self {
1072 Self { mode, uid, gid }
1073 }
1074}
1075
1076#[derive(Default, Clone, Copy)]
1078pub struct SetAttributes {
1079 pub size: Option<lx::off_t>,
1081
1082 pub atime: SetTime,
1084
1085 pub mtime: SetTime,
1087
1088 pub ctime: SetTime,
1092
1093 pub mode: Option<lx::mode_t>,
1103
1104 pub uid: Option<lx::uid_t>,
1106
1107 pub gid: Option<lx::gid_t>,
1109
1110 pub thread_uid: lx::uid_t,
1121}
1122
1123#[derive(Clone, Copy, Default)]
1125pub enum SetTime {
1126 #[default]
1128 Omit,
1129 Set(std::time::Duration),
1131 Now,
1133}
1134
1135impl SetTime {
1136 pub fn is_omit(&self) -> bool {
1138 matches!(self, SetTime::Omit)
1139 }
1140}
1141
1142#[cfg(test)]
1143#[cfg_attr(all(test, unix), expect(unsafe_code))]
1145mod tests {
1146 use super::*;
1147 use std::collections::HashMap;
1148 use std::fs;
1149 use std::path::Path;
1150 use std::path::PathBuf;
1151 use std::time::Duration;
1152 use tempfile::TempDir;
1153
1154 #[test]
1155 fn lstat() {
1156 let env = TestEnv::new();
1157 env.create_file("testfile", "test");
1158 let stat = env.volume.lstat("testfile").unwrap();
1159 println!("{:#?}", stat);
1160 assert_ne!(stat.inode_nr, 0);
1161 assert_eq!(stat.link_count, 1);
1162 assert_eq!(stat.mode & lx::S_IFMT, lx::S_IFREG);
1163 assert_ne!(stat.mode & 0o777, 0);
1164 assert_eq!(stat.file_size, 4);
1165
1166 let result = env.volume.lstat("no_ent").unwrap_err();
1167 assert_eq!(result.value(), lx::ENOENT);
1168
1169 let stat = env.volume.lstat("").unwrap();
1170 println!("{:#?}", stat);
1171 assert_ne!(stat.inode_nr, 0);
1172 assert!(stat.link_count >= 1);
1173 assert_eq!(stat.mode & lx::S_IFMT, lx::S_IFDIR);
1174 assert_ne!(stat.mode & 0o777, 0);
1175 }
1176
1177 #[test]
1178 fn fstat() {
1179 let env = TestEnv::new();
1180 env.create_file("testfile", "test");
1181 let stat = env.volume.lstat("testfile").unwrap();
1182 let file = env.volume.open("testfile", lx::O_RDONLY, None).unwrap();
1183 let fstat = file.fstat().unwrap().into();
1184 println!("{:#?}", fstat);
1185 assert_eq!(stat, fstat);
1186
1187 let stat = env.volume.lstat("").unwrap();
1188 let file = env
1189 .volume
1190 .open("", lx::O_RDONLY | lx::O_DIRECTORY, None)
1191 .unwrap();
1192
1193 let fstat = file.fstat().unwrap().into();
1194 println!("{:#?}", fstat);
1195 assert_eq!(stat, fstat);
1196 }
1197
1198 #[test]
1199 fn read_write() {
1200 let env = TestEnv::new();
1201 let file = env
1202 .volume
1203 .open(
1204 "testfile",
1205 lx::O_RDWR | lx::O_CREAT | lx::O_EXCL,
1206 Some(LxCreateOptions::new(0o666, 0, 0)),
1207 )
1208 .unwrap();
1209
1210 assert_eq!(file.fstat().unwrap().file_size, 0);
1211
1212 assert_eq!(file.pwrite(b"Hello", 0, 0).unwrap(), 5);
1214 assert_eq!(file.fstat().unwrap().file_size, 5);
1215 assert_eq!(file.pwrite(b", world!", 5, 0).unwrap(), 8);
1216 assert_eq!(file.fstat().unwrap().file_size, 13);
1217
1218 let mut buffer = [0; 1024];
1220 assert_eq!(file.pread(&mut buffer, 0).unwrap(), 13);
1221 assert_eq!(&buffer[..13], b"Hello, world!");
1222
1223 assert_eq!(file.pread(&mut buffer, 13).unwrap(), 0);
1225
1226 assert_eq!(file.pwrite(b"Bye", 4, 0).unwrap(), 3);
1228 assert_eq!(file.fstat().unwrap().file_size, 13);
1229
1230 assert_eq!(file.pread(&mut buffer[..8], 2).unwrap(), 8);
1232 assert_eq!(&buffer[..8], b"llByewor");
1233
1234 let file = env.volume.open("testfile", lx::O_RDONLY, None).unwrap();
1236 assert_eq!(file.pwrite(b"Hello", 0, 0).unwrap_err().value(), lx::EBADF);
1237 assert_eq!(file.pread(&mut buffer, 0).unwrap(), 13);
1238
1239 let file = env.volume.open("testfile", lx::O_WRONLY, None).unwrap();
1241 assert_eq!(file.pread(&mut buffer, 0).unwrap_err().value(), lx::EBADF);
1242 assert_eq!(file.pwrite(b"Hello", 0, 0).unwrap(), 5);
1243
1244 let file = env.volume.open("testfile", lx::O_NOACCESS, None).unwrap();
1246 assert_eq!(file.pwrite(b"Hello", 0, 0).unwrap_err().value(), lx::EBADF);
1247 assert_eq!(file.pread(&mut buffer, 0).unwrap_err().value(), lx::EBADF);
1248 }
1249
1250 #[test]
1251 fn read_dir() {
1252 let env = TestEnv::new();
1253 let mut map = HashMap::new();
1254 map.insert(String::from("."), false);
1255 map.insert(String::from(".."), false);
1256 for i in 0..10 {
1257 let name = format!("file{}", i);
1258 env.create_file(&name, "test");
1259 assert!(map.insert(name, false).is_none());
1260 }
1261
1262 let mut dir = env
1263 .volume
1264 .open("", lx::O_RDONLY | lx::O_DIRECTORY, None)
1265 .unwrap();
1266
1267 let mut count = 0;
1268 let mut next_offset = 0;
1269 let mut seek_file = String::new();
1270 let mut seek_offset = 0;
1271 let mut prev_offset = 0;
1272
1273 dir.read_dir(0, |entry| {
1275 if count == 6 {
1276 return Ok(false);
1277 }
1278
1279 count += 1;
1280 next_offset = entry.offset;
1281 println!("Entry 1: {:?}", entry);
1282 env.check_dir_entry(&entry);
1283 let name = entry.name.to_str().unwrap();
1284 let found_entry = map.get_mut(name).unwrap();
1285 assert!(!*found_entry);
1286 *found_entry = true;
1287
1288 if count == 4 {
1290 seek_file = String::from(name);
1291 seek_offset = prev_offset;
1292 }
1293
1294 prev_offset = entry.offset;
1295 Ok(true)
1296 })
1297 .unwrap();
1298
1299 dir.read_dir(next_offset, |entry| {
1301 count += 1;
1302 println!("Entry 2: {:?}", entry);
1303 env.check_dir_entry(&entry);
1304 let name = entry.name.to_str().unwrap();
1305 let found_entry = map.get_mut(name).unwrap();
1306 assert!(!*found_entry);
1307 *found_entry = true;
1308 Ok(true)
1309 })
1310 .unwrap();
1311
1312 assert_eq!(count, 12);
1314 for (_, value) in map {
1315 assert!(value);
1316 }
1317
1318 count = 0;
1320 dir.read_dir(0, |_| {
1321 count += 1;
1322 Ok(true)
1323 })
1324 .unwrap();
1325
1326 assert_eq!(count, 12);
1327
1328 count = 0;
1330 dir.read_dir(seek_offset, |entry| {
1331 assert_eq!(entry.name.to_str().unwrap(), seek_file);
1332 count += 1;
1333 Ok(false)
1334 })
1335 .unwrap();
1336
1337 assert_eq!(count, 1);
1338
1339 count = 0;
1341 let error = dir
1342 .read_dir(0, |_| {
1343 count += 1;
1344 Err(lx::Error::ECONNREFUSED)
1345 })
1346 .unwrap_err();
1347
1348 assert_eq!(count, 1);
1349 assert_eq!(error.value(), lx::ECONNREFUSED);
1350 }
1351
1352 #[test]
1353 #[should_panic(expected = "at the disco")]
1354 fn read_dir_panic() {
1355 let env = TestEnv::new();
1356 env.create_file("testfile", "test");
1357 let mut dir = env
1358 .volume
1359 .open("", lx::O_RDONLY | lx::O_DIRECTORY, None)
1360 .unwrap();
1361
1362 dir.read_dir(0, |entry| {
1365 if entry.file_type == lx::DT_REG {
1366 panic!("at the disco");
1367 }
1368
1369 Ok(true)
1370 })
1371 .unwrap();
1372 }
1373
1374 #[test]
1375 fn metadata() {
1376 let env = TestEnv::with_options(LxVolumeOptions::new().metadata(true));
1377 let file = env
1378 .volume
1379 .open(
1380 "testfile",
1381 lx::O_RDWR | lx::O_CREAT | lx::O_EXCL,
1382 Some(LxCreateOptions::new(0o640, 1000, 2000)),
1383 )
1384 .unwrap();
1385
1386 let stat = file.fstat().unwrap();
1387 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o640);
1388 if cfg!(windows) {
1390 assert_eq!(stat.uid, 1000);
1391 assert_eq!(stat.gid, 2000);
1392 }
1393
1394 env.volume
1395 .mkdir("testdir", LxCreateOptions::new(0o751, 1001, 2001))
1396 .unwrap();
1397
1398 let stat = env.volume.lstat("testdir").unwrap();
1399 assert_eq!(stat.mode, lx::S_IFDIR | 0o751);
1400 if cfg!(windows) {
1401 assert_eq!(stat.uid, 1001);
1402 assert_eq!(stat.gid, 2001);
1403 }
1404
1405 let stat = env
1406 .volume
1407 .mkdir_stat("testdir2", LxCreateOptions::new(0o777, 1002, 2002))
1408 .unwrap();
1409
1410 assert_eq!(stat.mode, lx::S_IFDIR | 0o777);
1411 if cfg!(windows) {
1412 assert_eq!(stat.uid, 1002);
1413 assert_eq!(stat.gid, 2002);
1414 }
1415
1416 env.volume
1417 .symlink("testlink", "testdir", LxCreateOptions::new(0, 2000, 3000))
1418 .unwrap();
1419
1420 let stat = env.volume.lstat("testlink").unwrap();
1421 assert_eq!(stat.mode, lx::S_IFLNK | 0o777);
1422 assert_eq!(stat.file_size, 7);
1423 if cfg!(windows) {
1424 assert_eq!(stat.uid, 2000);
1425 assert_eq!(stat.gid, 3000);
1426 }
1427
1428 let stat = env
1429 .volume
1430 .symlink_stat("testlink2", "testdir2", LxCreateOptions::new(0, 2001, 3001))
1431 .unwrap();
1432
1433 assert_eq!(stat.mode, lx::S_IFLNK | 0o777);
1434 assert_eq!(stat.file_size, 8);
1435 if cfg!(windows) {
1436 assert_eq!(stat.uid, 2001);
1437 assert_eq!(stat.gid, 3001);
1438 }
1439
1440 let mut attr = SetAttributes::default();
1442 attr.mode = Some(lx::S_IFREG | 0o664);
1443 if cfg!(windows) {
1444 attr.uid = Some(2000);
1445 attr.gid = Some(3000);
1446 }
1447
1448 env.volume.set_attr("testfile", attr).unwrap();
1449 let stat = env.volume.lstat("testfile").unwrap();
1450 assert_eq!(stat.mode, lx::S_IFREG | 0o664);
1451 if cfg!(windows) {
1452 assert_eq!(stat.uid, 2000);
1453 assert_eq!(stat.gid, 3000);
1454 }
1455
1456 if is_lx_root() {
1458 env.volume
1459 .mknod(
1460 "testchr",
1461 LxCreateOptions::new(lx::S_IFCHR | 0o640, 1000, 2000),
1462 lx::make_dev(1, 5),
1463 )
1464 .unwrap();
1465
1466 let stat = env.volume.lstat("testchr").unwrap();
1467 assert_eq!(stat.mode, lx::S_IFCHR | 0o640);
1468 if cfg!(windows) {
1469 assert_eq!(stat.uid, 1000);
1470 assert_eq!(stat.gid, 2000);
1471 }
1472
1473 assert_eq!(lx::major64(stat.device_nr_special as lx::dev_t), 1);
1474 assert_eq!(lx::minor(stat.device_nr_special as lx::dev_t), 5);
1475
1476 let stat = env
1477 .volume
1478 .mknod_stat(
1479 "testblk",
1480 LxCreateOptions::new(lx::S_IFBLK | 0o660, 1001, 2001),
1481 lx::make_dev(2, 6),
1482 )
1483 .unwrap();
1484
1485 assert_eq!(stat.mode, lx::S_IFBLK | 0o660);
1486 if cfg!(windows) {
1487 assert_eq!(stat.uid, 1001);
1488 assert_eq!(stat.gid, 2001);
1489 }
1490
1491 assert_eq!(lx::major64(stat.device_nr_special as lx::dev_t), 2);
1492 assert_eq!(lx::minor(stat.device_nr_special as lx::dev_t), 6);
1493 }
1494
1495 env.volume
1496 .mknod(
1497 "testfifo",
1498 LxCreateOptions::new(lx::S_IFIFO | 0o666, 1002, 2002),
1499 lx::make_dev(2, 6),
1500 )
1501 .unwrap();
1502
1503 let stat = env.volume.lstat("testfifo").unwrap();
1504 assert_eq!(stat.mode, lx::S_IFIFO | 0o666);
1505 if cfg!(windows) {
1506 assert_eq!(stat.uid, 1002);
1507 assert_eq!(stat.gid, 2002);
1508 }
1509
1510 assert_eq!(stat.device_nr_special, 0);
1511
1512 let stat = env
1513 .volume
1514 .mknod_stat(
1515 "testsock",
1516 LxCreateOptions::new(lx::S_IFSOCK | 0o600, 1003, 2003),
1517 lx::make_dev(2, 6),
1518 )
1519 .unwrap();
1520
1521 assert_eq!(stat.mode, lx::S_IFSOCK | 0o600);
1522 if cfg!(windows) {
1523 assert_eq!(stat.uid, 1003);
1524 assert_eq!(stat.gid, 2003);
1525 }
1526
1527 assert_eq!(stat.device_nr_special, 0);
1528 }
1529
1530 #[test]
1531 fn path_escape() {
1532 let env = TestEnv::new();
1533 env.volume
1534 .mkdir("testdir", LxCreateOptions::new(0o777, 0, 0))
1535 .unwrap();
1536 env.create_file("testdir/testfile", "foo");
1537 env.volume
1538 .lstat(Path::from_lx("testdir/testfile").unwrap())
1539 .unwrap();
1540 let path = PathBuf::from_lx("testdir/foo:bar").unwrap();
1541 let file = env
1542 .volume
1543 .open(
1544 &path,
1545 lx::O_RDONLY | lx::O_CREAT,
1546 Some(LxCreateOptions::new(0o666, 0, 0)),
1547 )
1548 .unwrap();
1549
1550 let file_stat: lx::Stat = file.fstat().unwrap().into();
1551 assert_eq!(file_stat, env.volume.lstat(&path).unwrap());
1552 }
1553
1554 #[test]
1555 fn symlink() {
1556 let env = TestEnv::new();
1557 env.create_file("testdir/testfile", "foo");
1558 check_symlink(&env.volume, "testlink", "testdir/testfile");
1559 check_symlink(&env.volume, "testlink2", "doesntexit");
1560 check_symlink(&env.volume, "testlink3", "/proc");
1561 check_symlink(&env.volume, "testlink4", "../foo");
1562
1563 assert_eq!(
1564 env.volume.read_link("doesntexit").unwrap_err().value(),
1565 lx::ENOENT
1566 );
1567 assert_eq!(
1568 env.volume
1569 .read_link(Path::from_lx("testdir/testfile").unwrap())
1570 .unwrap_err()
1571 .value(),
1572 lx::EINVAL
1573 );
1574 }
1575
1576 #[test]
1577 fn unlink() {
1578 let env = TestEnv::new();
1579 env.create_file("testfile", "test");
1580 env.volume
1581 .mkdir("testdir", LxCreateOptions::new(0o777, 0, 0))
1582 .unwrap();
1583
1584 env.volume
1585 .symlink("testlink", "testfile", LxCreateOptions::new(0, 0, 0))
1586 .unwrap();
1587
1588 env.volume
1592 .symlink("testlink2", "testdir", LxCreateOptions::new(0, 0, 0))
1593 .unwrap();
1594
1595 check_unlink(&env.volume, "testfile", false);
1596 check_unlink(&env.volume, "testdir", true);
1597 check_unlink(&env.volume, "testlink", false);
1598 check_unlink(&env.volume, "testlink2", false);
1599
1600 env.volume
1601 .open(
1602 "readonly",
1603 lx::O_RDONLY | lx::O_CREAT | lx::O_EXCL,
1604 Some(LxCreateOptions::new(0o444, 0, 0)),
1605 )
1606 .unwrap();
1607 check_unlink(&env.volume, "readonly", false);
1608 }
1609
1610 #[test]
1611 fn set_attr() {
1612 let env = TestEnv::new();
1613 env.create_file("testfile", "test");
1614 let stat = env.volume.lstat("testfile").unwrap();
1615 assert_eq!(stat.file_size, 4);
1616
1617 let mut attr = SetAttributes::default();
1619 attr.size = Some(2);
1620 env.volume.set_attr("testfile", attr).unwrap();
1621 let stat = env.volume.lstat("testfile").unwrap();
1622 assert_eq!(stat.file_size, 2);
1623
1624 let mut attr = SetAttributes::default();
1626 attr.mode = Some(lx::S_IFREG | 0o444);
1627 env.volume.set_attr("testfile", attr).unwrap();
1628 let stat = env.volume.lstat("testfile").unwrap();
1629 assert_eq!(stat.mode & 0o222, 0);
1630 attr.mode = Some(lx::S_IFREG | 0o666);
1631 env.volume.set_attr("testfile", attr).unwrap();
1632 let stat = env.volume.lstat("testfile").unwrap();
1633 assert_eq!(stat.mode & 0o222, 0o222);
1634
1635 if cfg!(windows) {
1637 let mut attr = SetAttributes::default();
1638 attr.uid = Some(1000);
1639 attr.gid = Some(1000);
1640 env.volume.set_attr("testfile", attr).unwrap();
1641 }
1642
1643 let mut attr = SetAttributes::default();
1645 attr.atime = SetTime::Set(Duration::new(111111, 222200));
1646 attr.mtime = SetTime::Set(Duration::new(333333, 444400));
1647 let stat = env.volume.set_attr_stat("testfile", attr).unwrap();
1648 assert_eq!(stat.access_time.seconds, 111111);
1649 assert_eq!(stat.access_time.nanoseconds, 222200);
1650 assert_eq!(stat.write_time.seconds, 333333);
1651 assert_eq!(stat.write_time.nanoseconds, 444400);
1652 }
1653
1654 #[test]
1655 fn file_set_attr() {
1656 let env = TestEnv::new();
1657 env.create_file("testfile", "test");
1658
1659 let file = env.volume.open("testfile", lx::O_RDWR, None).unwrap();
1660 let stat = file.fstat().unwrap();
1661 assert_eq!(stat.file_size, 4);
1662
1663 let mut attr = SetAttributes::default();
1665 attr.size = Some(2);
1666 file.set_attr(attr).unwrap();
1667 let stat = file.fstat().unwrap();
1668 assert_eq!(stat.file_size, 2);
1669
1670 let mut attr = SetAttributes::default();
1672 attr.mode = Some(lx::S_IFREG | 0o444);
1673 file.set_attr(attr).unwrap();
1674 let stat = file.fstat().unwrap();
1675 assert_eq!(stat.mode & 0o222, 0);
1676 attr.mode = Some(lx::S_IFREG | 0o666);
1677 file.set_attr(attr).unwrap();
1678 let stat = file.fstat().unwrap();
1679 assert_eq!(stat.mode & 0o222, 0o222);
1680
1681 if cfg!(windows) {
1683 let mut attr = SetAttributes::default();
1684 attr.uid = Some(1000);
1685 attr.gid = Some(1000);
1686 file.set_attr(attr).unwrap();
1687 }
1688
1689 let mut attr = SetAttributes::default();
1691 attr.atime = SetTime::Set(Duration::new(111111, 222200));
1692 attr.mtime = SetTime::Set(Duration::new(333333, 444400));
1693 file.set_attr(attr).unwrap();
1694 let stat = file.fstat().unwrap();
1695 assert_eq!(stat.access_time.seconds, 111111);
1696 assert_eq!(stat.access_time.nanoseconds, 222200);
1697 assert_eq!(stat.write_time.seconds, 333333);
1698 assert_eq!(stat.write_time.nanoseconds, 444400);
1699 }
1700
1701 #[test]
1702 fn kill_priv() {
1703 let env = TestEnv::with_options(LxVolumeOptions::new().metadata(true));
1704 let file = env
1705 .volume
1706 .open(
1707 "testfile",
1708 lx::O_RDWR | lx::O_CREAT | lx::O_EXCL,
1709 Some(LxCreateOptions::new(
1710 lx::S_ISUID | lx::S_ISGID | 0o777,
1711 1000,
1712 2000,
1713 )),
1714 )
1715 .unwrap();
1716
1717 let stat = file.fstat().unwrap();
1718 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1719
1720 let write_result = if cfg!(windows) || !is_lx_root() {
1721 lx::S_IFREG | 0o777
1722 } else {
1723 lx::S_IFREG | 0o6777
1724 };
1725
1726 file.pwrite(b"hello", 0, 1000).unwrap();
1728 let stat = file.fstat().unwrap();
1729 assert_eq!(stat.mode as u32, write_result);
1730 if cfg!(windows) {
1731 file.chmod(lx::S_IFREG | 0o6777).unwrap();
1733 let stat = file.fstat().unwrap();
1734 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1735 file.pwrite(b"hello", 0, 0).unwrap();
1736 let stat = file.fstat().unwrap();
1737 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1738 }
1739
1740 file.chmod(lx::S_IFREG | 0o6777).unwrap();
1741 let stat = file.fstat().unwrap();
1742 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1743
1744 file.truncate(2, 1000).unwrap();
1746 let stat = file.fstat().unwrap();
1747 assert_eq!(stat.file_size, 2);
1748 assert_eq!(stat.mode as u32, write_result);
1749 if cfg!(windows) {
1750 file.chmod(lx::S_IFREG | 0o6777).unwrap();
1752 let stat = file.fstat().unwrap();
1753 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1754 file.truncate(2, 0).unwrap();
1755 let stat = file.fstat().unwrap();
1756 assert_eq!(stat.file_size, 2);
1757 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1758 }
1759
1760 file.chmod(lx::S_IFREG | 0o6777).unwrap();
1761 let stat = file.fstat().unwrap();
1762 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1763
1764 let stat = file.fstat().unwrap();
1766 file.chown(None, None).unwrap();
1767 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1768
1769 if is_lx_root() {
1772 file.chown(Some(1001), Some(2001)).unwrap();
1773 let stat = file.fstat().unwrap();
1774 assert_eq!(stat.uid, 1001);
1775 assert_eq!(stat.gid, 2001);
1776 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o777);
1777
1778 file.chmod(lx::S_IFREG | 0o6767).unwrap();
1780 let stat = file.fstat().unwrap();
1781 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6767);
1782 file.chown(Some(1001), Some(2001)).unwrap();
1783 let stat = file.fstat().unwrap();
1784 assert_eq!(stat.uid, 1001);
1785 assert_eq!(stat.gid, 2001);
1786 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o2767);
1787 }
1788 }
1789
1790 #[test]
1791 fn mknod() {
1792 let env = TestEnv::new();
1793 env.create_file("mknod", "test");
1794
1795 env.volume
1798 .mknod(
1799 "testfile",
1800 LxCreateOptions::new(lx::S_IFREG | 0o640, 1000, 2000),
1801 0,
1802 )
1803 .unwrap();
1804
1805 let stat = env.volume.lstat("testfile").unwrap();
1806 assert!(lx::s_isreg(stat.mode));
1807 assert_eq!(stat.file_size, 0);
1808
1809 let stat = env
1810 .volume
1811 .mknod_stat(
1812 "testfile2",
1813 LxCreateOptions::new(lx::S_IFREG | 0o640, 1000, 2000),
1814 0,
1815 )
1816 .unwrap();
1817
1818 let stat2 = env.volume.lstat("testfile2").unwrap();
1819 assert_eq!(stat, stat2);
1820 }
1821
1822 #[test]
1823 fn rename() {
1824 let env = TestEnv::new();
1825 env.create_file("testfile", "test");
1826 let stat = env.volume.lstat("testfile").unwrap();
1827
1828 env.volume.rename("testfile", "testfile2", 0).unwrap();
1830 let stat2 = env.volume.lstat("testfile2").unwrap();
1831 assert_eq!(stat.inode_nr, stat2.inode_nr);
1832 let err = env.volume.lstat("testfile").unwrap_err();
1833 assert_eq!(err.value(), lx::ENOENT);
1834
1835 env.volume
1837 .mkdir("testdir", LxCreateOptions::new(0o755, 0, 0))
1838 .unwrap();
1839
1840 env.volume
1841 .rename("testfile2", Path::from_lx("testdir/testfile").unwrap(), 0)
1842 .unwrap();
1843
1844 let stat2 = env
1845 .volume
1846 .lstat(Path::from_lx("testdir/testfile").unwrap())
1847 .unwrap();
1848
1849 assert_eq!(stat.inode_nr, stat2.inode_nr);
1850 let err = env.volume.lstat("testfile2").unwrap_err();
1851 assert_eq!(err.value(), lx::ENOENT);
1852
1853 env.volume
1855 .mkdir("testdir2", LxCreateOptions::new(0o755, 0, 0))
1856 .unwrap();
1857
1858 let dirstat = env.volume.lstat("testdir").unwrap();
1859 let dirstat2 = env.volume.lstat("testdir2").unwrap();
1860 assert_ne!(dirstat.inode_nr, dirstat2.inode_nr);
1861 let err = env.volume.rename("testdir2", "testdir", 0).unwrap_err();
1862 assert_eq!(err.value(), lx::ENOTEMPTY);
1863
1864 env.create_file("testfile3", "foo");
1866 let stat2 = env.volume.lstat("testfile3").unwrap();
1867 assert_ne!(stat2.inode_nr, stat.inode_nr);
1868 env.volume
1869 .rename(Path::from_lx("testdir/testfile").unwrap(), "testfile3", 0)
1870 .unwrap();
1871
1872 let stat2 = env.volume.lstat("testfile3").unwrap();
1873 assert_eq!(stat.inode_nr, stat2.inode_nr);
1874 let err = env
1875 .volume
1876 .lstat(Path::from_lx("testdir/testfile").unwrap())
1877 .unwrap_err();
1878
1879 assert_eq!(err.value(), lx::ENOENT);
1880
1881 env.volume.rename("testdir2", "testdir", 0).unwrap();
1883 let dirstat = env.volume.lstat("testdir").unwrap();
1884 assert_eq!(dirstat.inode_nr, dirstat2.inode_nr);
1885 let err = env.volume.lstat("testdir2").unwrap_err();
1886 assert_eq!(err.value(), lx::ENOENT);
1887
1888 let err = env.volume.rename("testfile3", "testdir", 0).unwrap_err();
1890 assert_eq!(err.value(), lx::EISDIR);
1891
1892 let err = env.volume.rename("testdir", "testfile3", 0).unwrap_err();
1894 assert_eq!(err.value(), lx::ENOTDIR);
1895
1896 let _exit = pal::ScopeExit::new(|| {
1899 env.volume.unlink("testfile4", 0).unwrap_or_default();
1900 env.volume.unlink("testfile5", 0).unwrap_or_default();
1901 });
1902
1903 env.volume
1905 .mknod(
1906 "testfile4",
1907 LxCreateOptions::new(lx::S_IFREG | 0o444, 0, 0),
1908 0,
1909 )
1910 .unwrap();
1911
1912 env.volume
1913 .mknod(
1914 "testfile5",
1915 LxCreateOptions::new(lx::S_IFREG | 0o444, 0, 0),
1916 0,
1917 )
1918 .unwrap();
1919
1920 env.volume.rename("testfile4", "testfile5", 0).unwrap();
1921
1922 let dirstat = env.volume.lstat("testdir").unwrap();
1924 env.volume.rename("testdir", "TestDir", 0).unwrap();
1925 let dirstat2 = env.volume.lstat("TestDir").unwrap();
1926 assert_eq!(dirstat.inode_nr, dirstat2.inode_nr);
1927 }
1928
1929 #[test]
1930 fn link() {
1931 let env = TestEnv::new();
1932 env.create_file("testfile", "test");
1933 let stat = env.volume.lstat("testfile").unwrap();
1934 assert_eq!(stat.link_count, 1);
1935
1936 env.volume.link("testfile", "testfile2").unwrap();
1937 let stat2 = env.volume.lstat("testfile2").unwrap();
1938 assert_eq!(stat2.inode_nr, stat.inode_nr);
1939 assert_eq!(stat2.link_count, 2);
1940
1941 let stat2 = env.volume.link_stat("testfile", "testfile3").unwrap();
1942 assert_eq!(stat2.inode_nr, stat.inode_nr);
1943 assert_eq!(stat2.link_count, 3);
1944 }
1945
1946 #[test]
1947 fn stat_fs() {
1948 let env = TestEnv::new();
1949 let stat_fs = env.volume.stat_fs("").unwrap();
1950 let stat = env.volume.lstat("").unwrap();
1951 assert_eq!(stat_fs.block_size, stat.block_size as usize);
1952 }
1953
1954 #[test]
1955 fn fsync() {
1956 let env = TestEnv::new();
1957 {
1958 let file = env
1959 .volume
1960 .open(
1961 "testfile",
1962 lx::O_WRONLY | lx::O_CREAT,
1963 Some(LxCreateOptions::new(0o666, 0, 0)),
1964 )
1965 .unwrap();
1966
1967 file.pwrite(b"test", 0, 0).unwrap();
1968 file.fsync(false).unwrap();
1969 file.fsync(true).unwrap();
1970 }
1971
1972 let file = env.volume.open("testfile", lx::O_RDONLY, None).unwrap();
1974 file.fsync(false).unwrap();
1975 file.fsync(true).unwrap();
1976 }
1977
1978 #[test]
1979 fn xattr() {
1980 let env = TestEnv::new();
1981 env.create_file("testfile", "test");
1982
1983 let err = env
1985 .volume
1986 .get_xattr("testfile", "user.test", None)
1987 .unwrap_err();
1988
1989 assert_eq!(err.value(), lx::ENODATA);
1990
1991 let size = env.volume.list_xattr("testfile", None).unwrap();
1992 assert_eq!(size, 0);
1993
1994 let err = env
1995 .volume
1996 .remove_xattr("testfile", "user.test")
1997 .unwrap_err();
1998
1999 assert_eq!(err.value(), lx::ENODATA);
2000
2001 env.volume
2003 .set_xattr("testfile", "user.test", b"foo", 0)
2004 .unwrap();
2005
2006 let size = env.volume.get_xattr("testfile", "user.test", None).unwrap();
2007
2008 assert_eq!(size, 3);
2009 let mut buffer = [0u8; 1024];
2010 let size = env
2011 .volume
2012 .get_xattr("testfile", "user.test", Some(&mut buffer))
2013 .unwrap();
2014
2015 assert_eq!(size, 3);
2016 assert_eq!(&buffer[..3], b"foo");
2017
2018 env.volume
2020 .set_xattr("testfile", "user.empty", b"", 0)
2021 .unwrap();
2022
2023 let size = env
2024 .volume
2025 .get_xattr("testfile", "user.empty", None)
2026 .unwrap();
2027
2028 assert_eq!(size, 0);
2029
2030 let size = env.volume.list_xattr("testfile", None).unwrap();
2032 assert_eq!(size, 21);
2033 let size = env
2034 .volume
2035 .list_xattr("testfile", Some(&mut buffer))
2036 .unwrap();
2037
2038 assert_eq!(size, 21);
2039 assert_eq!(&buffer[..21], b"user.test\0user.empty\0");
2040
2041 env.volume.remove_xattr("testfile", "user.empty").unwrap();
2043
2044 let err = env
2045 .volume
2046 .get_xattr("testfile", "user.empty", None)
2047 .unwrap_err();
2048
2049 assert_eq!(err.value(), lx::ENODATA);
2050 let size = env
2051 .volume
2052 .list_xattr("testfile", Some(&mut buffer))
2053 .unwrap();
2054
2055 assert_eq!(size, 10);
2056 assert_eq!(&buffer[..10], b"user.test\0");
2057
2058 let err = env
2060 .volume
2061 .set_xattr("testfile", "user.test", b"bar", lx::XATTR_CREATE)
2062 .unwrap_err();
2063
2064 assert_eq!(err.value(), lx::EEXIST);
2065 let size = env
2066 .volume
2067 .get_xattr("testfile", "user.test", Some(&mut buffer))
2068 .unwrap();
2069
2070 assert_eq!(size, 3);
2071 assert_eq!(&buffer[..3], b"foo");
2072 env.volume
2073 .set_xattr("testfile", "user.test2", b"bar", lx::XATTR_CREATE)
2074 .unwrap();
2075
2076 let size = env
2077 .volume
2078 .get_xattr("testfile", "user.test2", Some(&mut buffer))
2079 .unwrap();
2080
2081 assert_eq!(size, 3);
2082 assert_eq!(&buffer[..3], b"bar");
2083 let err = env
2084 .volume
2085 .set_xattr("testfile", "user.test3", b"baz", lx::XATTR_REPLACE)
2086 .unwrap_err();
2087
2088 assert_eq!(err.value(), lx::ENODATA);
2089 let err = env
2090 .volume
2091 .get_xattr("testfile", "user.test3", None)
2092 .unwrap_err();
2093
2094 assert_eq!(err.value(), lx::ENODATA);
2095 env.volume
2096 .set_xattr("testfile", "user.test2", b"baz", lx::XATTR_REPLACE)
2097 .unwrap();
2098
2099 let size = env
2100 .volume
2101 .get_xattr("testfile", "user.test2", Some(&mut buffer))
2102 .unwrap();
2103
2104 assert_eq!(size, 3);
2105 assert_eq!(&buffer[..3], b"baz");
2106 }
2107
2108 #[test]
2112 #[cfg(not(all(windows, feature = "ci")))]
2113 fn case_sensitive() {
2114 let env = TestEnv::with_options(LxVolumeOptions::new().create_case_sensitive_dirs(true));
2115
2116 env.volume
2117 .mkdir("testdir", LxCreateOptions::new(0o777, 0, 0))
2118 .expect("Could not create case sensitive directory. This may indicate WSL needs to be installed.");
2119
2120 env.volume
2121 .mknod(
2122 Path::from_lx("testdir/testfile").unwrap(),
2123 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2124 0,
2125 )
2126 .unwrap();
2127
2128 env.volume
2129 .mknod(
2130 Path::from_lx("testdir/TESTFILE").unwrap(),
2131 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2132 0,
2133 )
2134 .unwrap();
2135
2136 let stat1 = env
2137 .volume
2138 .lstat(Path::from_lx("testdir/testfile").unwrap())
2139 .unwrap();
2140
2141 let stat2 = env
2142 .volume
2143 .lstat(Path::from_lx("testdir/TESTFILE").unwrap())
2144 .unwrap();
2145
2146 assert_ne!(stat1.inode_nr, stat2.inode_nr);
2147 }
2148
2149 #[test]
2150 #[cfg(windows)]
2151 fn case_insensitive() {
2152 let env = TestEnv::new();
2153 env.volume
2154 .mkdir("testdir", LxCreateOptions::new(0o777, 0, 0))
2155 .unwrap();
2156
2157 env.volume
2158 .mknod(
2159 Path::from_lx("testdir/testfile").unwrap(),
2160 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2161 0,
2162 )
2163 .unwrap();
2164
2165 let err = env
2166 .volume
2167 .mknod(
2168 Path::from_lx("testdir/TESTFILE").unwrap(),
2169 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2170 0,
2171 )
2172 .unwrap_err();
2173
2174 assert_eq!(err.value(), lx::EEXIST);
2175 }
2176
2177 #[test]
2180 #[cfg(all(windows, not(feature = "ci")))]
2181 fn case_sensitive_dir_xattr() {
2182 let env = TestEnv::new();
2183 env.volume
2184 .mkdir("testdir", LxCreateOptions::new(0o777, 0, 0))
2185 .unwrap();
2186
2187 let size = env.volume.list_xattr("testdir", None).unwrap();
2188 assert_eq!(size, b"system.wsl_case_sensitive\0".len());
2189
2190 let mut buffer = [0u8; 1024];
2191 let size = env.volume.list_xattr("testdir", Some(&mut buffer)).unwrap();
2192 assert_eq!(&buffer[..size], b"system.wsl_case_sensitive\0");
2193
2194 let size = env
2195 .volume
2196 .get_xattr("testdir", "system.wsl_case_sensitive", Some(&mut buffer))
2197 .unwrap();
2198
2199 assert_eq!(&buffer[..size], b"0");
2200
2201 env.volume
2202 .set_xattr("testdir", "system.wsl_case_sensitive", b"1", 0)
2203 .expect("Could not create case sensitive directory. This may indicate WSL needs to be installed.");
2204
2205 let size = env
2206 .volume
2207 .get_xattr("testdir", "system.wsl_case_sensitive", Some(&mut buffer))
2208 .unwrap();
2209
2210 assert_eq!(&buffer[..size], b"1");
2211
2212 env.volume
2213 .mknod(
2214 Path::from_lx("testdir/testfile").unwrap(),
2215 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2216 0,
2217 )
2218 .unwrap();
2219
2220 env.volume
2221 .mknod(
2222 Path::from_lx("testdir/TESTFILE").unwrap(),
2223 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2224 0,
2225 )
2226 .unwrap();
2227
2228 let stat1 = env
2229 .volume
2230 .lstat(Path::from_lx("testdir/testfile").unwrap())
2231 .unwrap();
2232
2233 let stat2 = env
2234 .volume
2235 .lstat(Path::from_lx("testdir/TESTFILE").unwrap())
2236 .unwrap();
2237
2238 assert_ne!(stat1.inode_nr, stat2.inode_nr);
2239 }
2240
2241 fn check_symlink(volume: &LxVolume, path: impl AsRef<Path>, target: &str) {
2242 volume
2243 .symlink(&path, target, LxCreateOptions::new(0, 0, 0))
2244 .unwrap();
2245
2246 let stat = volume.lstat(&path).unwrap();
2247 assert_eq!(stat.mode, lx::S_IFLNK | 0o777);
2248 assert_eq!(stat.file_size, target.len() as u64);
2249 assert_eq!(volume.read_link(&path).unwrap(), target);
2250 }
2251
2252 fn check_unlink(volume: &LxVolume, path: impl AsRef<Path>, dir: bool) {
2253 let (good_flags, bad_flags, error) = if dir {
2254 (lx::AT_REMOVEDIR, 0, lx::EISDIR)
2255 } else {
2256 (0, lx::AT_REMOVEDIR, lx::ENOTDIR)
2257 };
2258
2259 assert_eq!(volume.unlink(&path, bad_flags).unwrap_err().value(), error);
2260 volume.lstat(&path).unwrap();
2261 volume.unlink(&path, good_flags).unwrap();
2262 assert_eq!(volume.lstat(&path).unwrap_err().value(), lx::ENOENT);
2263 }
2264
2265 #[cfg(unix)]
2266 fn is_lx_root() -> bool {
2267 unsafe { libc::getuid() == 0 }
2269 }
2270
2271 #[cfg(windows)]
2272 fn is_lx_root() -> bool {
2273 true
2274 }
2275
2276 struct TestEnv {
2277 volume: LxVolume,
2278 root_dir: TempDir,
2279 }
2280
2281 impl TestEnv {
2282 fn new() -> Self {
2283 Self::with_options(&LxVolumeOptions::new())
2284 }
2285
2286 fn with_options(options: &LxVolumeOptions) -> Self {
2287 Self::clear_umask();
2288 let root_dir = tempfile::tempdir().unwrap();
2289 let volume = options.new_volume(root_dir.path()).unwrap();
2290 Self { volume, root_dir }
2291 }
2292
2293 fn create_file(&self, name: &str, contents: &str) {
2294 let mut path: PathBuf = self.root_dir.path().into();
2295 path.push(name);
2296 fs::create_dir_all(path.parent().unwrap()).unwrap();
2297 fs::write(path, contents).unwrap();
2298 }
2299
2300 fn check_dir_entry(&self, entry: &lx::DirEntry) {
2301 assert_ne!(entry.offset, 0);
2304
2305 let name = entry.name.to_str().unwrap();
2306 if name == "." || name == ".." {
2307 assert_eq!(entry.file_type, lx::DT_DIR);
2309 } else {
2310 let stat = self.volume.lstat(name).unwrap();
2311 assert_eq!(entry.inode_nr, stat.inode_nr);
2312 assert_eq!((entry.file_type as u32) << 12, stat.mode & lx::S_IFMT);
2313 }
2314 }
2315
2316 #[cfg(unix)]
2317 fn clear_umask() {
2318 unsafe { libc::umask(0) };
2320 }
2321
2322 #[cfg(windows)]
2323 fn clear_umask() {}
2324 }
2325}