Coverage Report

Created: 2026-03-23 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.4/src/fs/at.rs
Line
Count
Source
1
//! POSIX-style `*at` functions.
2
//!
3
//! The `dirfd` argument to these functions may be a file descriptor for a
4
//! directory, the special value [`CWD`], or the special value [`ABS`].
5
//!
6
//! [`CWD`]: crate::fs::CWD
7
//! [`ABS`]: crate::fs::ABS
8
9
#![allow(unsafe_code)]
10
11
use crate::buffer::Buffer;
12
use crate::fd::OwnedFd;
13
#[cfg(not(any(target_os = "espidf", target_os = "horizon", target_os = "vita")))]
14
use crate::fs::Access;
15
#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
16
use crate::fs::AtFlags;
17
#[cfg(apple)]
18
use crate::fs::CloneFlags;
19
#[cfg(any(linux_kernel, apple, target_os = "redox"))]
20
use crate::fs::RenameFlags;
21
#[cfg(not(target_os = "espidf"))]
22
use crate::fs::Stat;
23
#[cfg(not(any(apple, target_os = "espidf", target_os = "vita", target_os = "wasi")))]
24
use crate::fs::{Dev, FileType};
25
#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
26
use crate::fs::{Gid, Uid};
27
use crate::fs::{Mode, OFlags};
28
use crate::{backend, io, path};
29
use backend::fd::AsFd;
30
#[cfg(feature = "alloc")]
31
use {
32
    crate::ffi::{CStr, CString},
33
    crate::path::SMALL_PATH_BUFFER_SIZE,
34
    alloc::vec::Vec,
35
    backend::fd::BorrowedFd,
36
};
37
#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
38
use {crate::fs::Timestamps, crate::timespec::Nsecs};
39
40
/// `UTIME_NOW` for use with [`utimensat`].
41
///
42
/// [`utimensat`]: crate::fs::utimensat
43
#[cfg(not(any(
44
    target_os = "espidf",
45
    target_os = "horizon",
46
    target_os = "redox",
47
    target_os = "vita"
48
)))]
49
pub const UTIME_NOW: Nsecs = backend::c::UTIME_NOW as Nsecs;
50
51
/// `UTIME_OMIT` for use with [`utimensat`].
52
///
53
/// [`utimensat`]: crate::fs::utimensat
54
#[cfg(not(any(
55
    target_os = "espidf",
56
    target_os = "horizon",
57
    target_os = "redox",
58
    target_os = "vita"
