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 append_truncate() {
1252 let env = TestEnv::new();
1256 let file = env
1257 .volume
1258 .open(
1259 "testfile",
1260 lx::O_WRONLY | lx::O_APPEND | lx::O_CREAT | lx::O_EXCL,
1261 Some(LxCreateOptions::new(0o666, 0, 0)),
1262 )
1263 .unwrap();
1264
1265 assert_eq!(file.pwrite(b"hello", 0, 0).unwrap(), 5);
1266 assert_eq!(file.fstat().unwrap().file_size, 5);
1267
1268 file.truncate(1, 0).unwrap();
1270 assert_eq!(file.fstat().unwrap().file_size, 1);
1271
1272 file.truncate(0, 0).unwrap();
1273 assert_eq!(file.fstat().unwrap().file_size, 0);
1274 }
1275
1276 #[test]
1277 fn readonly_truncate() {
1278 let env = TestEnv::new();
1282 env.create_file("testfile", "hello");
1283
1284 let file = env.volume.open("testfile", lx::O_RDONLY, None).unwrap();
1285 assert_eq!(file.truncate(1, 0).unwrap_err().value(), lx::EINVAL);
1286 assert_eq!(file.fstat().unwrap().file_size, 5);
1288 }
1289
1290 #[test]
1291 fn read_dir() {
1292 let env = TestEnv::new();
1293 let mut map = HashMap::new();
1294 map.insert(String::from("."), false);
1295 map.insert(String::from(".."), false);
1296 for i in 0..10 {
1297 let name = format!("file{}", i);
1298 env.create_file(&name, "test");
1299 assert!(map.insert(name, false).is_none());
1300 }
1301
1302 let mut dir = env
1303 .volume
1304 .open("", lx::O_RDONLY | lx::O_DIRECTORY, None)
1305 .unwrap();
1306
1307 let mut count = 0;
1308 let mut next_offset = 0;
1309 let mut seek_file = String::new();
1310 let mut seek_offset = 0;
1311 let mut prev_offset = 0;
1312
1313 dir.read_dir(0, |entry| {
1315 if count == 6 {
1316 return Ok(false);
1317 }
1318
1319 count += 1;
1320 next_offset = entry.offset;
1321 println!("Entry 1: {:?}", entry);
1322 env.check_dir_entry(&entry);
1323 let name = entry.name.to_str().unwrap();
1324 let found_entry = map.get_mut(name).unwrap();
1325 assert!(!*found_entry);
1326 *found_entry = true;
1327
1328 if count == 4 {
1330 seek_file = String::from(name);
1331 seek_offset = prev_offset;
1332 }
1333
1334 prev_offset = entry.offset;
1335 Ok(true)
1336 })
1337 .unwrap();
1338
1339 dir.read_dir(next_offset, |entry| {
1341 count += 1;
1342 println!("Entry 2: {:?}", entry);
1343 env.check_dir_entry(&entry);
1344 let name = entry.name.to_str().unwrap();
1345 let found_entry = map.get_mut(name).unwrap();
1346 assert!(!*found_entry);
1347 *found_entry = true;
1348 Ok(true)
1349 })
1350 .unwrap();
1351
1352 assert_eq!(count, 12);
1354 for (_, value) in map {
1355 assert!(value);
1356 }
1357
1358 count = 0;
1360 dir.read_dir(0, |_| {
1361 count += 1;
1362 Ok(true)
1363 })
1364 .unwrap();
1365
1366 assert_eq!(count, 12);
1367
1368 count = 0;
1370 dir.read_dir(seek_offset, |entry| {
1371 assert_eq!(entry.name.to_str().unwrap(), seek_file);
1372 count += 1;
1373 Ok(false)
1374 })
1375 .unwrap();
1376
1377 assert_eq!(count, 1);
1378
1379 count = 0;
1381 let error = dir
1382 .read_dir(0, |_| {
1383 count += 1;
1384 Err(lx::Error::ECONNREFUSED)
1385 })
1386 .unwrap_err();
1387
1388 assert_eq!(count, 1);
1389 assert_eq!(error.value(), lx::ECONNREFUSED);
1390 }
1391
1392 #[test]
1393 #[should_panic(expected = "at the disco")]
1394 fn read_dir_panic() {
1395 let env = TestEnv::new();
1396 env.create_file("testfile", "test");
1397 let mut dir = env
1398 .volume
1399 .open("", lx::O_RDONLY | lx::O_DIRECTORY, None)
1400 .unwrap();
1401
1402 dir.read_dir(0, |entry| {
1405 if entry.file_type == lx::DT_REG {
1406 panic!("at the disco");
1407 }
1408
1409 Ok(true)
1410 })
1411 .unwrap();
1412 }
1413
1414 #[test]
1415 fn metadata() {
1416 let env = TestEnv::with_options(LxVolumeOptions::new().metadata(true));
1417 let file = env
1418 .volume
1419 .open(
1420 "testfile",
1421 lx::O_RDWR | lx::O_CREAT | lx::O_EXCL,
1422 Some(LxCreateOptions::new(0o640, 1000, 2000)),
1423 )
1424 .unwrap();
1425
1426 let stat = file.fstat().unwrap();
1427 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o640);
1428 if cfg!(windows) {
1430 assert_eq!(stat.uid, 1000);
1431 assert_eq!(stat.gid, 2000);
1432 }
1433
1434 env.volume
1435 .mkdir("testdir", LxCreateOptions::new(0o751, 1001, 2001))
1436 .unwrap();
1437
1438 let stat = env.volume.lstat("testdir").unwrap();
1439 assert_eq!(stat.mode, lx::S_IFDIR | 0o751);
1440 if cfg!(windows) {
1441 assert_eq!(stat.uid, 1001);
1442 assert_eq!(stat.gid, 2001);
1443 }
1444
1445 let stat = env
1446 .volume
1447 .mkdir_stat("testdir2", LxCreateOptions::new(0o777, 1002, 2002))
1448 .unwrap();
1449
1450 assert_eq!(stat.mode, lx::S_IFDIR | 0o777);
1451 if cfg!(windows) {
1452 assert_eq!(stat.uid, 1002);
1453 assert_eq!(stat.gid, 2002);
1454 }
1455
1456 env.volume
1457 .symlink("testlink", "testdir", LxCreateOptions::new(0, 2000, 3000))
1458 .unwrap();
1459
1460 let stat = env.volume.lstat("testlink").unwrap();
1461 assert_eq!(stat.mode, lx::S_IFLNK | 0o777);
1462 assert_eq!(stat.file_size, 7);
1463 if cfg!(windows) {
1464 assert_eq!(stat.uid, 2000);
1465 assert_eq!(stat.gid, 3000);
1466 }
1467
1468 let stat = env
1469 .volume
1470 .symlink_stat("testlink2", "testdir2", LxCreateOptions::new(0, 2001, 3001))
1471 .unwrap();
1472
1473 assert_eq!(stat.mode, lx::S_IFLNK | 0o777);
1474 assert_eq!(stat.file_size, 8);
1475 if cfg!(windows) {
1476 assert_eq!(stat.uid, 2001);
1477 assert_eq!(stat.gid, 3001);
1478 }
1479
1480 let mut attr = SetAttributes::default();
1482 attr.mode = Some(lx::S_IFREG | 0o664);
1483 if cfg!(windows) {
1484 attr.uid = Some(2000);
1485 attr.gid = Some(3000);
1486 }
1487
1488 env.volume.set_attr("testfile", attr).unwrap();
1489 let stat = env.volume.lstat("testfile").unwrap();
1490 assert_eq!(stat.mode, lx::S_IFREG | 0o664);
1491 if cfg!(windows) {
1492 assert_eq!(stat.uid, 2000);
1493 assert_eq!(stat.gid, 3000);
1494 }
1495
1496 if is_lx_root() {
1498 env.volume
1499 .mknod(
1500 "testchr",
1501 LxCreateOptions::new(lx::S_IFCHR | 0o640, 1000, 2000),
1502 lx::make_dev(1, 5),
1503 )
1504 .unwrap();
1505
1506 let stat = env.volume.lstat("testchr").unwrap();
1507 assert_eq!(stat.mode, lx::S_IFCHR | 0o640);
1508 if cfg!(windows) {
1509 assert_eq!(stat.uid, 1000);
1510 assert_eq!(stat.gid, 2000);
1511 }
1512
1513 assert_eq!(lx::major64(stat.device_nr_special as lx::dev_t), 1);
1514 assert_eq!(lx::minor(stat.device_nr_special as lx::dev_t), 5);
1515
1516 let stat = env
1517 .volume
1518 .mknod_stat(
1519 "testblk",
1520 LxCreateOptions::new(lx::S_IFBLK | 0o660, 1001, 2001),
1521 lx::make_dev(2, 6),
1522 )
1523 .unwrap();
1524
1525 assert_eq!(stat.mode, lx::S_IFBLK | 0o660);
1526 if cfg!(windows) {
1527 assert_eq!(stat.uid, 1001);
1528 assert_eq!(stat.gid, 2001);
1529 }
1530
1531 assert_eq!(lx::major64(stat.device_nr_special as lx::dev_t), 2);
1532 assert_eq!(lx::minor(stat.device_nr_special as lx::dev_t), 6);
1533 }
1534
1535 env.volume
1536 .mknod(
1537 "testfifo",
1538 LxCreateOptions::new(lx::S_IFIFO | 0o666, 1002, 2002),
1539 lx::make_dev(2, 6),
1540 )
1541 .unwrap();
1542
1543 let stat = env.volume.lstat("testfifo").unwrap();
1544 assert_eq!(stat.mode, lx::S_IFIFO | 0o666);
1545 if cfg!(windows) {
1546 assert_eq!(stat.uid, 1002);
1547 assert_eq!(stat.gid, 2002);
1548 }
1549
1550 assert_eq!(stat.device_nr_special, 0);
1551
1552 let stat = env
1553 .volume
1554 .mknod_stat(
1555 "testsock",
1556 LxCreateOptions::new(lx::S_IFSOCK | 0o600, 1003, 2003),
1557 lx::make_dev(2, 6),
1558 )
1559 .unwrap();
1560
1561 assert_eq!(stat.mode, lx::S_IFSOCK | 0o600);
1562 if cfg!(windows) {
1563 assert_eq!(stat.uid, 1003);
1564 assert_eq!(stat.gid, 2003);
1565 }
1566
1567 assert_eq!(stat.device_nr_special, 0);
1568 }
1569
1570 #[test]
1571 fn path_escape() {
1572 let env = TestEnv::new();
1573 env.volume
1574 .mkdir("testdir", LxCreateOptions::new(0o777, 0, 0))
1575 .unwrap();
1576 env.create_file("testdir/testfile", "foo");
1577 env.volume
1578 .lstat(Path::from_lx("testdir/testfile").unwrap())
1579 .unwrap();
1580 let path = PathBuf::from_lx("testdir/foo:bar").unwrap();
1581 let file = env
1582 .volume
1583 .open(
1584 &path,
1585 lx::O_RDONLY | lx::O_CREAT,
1586 Some(LxCreateOptions::new(0o666, 0, 0)),
1587 )
1588 .unwrap();
1589
1590 let file_stat: lx::Stat = file.fstat().unwrap().into();
1591 assert_eq!(file_stat, env.volume.lstat(&path).unwrap());
1592 }
1593
1594 #[test]
1595 fn symlink() {
1596 let env = TestEnv::new();
1597 env.create_file("testdir/testfile", "foo");
1598 check_symlink(&env.volume, "testlink", "testdir/testfile");
1599 check_symlink(&env.volume, "testlink2", "doesntexit");
1600 check_symlink(&env.volume, "testlink3", "/proc");
1601 check_symlink(&env.volume, "testlink4", "../foo");
1602
1603 assert_eq!(
1604 env.volume.read_link("doesntexit").unwrap_err().value(),
1605 lx::ENOENT
1606 );
1607 assert_eq!(
1608 env.volume
1609 .read_link(Path::from_lx("testdir/testfile").unwrap())
1610 .unwrap_err()
1611 .value(),
1612 lx::EINVAL
1613 );
1614 }
1615
1616 #[test]
1617 fn unlink() {
1618 let env = TestEnv::new();
1619 env.create_file("testfile", "test");
1620 env.volume
1621 .mkdir("testdir", LxCreateOptions::new(0o777, 0, 0))
1622 .unwrap();
1623
1624 env.volume
1625 .symlink("testlink", "testfile", LxCreateOptions::new(0, 0, 0))
1626 .unwrap();
1627
1628 env.volume
1632 .symlink("testlink2", "testdir", LxCreateOptions::new(0, 0, 0))
1633 .unwrap();
1634
1635 check_unlink(&env.volume, "testfile", false);
1636 check_unlink(&env.volume, "testdir", true);
1637 check_unlink(&env.volume, "testlink", false);
1638 check_unlink(&env.volume, "testlink2", false);
1639
1640 env.volume
1641 .open(
1642 "readonly",
1643 lx::O_RDONLY | lx::O_CREAT | lx::O_EXCL,
1644 Some(LxCreateOptions::new(0o444, 0, 0)),
1645 )
1646 .unwrap();
1647 check_unlink(&env.volume, "readonly", false);
1648 }
1649
1650 #[test]
1651 fn set_attr() {
1652 let env = TestEnv::new();
1653 env.create_file("testfile", "test");
1654 let stat = env.volume.lstat("testfile").unwrap();
1655 assert_eq!(stat.file_size, 4);
1656
1657 let mut attr = SetAttributes::default();
1659 attr.size = Some(2);
1660 env.volume.set_attr("testfile", attr).unwrap();
1661 let stat = env.volume.lstat("testfile").unwrap();
1662 assert_eq!(stat.file_size, 2);
1663
1664 let mut attr = SetAttributes::default();
1666 attr.mode = Some(lx::S_IFREG | 0o444);
1667 env.volume.set_attr("testfile", attr).unwrap();
1668 let stat = env.volume.lstat("testfile").unwrap();
1669 assert_eq!(stat.mode & 0o222, 0);
1670 attr.mode = Some(lx::S_IFREG | 0o666);
1671 env.volume.set_attr("testfile", attr).unwrap();
1672 let stat = env.volume.lstat("testfile").unwrap();
1673 assert_eq!(stat.mode & 0o222, 0o222);
1674
1675 if cfg!(windows) {
1677 let mut attr = SetAttributes::default();
1678 attr.uid = Some(1000);
1679 attr.gid = Some(1000);
1680 env.volume.set_attr("testfile", attr).unwrap();
1681 }
1682
1683 let mut attr = SetAttributes::default();
1685 attr.atime = SetTime::Set(Duration::new(111111, 222200));
1686 attr.mtime = SetTime::Set(Duration::new(333333, 444400));
1687 let stat = env.volume.set_attr_stat("testfile", attr).unwrap();
1688 assert_eq!(stat.access_time.seconds, 111111);
1689 assert_eq!(stat.access_time.nanoseconds, 222200);
1690 assert_eq!(stat.write_time.seconds, 333333);
1691 assert_eq!(stat.write_time.nanoseconds, 444400);
1692 }
1693
1694 #[test]
1695 fn file_set_attr() {
1696 let env = TestEnv::new();
1697 env.create_file("testfile", "test");
1698
1699 let file = env.volume.open("testfile", lx::O_RDWR, None).unwrap();
1700 let stat = file.fstat().unwrap();
1701 assert_eq!(stat.file_size, 4);
1702
1703 let mut attr = SetAttributes::default();
1705 attr.size = Some(2);
1706 file.set_attr(attr).unwrap();
1707 let stat = file.fstat().unwrap();
1708 assert_eq!(stat.file_size, 2);
1709
1710 let mut attr = SetAttributes::default();
1712 attr.mode = Some(lx::S_IFREG | 0o444);
1713 file.set_attr(attr).unwrap();
1714 let stat = file.fstat().unwrap();
1715 assert_eq!(stat.mode & 0o222, 0);
1716 attr.mode = Some(lx::S_IFREG | 0o666);
1717 file.set_attr(attr).unwrap();
1718 let stat = file.fstat().unwrap();
1719 assert_eq!(stat.mode & 0o222, 0o222);
1720
1721 if cfg!(windows) {
1723 let mut attr = SetAttributes::default();
1724 attr.uid = Some(1000);
1725 attr.gid = Some(1000);
1726 file.set_attr(attr).unwrap();
1727 }
1728
1729 let mut attr = SetAttributes::default();
1731 attr.atime = SetTime::Set(Duration::new(111111, 222200));
1732 attr.mtime = SetTime::Set(Duration::new(333333, 444400));
1733 file.set_attr(attr).unwrap();
1734 let stat = file.fstat().unwrap();
1735 assert_eq!(stat.access_time.seconds, 111111);
1736 assert_eq!(stat.access_time.nanoseconds, 222200);
1737 assert_eq!(stat.write_time.seconds, 333333);
1738 assert_eq!(stat.write_time.nanoseconds, 444400);
1739 }
1740
1741 #[test]
1742 fn kill_priv() {
1743 let env = TestEnv::with_options(LxVolumeOptions::new().metadata(true));
1744 let file = env
1745 .volume
1746 .open(
1747 "testfile",
1748 lx::O_RDWR | lx::O_CREAT | lx::O_EXCL,
1749 Some(LxCreateOptions::new(
1750 lx::S_ISUID | lx::S_ISGID | 0o777,
1751 1000,
1752 2000,
1753 )),
1754 )
1755 .unwrap();
1756
1757 let stat = file.fstat().unwrap();
1758 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1759
1760 let write_result = if cfg!(windows) || !is_lx_root() {
1761 lx::S_IFREG | 0o777
1762 } else {
1763 lx::S_IFREG | 0o6777
1764 };
1765
1766 file.pwrite(b"hello", 0, 1000).unwrap();
1768 let stat = file.fstat().unwrap();
1769 assert_eq!(stat.mode as u32, write_result);
1770 if cfg!(windows) {
1771 file.chmod(lx::S_IFREG | 0o6777).unwrap();
1773 let stat = file.fstat().unwrap();
1774 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1775 file.pwrite(b"hello", 0, 0).unwrap();
1776 let stat = file.fstat().unwrap();
1777 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1778 }
1779
1780 file.chmod(lx::S_IFREG | 0o6777).unwrap();
1781 let stat = file.fstat().unwrap();
1782 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1783
1784 file.truncate(2, 1000).unwrap();
1786 let stat = file.fstat().unwrap();
1787 assert_eq!(stat.file_size, 2);
1788 assert_eq!(stat.mode as u32, write_result);
1789 if cfg!(windows) {
1790 file.chmod(lx::S_IFREG | 0o6777).unwrap();
1792 let stat = file.fstat().unwrap();
1793 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1794 file.truncate(2, 0).unwrap();
1795 let stat = file.fstat().unwrap();
1796 assert_eq!(stat.file_size, 2);
1797 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1798 }
1799
1800 file.chmod(lx::S_IFREG | 0o6777).unwrap();
1801 let stat = file.fstat().unwrap();
1802 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1803
1804 let stat = file.fstat().unwrap();
1806 file.chown(None, None).unwrap();
1807 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1808
1809 if is_lx_root() {
1812 file.chown(Some(1001), Some(2001)).unwrap();
1813 let stat = file.fstat().unwrap();
1814 assert_eq!(stat.uid, 1001);
1815 assert_eq!(stat.gid, 2001);
1816 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o777);
1817
1818 file.chmod(lx::S_IFREG | 0o6767).unwrap();
1820 let stat = file.fstat().unwrap();
1821 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6767);
1822 file.chown(Some(1001), Some(2001)).unwrap();
1823 let stat = file.fstat().unwrap();
1824 assert_eq!(stat.uid, 1001);
1825 assert_eq!(stat.gid, 2001);
1826 assert_eq!(stat.mode as u32, lx::S_IFREG | 0o2767);
1827 }
1828 }
1829
1830 #[test]
1831 fn mknod() {
1832 let env = TestEnv::new();
1833 env.create_file("mknod", "test");
1834
1835 env.volume
1838 .mknod(
1839 "testfile",
1840 LxCreateOptions::new(lx::S_IFREG | 0o640, 1000, 2000),
1841 0,
1842 )
1843 .unwrap();
1844
1845 let stat = env.volume.lstat("testfile").unwrap();
1846 assert!(lx::s_isreg(stat.mode));
1847 assert_eq!(stat.file_size, 0);
1848
1849 let stat = env
1850 .volume
1851 .mknod_stat(
1852 "testfile2",
1853 LxCreateOptions::new(lx::S_IFREG | 0o640, 1000, 2000),
1854 0,
1855 )
1856 .unwrap();
1857
1858 let stat2 = env.volume.lstat("testfile2").unwrap();
1859 assert_eq!(stat, stat2);
1860 }
1861
1862 #[test]
1863 fn rename() {
1864 let env = TestEnv::new();
1865 env.create_file("testfile", "test");
1866 let stat = env.volume.lstat("testfile").unwrap();
1867
1868 env.volume.rename("testfile", "testfile2", 0).unwrap();
1870 let stat2 = env.volume.lstat("testfile2").unwrap();
1871 assert_eq!(stat.inode_nr, stat2.inode_nr);
1872 let err = env.volume.lstat("testfile").unwrap_err();
1873 assert_eq!(err.value(), lx::ENOENT);
1874
1875 env.volume
1877 .mkdir("testdir", LxCreateOptions::new(0o755, 0, 0))
1878 .unwrap();
1879
1880 env.volume
1881 .rename("testfile2", Path::from_lx("testdir/testfile").unwrap(), 0)
1882 .unwrap();
1883
1884 let stat2 = env
1885 .volume
1886 .lstat(Path::from_lx("testdir/testfile").unwrap())
1887 .unwrap();
1888
1889 assert_eq!(stat.inode_nr, stat2.inode_nr);
1890 let err = env.volume.lstat("testfile2").unwrap_err();
1891 assert_eq!(err.value(), lx::ENOENT);
1892
1893 env.volume
1895 .mkdir("testdir2", LxCreateOptions::new(0o755, 0, 0))
1896 .unwrap();
1897
1898 let dirstat = env.volume.lstat("testdir").unwrap();
1899 let dirstat2 = env.volume.lstat("testdir2").unwrap();
1900 assert_ne!(dirstat.inode_nr, dirstat2.inode_nr);
1901 let err = env.volume.rename("testdir2", "testdir", 0).unwrap_err();
1902 assert_eq!(err.value(), lx::ENOTEMPTY);
1903
1904 env.create_file("testfile3", "foo");
1906 let stat2 = env.volume.lstat("testfile3").unwrap();
1907 assert_ne!(stat2.inode_nr, stat.inode_nr);
1908 env.volume
1909 .rename(Path::from_lx("testdir/testfile").unwrap(), "testfile3", 0)
1910 .unwrap();
1911
1912 let stat2 = env.volume.lstat("testfile3").unwrap();
1913 assert_eq!(stat.inode_nr, stat2.inode_nr);
1914 let err = env
1915 .volume
1916 .lstat(Path::from_lx("testdir/testfile").unwrap())
1917 .unwrap_err();
1918
1919 assert_eq!(err.value(), lx::ENOENT);
1920
1921 env.volume.rename("testdir2", "testdir", 0).unwrap();
1923 let dirstat = env.volume.lstat("testdir").unwrap();
1924 assert_eq!(dirstat.inode_nr, dirstat2.inode_nr);
1925 let err = env.volume.lstat("testdir2").unwrap_err();
1926 assert_eq!(err.value(), lx::ENOENT);
1927
1928 let err = env.volume.rename("testfile3", "testdir", 0).unwrap_err();
1930 assert_eq!(err.value(), lx::EISDIR);
1931
1932 let err = env.volume.rename("testdir", "testfile3", 0).unwrap_err();
1934 assert_eq!(err.value(), lx::ENOTDIR);
1935
1936 let _exit = pal::ScopeExit::new(|| {
1939 env.volume.unlink("testfile4", 0).unwrap_or_default();
1940 env.volume.unlink("testfile5", 0).unwrap_or_default();
1941 });
1942
1943 env.volume
1945 .mknod(
1946 "testfile4",
1947 LxCreateOptions::new(lx::S_IFREG | 0o444, 0, 0),
1948 0,
1949 )
1950 .unwrap();
1951
1952 env.volume
1953 .mknod(
1954 "testfile5",
1955 LxCreateOptions::new(lx::S_IFREG | 0o444, 0, 0),
1956 0,
1957 )
1958 .unwrap();
1959
1960 env.volume.rename("testfile4", "testfile5", 0).unwrap();
1961
1962 let dirstat = env.volume.lstat("testdir").unwrap();
1964 env.volume.rename("testdir", "TestDir", 0).unwrap();
1965 let dirstat2 = env.volume.lstat("TestDir").unwrap();
1966 assert_eq!(dirstat.inode_nr, dirstat2.inode_nr);
1967 }
1968
1969 #[test]
1970 fn link() {
1971 let env = TestEnv::new();
1972 env.create_file("testfile", "test");
1973 let stat = env.volume.lstat("testfile").unwrap();
1974 assert_eq!(stat.link_count, 1);
1975
1976 env.volume.link("testfile", "testfile2").unwrap();
1977 let stat2 = env.volume.lstat("testfile2").unwrap();
1978 assert_eq!(stat2.inode_nr, stat.inode_nr);
1979 assert_eq!(stat2.link_count, 2);
1980
1981 let stat2 = env.volume.link_stat("testfile", "testfile3").unwrap();
1982 assert_eq!(stat2.inode_nr, stat.inode_nr);
1983 assert_eq!(stat2.link_count, 3);
1984 }
1985
1986 #[test]
1987 fn stat_fs() {
1988 let env = TestEnv::new();
1989 let stat_fs = env.volume.stat_fs("").unwrap();
1990 let stat = env.volume.lstat("").unwrap();
1991 assert_eq!(stat_fs.block_size, stat.block_size as usize);
1992 }
1993
1994 #[test]
1995 fn fsync() {
1996 let env = TestEnv::new();
1997 {
1998 let file = env
1999 .volume
2000 .open(
2001 "testfile",
2002 lx::O_WRONLY | lx::O_CREAT,
2003 Some(LxCreateOptions::new(0o666, 0, 0)),
2004 )
2005 .unwrap();
2006
2007 file.pwrite(b"test", 0, 0).unwrap();
2008 file.fsync(false).unwrap();
2009 file.fsync(true).unwrap();
2010 }
2011
2012 let file = env.volume.open("testfile", lx::O_RDONLY, None).unwrap();
2014 file.fsync(false).unwrap();
2015 file.fsync(true).unwrap();
2016 }
2017
2018 #[test]
2019 fn xattr() {
2020 let env = TestEnv::new();
2021 env.create_file("testfile", "test");
2022
2023 let err = env
2025 .volume
2026 .get_xattr("testfile", "user.test", None)
2027 .unwrap_err();
2028
2029 assert_eq!(err.value(), lx::ENODATA);
2030
2031 let size = env.volume.list_xattr("testfile", None).unwrap();
2032 assert_eq!(size, 0);
2033
2034 let err = env
2035 .volume
2036 .remove_xattr("testfile", "user.test")
2037 .unwrap_err();
2038
2039 assert_eq!(err.value(), lx::ENODATA);
2040
2041 env.volume
2043 .set_xattr("testfile", "user.test", b"foo", 0)
2044 .unwrap();
2045
2046 let size = env.volume.get_xattr("testfile", "user.test", None).unwrap();
2047
2048 assert_eq!(size, 3);
2049 let mut buffer = [0u8; 1024];
2050 let size = env
2051 .volume
2052 .get_xattr("testfile", "user.test", Some(&mut buffer))
2053 .unwrap();
2054
2055 assert_eq!(size, 3);
2056 assert_eq!(&buffer[..3], b"foo");
2057
2058 env.volume
2060 .set_xattr("testfile", "user.empty", b"", 0)
2061 .unwrap();
2062
2063 let size = env
2064 .volume
2065 .get_xattr("testfile", "user.empty", None)
2066 .unwrap();
2067
2068 assert_eq!(size, 0);
2069
2070 let size = env.volume.list_xattr("testfile", None).unwrap();
2072 assert_eq!(size, 21);
2073 let size = env
2074 .volume
2075 .list_xattr("testfile", Some(&mut buffer))
2076 .unwrap();
2077
2078 assert_eq!(size, 21);
2079 assert_eq!(&buffer[..21], b"user.test\0user.empty\0");
2080
2081 env.volume.remove_xattr("testfile", "user.empty").unwrap();
2083
2084 let err = env
2085 .volume
2086 .get_xattr("testfile", "user.empty", None)
2087 .unwrap_err();
2088
2089 assert_eq!(err.value(), lx::ENODATA);
2090 let size = env
2091 .volume
2092 .list_xattr("testfile", Some(&mut buffer))
2093 .unwrap();
2094
2095 assert_eq!(size, 10);
2096 assert_eq!(&buffer[..10], b"user.test\0");
2097
2098 let err = env
2100 .volume
2101 .set_xattr("testfile", "user.test", b"bar", lx::XATTR_CREATE)
2102 .unwrap_err();
2103
2104 assert_eq!(err.value(), lx::EEXIST);
2105 let size = env
2106 .volume
2107 .get_xattr("testfile", "user.test", Some(&mut buffer))
2108 .unwrap();
2109
2110 assert_eq!(size, 3);
2111 assert_eq!(&buffer[..3], b"foo");
2112 env.volume
2113 .set_xattr("testfile", "user.test2", b"bar", lx::XATTR_CREATE)
2114 .unwrap();
2115
2116 let size = env
2117 .volume
2118 .get_xattr("testfile", "user.test2", Some(&mut buffer))
2119 .unwrap();
2120
2121 assert_eq!(size, 3);
2122 assert_eq!(&buffer[..3], b"bar");
2123 let err = env
2124 .volume
2125 .set_xattr("testfile", "user.test3", b"baz", lx::XATTR_REPLACE)
2126 .unwrap_err();
2127
2128 assert_eq!(err.value(), lx::ENODATA);
2129 let err = env
2130 .volume
2131 .get_xattr("testfile", "user.test3", None)
2132 .unwrap_err();
2133
2134 assert_eq!(err.value(), lx::ENODATA);
2135 env.volume
2136 .set_xattr("testfile", "user.test2", b"baz", lx::XATTR_REPLACE)
2137 .unwrap();
2138
2139 let size = env
2140 .volume
2141 .get_xattr("testfile", "user.test2", Some(&mut buffer))
2142 .unwrap();
2143
2144 assert_eq!(size, 3);
2145 assert_eq!(&buffer[..3], b"baz");
2146 }
2147
2148 #[test]
2152 #[cfg(not(all(windows, feature = "ci")))]
2153 fn case_sensitive() {
2154 let env = TestEnv::with_options(LxVolumeOptions::new().create_case_sensitive_dirs(true));
2155
2156 env.volume
2157 .mkdir("testdir", LxCreateOptions::new(0o777, 0, 0))
2158 .expect("Could not create case sensitive directory. This may indicate WSL needs to be installed.");
2159
2160 env.volume
2161 .mknod(
2162 Path::from_lx("testdir/testfile").unwrap(),
2163 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2164 0,
2165 )
2166 .unwrap();
2167
2168 env.volume
2169 .mknod(
2170 Path::from_lx("testdir/TESTFILE").unwrap(),
2171 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2172 0,
2173 )
2174 .unwrap();
2175
2176 let stat1 = env
2177 .volume
2178 .lstat(Path::from_lx("testdir/testfile").unwrap())
2179 .unwrap();
2180
2181 let stat2 = env
2182 .volume
2183 .lstat(Path::from_lx("testdir/TESTFILE").unwrap())
2184 .unwrap();
2185
2186 assert_ne!(stat1.inode_nr, stat2.inode_nr);
2187 }
2188
2189 #[test]
2190 #[cfg(windows)]
2191 fn case_insensitive() {
2192 let env = TestEnv::new();
2193 env.volume
2194 .mkdir("testdir", LxCreateOptions::new(0o777, 0, 0))
2195 .unwrap();
2196
2197 env.volume
2198 .mknod(
2199 Path::from_lx("testdir/testfile").unwrap(),
2200 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2201 0,
2202 )
2203 .unwrap();
2204
2205 let err = env
2206 .volume
2207 .mknod(
2208 Path::from_lx("testdir/TESTFILE").unwrap(),
2209 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2210 0,
2211 )
2212 .unwrap_err();
2213
2214 assert_eq!(err.value(), lx::EEXIST);
2215 }
2216
2217 #[test]
2220 #[cfg(all(windows, not(feature = "ci")))]
2221 fn case_sensitive_dir_xattr() {
2222 let env = TestEnv::new();
2223 env.volume
2224 .mkdir("testdir", LxCreateOptions::new(0o777, 0, 0))
2225 .unwrap();
2226
2227 let size = env.volume.list_xattr("testdir", None).unwrap();
2228 assert_eq!(size, b"system.wsl_case_sensitive\0".len());
2229
2230 let mut buffer = [0u8; 1024];
2231 let size = env.volume.list_xattr("testdir", Some(&mut buffer)).unwrap();
2232 assert_eq!(&buffer[..size], b"system.wsl_case_sensitive\0");
2233
2234 let size = env
2235 .volume
2236 .get_xattr("testdir", "system.wsl_case_sensitive", Some(&mut buffer))
2237 .unwrap();
2238
2239 assert_eq!(&buffer[..size], b"0");
2240
2241 env.volume
2242 .set_xattr("testdir", "system.wsl_case_sensitive", b"1", 0)
2243 .expect("Could not create case sensitive directory. This may indicate WSL needs to be installed.");
2244
2245 let size = env
2246 .volume
2247 .get_xattr("testdir", "system.wsl_case_sensitive", Some(&mut buffer))
2248 .unwrap();
2249
2250 assert_eq!(&buffer[..size], b"1");
2251
2252 env.volume
2253 .mknod(
2254 Path::from_lx("testdir/testfile").unwrap(),
2255 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2256 0,
2257 )
2258 .unwrap();
2259
2260 env.volume
2261 .mknod(
2262 Path::from_lx("testdir/TESTFILE").unwrap(),
2263 LxCreateOptions::new(lx::S_IFREG | 0o666, 0, 0),
2264 0,
2265 )
2266 .unwrap();
2267
2268 let stat1 = env
2269 .volume
2270 .lstat(Path::from_lx("testdir/testfile").unwrap())
2271 .unwrap();
2272
2273 let stat2 = env
2274 .volume
2275 .lstat(Path::from_lx("testdir/TESTFILE").unwrap())
2276 .unwrap();
2277
2278 assert_ne!(stat1.inode_nr, stat2.inode_nr);
2279 }
2280
2281 fn check_symlink(volume: &LxVolume, path: impl AsRef<Path>, target: &str) {
2282 volume
2283 .symlink(&path, target, LxCreateOptions::new(0, 0, 0))
2284 .unwrap();
2285
2286 let stat = volume.lstat(&path).unwrap();
2287 assert_eq!(stat.mode, lx::S_IFLNK | 0o777);
2288 assert_eq!(stat.file_size, target.len() as u64);
2289 assert_eq!(volume.read_link(&path).unwrap(), target);
2290 }
2291
2292 fn check_unlink(volume: &LxVolume, path: impl AsRef<Path>, dir: bool) {
2293 let (good_flags, bad_flags, error) = if dir {
2294 (lx::AT_REMOVEDIR, 0, lx::EISDIR)
2295 } else {
2296 (0, lx::AT_REMOVEDIR, lx::ENOTDIR)
2297 };
2298
2299 assert_eq!(volume.unlink(&path, bad_flags).unwrap_err().value(), error);
2300 volume.lstat(&path).unwrap();
2301 volume.unlink(&path, good_flags).unwrap();
2302 assert_eq!(volume.lstat(&path).unwrap_err().value(), lx::ENOENT);
2303 }
2304
2305 #[cfg(unix)]
2306 fn is_lx_root() -> bool {
2307 unsafe { libc::getuid() == 0 }
2309 }
2310
2311 #[cfg(windows)]
2312 fn is_lx_root() -> bool {
2313 true
2314 }
2315
2316 struct TestEnv {
2317 volume: LxVolume,
2318 root_dir: TempDir,
2319 }
2320
2321 impl TestEnv {
2322 fn new() -> Self {
2323 Self::with_options(&LxVolumeOptions::new())
2324 }
2325
2326 fn with_options(options: &LxVolumeOptions) -> Self {
2327 Self::clear_umask();
2328 let root_dir = tempfile::tempdir().unwrap();
2329 let volume = options.new_volume(root_dir.path()).unwrap();
2330 Self { volume, root_dir }
2331 }
2332
2333 fn create_file(&self, name: &str, contents: &str) {
2334 let mut path: PathBuf = self.root_dir.path().into();
2335 path.push(name);
2336 fs::create_dir_all(path.parent().unwrap()).unwrap();
2337 fs::write(path, contents).unwrap();
2338 }
2339
2340 fn check_dir_entry(&self, entry: &lx::DirEntry) {
2341 assert_ne!(entry.offset, 0);
2344
2345 let name = entry.name.to_str().unwrap();
2346 if name == "." || name == ".." {
2347 assert_eq!(entry.file_type, lx::DT_DIR);
2349 } else {
2350 let stat = self.volume.lstat(name).unwrap();
2351 assert_eq!(entry.inode_nr, stat.inode_nr);
2352 assert_eq!((entry.file_type as u32) << 12, stat.mode & lx::S_IFMT);
2353 }
2354 }
2355
2356 #[cfg(unix)]
2357 fn clear_umask() {
2358 unsafe { libc::umask(0) };
2360 }
2361
2362 #[cfg(windows)]
2363 fn clear_umask() {}
2364 }
2365}