Skip to main content

lxutil/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! The LxUtil crate provides an API that allows you to write the same file system code on Windows
5//! and Linux, using Linux semantics on both platforms (subject to the limitations of the underlying
6//! file system).
7
8#![cfg(any(windows, target_os = "linux"))]
9#![expect(clippy::field_reassign_with_default)] // protocol code benefits from imperative field assignment
10
11mod 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
29/// A platform-independent abstraction that allows you to treat an area of the file system as if
30/// it has Unix semantics.
31///
32/// N.B.: all methods take relative paths, but do not attempt to make sure the path does not escape
33///       the root of the `LxVolume`, and therefore should not be relied upon for security.
34///
35/// Use `PathExt` and `PathBufExt` to write cross-platform code that deals only with Unix-style
36/// paths.
37///
38/// # Windows
39///
40/// Unix behavior is emulated on Windows, specifically targeting the behavior of Linux. The
41/// semantics of some calls may differ slightly. In particular:
42///
43/// - Linux specific attributes (such as a file's mode and owner, and special file types like fifos
44///   and device files) are only supported if metadata is enabled in the `LxVolumeOptions`, and
45///   the underlying file system supports the required functionality (extended attributes and
46///   reparse points). If this is not enabled, emulation of certain behavior like `chmod` is
47///   limited.
48/// - For files that don't have Linux metadata, `LxVolumeOptions` can be used to influence how
49///   values for Linux attributes are created.
50/// - The owner of a newly created file is specified in the `LxCreateOptions` passed to the
51///   relevant create call.
52/// - Linux permissions are not enforced, even if metadata is enabled.
53///
54/// # Unix
55///
56/// All calls pass through directly to their libc equivalent. Attributes like mode are always
57/// enabled if the file system supports them. `LxVolumeOptions` is entirely ignored, as are the
58/// `uid` and `gid` fields of `LxCreateOptions`.
59pub struct LxVolume {
60    inner: sys::LxVolume,
61}
62
63// This top-level implementation exists to ensure the Windows and Unix implementation have the same
64// interface.
65impl LxVolume {
66    /// Creates a new instance of `LxVolume` using the specified root path.
67    pub fn new(root_path: impl AsRef<Path>) -> lx::Result<Self> {
68        Self::new_with_options(root_path, &LxVolumeOptions::new())
69    }
70
71    /// Indicates whether the file IDs (inode numbers) on this file system are stable.
72    ///
73    /// # Windows
74    ///
75    /// This is determined by whether or not a file system supports the
76    /// `FILE_SUPPORTS_OPEN_BY_FILE_ID` flag. For example, FAT does not have stable inode numbers.
77    ///
78    /// If a file system doesn't have stable inode numbers, it means a file's inode number can
79    /// change when that file is renamed, and the original inode number can be reused by another
80    /// file.
81    ///
82    /// # Unix
83    ///
84    /// This is a requirement for file systems in Linux, so this is always `true`. Note that IDs
85    /// may still conflict if a path traverses a mount point.
86    pub fn supports_stable_file_id(&self) -> bool {
87        self.inner.supports_stable_file_id()
88    }
89
90    /// Retrieves the attributes of a file. Symlinks are not followed.
91    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    /// Retrieves the statx details of a file. Symlinks are not followed.
96    pub fn statx(&self, path: impl AsRef<Path>) -> lx::Result<lx::StatEx> {
97        self.inner.lstat(path.as_ref())
98    }
99
100    /// Sets the attributes of a file. Symlinks are not followed.
101    ///
102    /// This function combines the functionality of `truncate`, `chmod`, `chown` and `utimensat`.
103    ///
104    /// If this function fails, some of the operations may still have succeeded.
105    ///
106    /// # Windows
107    ///
108    /// Chmod and chown are only fully supported if metadata is enabled and the file system supports
109    /// it. Without metadata, chmod only changes the read-only attribute if all write bits are
110    /// removed from the mode, and chown silently succeeds without taking any action.
111    ///
112    /// This function disables the set-user-ID and set-group-ID as required if a request is made
113    /// to change the size, owner or group of a file. This is done based on whether the
114    /// `SetAttributes::thread_uid` field indicates the user is root.
115    ///
116    /// # Unix
117    ///
118    /// Symlinks are followed for chmod, because the `fchmodat` syscall does not offer a way to not
119    /// follow symlinks.
120    ///
121    /// The `SetAttributes::thread_uid` field is ignored, and the thread's actual capabilities are
122    /// are used.
123    ///
124    /// If `SetAttributes::ctime` is set, the ctime is set to the current time rather than the
125    /// specified value.
126    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    /// Sets the attributes of a file, and gets the new attributes. Symlinks are not followed.
131    ///
132    /// See `set_attr` for more details.
133    ///
134    /// # Windowows
135    ///
136    /// Attributes are set and retrieved using the same handle, and is therefore faster than
137    /// calling `set_attr` and `lstat` separately.
138    ///
139    /// # Unix
140    ///
141    /// This does the operations separately, and is therefore susceptible to a race if the item is
142    /// removed or replaced between creation and retrieving its attributes.
143    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    /// Truncates a file.
152    ///
153    /// # Windows
154    ///
155    /// The `thread_uid` argument is used to determine whether or not the set-user-ID and
156    /// set-group-ID bits should be cleared. This is ignored if metadata is disabled.
157    ///
158    /// # Unix
159    ///
160    /// Unlike the normal `truncate` syscall on Linux, this function does not follow symlinks.
161    /// The `thread_uid` argument is ignored, and the thread's actual capabilities are used.
162    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    /// Changes the permissions of a file.
175    ///
176    /// # Windows
177    ///
178    /// Chmod is only fully supported if metadata is enabled and the file system supports it.
179    /// Without metadata, chmod only changes the read-only attribute if all write bites are
180    /// removed from the mode.
181    ///
182    /// # Unix
183    ///
184    /// Symlinks are followed for chmod, because the `fchmodat` syscall does not offer a way to not
185    /// follow symlinks.
186    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    /// Changes the owner and/or group of a file.
193    ///
194    /// # Windows
195    ///
196    /// Chown is only fully supported if metadata is enabled and the file system supports it.
197    /// Without metadata, chown silently succeeds without taking any action.
198    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    /// Changes a file's time stamps.
211    ///
212    /// The change time of the file is always set to the current time if this function is called.
213    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    /// Opens or creates a file.
227    ///
228    /// # Windows
229    ///
230    /// Not all open flags are supported. In particular, only the flags present in the `lx` module
231    /// are supported. Unknown flags are ignored.
232    ///
233    /// The `O_NOFOLLOW` flag will successfully open a symbolic link, whereas on Unix it will fail
234    /// without the `O_PATH` flag (the O_PATH flag is ignored on Windows).
235    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    /// Creates a new directory.
247    pub fn mkdir(&self, path: impl AsRef<Path>, options: LxCreateOptions) -> lx::Result<()> {
248        self.inner.mkdir(path.as_ref(), options)
249    }
250
251    /// Creates a new directory and retrieves its attributes.
252    ///
253    /// # Windows
254    ///
255    /// This uses the handle opened during creation, and is therefore faster than doing the
256    /// operations separately.
257    ///
258    /// # Unix
259    ///
260    /// This does the operations separately, and is therefore susceptible to a race if the item is
261    /// removed or replaced between creation and retrieving its attributes.
262    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    /// Creates a new symbolic link.
271    ///
272    /// The mode on the create options is ignored, as symbolic links always have a mode of 0o777.
273    ///
274    /// # Windows
275    ///
276    /// This will attempt to create an NTFS symbolic link, but will fall back to a WSL-style link
277    /// if this is not possible.
278    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    /// Creates a new symbolic link and retrieves its attributes.
288    ///
289    /// The mode on the create options is ignored, as symbolic links always have a mode of 0o777.
290    ///
291    /// # Windows
292    ///
293    /// This uses the handle opened during creation, and is therefore faster than doing the
294    /// operations separately.
295    ///
296    /// # Unix
297    ///
298    /// This does the operations separately, and is therefore susceptible to a race if the item is
299    /// removed or replaced between creation and retrieving its attributes.
300    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    /// Reads the target of a symbolic link.
311    ///
312    /// # Windows
313    ///
314    /// NTFS symlinks will be translated to a Unix-style path. WSL-style symlinks are returned as
315    /// is. Use `PathExt` or `PathBufExt` to convert the result to a native path if required.
316    pub fn read_link(&self, path: impl AsRef<Path>) -> lx::Result<lx::LxString> {
317        self.inner.read_link(path.as_ref())
318    }
319
320    /// Removes a file or directory.
321    ///
322    /// When the `lx::AT_REMOVEDIR` flag is specified, this method removes directories; otherwise,
323    /// it removes files.
324    ///
325    /// # Windows
326    ///
327    /// NTFS directory symbolic links are counted as files, not directories.
328    pub fn unlink(&self, path: impl AsRef<Path>, flags: i32) -> lx::Result<()> {
329        self.inner.unlink(path.as_ref(), flags)
330    }
331
332    /// Creates a regular, character device, block device, fifo or socket file.
333    ///
334    /// # Windows
335    ///
336    /// Only regular files are supported unless metadata is enabled.
337    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    /// Creates a regular, character device, block device, fifo or socket file, and retrieves its
347    /// attributes.
348    ///
349    /// # Windows
350    ///
351    /// Only regular files are supported unless metadata is enabled.
352    ///
353    /// This uses the handle opened during creation, and is therefore faster than doing the
354    /// operations separately.
355    ///
356    /// # Unix
357    ///
358    /// This does the operations separately, and is therefore susceptible to a race if the item is
359    /// removed or replaced between creation and retrieving its attributes.
360    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    /// Renames a file.
370    ///
371    /// Flags correspond to the flags of the `renameat2` syscall in Linux.
372    ///
373    /// # Windows
374    ///
375    /// This function will use POSIX rename if the file system supports it. No flags are currently
376    /// supported.
377    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    /// Creates a new hard link to a file.
387    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    /// Creates a new hard link to a file and retrieves its attributes.
392    ///
393    /// # Windows
394    ///
395    /// This uses the handle opened during creation, and is therefore faster than doing the
396    /// operations separately.
397    ///
398    /// # Unix
399    ///
400    /// This does the operations separately, and is therefore susceptible to a race if the item is
401    /// removed or replaced between creation and retrieving its attributes.
402    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    /// Retrieve attributes of the file system.
411    ///
412    /// The path passed should not really matter, unless there are multiple file systems accessible
413    /// from this LxVolume.
414    ///
415    /// # Windows
416    ///
417    /// The `StatFs::fs_type` and `StatFs::flags` field will not be set as they are not relevant
418    /// to Windows.
419    pub fn stat_fs(&self, path: impl AsRef<Path>) -> lx::Result<lx::StatFs> {
420        self.inner.stat_fs(path.as_ref())
421    }
422
423    /// Sets an extended attribute on a file.
424    ///
425    /// # Windows
426    ///
427    /// Extended attribute names are not case sensitive. They are stored as upper case in NTFS but
428    /// `list_xattr` will report them as lower case for greater compatibility with Linux.
429    ///
430    /// Extended attribute names are prefixed with "LX.", and have a slightly shorter maximum length
431    /// limit than Linux. Attribute values are prefixed with a 4-byte header to allow for "empty"
432    /// values, which NTFS does not normally allow. `get_xattr` and `list_xattr` will strip these
433    /// prefixes.
434    ///
435    /// Security for accessing the various attribute namespaces is not enforced.
436    ///
437    /// If the flags `XATTR_CREATE` or `XATTR_REPLACE` are used, the operation is not atomic
438    /// because Windows has to separately check for the attribute's existence. In this case, there
439    /// is a small possibility of a race where an attribute created by another thread gets
440    /// overwritten.
441    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    /// Gets the value or size of an extended attribute on a file.
453    ///
454    /// This function will return the size of the attribute.
455    ///
456    /// # Windows
457    ///
458    /// Extended attribute names are not case sensitive. They are stored as upper case in NTFS but
459    /// `list_xattr` will report them as lower case for greater compatibility with Linux.
460    ///
461    /// Extended attribute names are prefixed with "LX.", and have a slightly shorter maximum length
462    /// limit than Linux. Attribute values are prefixed with a 4-byte header to allow for "empty"
463    /// values, which NTFS does not normally allow. `get_xattr` and `list_xattr` will strip these
464    /// prefixes.
465    ///
466    /// Security for accessing the various attribute namespaces is not enforced.
467    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    /// Gets a list of all the extended attributes on a file.
477    ///
478    /// This function will return the size of the list.
479    ///
480    /// The list contains the names of all the attributes, separated by NULL characters.
481    ///
482    /// # Windows
483    ///
484    /// Extended attribute names are not case sensitive. They are stored as upper case in NTFS but
485    /// `list_xattr` will report them as lower case for greater compatibility with Linux.
486    ///
487    /// Extended attribute names are prefixed with "LX.", and have a slightly shorter maximum length
488    /// limit than Linux. Attribute values are prefixed with a 4-byte header to allow for "empty"
489    /// values, which NTFS does not normally allow. `get_xattr` and `list_xattr` will strip these
490    /// prefixes.
491    ///
492    /// Security for accessing the various attribute namespaces is not enforced.
493    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    /// Removes an extended attribute from the file.
498    ///
499    /// # Windows
500    ///
501    /// Extended attribute names are not case sensitive. They are stored as upper case in NTFS but
502    /// `list_xattr` will report them as lower case for greater compatibility with Linux.
503    ///
504    /// Extended attribute names are prefixed with "LX.", and have a slightly shorter maximum length
505    /// limit than Linux. Attribute values are prefixed with a 4-byte header to allow for "empty"
506    /// values, which NTFS does not normally allow. `get_xattr` and `list_xattr` will strip these
507    /// prefixes.
508    ///
509    /// Security for accessing the various attribute namespaces is not enforced.
510    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    /// Creates a new instance of `LxVolume` using the specified root path and options.
519    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
529/// A platform-independent abstraction that allows you to treat a file as if it has Unix semantics.
530///
531/// `LxFile` instances are created by using `LxVolume::open`.
532pub struct LxFile {
533    inner: sys::LxFile,
534}
535
536impl LxFile {
537    /// Retrieves the attributes of the file.
538    pub fn fstat(&self) -> lx::Result<lx::StatEx> {
539        self.inner.fstat()
540    }
541
542    /// Sets the attributes of the file.
543    ///
544    /// This function combines the functionality of `truncate`, `chmod`, `chown` and `utimensat`.
545    ///
546    /// If this function fails, some of the operations may still have succeeded.
547    ///
548    /// # Windows
549    ///
550    /// Chmod and chown are only fully supported if metadata is enabled and the file system supports
551    /// it. Without metadata, chmod only changes the read-only attribute if all write bits are
552    /// removed from the mode, and chown silently succeeds without taking any action.
553    ///
554    /// This function disables the set-user-ID and set-group-ID as required if a request is made
555    /// to change the size, owner or group of a file. This is done based on whether the
556    /// `SetAttributes::thread_uid` field indicates the user is root.
557    ///
558    /// # Unix
559    ///
560    /// The `SetAttributes::thread_uid` field is ignored, and the thread's actual capabilities are
561    /// are used.
562    ///
563    /// If `SetAttributes::ctime` is set, the ctime is set to the current time rather than the
564    /// specified value.
565    pub fn set_attr(&self, attr: SetAttributes) -> lx::Result<()> {
566        self.inner.set_attr(attr)
567    }
568
569    /// Truncates a file.
570    ///
571    /// # Windows
572    ///
573    /// The `thread_uid` argument is used to determine whether or not the set-user-ID and
574    /// set-group-ID bits should be cleared. This is ignored if metadata is disabled.
575    ///
576    /// # Unix
577    ///
578    /// Unlike the normal `truncate` syscall on Linux, this function does not follow symlinks.
579    /// The `thread_uid` argument is ignored, and the thread's actual capabilities are used.
580    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    /// Changes the permissions of a file.
588    ///
589    /// # Windows
590    ///
591    /// Chmod is only fully supported if metadata is enabled and the file system supports it.
592    /// Without metadata, chmod only changes the read-only attribute if all write bites are
593    /// removed from the mode.
594    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    /// Changes the owner and/or group of a file.
601    ///
602    /// # Windows
603    ///
604    /// Chown is only fully supported if metadata is enabled and the file system supports it.
605    /// Without metadata, chown silently succeeds without taking any action.
606    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    /// Changes a file's time stamps.
614    ///
615    /// The change time of the file is always set to the current time if this function is called.
616    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    /// Reads a number of bytes starting from a given offset.
625    ///
626    /// Returns the number of bytes read.
627    ///
628    /// On Windows, the file pointer is changed after this operation, while on Unix, it is not.
629    pub fn pread(&self, buffer: &mut [u8], offset: lx::off_t) -> lx::Result<usize> {
630        self.inner.pread(buffer, offset)
631    }
632
633    /// Writes a number of bytes starting from a given offset.
634    ///
635    /// Returns the number of bytes written.
636    ///
637    /// # Windows
638    ///
639    /// The file pointer is changed after this operation, while on Unix, it is not.
640    ///
641    /// The `thread_uid` argument is used to determine whether or not the set-user-ID and
642    /// set-group-ID bits should be cleared. This is ignored if metadata is disabled.
643    ///
644    /// # Unix
645    ///
646    /// The `thread_uid` argument is ignored, and the thread's actual capabilities are used.
647    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    /// Reads the contents of the directory, invoking the callback for each item.
657    ///
658    /// If the callback returns an error, it is propagated to the caller. If the callback returns
659    /// false, enumeration is stopped but no error is returned. Enumeration can be continued
660    /// from the same position by calling this function again with the offset of the entry *before*
661    /// the one that cancelled the enumeration.
662    ///
663    /// # Windows
664    ///
665    /// The . and .. entries are always returned, but their inode number is not set.
666    ///
667    /// # Unix
668    ///
669    /// The . and .. entries are returned only if the underlying file system returns them.
670    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    /// Synchronizes the file's buffer.
678    ///
679    /// This function can optionally synchronize only data, not metadata.
680    pub fn fsync(&self, data_only: bool) -> lx::Result<()> {
681        self.inner.fsync(data_only)
682    }
683}
684
685/// Sets options used by an LxVolume. These control whether metadata is enabled, and set defaults
686/// to use for files without metadata.
687///
688/// # Unix
689///
690/// These options have no effect on Unix platforms.
691#[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    /// Create a new `LxVolumeOptions` with default options.
712    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    /// Create a new 'LxVolumeOptions' using a semi-colon separated list of options of the form
733    /// uid=1000;gid=1000;symlinkroot=/mnt/
734    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    /// Creates a new `LxVolume` with the current options.
917    pub fn new_volume(&self, root_path: impl AsRef<Path>) -> lx::Result<LxVolume> {
918        LxVolume::new_with_options(root_path, self)
919    }
920
921    /// Set the owner user ID for all files.
922    pub fn uid(&mut self, uid: lx::uid_t) -> &mut Self {
923        self.uid = Some(uid);
924        self
925    }
926
927    /// Set the owner group ID for all files.
928    pub fn gid(&mut self, gid: lx::gid_t) -> &mut Self {
929        self.gid = Some(gid);
930        self
931    }
932
933    /// Sets the mode bits for all files. Directories will add 'x' automatically if 'r' is set to allow list.
934    pub fn mode(&mut self, mode: u32) -> &mut Self {
935        self.mode = Some(mode & 0o777);
936        self
937    }
938
939    /// Set the owner user ID for files without metadata.
940    pub fn default_uid(&mut self, uid: lx::uid_t) -> &mut Self {
941        self.default_uid = uid;
942        self
943    }
944
945    /// Set the owner group ID for files without metadata.
946    pub fn default_gid(&mut self, gid: lx::gid_t) -> &mut Self {
947        self.default_gid = gid;
948        self
949    }
950
951    /// Set a mask of mode bits that will always be disabled on files and directories that have no
952    /// metadata.
953    pub fn umask(&mut self, umask: u32) -> &mut Self {
954        self.umask = !(umask & 0o7777);
955        self
956    }
957
958    /// Set a mask of mode bits that will always be disabled on files that have no metadata.
959    pub fn fmask(&mut self, fmask: u32) -> &mut Self {
960        self.fmask = !(fmask & 0o7777);
961        self
962    }
963
964    /// Set a mask of mode bits that will always be disabled on directories that have no metadata.
965    pub fn dmask(&mut self, dmask: u32) -> &mut Self {
966        self.dmask = !(dmask & 0o7777);
967        self
968    }
969
970    /// Enable or disable metadata for the volume.
971    ///
972    /// This will be ignored if the underlying file system does not support the required features
973    /// to emulate Linux attributes on Windows.
974    pub fn metadata(&mut self, metadata: bool) -> &mut Self {
975        self.metadata = metadata;
976        self
977    }
978
979    /// Apply additional file restrictions.
980    ///
981    /// Hide files and directories that are marked as hidden or may cause hydration (e.g. OneDrive backed).
982    pub fn sandbox(&mut self, enabled: bool) -> &mut Self {
983        self.sandbox = enabled;
984        self
985    }
986
987    /// Exclude specific file extensions when sandbox mode is enabled.
988    ///
989    /// Hide files and directories with specific file extensions. Do not allow creation of new files with these extensions.
990    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    /// Enable or disable whether new directories are created as case sensitive.
1002    ///
1003    /// This will be ignored if the underlying file system does not support case sensitive
1004    /// directories.
1005    ///
1006    /// This does not affect the behavior of existing case sensitive directories, where operations
1007    /// will be case sensitive and new directories will inherit the flag.
1008    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    /// Set the root used to translate absolute Windows symlinks paths.
1014    ///
1015    /// EXAMPLE: A symlink to C:\my\target will return /mnt/c/my/target if symlink_root is set to "/mnt/".
1016    pub fn symlink_root(&mut self, symlink_root: &str) -> &mut Self {
1017        self.symlink_root = symlink_root.to_string();
1018        self
1019    }
1020
1021    /// Add an extended attribute to return with every file in the volume.
1022    ///
1023    /// This will be used in place of any actual extended attributes associated with the file.
1024    ///
1025    /// N.B. Since some attributes may be related, replacing the returned attributes is deemed easier and safer than
1026    ///      trying to union overrides with existing attributes.
1027    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    /// Enable or disable readonly mode for the volume.
1035    ///
1036    /// This flag is not enforced by `LxVolume` itself. It is intended to be
1037    /// read by higher-level layers (e.g., virtio-fs) via [`is_readonly`](Self::is_readonly)
1038    /// to reject write operations with `EROFS`.
1039    pub fn readonly(&mut self, readonly: bool) -> &mut Self {
1040        self.readonly = readonly;
1041        self
1042    }
1043
1044    /// Returns whether the volume is configured as readonly.
1045    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/// Specifies options to use when creating a file.
1057///
1058/// The user ID and group ID are only used on Windows; on Unix platforms, set the thread's effective
1059/// user ID and group ID to change the owner of a newly created file.
1060#[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    /// Creates a new `LxCreateOptions`.
1071    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/// Supplies the attributes to change for `set_attr`.
1077#[derive(Default, Clone, Copy)]
1078pub struct SetAttributes {
1079    /// Truncate the file.
1080    pub size: Option<lx::off_t>,
1081
1082    /// Set the access time.
1083    pub atime: SetTime,
1084
1085    /// Set the modified time.
1086    pub mtime: SetTime,
1087
1088    /// Set the change time.
1089    ///
1090    /// Some file systems only support setting the change time to the current time.
1091    pub ctime: SetTime,
1092
1093    /// Set the file's mode.
1094    ///
1095    /// # Windows
1096    ///
1097    /// The mode must include the file type, and must match the existing file type.
1098    ///
1099    /// # Unix
1100    ///
1101    /// The file type will be ignored.
1102    pub mode: Option<lx::mode_t>,
1103
1104    /// Set the file's owner user ID.
1105    pub uid: Option<lx::uid_t>,
1106
1107    /// Set the file's owner group ID.
1108    pub gid: Option<lx::gid_t>,
1109
1110    /// The current thread's effective user ID.
1111    ///
1112    /// # Windows
1113    ///
1114    /// This is used to determine whether truncation needs to clear the set-user-ID and set-group-ID
1115    /// attributes. It is ignored if metadata is disabled.
1116    ///
1117    /// # Unix
1118    ///
1119    /// The actual thread's capabilities are used, so this value is ignored.
1120    pub thread_uid: lx::uid_t,
1121}
1122
1123/// Supplies the value to set a time attribute to.
1124#[derive(Clone, Copy, Default)]
1125pub enum SetTime {
1126    /// Don't change the time.
1127    #[default]
1128    Omit,
1129    /// Set the time to the specified value.
1130    Set(std::time::Duration),
1131    /// Set the time to the current time.
1132    Now,
1133}
1134
1135impl SetTime {
1136    /// Checks whether the value matches the `Omit` variant.
1137    pub fn is_omit(&self) -> bool {
1138        matches!(self, SetTime::Omit)
1139    }
1140}
1141
1142#[cfg(test)]
1143// UNSAFETY: Calls to libc to check and manipulate permissions.
1144#[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        // Write some text.
1213        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        // Read the whole thing back.
1219        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        // Read at EOF.
1224        assert_eq!(file.pread(&mut buffer, 13).unwrap(), 0);
1225
1226        // Write over part of it.
1227        assert_eq!(file.pwrite(b"Bye", 4, 0).unwrap(), 3);
1228        assert_eq!(file.fstat().unwrap().file_size, 13);
1229
1230        // Read part of it.
1231        assert_eq!(file.pread(&mut buffer[..8], 2).unwrap(), 8);
1232        assert_eq!(&buffer[..8], b"llByewor");
1233
1234        // Can't write if O_RDONLY.
1235        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        // Can't read if O_WRONLY
1240        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        // Can't do either if O_NOACCESS
1245        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        // A file opened with O_APPEND must still support ftruncate, matching Linux behavior. On
1253        // Windows, O_APPEND strips FILE_WRITE_DATA to enforce append-only writes, which previously
1254        // caused the truncate to fail with EACCES.
1255        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        // ftruncate on an O_APPEND descriptor should succeed.
1269        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        // ftruncate on a descriptor that was not opened for writing must fail, matching Linux.
1279        // This guards against the Windows truncate path reopening the file with write access and
1280        // succeeding on a read-only descriptor.
1281        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        // The file must be unchanged.
1287        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        // Read up until the 6th file.
1314        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            // Remember a file name and its offset so we can seek back to it later.
1329            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        // Continue from the last offset seen; this tests that files aren't skipped or double reported.
1340        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        // Confirm that every file has been seen exactly once.
1353        assert_eq!(count, 12);
1354        for (_, value) in map {
1355            assert!(value);
1356        }
1357
1358        // Confirm that we can use the same file to seek to the start and enumerate again.
1359        count = 0;
1360        dir.read_dir(0, |_| {
1361            count += 1;
1362            Ok(true)
1363        })
1364        .unwrap();
1365
1366        assert_eq!(count, 12);
1367
1368        // Check that we can seek to a specific file.
1369        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        // Check that errors are propagated.
1380        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        // Make sure the closure can safely panic even when invoked through the C callback on
1403        // Windows.
1404        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        // Only Windows uses the uid/gid
1429        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        // Chmod/chown (chown is skipped on Linux because we may not have permission).
1481        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        // On real Linux, root is needed to create device files.
1497        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        // Symlink to a directory, which on Windows will be a directory but should be treated by
1629        // unlink as a file.
1630        // N.B. This only tests the right thing if developer mode is on, otherwise it creates an LX symlink.
1631        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        // Truncate.
1658        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        // Chmod (read-only attribute)
1665        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        // Chown (silent succeed on Windows; skip on Linux since we may not have permission)
1676        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        // Set times, and test set_and_get_attr()
1684        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        // Truncate.
1704        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        // Chmod (read-only attribute)
1711        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        // Chown (silent succeed on Windows; skip on Linux since we may not have permission)
1722        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        // Set times
1730        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        // Write clears it (except for root).
1767        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            // Write does not clear it for root.
1772            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        // Truncate clears it (except for root).
1785        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            // Truncate does not clear it as root.
1791            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        // Chown no changes does not clear it.
1805        let stat = file.fstat().unwrap();
1806        file.chown(None, None).unwrap();
1807        assert_eq!(stat.mode as u32, lx::S_IFREG | 0o6777);
1808
1809        // Chown clears it.
1810        // N.B. Only perform this test if we have permissions to do so.
1811        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            // Chown doesn't clear setgid if not group executable.
1819            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        // Test without metadata, so on Windows only regular files will work, and mode/uid/gid are
1836        // not used.
1837        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        // Rename to a new name.
1869        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        // Into a directory.
1876        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        // Dir over dir, not empty.
1894        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        // File over file.
1905        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        // Dir over dir.
1922        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        // File over dir.
1929        let err = env.volume.rename("testfile3", "testdir", 0).unwrap_err();
1930        assert_eq!(err.value(), lx::EISDIR);
1931
1932        // Dir over file.
1933        let err = env.volume.rename("testdir", "testfile3", 0).unwrap_err();
1934        assert_eq!(err.value(), lx::ENOTDIR);
1935
1936        // Scope exit to unlink these files explicitly since they're read-only, which doesn't work
1937        // with TestEnv's drop method.
1938        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        // Readonly file over file.
1944        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        // Rename changing only the case.
1963        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        // Ensure no error is returned for read-only files.
2013        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        // No attributes to start with.
2024        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        // Set an attribute and retrieve it.
2042        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        // Set an empty attribute and retrieve it.
2059        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        // List the attributes.
2071        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        // Remove an attribute.
2082        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        // Test flags.
2099        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    // This test is disabled in CI on Windows only, because it requires NTFS support for setting
2149    // the case sensitive directory attribute, which is only enabled if the WSL optional component
2150    // is installed.
2151    #[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    // This test is disabled in CI, because it requires NTFS support for setting the case sensitive
2218    // directory attribute, which is only enabled if the WSL optional component is installed.
2219    #[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        // SAFETY: Calling C API as documented, with no special requirements.
2308        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            // Since seeking to an offset gives you the next entry after that offset, no entry
2342            // should ever have an offset of zero.
2343            assert_ne!(entry.offset, 0);
2344
2345            let name = entry.name.to_str().unwrap();
2346            if name == "." || name == ".." {
2347                // On Windows, the inode number is not set for . and .. entries, so don't check it.
2348                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            // SAFETY: Calling C API as documented, with no special requirements.
2359            unsafe { libc::umask(0) };
2360        }
2361
2362        #[cfg(windows)]
2363        fn clear_umask() {}
2364    }
2365}