59
)))]
60
pub const UTIME_OMIT: Nsecs = backend::c::UTIME_OMIT as Nsecs;
61
62
/// `openat(dirfd, path, oflags, mode)`—Opens a file.
63
///
64
/// POSIX guarantees that `openat` will use the lowest unused file descriptor,
65
/// however it is not safe in general to rely on this, as file descriptors may
66
/// be unexpectedly allocated on other threads or in libraries.
67
///
68
/// The `Mode` argument is only significant when creating a file.
69
///
70
/// # References
71
///  - [POSIX]
72
///  - [Linux]
73
///
74
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/openat.html
75
/// [Linux]: https://man7.org/linux/man-pages/man2/openat.2.html
76
#[cfg(not(target_os = "redox"))]
77
#[inline]
78
0
pub fn openat<P: path::Arg, Fd: AsFd>(
79
0
    dirfd: Fd,
80
0
    path: P,
81
0
    oflags: OFlags,
82
0
    create_mode: Mode,
83
0
) -> io::Result<OwnedFd> {
84
0
    path.into_with_c_str(|path| {
85
0
        backend::fs::syscalls::openat(dirfd.as_fd(), path, oflags, create_mode)
86
0
    })
87
0
}
88
89
/// `readlinkat(fd, path)`—Reads the contents of a symlink.
90
///
91
/// If `reuse` already has available capacity, reuse it if possible.
92
///
93
/// # References
94
///  - [POSIX]
95
///  - [Linux]
96
///
97
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/readlinkat.html
98
/// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
99
#[cfg(all(feature = "alloc", not(target_os = "redox")))]
100
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
101
#[inline]
102
0
pub fn readlinkat<P: path::Arg, Fd: AsFd, B: Into<Vec<u8>>>(
103
0
    dirfd: Fd,
104
0
    path: P,
105
0
    reuse: B,
106
0
) -> io::Result<CString> {
107
0
    path.into_with_c_str(|path| _readlinkat(dirfd.as_fd(), path, reuse.into()))
108
0
}
109
110
#[cfg(all(feature = "alloc", not(target_os = "redox")))]
111
#[allow(unsafe_code)]
112
0
fn _readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, mut buffer: Vec<u8>) -> io::Result<CString> {
113
0
    buffer.clear();
114
0
    buffer.reserve(SMALL_PATH_BUFFER_SIZE);
115
116
    loop {
117
0
        let buf = buffer.spare_capacity_mut();
118
119
        // SAFETY: `readlinkat` behaves.
120
0
        let nread = unsafe {
121
0
            backend::fs::syscalls::readlinkat(
122
0
                dirfd.as_fd(),
123
0
                path,
124
0
                (buf.as_mut_ptr().cast(), buf.len()),
125
0
            )?
126
        };
127
128
0
        debug_assert!(nread <= buffer.capacity());
129
0
        if nread < buffer.capacity() {
130
            // SAFETY: From the [documentation]: “On success, these calls
131
            // return the number of bytes placed in buf.”
132
            //
133
            // [documentation]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
134
0
            unsafe {
135
0
                buffer.set_len(nread);
136
0
            }
137
138
            // SAFETY:
139
            // - “readlink places the contents of the symbolic link pathname
140
            //   in the buffer buf”
141
            // - [POSIX definition 3.271: Pathname]: “A string that is used
142
            //   to identify a file.”
143
            // - [POSIX definition 3.375: String]: “A contiguous sequence of
144
            //   bytes terminated by and including the first null byte.”
145
            // - “readlink does not append a terminating null byte to buf.”
146
            //
147
            // Thus, there will be no NUL bytes in the string.
148
            //
149
            // [POSIX definition 3.271: Pathname]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap03.html#tag_03_271
150
            // [POSIX definition 3.375: String]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap03.html#tag_03_375
151
            unsafe {
152
0
                return Ok(CString::from_vec_unchecked(buffer));
153
            }
154
0
        }
155
156
        // Use `Vec` reallocation strategy to grow capacity exponentially.
157
0
        buffer.reserve(buffer.capacity() + 1);
158
    }
159
0
}
160
161
/// `readlinkat(fd, path)`—Reads the contents of a symlink, without
162
/// allocating.
163
///
164
/// This is the "raw" version which avoids allocating, but which truncates the
165
/// string if it doesn't fit in the provided buffer, and doesn't NUL-terminate
166
/// the string.
167
///
168
/// # References
169
///  - [POSIX]
170
///  - [Linux]
171
///
172
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/readlinkat.html
173
/// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
174
#[cfg(not(target_os = "redox"))]
175
#[inline]
176
0
pub fn readlinkat_raw<P: path::Arg, Fd: AsFd, Buf: Buffer<u8>>(
177
0
    dirfd: Fd,
178
0
    path: P,
179
0
    mut buf: Buf,
180
0
) -> io::Result<Buf::Output> {
181
    // SAFETY: `readlinkat` behaves.
182
0
    let len = path.into_with_c_str(|path| unsafe {
183
0
        backend::fs::syscalls::readlinkat(dirfd.as_fd(), path, buf.parts_mut())
184
0
    })?;
185
    // SAFETY: `readlinkat` behaves.
186
0
    unsafe { Ok(buf.assume_init(len)) }
187
0
}
188
189
/// `mkdirat(fd, path, mode)`—Creates a directory.
190
///
191
/// # References
192
///  - [POSIX]
193
///  - [Linux]
194
///
195
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/mkdirat.html
196
/// [Linux]: https://man7.org/linux/man-pages/man2/mkdirat.2.html
197
#[cfg(not(target_os = "redox"))]
198
#[inline]
199
0
pub fn mkdirat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> {
200
0
    path.into_with_c_str(|path| backend::fs::syscalls::mkdirat(dirfd.as_fd(), path, mode))
201
0
}
202
203
/// `linkat(old_dirfd, old_path, new_dirfd, new_path, flags)`—Creates a hard
204
/// link.
205
///
206
/// # References
207
///  - [POSIX]
208
///  - [Linux]
209
///
210
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/linkat.html
211
/// [Linux]: https://man7.org/linux/man-pages/man2/linkat.2.html
212
#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
213
#[inline]
214
0
pub fn linkat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
215
0
    old_dirfd: PFd,
216
0
    old_path: P,
217
0
    new_dirfd: QFd,
218
0
    new_path: Q,
