lx/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Provides constants and types derived from Linux without requiring libc.
5
6#![expect(missing_docs)]
7
8#[macro_use]
9mod macros;
10mod string;
11
12use bitfield_struct::bitfield;
13use static_assertions::const_assert_eq;
14use std::io;
15use thiserror::Error;
16use zerocopy::FromBytes;
17use zerocopy::Immutable;
18use zerocopy::IntoBytes;
19use zerocopy::KnownLayout;
20
21pub use string::LxStr;
22pub use string::LxString;
23
24#[expect(non_camel_case_types)]
25pub type uid_t = u32;
26#[expect(non_camel_case_types)]
27pub type gid_t = u32;
28#[expect(non_camel_case_types)]
29pub type mode_t = u32;
30#[expect(non_camel_case_types)]
31pub type ino_t = u64;
32#[expect(non_camel_case_types)]
33pub type off_t = i64;
34#[expect(non_camel_case_types)]
35pub type dev_t = usize;
36
37pub const MODE_INVALID: mode_t = mode_t::MAX;
38pub const MODE_VALID_BITS: mode_t = S_IFMT | 0o7777;
39pub const UID_INVALID: uid_t = uid_t::MAX;
40pub const GID_INVALID: gid_t = gid_t::MAX;
41
42pub const S_IFIFO: u32 = 0x1000;
43pub const S_IFCHR: u32 = 0x2000;
44pub const S_IFDIR: u32 = 0x4000;
45pub const S_IFBLK: u32 = 0x6000;
46pub const S_IFREG: u32 = 0x8000;
47pub const S_IFLNK: u32 = 0xa000;
48pub const S_IFSOCK: u32 = 0xc000;
49pub const S_IFMT: u32 = 0xf000;
50pub const S_ISUID: u32 = 0o4000;
51pub const S_ISGID: u32 = 0o2000;
52pub const S_IXGRP: u32 = 0o010;
53
54pub const DT_UNK: u8 = 0;
55pub const DT_FIFO: u8 = 1;
56pub const DT_CHR: u8 = 2;
57pub const DT_DIR: u8 = 4;
58pub const DT_BLK: u8 = 6;
59pub const DT_REG: u8 = 8;
60pub const DT_LNK: u8 = 10;
61pub const DT_SOCK: u8 = 12;
62pub const DT_WHT: u8 = 14;
63
64lx_errors! {
65    EPERM = 1;
66    ENOENT = 2;
67    ESRCH = 3;
68    EINTR = 4;
69    EIO = 5;
70    ENXIO = 6;
71    E2BIG = 7;
72    ENOEXEC = 8;
73    EBADF = 9;
74    ECHILD = 10;
75    EAGAIN = 11;
76    ENOMEM = 12;
77    EACCES = 13;
78    EFAULT = 14;
79    EBUSY = 16;
80    EEXIST = 17;
81    EXDEV = 18;
82    ENODEV = 19;
83    ENOTDIR = 20;
84    EISDIR = 21;
85    EINVAL = 22;
86    ENFILE = 23;
87    EMFILE = 24;
88    ENOTTY = 25;
89    EFBIG = 27;
90    ENOSPC = 28;
91    ESPIPE = 29;
92    EROFS = 30;
93    EMLINK = 31;
94    EPIPE = 32;
95    ERANGE = 34;
96    EDEADLK = 35;
97    ENAMETOOLONG = 36;
98    ENOLCK = 37;
99    ENOSYS = 38;
100    ENOTEMPTY = 39;
101    ELOOP = 40;
102    EIDRM = 43;
103    ENODATA = 61;
104    EPROTO = 71;
105    EOVERFLOW = 75;
106    EUSERS = 87;
107    ENOTSOCK = 88;
108    EDESTADDRREQ = 89;
109    EMSGSIZE = 90;
110    EPROTOTYPE = 91;
111    ENOPROTOOPT = 92;
112    EPROTONOSUPPORT = 93;
113    ESOCKTNOSUPPORT = 94;
114    ENOTSUP = 95;
115    EAFNOSUPPORT = 97;
116    EADDRINUSE = 98;
117    EADDRNOTAVAIL = 99;
118    ENETUNREACH = 101;
119    ECONNABORTED = 103;
120    ECONNRESET = 104;
121    ENOBUFS = 105;
122    EISCONN = 106;
123    ENOTCONN = 107;
124    ETIMEDOUT = 110;
125    ECONNREFUSED = 111;
126    EHOSTDOWN = 112;
127    EHOSTUNREACH = 113;
128    EALREADY = 114;
129    EINPROGRESS = 115;
130    ENOMEDIUM = 123;
131    EMEDIUMTYPE = 124;
132    ENOKEY = 126;
133}
134
135pub const O_RDONLY: i32 = 0x000000;
136pub const O_WRONLY: i32 = 0x000001;
137pub const O_RDWR: i32 = 0x000002;
138pub const O_NOACCESS: i32 = 0x000003;
139pub const O_CREAT: i32 = 0x000040;
140pub const O_EXCL: i32 = 0x000080;
141pub const O_TRUNC: i32 = 0x000200;
142pub const O_APPEND: i32 = 0x000400;
143
144// xtask-fmt allow-target-arch sys-crate
145#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
146pub const O_DIRECTORY: i32 = 0x010000;
147// xtask-fmt allow-target-arch sys-crate
148#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
149pub const O_NOFOLLOW: i32 = 0x020000;
150
151// xtask-fmt allow-target-arch sys-crate
152#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
153pub const O_DIRECTORY: i32 = 0x004000;
154// xtask-fmt allow-target-arch sys-crate
155#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
156pub const O_NOFOLLOW: i32 = 0x008000;
157
158pub const O_NOATIME: i32 = 0x040000;
159
160pub const O_ACCESS_MASK: i32 = 0x000003;
161
162pub const AT_REMOVEDIR: i32 = 0x200;
163
164pub const XATTR_CREATE: i32 = 0x1;
165pub const XATTR_REPLACE: i32 = 0x2;
166
167/// Wraps a Linux error code in a strongly-typed struct.
168#[derive(Copy, Clone, Error, Eq, PartialEq)]
169#[error("{err} ({0})", err = str_error(*.0))]
170pub struct Error(i32);
171
172impl From<io::Error> for Error {
173    // Map IO errors to the appropriate Linux error code.
174    fn from(error: io::Error) -> Self {
175        let e = match error.kind() {
176            io::ErrorKind::NotFound => ENOENT,
177            io::ErrorKind::PermissionDenied => EACCES,
178            io::ErrorKind::ConnectionRefused => ECONNREFUSED,
179            io::ErrorKind::ConnectionReset => ECONNRESET,
180            io::ErrorKind::ConnectionAborted => ECONNABORTED,
181            io::ErrorKind::NotConnected => ENOTCONN,
182            io::ErrorKind::AddrInUse => EADDRINUSE,
183            io::ErrorKind::AddrNotAvailable => EADDRNOTAVAIL,
184            io::ErrorKind::BrokenPipe => EPIPE,
185            io::ErrorKind::AlreadyExists => EEXIST,
186            io::ErrorKind::WouldBlock => EAGAIN,
187            io::ErrorKind::TimedOut => ETIMEDOUT,
188            io::ErrorKind::Interrupted => EINTR,
189            _ => EINVAL,
190        };
191
192        Error(e)
193    }
194}
195
196impl Error {
197    /// Creates an `Error` from the last operating system error.
198    #[cfg(target_os = "linux")]
199    pub fn last_os_error() -> Self {
200        Self(io::Error::last_os_error().raw_os_error().unwrap())
201    }
202
203    /// Creates an `Error` from an existing Linux error code.
204    pub fn from_lx(error: i32) -> Self {
205        Self(error)
206    }
207
208    /// Returns the error code value.
209    pub fn value(&self) -> i32 {
210        self.0
211    }
212}
213
214impl std::fmt::Debug for Error {
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        f.write_fmt(format_args!("Error({} ({}))", str_error(self.0), self.0))
217    }
218}
219
220/// A specialized `Result` type for operations that return Linux error codes.
221pub type Result<T> = std::result::Result<T, Error>;
222
223pub const UTIME_NOW: usize = (1 << 30) - 1;
224pub const UTIME_OMIT: usize = (1 << 30) - 2;
225
226/// A Linux `timespec` structure.
227///
228/// This is similar to `Duration` but matches the memory layout of `timespec`.
229#[repr(C)]
230#[derive(Debug, Eq, PartialEq)]
231pub struct Timespec {
232    pub seconds: usize,
233    pub nanoseconds: usize,
234}
235
236impl Timespec {
237    /// Creates a `Timespec` with the value UTIME_OMIT.
238    pub fn omit() -> Self {
239        Self {
240            seconds: 0,
241            nanoseconds: UTIME_OMIT,
242        }
243    }
244
245    /// Creates a `Timespec` with the value UTIME_NOW.
246    pub fn now() -> Self {
247        Self {
248            seconds: 0,
249            nanoseconds: UTIME_NOW,
250        }
251    }
252}
253
254impl From<&std::time::Duration> for Timespec {
255    fn from(time: &std::time::Duration) -> Self {
256        Self {
257            seconds: time.as_secs() as usize,
258            nanoseconds: time.subsec_nanos() as usize,
259        }
260    }
261}
262
263#[bitfield(u32)]
264#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
265pub struct StatExMask {
266    pub file_type: bool,     // STATX_TYPE
267    pub mode: bool,          // STATX_MODE
268    pub nlink: bool,         // STATX_NLINK
269    pub uid: bool,           // STATX_UID
270    pub gid: bool,           // STATX_GID
271    pub atime: bool,         // STATX_ATIME
272    pub mtime: bool,         // STATX_MTIME
273    pub ctime: bool,         // STATX_CTIME
274    pub ino: bool,           // STATX_INO
275    pub size: bool,          // STATX_SIZE
276    pub blocks: bool,        // STATX_BLOCKS
277    pub btime: bool,         // STATX_BTIME
278    pub mnt_id: bool,        // STATX_MNT_ID
279    pub dio_align: bool,     // STATX_DIOALIGN
280    pub mnt_id_unique: bool, // STATX_MNT_ID_UNIQUE
281    pub subvol: bool,        // STATX_SUBVOL
282    pub write_atomic: bool,  // STATX_WRITE_ATOMIC
283    #[bits(15)]
284    pub _rsvd: u32,
285}
286
287#[bitfield(u64)]
288#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
289pub struct StatExAttributes {
290    #[bits(2)]
291    pub _rsvd1: u8,
292    pub compressed: bool, // STATX_ATTR_COMPRESSED
293    pub _rsvd2: bool,
294    pub immutable: bool, // STATX_ATTR_IMMUTABLE
295    pub append: bool,    // STATX_ATTR_APPEND
296    pub nodump: bool,    // STATX_ATTR_NODUMP
297    #[bits(4)]
298    pub _rsvd3: u8,
299    pub encrypted: bool,  // STATX_ATTR_ENCRYPTED
300    pub automount: bool,  // STATX_ATTR_AUTOMOUNT
301    pub mount_root: bool, // STATX_ATTR_MOUNT_ROOT
302    #[bits(6)]
303    pub _rsvd4: u8,
304    pub verity: bool,       // STATX_ATTR_VERITY
305    pub dax: bool,          // STATX_ATTR_DAX,
306    pub write_atomic: bool, // STATX_ATTR_WRITE_ATOMIC
307    #[bits(41)]
308    pub _rsvd: u64,
309}
310
311#[repr(C)]
312#[derive(Debug, Default, Eq, PartialEq)]
313pub struct StatExTimestamp {
314    pub seconds: i64,
315    pub nanoseconds: u32,
316    pub _rsvd: i32,
317}
318
319impl From<StatExTimestamp> for Timespec {
320    fn from(ts: StatExTimestamp) -> Self {
321        Timespec {
322            seconds: ts.seconds as usize,
323            nanoseconds: ts.nanoseconds as usize,
324        }
325    }
326}
327impl From<Timespec> for StatExTimestamp {
328    fn from(ts: Timespec) -> Self {
329        StatExTimestamp {
330            seconds: ts.seconds as i64,
331            nanoseconds: ts.nanoseconds as u32,
332            _rsvd: 0,
333        }
334    }
335}
336
337#[repr(C)]
338#[derive(Debug, Default)]
339pub struct StatEx {
340    pub mask: StatExMask,
341    pub block_size: u32,
342    pub attributes: StatExAttributes,
343    pub link_count: u32,
344    pub uid: uid_t,
345    pub gid: gid_t,
346    pub mode: u16,
347    pub _rsvd1: u16,
348    pub inode_id: ino_t,
349    pub file_size: u64,
350    pub block_count: u64,
351    pub attributes_mask: StatExAttributes,
352    pub access_time: StatExTimestamp,
353    pub creation_time: StatExTimestamp,
354    pub change_time: StatExTimestamp,
355    pub write_time: StatExTimestamp,
356    pub rdev_major: u32,
357    pub rdev_minor: u32,
358    pub dev_major: u32,
359    pub dev_minor: u32,
360    pub mount_id: u64,
361    pub dio_mem_align: u32,
362    pub dio_offset_align: u32,
363    pub subvolume_id: u64,
364    pub atomic_write_unit_min: u32,
365    pub atomic_write_unit_max: u32,
366    pub atomic_write_segments_max: u32,
367    pub _rsvd2: u32,
368    pub _rsvd3: [u64; 9],
369}
370
371const_assert_eq!(size_of::<StatEx>(), 256);
372
373/// A Linux `stat` structure.
374#[cfg(target_arch = "x86_64")] // xtask-fmt allow-target-arch sys-crate
375#[repr(C)]
376#[derive(Debug, Eq, PartialEq)]
377pub struct Stat {
378    pub device_nr: u64,
379    pub inode_nr: ino_t,
380    pub link_count: usize,
381    pub mode: mode_t,
382    pub uid: uid_t,
383    pub gid: gid_t,
384    pub pad0: u32,
385    pub device_nr_special: u64,
386    pub file_size: u64,
387    pub block_size: isize,
388    pub block_count: u64,
389    pub access_time: Timespec,
390    pub write_time: Timespec,
391    pub change_time: Timespec,
392    pub pad1: [isize; 3],
393}
394
395/// A Linux `stat` structure.
396#[cfg(target_arch = "aarch64")] // xtask-fmt allow-target-arch sys-crate
397#[repr(C)]
398#[derive(Debug, Eq, PartialEq)]
399pub struct Stat {
400    pub device_nr: u64,
401    pub inode_nr: ino_t,
402    pub mode: mode_t,
403    pub link_count: u32,
404    pub uid: uid_t,
405    pub gid: gid_t,
406    pub device_nr_special: u64,
407    pub pad0: u32,
408    pub file_size: u64,
409    pub block_size: u32,
410    pub pad1: u32,
411    pub block_count: u64,
412    pub access_time: Timespec,
413    pub write_time: Timespec,
414    pub change_time: Timespec,
415    pub unused: [u32; 2],
416}
417
418impl From<StatEx> for Stat {
419    fn from(statx: StatEx) -> Self {
420        Stat {
421            device_nr: make_dev(statx.dev_major, statx.dev_minor) as _,
422            inode_nr: statx.inode_id,
423            link_count: statx.link_count as _,
424            mode: statx.mode as _,
425            uid: statx.uid,
426            gid: statx.gid,
427            device_nr_special: make_dev(statx.rdev_major, statx.rdev_minor) as _,
428            file_size: statx.file_size,
429            block_size: statx.block_size as _,
430            block_count: statx.block_count,
431            access_time: statx.access_time.into(),
432            write_time: statx.write_time.into(),
433            change_time: statx.change_time.into(),
434            pad0: 0,
435            #[cfg(target_arch = "x86_64")] // xtask-fmt allow-target-arch sys-crate
436            pad1: [0; 3],
437            #[cfg(target_arch = "aarch64")] // xtask-fmt allow-target-arch sys-crate
438            pad1: 0,
439            #[cfg(target_arch = "aarch64")] // xtask-fmt allow-target-arch sys-crate
440            unused: [0; 2],
441        }
442    }
443}
444
445#[repr(C)]
446#[derive(Debug, Eq, PartialEq)]
447pub struct StatFs {
448    pub fs_type: usize,
449    pub block_size: usize,
450    pub block_count: u64,
451    pub free_block_count: u64,
452    pub available_block_count: u64,
453    pub file_count: u64,
454    pub available_file_count: u64,
455    pub file_system_id: [u8; 8],
456    pub maximum_file_name_length: usize,
457    pub file_record_size: usize,
458    pub flags: usize,
459    pub spare: [usize; 4],
460}
461
462/// A directory entry returned by `LxFile::read_dir`.
463#[derive(Debug)]
464pub struct DirEntry {
465    pub name: LxString,
466    pub inode_nr: ino_t,
467    pub offset: off_t,
468    pub file_type: u8,
469}
470
471pub fn s_isreg(mode: mode_t) -> bool {
472    mode & S_IFMT == S_IFREG
473}
474
475pub fn s_isdir(mode: mode_t) -> bool {
476    mode & S_IFMT == S_IFDIR
477}
478
479pub fn s_ischr(mode: mode_t) -> bool {
480    mode & S_IFMT == S_IFCHR
481}
482
483pub fn s_isblk(mode: mode_t) -> bool {
484    mode & S_IFMT == S_IFBLK
485}
486
487pub fn s_isfifo(mode: mode_t) -> bool {
488    mode & S_IFMT == S_IFIFO
489}
490
491pub fn s_issock(mode: mode_t) -> bool {
492    mode & S_IFMT == S_IFSOCK
493}
494
495pub fn s_islnk(mode: mode_t) -> bool {
496    mode & S_IFMT == S_IFLNK
497}
498
499pub fn major32(dev: dev_t) -> u32 {
500    ((dev & 0xfff00) >> 8) as u32
501}
502
503pub fn make_major32(major: u32) -> dev_t {
504    ((major as dev_t) & 0xfff) << 8
505}
506
507pub fn major64(dev: dev_t) -> u32 {
508    (((dev >> 32) & 0xfffff000) | major32(dev) as dev_t) as u32
509}
510
511pub fn make_major64(major: u32) -> dev_t {
512    (((major as dev_t) & !0xfff) << 32) | make_major32(major)
513}
514
515pub fn minor(dev: dev_t) -> u32 {
516    (((dev >> 12) & 0xffffff00) | (dev & 0xff)) as u32
517}
518
519pub fn make_minor(minor: u32) -> dev_t {
520    ((minor as dev_t & 0xffffff00) << 12) | (minor as dev_t & 0xff)
521}
522
523pub fn make_dev(major: u32, minor: u32) -> dev_t {
524    make_major64(major) | make_minor(minor)
525}