1#![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#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
146pub const O_DIRECTORY: i32 = 0x010000;
147#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
149pub const O_NOFOLLOW: i32 = 0x020000;
150
151#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
153pub const O_DIRECTORY: i32 = 0x004000;
154#[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#[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 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 #[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 pub fn from_lx(error: i32) -> Self {
205 Self(error)
206 }
207
208 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
220pub 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#[repr(C)]
230#[derive(Debug, Eq, PartialEq)]
231pub struct Timespec {
232 pub seconds: usize,
233 pub nanoseconds: usize,
234}
235
236impl Timespec {
237 pub fn omit() -> Self {
239 Self {
240 seconds: 0,
241 nanoseconds: UTIME_OMIT,
242 }
243 }
244
245 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, pub mode: bool, pub nlink: bool, pub uid: bool, pub gid: bool, pub atime: bool, pub mtime: bool, pub ctime: bool, pub ino: bool, pub size: bool, pub blocks: bool, pub btime: bool, pub mnt_id: bool, pub dio_align: bool, pub mnt_id_unique: bool, pub subvol: bool, pub write_atomic: bool, #[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, pub _rsvd2: bool,
294 pub immutable: bool, pub append: bool, pub nodump: bool, #[bits(4)]
298 pub _rsvd3: u8,
299 pub encrypted: bool, pub automount: bool, pub mount_root: bool, #[bits(6)]
303 pub _rsvd4: u8,
304 pub verity: bool, pub dax: bool, pub write_atomic: bool, #[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#[cfg(target_arch = "x86_64")] #[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#[cfg(target_arch = "aarch64")] #[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")] pad1: [0; 3],
437 #[cfg(target_arch = "aarch64")] pad1: 0,
439 #[cfg(target_arch = "aarch64")] 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#[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}