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