219
0
    flags: AtFlags,
220
0
) -> io::Result<()> {
221
0
    old_path.into_with_c_str(|old_path| {
222
0
        new_path.into_with_c_str(|new_path| {
223
0
            backend::fs::syscalls::linkat(
224
0
                old_dirfd.as_fd(),
225
0
                old_path,
226
0
                new_dirfd.as_fd(),
227
0
                new_path,
228
0
                flags,
229
            )
230
0
        })
231
0
    })
232
0
}
233
234
/// `unlinkat(fd, path, flags)`—Unlinks a file or remove a directory.
235
///
236
/// With the [`REMOVEDIR`] flag, this removes a directory. This is in place of
237
/// a `rmdirat` function.
238
///
239
/// # References
240
///  - [POSIX]
241
///  - [Linux]
242
///
243
/// [`REMOVEDIR`]: AtFlags::REMOVEDIR
244
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/unlinkat.html
245
/// [Linux]: https://man7.org/linux/man-pages/man2/unlinkat.2.html
246
#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
247
#[inline]
248
0
pub fn unlinkat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<()> {
249
0
    path.into_with_c_str(|path| backend::fs::syscalls::unlinkat(dirfd.as_fd(), path, flags))
250
0
}
251
252
/// `renameat(old_dirfd, old_path, new_dirfd, new_path)`—Renames a file or
253
/// directory.
254
///
255
/// See [`renameat_with`] to pass additional flags.
256
///
257
/// # References
258
///  - [POSIX]
259
///  - [Linux]
260
///
261
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/renameat.html
262
/// [Linux]: https://man7.org/linux/man-pages/man2/renameat.2.html
263
#[inline]
264
0
pub fn renameat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
265
0
    old_dirfd: PFd,
266
0
    old_path: P,
267
0
    new_dirfd: QFd,
268
0
    new_path: Q,
269
0
) -> io::Result<()> {
270
0
    old_path.into_with_c_str(|old_path| {
271
0
        new_path.into_with_c_str(|new_path| {
272
0
            backend::fs::syscalls::renameat(
273
0
                old_dirfd.as_fd(),
274
0
                old_path,
275
0
                new_dirfd.as_fd(),
276
0
                new_path,
277
            )
278
0
        })
279
0
    })
280
0
}
281
282
/// `renameat2(old_dirfd, old_path, new_dirfd, new_path, flags)`—Renames a
283
/// file or directory.
284
///
285
/// `renameat_with` is the same as [`renameat`] but adds an additional
286
/// flags operand.
287
///
288
/// # References
289
///  - [Linux]
290
///
291
/// [Linux]: https://man7.org/linux/man-pages/man2/renameat2.2.html
292
#[cfg(any(apple, linux_kernel, target_os = "redox"))]
293
#[inline]
294
#[doc(alias = "renameat2")]
295
#[doc(alias = "renameatx_np")]
296
0
pub fn renameat_with<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
297
0
    old_dirfd: PFd,
298
0
    old_path: P,
299
0
    new_dirfd: QFd,
300
0
    new_path: Q,
301
0
    flags: RenameFlags,
