lxutil/unix/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4// UNSAFETY: Calling libc file APIs.
5#![expect(unsafe_code)]
6#![expect(clippy::undocumented_unsafe_blocks)]
7
8pub(crate) mod path;
9mod util;
10
11use crate::SetAttributes;
12use std::ffi;
13use std::mem;
14use std::os::unix::prelude::*;
15use std::path::Path;
16
17// Unix implementation of LxVolume.
18// See crate::LxVolume for more detailed comments.
19pub struct LxVolume {
20    root: std::fs::File,
21}
22
23impl LxVolume {
24    pub fn new(root_path: &Path, _options: &super::LxVolumeOptions) -> lx::Result<Self> {
25        let path = util::path_to_cstr(root_path)?;
26
27        // SAFETY: Calling C API as documented, with no special requirements.
28        unsafe {
29            // Open a file descriptor to the root to use with "*at" functions.
30            let fd = util::check_lx_errno(libc::open(
31                path.as_ptr(),
32                libc::O_RDONLY | libc::O_DIRECTORY,
33            ))?;
34
35            Ok(Self {
36                root: std::fs::File::from_raw_fd(fd),
37            })
38        }
39    }
40
41    pub fn supports_stable_file_id(&self) -> bool {
42        true
43    }
44
45    pub fn lstat(&self, path: &Path) -> lx::Result<lx::Stat> {
46        assert!(path.is_relative());
47        let path = util::path_to_cstr(path)?;
48
49        // SAFETY: Calling C API as documented, with no special requirements.
50        let stat = unsafe {
51            let mut stat = mem::zeroed();
52            util::check_lx_errno(libc::fstatat(
53                self.root.as_raw_fd(),
54                path.as_ptr(),
55                &mut stat,
56                libc::AT_SYMLINK_NOFOLLOW | libc::AT_EMPTY_PATH,
57            ))?;
58            stat
59        };
60
61        Ok(util::libc_stat_to_lx_stat(stat))
62    }
63
64    pub fn set_attr(&self, path: &Path, attr: SetAttributes) -> lx::Result<()> {
65        util::set_attr(&self.root, Some(path), &attr)
66    }
67
68    pub fn set_attr_stat(&self, path: &Path, attr: SetAttributes) -> lx::Result<lx::Stat> {
69        util::set_attr(&self.root, Some(path), &attr)?;
70        self.lstat(path)
71    }
72
73    pub fn open(
74        &self,
75        path: &Path,
76        flags: i32,
77        options: Option<super::LxCreateOptions>,
78    ) -> lx::Result<LxFile> {
79        assert!(path.is_relative());
80
81        let fd = util::openat(&self.root, path, flags, options)?;
82
83        Ok(LxFile {
84            fd,
85            enumerator: None,
86        })
87    }
88
89    pub fn mkdir(&self, path: &Path, options: super::LxCreateOptions) -> lx::Result<()> {
90        assert!(path.is_relative());
91
92        let path = util::path_to_cstr(path)?;
93
94        // SAFETY: Calling C API as documented, with no special requirements.
95        unsafe {
96            util::check_lx_errno(libc::mkdirat(
97                self.root.as_raw_fd(),
98                path.as_ptr(),
99                options.mode,
100            ))?;
101        }
102
103        Ok(())
104    }
105
106    pub fn mkdir_stat(&self, path: &Path, options: super::LxCreateOptions) -> lx::Result<lx::Stat> {
107        self.mkdir(path, options)?;
108        self.lstat(path)
109    }
110
111    // The options are entirely ignored on Unix, because uid/gid are never used, and mode isn't
112    // used for symlinks.
113    pub fn symlink(
114        &self,
115        path: &Path,
116        target: &lx::LxStr,
117        _: super::LxCreateOptions,
118    ) -> lx::Result<()> {
119        assert!(path.is_relative());
120
121        let path = util::path_to_cstr(path)?;
122        let target = util::create_cstr(target.as_bytes())?;
123
124        // SAFETY: Calling C API as documented, with no special requirements.
125        unsafe {
126            util::check_lx_errno(libc::symlinkat(
127                target.as_ptr(),
128                self.root.as_raw_fd(),
129                path.as_ptr(),
130            ))?;
131        }
132
133        Ok(())
134    }
135
136    pub fn symlink_stat(
137        &self,
138        path: &Path,
139        target: &lx::LxStr,
140        options: super::LxCreateOptions,
141    ) -> lx::Result<lx::Stat> {
142        self.symlink(path, target, options)?;
143        self.lstat(path)
144    }
145
146    pub fn read_link(&self, path: &Path) -> lx::Result<lx::LxString> {
147        assert!(path.is_relative());
148
149        let mut buffer = [0u8; libc::PATH_MAX as usize];
150        let path = util::path_to_cstr(path)?;
151
152        // SAFETY: Calling C API as documented, with no special requirements.
153        let size = unsafe {
154            util::check_lx_errno(libc::readlinkat(
155                self.root.as_raw_fd(),
156                path.as_ptr(),
157                buffer.as_mut_ptr().cast(),
158                buffer.len(),
159            ))?
160        };
161
162        // Size is guaranteed to be positive after check_lx_errno.
163        Ok(lx::LxString::from_vec(Vec::from(&buffer[..size as usize])))
164    }
165
166    pub fn unlink(&self, path: &Path, flags: i32) -> lx::Result<()> {
167        assert!(path.is_relative());
168
169        let path = util::path_to_cstr(path)?;
170
171        // SAFETY: Calling C API as documented, with no special requirements.
172        unsafe {
173            util::check_lx_errno(libc::unlinkat(self.root.as_raw_fd(), path.as_ptr(), flags))?;
174        }
175
176        Ok(())
177    }
178
179    pub fn mknod(
180        &self,
181        path: &Path,
182        options: super::LxCreateOptions,
183        device_id: lx::dev_t,
184    ) -> lx::Result<()> {
185        assert!(path.is_relative());
186
187        let path = util::path_to_cstr(path)?;
188
189        // SAFETY: Calling C API as documented, with no special requirements.
190        unsafe {
191            util::check_lx_errno(libc::mknodat(
192                self.root.as_raw_fd(),
193                path.as_ptr(),
194                options.mode,
195                device_id as u64,
196            ))?;
197        }
198
199        Ok(())
200    }
201
202    pub fn mknod_stat(
203        &self,
204        path: &Path,
205        options: super::LxCreateOptions,
206        device_id: lx::dev_t,
207    ) -> lx::Result<lx::Stat> {
208        self.mknod(path, options, device_id)?;
209        self.lstat(path)
210    }
211
212    pub fn rename(&self, path: &Path, new_path: &Path, flags: u32) -> lx::Result<()> {
213        assert!(path.is_relative());
214        assert!(new_path.is_relative());
215        let path = util::path_to_cstr(path)?;
216        let new_path = util::path_to_cstr(new_path)?;
217
218        // renameat2 does not have a wrapper in musl, though it does in glibc. Call it using syscall directly instead.
219        // SAFETY: Our arguments are valid for this syscall. We are passing arguments in the
220        // correct order and as the correct types. We are casting the return value to the correct type.
221        unsafe {
222            util::check_lx_errno(libc::syscall(
223                libc::SYS_renameat2,
224                self.root.as_raw_fd(),
225                path.as_ptr(),
226                self.root.as_raw_fd(),
227                new_path.as_ptr(),
228                flags,
229            ) as libc::c_int)?;
230        }
231
232        Ok(())
233    }
234
235    pub fn link(&self, path: &Path, new_path: &Path) -> lx::Result<()> {
236        assert!(path.is_relative());
237        assert!(new_path.is_relative());
238        let path = util::path_to_cstr(path)?;
239        let new_path = util::path_to_cstr(new_path)?;
240
241        // SAFETY: Calling C API as documented, with no special requirements.
242        unsafe {
243            util::check_lx_errno(libc::linkat(
244                self.root.as_raw_fd(),
245                path.as_ptr(),
246                self.root.as_raw_fd(),
247                new_path.as_ptr(),
248                0,
249            ))?;
250        }
251
252        Ok(())
253    }
254
255    pub fn link_stat(&self, path: &Path, new_path: &Path) -> lx::Result<lx::Stat> {
256        self.link(path, new_path)?;
257        self.lstat(new_path)
258    }
259
260    pub fn stat_fs(&self, path: &Path) -> lx::Result<lx::StatFs> {
261        assert!(path.is_relative());
262        let path = self.full_path(path)?;
263
264        // SAFETY: Calling C API as documented, with no special requirements.
265        let stat_fs = unsafe {
266            let mut stat_fs = mem::zeroed();
267            util::check_lx_errno(libc::statfs(path.as_ptr(), &mut stat_fs))?;
268            stat_fs
269        };
270
271        Ok(util::libc_stat_fs_to_lx_stat_fs(stat_fs))
272    }
273
274    pub fn set_xattr(
275        &self,
276        path: &Path,
277        name: &lx::LxStr,
278        value: &[u8],
279        flags: i32,
280    ) -> lx::Result<()> {
281        assert!(path.is_relative());
282
283        // There is no *at version of the xattr APIs.
284        let path = self.full_path(path)?;
285        let name = util::create_cstr(name.as_bytes())?;
286
287        // SAFETY: Calling C API as documented, with no special requirements.
288        unsafe {
289            util::check_lx_errno(libc::lsetxattr(
290                path.as_ptr(),
291                name.as_ptr(),
292                value.as_ptr().cast::<ffi::c_void>(),
293                value.len(),
294                flags,
295            ))?;
296        }
297
298        Ok(())
299    }
300
301    pub fn get_xattr(
302        &self,
303        path: &Path,
304        name: &lx::LxStr,
305        value: Option<&mut [u8]>,
306    ) -> lx::Result<usize> {
307        assert!(path.is_relative());
308
309        // There is no *at version of the xattr APIs.
310        let path = self.full_path(path)?;
311        let name = util::create_cstr(name.as_bytes())?;
312
313        // Set the pointer to NULL if no value buffer is provided, to query the attribute's size.
314        let (value_ptr, size) = if let Some(value) = value {
315            (value.as_mut_ptr(), value.len())
316        } else {
317            (std::ptr::null_mut(), 0)
318        };
319
320        // SAFETY: Calling C API as documented, with no special requirements.
321        let size = unsafe {
322            util::check_lx_errno(libc::lgetxattr(
323                path.as_ptr(),
324                name.as_ptr(),
325                value_ptr.cast::<ffi::c_void>(),
326                size,
327            ))?
328        };
329
330        // Size is guaranteed positive after the check.
331        Ok(size as usize)
332    }
333
334    pub fn list_xattr(&self, path: &Path, list: Option<&mut [u8]>) -> lx::Result<usize> {
335        assert!(path.is_relative());
336
337        // There is no *at version of the xattr APIs.
338        let path = self.full_path(path)?;
339
340        // Set the list pointer to NULL if no list buffer was provided, to query the size.
341        let (list_ptr, size) = if let Some(list) = list {
342            (list.as_mut_ptr(), list.len())
343        } else {
344            (std::ptr::null_mut(), 0)
345        };
346
347        // SAFETY: Calling C API as documented, with no special requirements.
348        let size = unsafe {
349            util::check_lx_errno(libc::llistxattr(path.as_ptr(), list_ptr.cast(), size))?
350        };
351
352        // Size is guaranteed positive after the check.
353        Ok(size as usize)
354    }
355
356    pub fn remove_xattr(&self, path: &Path, name: &lx::LxStr) -> lx::Result<()> {
357        assert!(path.is_relative());
358
359        // There is no *at version of the xattr APIs.
360        let path = self.full_path(path)?;
361        let name = util::create_cstr(name.as_bytes())?;
362
363        // SAFETY: Calling C API as documented, with no special requirements.
364        unsafe {
365            util::check_lx_errno(libc::lremovexattr(path.as_ptr(), name.as_ptr()))?;
366        }
367
368        Ok(())
369    }
370
371    fn full_path(&self, path: &Path) -> lx::Result<ffi::CString> {
372        let mut full_path = util::get_fd_path(&self.root)?;
373        full_path.push(path);
374        util::path_to_cstr(&full_path)
375    }
376}
377
378// Unix implementation of LxFile.
379// See crate::LxFile for more detailed comments.
380pub struct LxFile {
381    fd: std::fs::File,
382    enumerator: Option<util::DirectoryEnumerator>,
383}
384
385impl LxFile {
386    pub fn fstat(&self) -> lx::Result<lx::Stat> {
387        // SAFETY: Calling C API as documented, with no special requirements.
388        let stat = unsafe {
389            let mut stat = mem::zeroed();
390            util::check_lx_errno(libc::fstat(self.fd.as_raw_fd(), &mut stat))?;
391            stat
392        };
393
394        Ok(util::libc_stat_to_lx_stat(stat))
395    }
396
397    pub fn set_attr(&self, attr: SetAttributes) -> lx::Result<()> {
398        util::set_attr(&self.fd, None, &attr)
399    }
400
401    pub fn pread(&self, buffer: &mut [u8], offset: lx::off_t) -> lx::Result<usize> {
402        // SAFETY: Calling C API as documented, with no special requirements.
403        let size = unsafe {
404            util::check_lx_errno(libc::pread(
405                self.fd.as_raw_fd(),
406                buffer.as_mut_ptr().cast::<ffi::c_void>(),
407                buffer.len(),
408                offset,
409            ))?
410        };
411
412        // After checking for error, size is guaranteed positive.
413        Ok(size as usize)
414    }
415
416    pub fn pwrite(&self, buffer: &[u8], offset: lx::off_t, _: lx::uid_t) -> lx::Result<usize> {
417        // Linux will clear the set-user-ID and set-group-ID version on write, so unlike the
418        // Windows version, that doesn't need to be done here explicitly.
419        // SAFETY: Calling C API as documented, with no special requirements.
420        let size = unsafe {
421            util::check_lx_errno(libc::pwrite(
422                self.fd.as_raw_fd(),
423                buffer.as_ptr() as *mut ffi::c_void,
424                buffer.len(),
425                offset,
426            ))?
427        };
428
429        // After checking for error, size is guaranteed positive.
430        Ok(size as usize)
431    }
432
433    pub fn read_dir<F>(&mut self, offset: lx::off_t, mut callback: F) -> lx::Result<()>
434    where
435        F: FnMut(lx::DirEntry) -> lx::Result<bool>,
436    {
437        if self.enumerator.is_none() {
438            // The fd must be cloned because fdopendir takes ownership of the fd. This could be
439            // avoided by moving the fd out of self.fd.
440            // TODO: Use an enum with an "invalid" state to allow this.
441            self.enumerator = Some(util::DirectoryEnumerator::new(self.fd.try_clone()?)?);
442        }
443
444        let enumerator = self.enumerator.as_mut().unwrap();
445        enumerator.seek(offset);
446        for entry in enumerator {
447            let result = callback(entry?)?;
448            if !result {
449                break;
450            }
451        }
452
453        Ok(())
454    }
455
456    pub fn fsync(&self, data_only: bool) -> lx::Result<()> {
457        // SAFETY: Calling C APIs as documented, with no special requirements.
458        unsafe {
459            if data_only {
460                util::check_lx_errno(libc::fdatasync(self.fd.as_raw_fd()))?;
461            } else {
462                util::check_lx_errno(libc::fsync(self.fd.as_raw_fd()))?;
463            }
464        }
465
466        Ok(())
467    }
468}