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