302
0
) -> io::Result<()> {
303
0
    old_path.into_with_c_str(|old_path| {
304
0
        new_path.into_with_c_str(|new_path| {
305
0
            backend::fs::syscalls::renameat2(
306
0
                old_dirfd.as_fd(),
307
0
                old_path,
308
0
                new_dirfd.as_fd(),
309
0
                new_path,
310
0
                flags,
311
            )
312
0
        })
Unexecuted instantiation: rustix::fs::at::renameat_with::<&std::path::Path, &std::path::Path, std::os::fd::owned::BorrowedFd, std::os::fd::owned::BorrowedFd>::{closure#0}::{closure#0}
Unexecuted instantiation: rustix::fs::at::renameat_with::<_, _, _, _>::{closure#0}::{closure#0}
313
0
    })
Unexecuted instantiation: rustix::fs::at::renameat_with::<&std::path::Path, &std::path::Path, std::os::fd::owned::BorrowedFd, std::os::fd::owned::BorrowedFd>::{closure#0}
Unexecuted instantiation: rustix::fs::at::renameat_with::<_, _, _, _>::{closure#0}
314
0
}
Unexecuted instantiation: rustix::fs::at::renameat_with::<&std::path::Path, &std::path::Path, std::os::fd::owned::BorrowedFd, std::os::fd::owned::BorrowedFd>
Unexecuted instantiation: rustix::fs::at::renameat_with::<_, _, _, _>
315
316
/// `symlinkat(old_path, new_dirfd, new_path)`—Creates a symlink.
317
///
318
/// # References
319
///  - [POSIX]
320
///  - [Linux]
321
///
322
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/symlinkat.html
323
/// [Linux]: https://man7.org/linux/man-pages/man2/symlinkat.2.html
324
#[cfg(not(target_os = "redox"))]
325
#[inline]
326
0
pub fn symlinkat<P: path::Arg, Q: path::Arg, Fd: AsFd>(
327
0
    old_path: P,
328
0
    new_dirfd: Fd,
329
0
    new_path: Q,
330
0
) -> io::Result<()> {
331
0
    old_path.into_with_c_str(|old_path| {
332
0
        new_path.into_with_c_str(|new_path| {
333
0
            backend::fs::syscalls::symlinkat(old_path, new_dirfd.as_fd(), new_path)
334
0
        })
335
0
    })
336
0
}
337
338
/// `fstatat(dirfd, path, flags)`—Queries metadata for a file or directory.
339
///
340
/// [`Mode::from_raw_mode`] and [`FileType::from_raw_mode`] may be used to
341
/// interpret the `st_mode` field.
342
///
343
/// # References
344
///  - [POSIX]
345
///  - [Linux]
346
///
347
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/fstatat.html
348
/// [Linux]: https://man7.org/linux/man-pages/man2/fstatat.2.html
349
/// [`Mode::from_raw_mode`]: crate::fs::Mode::from_raw_mode
350
/// [`FileType::from_raw_mode`]: crate::fs::FileType::from_raw_mode
351
#[cfg(not(any(target_os = "espidf", target_os = "redox")))]
352
#[inline]
353
#[doc(alias = "fstatat")]
354
0
pub fn statat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<Stat> {
355
0
    path.into_with_c_str(|path| backend::fs::syscalls::statat(dirfd.as_fd(), path, flags))
356
0
}
357
358
/// `faccessat(dirfd, path, access, flags)`—Tests permissions for a file or
359
/// directory.
360
///
361
/// On Linux before 5.8, this function uses the `faccessat` system call which
362
/// doesn't support any flags. This function emulates support for the
363
/// [`AtFlags::EACCESS`] flag by checking whether the uid and gid of the
364
/// process match the effective uid and gid, in which case the `EACCESS` flag
365
/// can be ignored. In Linux 5.8 and beyond `faccessat2` is used, which
366
/// supports flags.
367
///
368
/// # References
369
///  - [POSIX]
370
///  - [Linux]
371
///
372
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/faccessat.html
373
/// [Linux]: https://man7.org/linux/man-pages/man2/faccessat.2.html
374
#[cfg(not(any(
375
    target_os = "espidf",
376
    target_os = "horizon",
377
    target_os = "vita",
378
    target_os = "redox"
379
)))]
380
#[inline]
381
#[doc(alias = "faccessat")]
382
0
pub fn accessat<P: path::Arg, Fd: AsFd>(
383
0
    dirfd: Fd,
384
0
    path: P,
385
0
    access: Access,
386
0
    flags: AtFlags,
387
0
) -> io::Result<()> {
388
0
    path.into_with_c_str(|path| backend::fs::syscalls::accessat(dirfd.as_fd(), path, access, flags))
389
0
}
390
391
/// `utimensat(dirfd, path, times, flags)`—Sets file or directory timestamps.
392
///
393
/// # References
394
///  - [POSIX]
395
///  - [Linux]
396
///
397
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/utimensat.html
398
/// [Linux]: https://man7.org/linux/man-pages/man2/utimensat.2.html
399
#[cfg(not(any(
400
    target_os = "espidf",
401
    target_os = "horizon",
402
    target_os = "vita",
403
    target_os = "redox"
