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//! This crate uses lxutil.dll, a library created for the Windows Subsystem for Linux to emulate
9//! Linux file system semantics on Windows.
10
11#![cfg(any(windows, target_os = "linux"))]
12#![expect(clippy::field_reassign_with_default)] // protocol code benefits from imperative field assignment
13
14mod 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
32/// A platform-independent abstraction that allows you to treat an area of the file system as if
33/// it has Unix semantics.
34///
35/// N.B.: all methods take relative paths, but do not attempt to make sure the path does not escape
36///       the root of the `LxVolume`, and therefore should not be relied upon for security.
37///
38/// Use `PathExt` and `PathBufExt` to write cross-platform code that deals only with Unix-style
39/// paths.
40///
41/// # Windows
42///
43/// Unix behavior is emulated on Windows, specifically targeting the behavior of Linux. The
44/// semantics of some calls may differ slightly. In particular:
45///
46/// - Linux specific attributes (such as a file's mode and owner, and special file types like fifos
47///   and device files) are only supported if metadata is enabled in the `LxVolumeOptions`, and
48///   the underlying file system supports the required functionality (extended attributes and
49///   reparse points). If this is not enabled, emulation of certain behavior like `chmod` is
50///   limited.
51/// - For files that don't have Linux metadata, `LxVolumeOptions` can be used to influence how
52///   values for Linux attributes are created.
53/// - The owner of a newly created file is specified in the `LxCreateOptions` passed to the
54///   relevant create call.
55/// - Linux permissions are not enforced, even if metadata is enabled.
56///
57/// # Unix
58///
59/// All calls pass through directly to their libc equivalent. Attributes like mode are always
60/// enabled if the file system supports them. `LxVolumeOptions` is entirely ignored, as are the
61/// `uid` and `gid` fields of `LxCreateOptions`.
62pub struct LxVolume {
63    inner: sys::LxVolume,
64}
65
66// This top-level implementation exists to ensure the Windows and Unix implementation have the same
67// interface.
68impl LxVolume {
69    /// Creates a new instance of `LxVolume` using the specified root path.
70    pub fn new(root_path: impl AsRef<Path>) -> lx::Result<Self> {
71        Self::new_with_options(root_path, &LxVolumeOptions::new())
72    }
73
74    /// Indicates whether the file IDs (inode numbers) on this file system are stable.
75    ///
76    /// # Windows
77    ///
78    /// This is determined by whether or not a file system supports the
79    /// `FILE_SUPPORTS_OPEN_BY_FILE_ID` flag. For example, FAT does not have stable inode numbers.
80    ///
81    /// If a file system doesn't have stable inode numbers, it means a file's inode number can
82    /// change when that file is renamed, and the original inode number can be reused by another
83    /// file.
84    ///
85    /// # Unix
86    ///
87    /// This is a requirement for file systems in Linux, so this is always `true`. Note that IDs
88    /// may still conflict if a path traverses a mount point.
89    pub fn supports_stable_file_id(&self) -> bool {
90        self.inner.supports_stable_file_id()
91    }
92
93    /// Retrieves the attributes of a file. Symlinks are not followed.
94    pub fn lstat(&self, path: impl AsRef<Path>) -> lx::Result<lx::Stat> {
95        self.inner.lstat(path.as_ref())
96    }
97
98    /// Sets the attributes of a file. Symlinks are not followed.
99    ///
100    /// This function combines the functionality of `truncate`, `chmod`, `chown` and `utimensat`.
101    ///
102    /// If this function fails, some of the operations may still have succeeded.
103    ///
104    /// # Windows
105    ///
106    /// Chmod and chown are only fully supported if metadata is enabled and the file system supports
107    /// it. Without metadata, chmod only changes the read-only attribute if all write bits are
108    /// removed from the mode, and chown silently succeeds without taking any action.
109    ///
110    /// This function disables the set-user-ID and set-group-ID as required if a request is made
111    /// to change the size, owner or group of a file. This is done based on whether the
112    /// `SetAttributes::thread_uid` field indicates the user is root.
113    ///
114    /// # Unix
115    ///
116    /// Symlinks are followed for chmod, because the `fchmodat` syscall does not offer a way to not
117    /// follow symlinks.
118    ///
119    /// The `SetAttributes::thread_uid` field is ignored, and the thread's actual capabilities are
120    /// are used.
121    ///
122    /// If `SetAttributes::ctime` is set, the ctime is set to the current time rather than the
123    /// specified value.
124    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    /// Sets the attributes of a file, and gets the new attributes. Symlinks are not followed.
129    ///
130    /// See `set_attr` for more details.
131    ///
132    /// # Windowows
133    ///
134    /// Attributes are set and retrieved using the same handle, and is therefore faster than
135    /// calling `set_attr` and `lstat` separately.
136    ///
137    /// # Unix
138    ///
139    /// This does the operations separately, and is therefore susceptible to a race if the item is
140    /// removed or replaced between creation and retrieving its attributes.
141    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    /// Truncates a file.
150    ///
151    /// # Windows
152    ///
153    /// The `thread_uid` argument is used to determine whether or not the set-user-ID and
154    /// set-group-ID bits should be cleared. This is ignored if metadata is disabled.
155    ///
156    /// # Unix
157    ///
158    /// Unlike the normal `truncate` syscall on Linux, this function does not follow symlinks.
159    /// The `thread_uid` argument is ignored, and the thread's actual capabilities are used.
160    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    /// Changes the permissions of a file.
173    ///
174    /// # Windows
175    ///
176    /// Chmod is only fully supported if metadata is enabled and the file system supports it.
177    /// Without metadata, chmod only changes the read-only attribute if all write bites are
178    /// removed from the mode.
179    ///
180    /// # Unix
181    ///
182    /// Symlinks are followed for chmod, because the `fchmodat` syscall does not offer a way to not
183    /// follow symlinks.
184    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    /// Changes the owner and/or group of a file.
191    ///
192    /// # Windows
193    ///
194    /// Chown is only fully supported if metadata is enabled and the file system supports it.
195    /// Without metadata, chown silently succeeds without taking any action.
196    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    /// Changes a file's time stamps.
209    ///
210    /// The change time of the file is always set to the current time if this function is called.
211    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    /// Opens or creates a file.
225    ///
226    /// # Windows
227    ///
228    /// Not all open flags are supported. In particular, only the flags present in the `lx` module
229    /// are supported. Unknown flags are ignored.
230    ///
231    /// The `O_NOFOLLOW` flag will successfully open a symbolic link, whereas on Unix it will fail
232    /// without the `O_PATH` flag (the O_PATH flag is ignored on Windows).
233    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    /// Creates a new directory.
245    pub fn mkdir(&self, path: impl AsRef<Path>, options: LxCreateOptions) -> lx::Result<()> {
246        self.inner.mkdir(path.as_ref(), options)
247    }
248
249    /// Creates a new directory and retrieves its attributes.
250    ///
251    /// # Windows
252    ///
253    /// This uses the handle opened during creation, and is therefore faster than doing the
254    /// operations separately.
255    ///
256    /// # Unix
257    ///
258    /// This does the operations separately, and is therefore susceptible to a race if the item is
259    /// removed or replaced between creation and retrieving its attributes.
260    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    /// Creates a new symbolic link.
269    ///
270    /// The mode on the create options is ignored, as symbolic links always have a mode of 0o777.
271    ///
272    /// # Windows
273    ///
274    /// This will attempt to create an NTFS symbolic link, but will fall back to a WSL-style link
275    /// if this is not possible.
276    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    /// Creates a new symbolic link and retrieves its attributes.
286    ///
287    /// The mode on the create options is ignored, as symbolic links always have a mode of 0o777.
288    ///
289    /// # Windows
290    ///
291    /// This uses the handle opened during creation, and is therefore faster than doing the
292    /// operations separately.
293    ///
294    /// # Unix
295    ///
296    /// This does the operations separately, and is therefore susceptible to a race if the item is
297    /// removed or replaced between creation and retrieving its attributes.
298    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    /// Reads the target of a symbolic link.
309    ///
310    /// # Windows
311    ///
312    /// NTFS symlinks will be translated to a Unix-style path. WSL-style symlinks are returned as
313    /// is. Use `PathExt` or `PathBufExt` to convert the result to a native path if required.
314    pub fn read_link(&self, path: impl AsRef<Path>) -> lx::Result<lx::LxString> {
315        self.inner.read_link(path.as_ref())
316    }
317
318    /// Removes a file or directory.
319    ///
320    /// When the `lx::AT_REMOVEDIR` flag is specified, this method removes directories; otherwise,
321    /// it removes files.
322    ///
323    /// # Windows
324    ///
325    /// NTFS directory symbolic links are counted as files, not directories.
326    pub fn unlink(&self, path: impl AsRef<Path>, flags: i32) -> lx::Result<()> {
327        self.inner.unlink(path.as_ref(), flags)
328    }
329
330    /// Creates a regular, character device, block device, fifo or socket file.
331    ///
332    /// # Windows
333    ///
334    /// Only regular files are supported unless metadata is enabled.
335    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    /// Creates a regular, character device, block device, fifo or socket file, and retrieves its
345    /// attributes.
346    ///
347    /// # Windows
348    ///
349    /// Only regular files are supported unless metadata is enabled.
350    ///
351    /// This uses the handle opened during creation, and is therefore faster than doing the
352    /// operations separately.
353    ///
354    /// # Unix
355    ///
356    /// This does the operations separately, and is therefore susceptible to a race if the item is
357    /// removed or replaced between creation and retrieving its attributes.
358    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    /// Renames a file.
368    ///
369    /// Flags correspond to the flags of the `renameat2` syscall in Linux.
370    ///
371    /// # Windows
372    ///
373    /// This function will use POSIX rename if the file system supports it. No flags are currently
374    /// supported.
375    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    /// Creates a new hard link to a file.
385    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    /// Creates a new hard link to a file and retrieves its attributes.
390    ///
391    /// # Windows
392    ///
393    /// This uses the handle opened during creation, and is therefore faster than doing the
394    /// operations separately.
395    ///
396    /// # Unix
397    ///
398    /// This does the operations separately, and is therefore susceptible to a race if the item is
399    /// removed or replaced between creation and retrieving its attributes.
400    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    /// Retrieve attributes of the file system.
409    ///
410    /// The path passed should not really matter, unless there are multiple file systems accessible
411    /// from this LxVolume.
412    ///
413    /// # Windows
414    ///
415    /// The `StatFs::fs_type` and `StatFs::flags` field will not be set as they are not relevant
416    /// to Windows.
417    pub fn stat_fs(&self, path: impl AsRef<Path>) -> lx::Result<lx::StatFs> {
418        self.inner.stat_fs(path.as_ref())
419    }
420
421    /// Sets an extended attribute on a file.
422    ///
423    /// # Windows
424    ///
425    /// Extended attribute names are not case sensitive. They are stored as upper case in NTFS but
426    /// `list_xattr` will report them as lower case for greater compatibility with Linux.
427    ///
428    /// Extended attribute names are prefixed with "LX.", and have a slightly shorter maximum length
429    /// limit than Linux. Attribute values are prefixed with a 4-byte header to allow for "empty"
430    /// values, which NTFS does not normally allow. `get_xattr` and `list_xattr` will strip these
431    /// prefixes.
432    ///
433    /// Security for accessing the various attribute namespaces is not enforced.
434    ///
435    /// If the flags `XATTR_CREATE` or `XATTR_REPLACE` are used, the operation is not atomic
436    /// because Windows has to separately check for the attribute's existence. In this case, there
437    /// is a small possibility of a race where an attribute created by another thread gets
438    /// overwritten.
439    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    /// Gets the value or size of an extended attribute on a file.
451    ///
452    /// This function will return the size of the attribute.
453    ///
454    /// # Windows
455    ///
456    /// Extended attribute names are not case sensitive. They are stored as upper case in NTFS but
457    /// `list_xattr` will report them as lower case for greater compatibility with Linux.
458    ///
459    /// Extended attribute names are prefixed with "LX.", and have a slightly shorter maximum length
460    /// limit than Linux. Attribute values are prefixed with a 4-byte header to allow for "empty"
461    /// values, which NTFS does not normally allow. `get_xattr` and `list_xattr` will strip these
462    /// prefixes.
463    ///
464    /// Security for accessing the various attribute namespaces is not enforced.
465    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    /// Gets a list of all the extended attributes on a file.
475    ///
476    /// This function will return the size of the list.
477    ///
478    /// The list contains the names of all the attributes, separated by NULL characters.
479    ///
480    /// # Windows
481    ///
482    /// Extended attribute names are not case sensitive. They are stored as upper case in NTFS but
483    /// `list_xattr` will report them as lower case for greater compatibility with Linux.
484    ///
485    /// Extended attribute names are prefixed with "LX.", and have a slightly shorter maximum length
486    /// limit than Linux. Attribute values are prefixed with a 4-byte header to allow for "empty"
487    /// values, which NTFS does not normally allow. `get_xattr` and `list_xattr` will strip these
488    /// prefixes.
489    ///
490    /// Security for accessing the various attribute namespaces is not enforced.
491    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    /// Removes an extended attribute from the file.
496    ///
497    /// # Windows
498    ///
499    /// Extended attribute names are not case sensitive. They are stored as upper case in NTFS but
500    /// `list_xattr` will report them as lower case for greater compatibility with Linux.
501    ///
502    /// Extended attribute names are prefixed with "LX.", and have a slightly shorter maximum length
503    /// limit than Linux. Attribute values are prefixed with a 4-byte header to allow for "empty"
504    /// values, which NTFS does not normally allow. `get_xattr` and `list_xattr` will strip these
505    /// prefixes.
506    ///
507    /// Security for accessing the various attribute namespaces is not enforced.
508    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    /// Creates a new instance of `LxVolume` using the specified root path and options.
517    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
527/// A platform-independent abstraction that allows you to treat a file as if it has Unix semantics.
528///
529/// `LxFile` instances are created by using `LxVolume::open`.
530pub struct LxFile {
531    inner: sys::LxFile,
532}
533
534impl LxFile {
535    /// Retrieves the attributes of the file.
536    pub fn fstat(&self) -> lx::Result<lx::Stat> {
537        self.inner.fstat()
538    }
539
540    /// Sets the attributes of the file.
541    ///
542    /// This function combines the functionality of `truncate`, `chmod`, `chown` and `utimensat`.
543    ///
544    /// If this function fails, some of the operations may still have succeeded.
545    ///
546    /// # Windows
547    ///
548    /// Chmod and chown are only fully supported if metadata is enabled and the file system supports
549    /// it. Without metadata, chmod only changes the read-only attribute if all write bits are
550    /// removed from the mode, and chown silently succeeds without taking any action.
551    ///
552    /// This function disables the set-user-ID and set-group-ID as required if a request is made
553    /// to change the size, owner or group of a file. This is done based on whether the
554    /// `SetAttributes::thread_uid` field indicates the user is root.
555    ///
556    /// # Unix
557    ///
558    /// The `SetAttributes::thread_uid` field is ignored, and the thread's actual capabilities are
559    /// are used.
560    ///
561    /// If `SetAttributes::ctime` is set, the ctime is set to the current time rather than the
562    /// specified value.
563    pub fn set_attr(&self, attr: SetAttributes) -> lx::Result<()> {
564        self.inner.set_attr(attr)
565    }
566
567    /// Truncates a file.
568    ///
569    /// # Windows
570    ///
571    /// The `thread_uid` argument is used to determine whether or not the set-user-ID and
572    /// set-group-ID bits should be cleared. This is ignored if metadata is disabled.
573    ///
574    /// # Unix
575    ///
576    /// Unlike the normal `truncate` syscall on Linux, this function does not follow symlinks.
577    /// The `thread_uid` argument is ignored, and the thread's actual capabilities are used.
578    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    /// Changes the permissions of a file.
586    ///
587    /// # Windows
588    ///
589    /// Chmod is only fully supported if metadata is enabled and the file system supports it.
590    /// Without metadata, chmod only changes the read-only attribute if all write bites are
591    /// removed from the mode.
592    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    /// Changes the owner and/or group of a file.
599    ///
600    /// # Windows
601    ///
602    /// Chown is only fully supported if metadata is enabled and the file system supports it.
603    /// Without metadata, chown silently succeeds without taking any action.
604    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    /// Changes a file's time stamps.
612    ///
613    /// The change time of the file is always set to the current time if this function is called.
614    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    /// Reads a number of bytes starting from a given offset.
623    ///
624    /// Returns the number of bytes read.
625    ///
626    /// On Windows, the file pointer is changed after this operation, while on Unix, it is not.
627    pub fn pread(&self, buffer: &mut [u8], offset: lx::off_t) -> lx::Result<usize> {
628        self.inner.pread(buffer, offset)
629    }
630
631    /// Writes a number of bytes starting from a given offset.
632    ///
633    /// Returns the number of bytes written.
634    ///
635    /// # Windows
636    ///
637    /// The file pointer is changed after this operation, while on Unix, it is not.
638    ///
639    /// The `thread_uid` argument is used to determine whether or not the set-user-ID and
640    /// set-group-ID bits should be cleared. This is ignored if metadata is disabled.
641    ///
642    /// # Unix
643    ///
644    /// The `thread_uid` argument is ignored, and the thread's actual capabilities are used.
645    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    /// Reads the contents of the directory, invoking the callback for each item.
655    ///
656    /// If the callback returns an error, it is propagated to the caller. If the callback returns
657    /// false, enumeration is stopped but no error is returned. Enumeration can be continued
658    /// from the same position by calling this function again with the offset of the entry *before*
659    /// the one that cancelled the enumeration.
660    ///
661    /// # Windows
662    ///
663    /// The . and .. entries are always returned, but their inode number is not set.
664    ///
665    /// # Unix
666    ///
667    /// The . and .. entries are returned only if the underlying file system returns them.
668    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    /// Synchronizes the file's buffer.
676    ///
677    /// This function can optionally synchronize only data, not metadata.
678    pub fn fsync(&self, data_only: bool) -> lx::Result<()> {
679        self.inner.fsync(data_only)
680    }
681}
682
683/// Sets options used by an LxVolume. These control whether metadata is enabled, and set defaults
684/// to use for files without metadata.
685///
686/// # Unix
687///
688/// These options have no effect on Unix platforms.
689#[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    /// Create a new `LxVolumeOptions` with default options.
709    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    /// Create a new 'LxVolumeOptions' using a semi-colon separated list of options of the form
729    /// uid=1000;gid=1000;symlinkroot=/mnt/
730    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    /// Creates a new `LxVolume` with the current options.
906    pub fn new_volume(&self, root_path: impl AsRef<Path>) -> lx::Result<LxVolume> {
907        LxVolume::new_with_options(root_path, self)
908    }
909
910    /// Set the owner user ID for all files.
911    pub fn uid(&mut self, uid: lx::uid_t) -> &mut Self {
912        self.uid = Some(uid);
913        self
914    }
915
916    /// Set the owner group ID for all files.
917    pub fn gid(&mut self, gid: lx::gid_t) -> &mut Self {
918        self.gid = Some(gid);
919        self
920    }
921
922    /// Sets the mode bits for all files. Directories will add 'x' automatically if 'r' is set to allow list.
923    pub fn mode(&mut self, mode: u32) -> &mut Self {
924        self.mode = Some(mode & 0o777);
925        self
926    }
927
928    /// Set the owner user ID for files without metadata.
929    pub fn default_uid(&mut self, uid: lx::uid_t) -> &mut Self {
930        self.default_uid = uid;
931        self
932    }
933
934    /// Set the owner group ID for files without metadata.
935    pub fn default_gid(&mut self, gid: lx::gid_t) -> &mut Self {
936        self.default_gid = gid;
937        self
938    }
939
940    /// Set a mask of mode bits that will always be disabled on files and directories that have no
941    /// metadata.
942    pub fn umask(&mut self, umask: u32) -> &mut Self {
943        self.umask = !(umask & 0o7777);
944        self
945    }
946
947    /// Set a mask of mode bits that will always be disabled on files that have no metadata.
948    pub fn fmask(&mut self, fmask: u32) -> &mut Self {
949        self.fmask = !(fmask & 0o7777);
950        self
951    }
952
953    /// Set a mask of mode bits that will always be disabled on directories that have no metadata.
954    pub fn dmask(&mut self, dmask: u32) -> &mut Self {
955        self.dmask = !(dmask & 0o7777);
956        self
957    }
958
959    /// Enable or disable metadata for the volume.
960    ///
961    /// This will be ignored if the underlying file system does not support the required features
962    /// to emulate Linux attributes on Windows.
963    pub fn metadata(&mut self, metadata: bool) -> &mut Self {
964        self.metadata = metadata;
965        self
966    }
967
968    /// Apply additional file restrictions.
969    ///
970    /// Hide files and directories that are marked as hidden or may cause hydration (e.g. OneDrive backed).
971    pub fn sandbox(&mut self, enabled: bool) -> &mut Self {
972        self.sandbox = enabled;
973        self
974    }
975
976    /// Exclude specific file extensions when sandbox mode is enabled.
977    ///
978    /// Hide files and directories with specific file extensions. Do not allow creation of new files with these extensions.
979    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    /// Enable or disable whether new directories are created as case sensitive.
991    ///
992    /// This will be ignored if the underlying file system does not support case sensitive
993    /// directories.
994    ///
995    /// This does not affect the behavior of existing case sensitive directories, where operations
996    /// will be case sensitive and new directories will inherit the flag.
997    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    /// Set the root used to translate absolute Windows symlinks paths.
1003    ///
1004    /// EXAMPLE: A symlink to C:\my\target will return /mnt/c/my/target if symlink_root is set to "/mnt/".
1005    pub fn symlink_root(&mut self, symlink_root: &str) -> &mut Self {
1006        self.symlink_root = symlink_root.to_string();
1007        self
1008    }
1009
1010    /// Add an extended attribute to return with every file in the volume.
1011    ///
1012    /// This will be used in place of any actual extended attributes associated with the file.
1013    ///
1014    /// N.B. Since some attributes may be related, replacing the returned attributes is deemed easier and safer than
1015    ///      trying to union overrides with existing attributes.
1016    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/// Specifies options to use when creating a file.
1031///
1032/// The user ID and group ID are only used on Windows; on Unix platforms, set the thread's effective
1033/// user ID and group ID to change the owner of a newly created file.
1034#[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    /// Creates a new `LxCreateOptions`.
1045    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/// Supplies the attributes to change for `set_attr`.
1051#[derive(Default, Clone, Copy)]
1052pub struct SetAttributes {
1053    /// Truncate the file.
1054    pub size: Option<lx::off_t>,
1055
1056    /// Set the access time.
1057    pub atime: SetTime,
1058
1059    /// Set the modified time.
1060    pub mtime: SetTime,
1061
1062    /// Set the change time.
1063    ///
1064    /// Some file systems only support setting the change time to the current time.
1065    pub ctime: SetTime,
1066
1067    /// Set the file's mode.
1068    ///
1069    /// # Windows
1070    ///
1071    /// The mode must include the file type, and must match the existing file type.
1072    ///
1073    /// # Unix
1074    ///
1075    /// The file type will be ignored.
1076    pub mode: Option<lx::mode_t>,
1077
1078    /// Set the file's owner user ID.
1079    pub uid: Option<lx::uid_t>,
1080
1081    /// Set the file's owner group ID.
1082    pub gid: Option<lx::gid_t>,
1083
1084    /// The current thread's effective user ID.
1085    ///
1086    /// # Windows
1087    ///
1088    /// This is used to determine whether truncation needs to clear the set-user-ID and set-group-ID
1089    /// attributes. It is ignored if metadata is disabled.
1090    ///
1091    /// # Unix
1092    ///
1093    /// The actual thread's capabilities are used, so this value is ignored.
1094    pub thread_uid: lx::uid_t,
1095}
1096
1097/// Supplies the value to set a time attribute to.
1098#[derive(Clone, Copy)]
1099pub enum SetTime {
1100    /// Don't change the time.
1101    Omit,
1102    /// Set the time to the specified vale.
1103    Set(std::time::Duration),
1104    /// Set the time to the current time.
1105    Now,
1106}
1107
1108impl SetTime {
1109    /// Checks whether the value matches the `Omit` variant.
1110    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// UNSAFETY: Calls to libc to check and manipulate permissions.
1123#[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        // Write some text.
1192        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        // Read the whole thing back.
1198        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        // Read at EOF.
1203        assert_eq!(file.pread(&mut buffer, 13).unwrap(), 0);
1204
1205        // Write over part of it.
1206        assert_eq!(file.pwrite(b"Bye", 4, 0).unwrap(), 3);
1207        assert_eq!(file.fstat().unwrap().file_size, 13);
1208
1209        // Read part of it.
1210        assert_eq!(file.pread(&mut buffer[..8], 2).unwrap(), 8);
1211        assert_eq!(&buffer[..8], b"llByewor");
1212
1213        // Can't write if O_RDONLY.
1214        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        // Can't read if O_WRONLY
1219        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        // Can't do either if O_NOACCESS
1224        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        // Read up until the 6th file.
1253        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            // Remember a file name and its offset so we can seek back to it later.
1268            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        // Continue from the last offset seen; this tests that files aren't skipped or double reported.
1279        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        // Confirm that every file has been seen exactly once.
1292        assert_eq!(count, 12);
1293        for (_, value) in map {
1294            assert!(value);
1295        }
1296
1297        // Confirm that we can use the same file to seek to the start and enumerate again.
1298        count = 0;
1299        dir.read_dir(0, |_| {
1300            count += 1;
1301            Ok(true)
1302        })
1303        .unwrap();
1304
1305        assert_eq!(count, 12);
1306
1307        // Check that we can seek to a specific file.
1308        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        // Check that errors are propagated.
1319        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        // Make sure the closure can safely panic even when invoked through the C callback on
1342        // Windows.
1343        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        // Only Windows uses the uid/gid
1368        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        // Chmod/chown (chown is skipped on Linux because we may not have permission).
1420        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        // On real Linux, root is needed to create device files.
1436        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        // Symlink to a directory, which on Windows will be a directory but should be treated by
1567        // unlink as a file.
1568        // N.B. This only tests the right thing if developer mode is on, otherwise it creates an LX symlink.
1569        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        // Truncate.
1596        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        // Chmod (read-only attribute)
1603        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        // Chown (silent succeed on Windows; skip on Linux since we may not have permission)
1614        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        // Set times, and test set_and_get_attr()
1622        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        // Truncate.
1642        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        // Chmod (read-only attribute)
1649        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        // Chown (silent succeed on Windows; skip on Linux since we may not have permission)
1660        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        // Set times
1668        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        // Write clears it (except for root).
1705        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            // Write does not clear it for root.
1710            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        // Truncate clears it (except for root).
1723        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            // Truncate does not clear it as root.
1729            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        // Chown no changes does not clear it.
1743        let stat = file.fstat().unwrap();
1744        file.chown(None, None).unwrap();
1745        assert_eq!(stat.mode, lx::S_IFREG | 0o6777);
1746
1747        // Chown clears it.
1748        // N.B. Only perform this test if we have permissions to do so.
1749        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            // Chown doesn't clear setgid if not group executable.
1757            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        // Test without metadata, so on Windows only regular files will work, and mode/uid/gid are
1774        // not used.
1775        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        // Rename to a new name.
1807        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        // Into a directory.
1814        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        // Dir over dir, not empty.
1832        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        // File over file.
1843        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        // Dir over dir.
1860        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        // File over dir.
1867        let err = env.volume.rename("testfile3", "testdir", 0).unwrap_err();
1868        assert_eq!(err.value(), lx::EISDIR);
1869
1870        // Dir over file.
1871        let err = env.volume.rename("testdir", "testfile3", 0).unwrap_err();
1872        assert_eq!(err.value(), lx::ENOTDIR);
1873
1874        // Scope exit to unlink these files explicitly since they're read-only, which doesn't work
1875        // with TestEnv's drop method.
1876        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        // Readonly file over file.
1882        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        // Rename changing only the case.
1901        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        // Ensure no error is returned for read-only files.
1951        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        // No attributes to start with.
1962        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        // Set an attribute and retrieve it.
1980        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        // Set an empty attribute and retrieve it.
1997        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        // List the attributes.
2009        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        // Remove an attribute.
2020        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        // Test flags.
2037        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    // This test is disabled in CI on Windows only, because it requires NTFS support for setting
2087    // the case sensitive directory attribute, which is only enabled if the WSL optional component
2088    // is installed.
2089    #[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    // This test is disabled in CI, because it requires NTFS support for setting the case sensitive
2156    // directory attribute, which is only enabled if the WSL optional component is installed.
2157    #[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        // SAFETY: Calling C API as documented, with no special requirements.
2246        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            // Since seeking to an offset gives you the next entry after that offset, no entry
2280            // should ever have an offset of zero.
2281            assert_ne!(entry.offset, 0);
2282
2283            let name = entry.name.to_str().unwrap();
2284            if name == "." || name == ".." {
2285                // On Windows, the inode number is not set for . and .. entries, so don't check it.
2286                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            // SAFETY: Calling C API as documented, with no special requirements.
2297            unsafe { libc::umask(0) };
2298        }
2299
2300        #[cfg(windows)]
2301        fn clear_umask() {}
2302    }
2303}