404
)))]
405
#[inline]
406
0
pub fn utimensat<P: path::Arg, Fd: AsFd>(
407
0
    dirfd: Fd,
408
0
    path: P,
409
0
    times: &Timestamps,
410
0
    flags: AtFlags,
411
0
) -> io::Result<()> {
412
0
    path.into_with_c_str(|path| backend::fs::syscalls::utimensat(dirfd.as_fd(), path, times, flags))
413
0
}
414
415
/// `fchmodat(dirfd, path, mode, flags)`—Sets file or directory permissions.
416
///
417
/// Platform support for flags varies widely, for example on Linux
418
/// [`AtFlags::SYMLINK_NOFOLLOW`] is not implemented and therefore
419
/// [`io::Errno::OPNOTSUPP`] will be returned.
420
///
421
/// # References
422
///  - [POSIX]
423
///  - [Linux]
424
///
425
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/fchmodat.html
426
/// [Linux]: https://man7.org/linux/man-pages/man2/fchmodat.2.html
427
#[cfg(not(any(target_os = "espidf", target_os = "wasi", target_os = "redox")))]
428
#[inline]
429
#[doc(alias = "fchmodat")]
430
0
pub fn chmodat<P: path::Arg, Fd: AsFd>(
431
0
    dirfd: Fd,
432
0
    path: P,
433
0
    mode: Mode,
434
0
    flags: AtFlags,
435
0
) -> io::Result<()> {
436
0
    path.into_with_c_str(|path| backend::fs::syscalls::chmodat(dirfd.as_fd(), path, mode, flags))
437
0
}
438
439
/// `fclonefileat(src, dst_dir, dst, flags)`—Efficiently copies between files.
440
///
441
/// # References
442
///  - [Apple]
443
///
444
/// [Apple]: https://github.com/apple-oss-distributions/xnu/blob/main/bsd/man/man2/clonefile.2
445
#[cfg(apple)]
446
#[inline]
447
pub fn fclonefileat<Fd: AsFd, DstFd: AsFd, P: path::Arg>(
448
    src: Fd,
449
    dst_dir: DstFd,
450
    dst: P,
451
    flags: CloneFlags,
452
) -> io::Result<()> {
453
    dst.into_with_c_str(|dst| {
454
        backend::fs::syscalls::fclonefileat(src.as_fd(), dst_dir.as_fd(), dst, flags)
455
    })
456
}
457
458
/// `mknodat(dirfd, path, mode, dev)`—Creates special or normal files.
459
///
460
/// # References
461
///  - [POSIX]
462
///  - [Linux]
463
///
464
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/mknodat.html
465
/// [Linux]: https://man7.org/linux/man-pages/man2/mknodat.2.html
466
#[cfg(not(any(
467
    apple,
468
    target_os = "espidf",
469
    target_os = "horizon",
470
    target_os = "vita",
471
    target_os = "wasi",
472
    target_os = "redox",
473
)))]
474
#[inline]
475
0
pub fn mknodat<P: path::Arg, Fd: AsFd>(
476
0
    dirfd: Fd,
477
0
    path: P,
478
0
    file_type: FileType,
479
0
    mode: Mode,
480
0
    dev: Dev,
481
0
) -> io::Result<()> {
482
0
    path.into_with_c_str(|path| {
483
0
        backend::fs::syscalls::mknodat(dirfd.as_fd(), path, file_type, mode, dev)
484
0
    })
485
0
}
486
487
/// `mkfifoat(dirfd, path, mode)`—Make a FIFO special file.
488
///
489
/// # References
490
///  - [POSIX]
491
///
492
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/mkfifoat.html
493
#[cfg(not(any(
494
    apple,
495
    target_os = "espidf",
496
    target_os = "horizon",
497
    target_os = "vita",
498
    target_os = "wasi",
499
    target_os = "redox",
500
)))]
501
#[inline]
502
0
pub fn mkfifoat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> {
503
0
    mknodat(dirfd, path, FileType::Fifo, mode, 0)
504
0
}
505
506
/// `fchownat(dirfd, path, owner, group, flags)`—Sets file or directory
507
/// ownership.
508
///
509
/// # References
510
///  - [POSIX]
511
///  - [Linux]
512
///
513
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/fchownat.html
514
/// [Linux]: https://man7.org/linux/man-pages/man2/fchownat.2.html
515
#[cfg(not(any(target_os = "espidf", target_os = "wasi", target_os = "redox")))]
516
#[inline]
517
#[doc(alias = "fchownat")]
518
0
pub fn chownat<P: path::Arg, Fd: AsFd>(
519
0
    dirfd: Fd,
520
0
    path: P,
521
0
    owner: Option<Uid>,
522
0
    group: Option<Gid>,
523
0
    flags: AtFlags,
524
0
) -> io::Result<()> {
525
0
    path.into_with_c_str(|path| {
526
0
        backend::fs::syscalls::chownat(dirfd.as_fd(), path, owner, group, flags)
527
0
    })
528
0
}