Implement O_TMPFILE support and related tests
This commit is contained in:
parent
153e530fe2
commit
67b41fba8b
|
|
@ -3,7 +3,6 @@
|
|||
use bitflags::bitflags;
|
||||
|
||||
bitflags! {
|
||||
// TODO: Add O_TMPFILE
|
||||
pub struct CreationFlags: u32 {
|
||||
/// create file if it does not exist
|
||||
const O_CREAT = 1 << 6;
|
||||
|
|
@ -19,5 +18,7 @@ bitflags! {
|
|||
const O_NOFOLLOW = 1 << 17;
|
||||
/// close on exec
|
||||
const O_CLOEXEC = 1 << 19;
|
||||
/// create an unnamed temporary file
|
||||
const O_TMPFILE = 1 << 22;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,27 @@ impl OpenArgs {
|
|||
pub fn from_flags_and_mode(flags: u32, inode_mode: InodeMode) -> Result<Self> {
|
||||
let creation_flags = CreationFlags::from_bits_truncate(flags);
|
||||
let status_flags = StatusFlags::from_bits_truncate(flags);
|
||||
if creation_flags.contains(CreationFlags::O_CREAT)
|
||||
let access_mode = AccessMode::from_u32(flags)?;
|
||||
|
||||
// When `O_PATH` is set, all other flags (including `O_TMPFILE`) are
|
||||
// ignored, so the `O_TMPFILE` validations are skipped.
|
||||
// Reference: <https://man7.org/linux/man-pages/man2/open.2.html>.
|
||||
if creation_flags.contains(CreationFlags::O_TMPFILE)
|
||||
&& !status_flags.contains(StatusFlags::O_PATH)
|
||||
{
|
||||
if !creation_flags.contains(CreationFlags::O_DIRECTORY) {
|
||||
return_errno_with_message!(Errno::EINVAL, "O_TMPFILE requires O_DIRECTORY");
|
||||
}
|
||||
if !access_mode.is_writable() {
|
||||
return_errno_with_message!(Errno::EINVAL, "O_TMPFILE requires O_RDWR or O_WRONLY");
|
||||
}
|
||||
if creation_flags.contains(CreationFlags::O_CREAT) {
|
||||
return_errno_with_message!(
|
||||
Errno::EINVAL,
|
||||
"O_TMPFILE and O_CREAT are mutually exclusive"
|
||||
);
|
||||
}
|
||||
} else if creation_flags.contains(CreationFlags::O_CREAT)
|
||||
&& creation_flags.contains(CreationFlags::O_DIRECTORY)
|
||||
{
|
||||
return_errno_with_message!(
|
||||
|
|
@ -27,7 +47,7 @@ impl OpenArgs {
|
|||
"O_CREAT and O_DIRECTORY cannot be specified together"
|
||||
);
|
||||
}
|
||||
let access_mode = AccessMode::from_u32(flags)?;
|
||||
|
||||
Ok(Self {
|
||||
creation_flags,
|
||||
status_flags,
|
||||
|
|
@ -52,4 +72,10 @@ impl OpenArgs {
|
|||
|| self.creation_flags.contains(CreationFlags::O_CREAT)
|
||||
&& self.creation_flags.contains(CreationFlags::O_EXCL))
|
||||
}
|
||||
|
||||
/// Returns whether this is an `O_TMPFILE` open request.
|
||||
pub fn is_tmpfile(&self) -> bool {
|
||||
self.creation_flags.contains(CreationFlags::O_TMPFILE)
|
||||
&& !self.status_flags.contains(StatusFlags::O_PATH)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ use crate::{
|
|||
utils::{CStr256, DirentVisitor},
|
||||
vfs::{
|
||||
file_system::{FileSystem, FsEventSubscriberStats, SuperBlock},
|
||||
inode::{Extension, FallocMode, FileOps, Inode, Metadata, MknodType, SymbolicLink},
|
||||
inode::{
|
||||
Extension, FallocMode, FileOps, HardLinkability, Inode, Metadata, MknodType,
|
||||
SymbolicLink,
|
||||
},
|
||||
path::{is_dot, is_dot_or_dotdot, is_dotdot},
|
||||
registry::{FsCreationCtx, FsProperties, FsType},
|
||||
xattr::{XattrName, XattrNamespace, XattrSetFlags},
|
||||
|
|
@ -108,6 +111,7 @@ impl RamFs {
|
|||
this: weak_root.clone(),
|
||||
fs: weak_fs.clone(),
|
||||
container_dev_id: root_dev_id,
|
||||
hard_linkability: HardLinkability::Linkable,
|
||||
extension: Extension::new(),
|
||||
xattr: RamXattr::new(),
|
||||
}),
|
||||
|
|
@ -162,6 +166,10 @@ pub(super) struct RamInode {
|
|||
/// Detached inodes such as `memfd` store it directly
|
||||
/// because they do not have a valid fs reference.
|
||||
container_dev_id: DeviceId,
|
||||
/// Hard linkability.
|
||||
/// All inodes except temporary files are set to linkable
|
||||
/// Linkability of temporary files is specified via [`RamInode::create_tmpfile`]
|
||||
hard_linkability: HardLinkability,
|
||||
/// Extensions
|
||||
extension: Extension,
|
||||
/// Extended attributes
|
||||
|
|
@ -323,6 +331,12 @@ impl InodeMeta {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_tmpfile(mode: InodeMode, uid: Uid, gid: Gid) -> Self {
|
||||
let mut meta = Self::new(mode, uid, gid);
|
||||
meta.nlinks = 0;
|
||||
meta
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, new_size: usize) {
|
||||
self.size = new_size;
|
||||
self.blocks = new_size.align_up(BLOCK_SIZE) / BLOCK_SIZE;
|
||||
|
|
@ -504,6 +518,7 @@ impl RamInode {
|
|||
this: weak_self.clone(),
|
||||
fs: Arc::downgrade(fs),
|
||||
container_dev_id: fs.sb.container_dev_id,
|
||||
hard_linkability: HardLinkability::Linkable,
|
||||
extension: Extension::new(),
|
||||
xattr: RamXattr::new(),
|
||||
})
|
||||
|
|
@ -518,6 +533,28 @@ impl RamInode {
|
|||
this: weak_self.clone(),
|
||||
fs: Arc::downgrade(fs),
|
||||
container_dev_id: fs.sb.container_dev_id,
|
||||
hard_linkability: HardLinkability::Linkable,
|
||||
extension: Extension::new(),
|
||||
xattr: RamXattr::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn new_tmpfile(
|
||||
fs: &Arc<RamFs>,
|
||||
mode: InodeMode,
|
||||
uid: Uid,
|
||||
gid: Gid,
|
||||
hard_linkability: HardLinkability,
|
||||
) -> Arc<Self> {
|
||||
Arc::new_cyclic(|weak_self| RamInode {
|
||||
inner: Inner::new_file(),
|
||||
metadata: SpinLock::new(InodeMeta::new_tmpfile(mode, uid, gid)),
|
||||
ino: fs.alloc_id(),
|
||||
typ: InodeType::File,
|
||||
this: weak_self.clone(),
|
||||
fs: Arc::downgrade(fs),
|
||||
container_dev_id: fs.sb.container_dev_id,
|
||||
hard_linkability,
|
||||
extension: Extension::new(),
|
||||
xattr: RamXattr::new(),
|
||||
})
|
||||
|
|
@ -539,6 +576,7 @@ impl RamInode {
|
|||
this: Weak::new(),
|
||||
fs: Weak::new(),
|
||||
container_dev_id: dev_id,
|
||||
hard_linkability: HardLinkability::Linkable,
|
||||
extension: Extension::new(),
|
||||
xattr: RamXattr::new(),
|
||||
}
|
||||
|
|
@ -553,6 +591,7 @@ impl RamInode {
|
|||
this: weak_self.clone(),
|
||||
fs: Arc::downgrade(fs),
|
||||
container_dev_id: fs.sb.container_dev_id,
|
||||
hard_linkability: HardLinkability::Linkable,
|
||||
extension: Extension::new(),
|
||||
xattr: RamXattr::new(),
|
||||
})
|
||||
|
|
@ -579,6 +618,7 @@ impl RamInode {
|
|||
this: weak_self.clone(),
|
||||
fs: Arc::downgrade(fs),
|
||||
container_dev_id: fs.sb.container_dev_id,
|
||||
hard_linkability: HardLinkability::Linkable,
|
||||
extension: Extension::new(),
|
||||
xattr: RamXattr::new(),
|
||||
})
|
||||
|
|
@ -593,6 +633,7 @@ impl RamInode {
|
|||
this: weak_self.clone(),
|
||||
fs: Arc::downgrade(fs),
|
||||
container_dev_id: fs.sb.container_dev_id,
|
||||
hard_linkability: HardLinkability::Linkable,
|
||||
extension: Extension::new(),
|
||||
xattr: RamXattr::new(),
|
||||
})
|
||||
|
|
@ -607,6 +648,7 @@ impl RamInode {
|
|||
this: weak_self.clone(),
|
||||
fs: Arc::downgrade(fs),
|
||||
container_dev_id: fs.sb.container_dev_id,
|
||||
hard_linkability: HardLinkability::Linkable,
|
||||
extension: Extension::new(),
|
||||
xattr: RamXattr::new(),
|
||||
})
|
||||
|
|
@ -911,6 +953,25 @@ impl Inode for RamInode {
|
|||
Ok(new_inode)
|
||||
}
|
||||
|
||||
fn create_tmpfile(
|
||||
&self,
|
||||
mode: InodeMode,
|
||||
hard_linkability: HardLinkability,
|
||||
) -> Result<Arc<dyn Inode>> {
|
||||
if self.typ != InodeType::Dir {
|
||||
return_errno_with_message!(Errno::ENOTDIR, "self is not dir");
|
||||
}
|
||||
|
||||
let fs = self.fs.upgrade().unwrap();
|
||||
Ok(RamInode::new_tmpfile(
|
||||
&fs,
|
||||
mode,
|
||||
Uid::new_root(),
|
||||
Gid::new_root(),
|
||||
hard_linkability,
|
||||
))
|
||||
}
|
||||
|
||||
fn link(&self, old: &Arc<dyn Inode>, name: &str) -> Result<()> {
|
||||
if !Arc::ptr_eq(&self.fs(), &old.fs()) {
|
||||
return_errno_with_message!(Errno::EXDEV, "not same fs");
|
||||
|
|
@ -925,24 +986,29 @@ impl Inode for RamInode {
|
|||
if old.typ == InodeType::Dir {
|
||||
return_errno_with_message!(Errno::EPERM, "old is a dir");
|
||||
}
|
||||
if old.hard_linkability == HardLinkability::Unlinkable {
|
||||
return_errno_with_message!(Errno::ENOENT, "tmpfile is not linkable");
|
||||
}
|
||||
|
||||
let mut self_dir = self.inner.as_direntry().unwrap().write();
|
||||
if self_dir.contains_entry(name) {
|
||||
return_errno_with_message!(Errno::EEXIST, "entry exist");
|
||||
}
|
||||
self_dir.append_entry(name, old.this.upgrade().unwrap());
|
||||
let now = now();
|
||||
|
||||
// An `O_TMPFILE` inode starts with zero links. Bump the link count
|
||||
// before exposing the new entry to concurrent lookup or unlink.
|
||||
let mut old_meta = old.metadata.lock();
|
||||
old_meta.inc_nlinks();
|
||||
old_meta.set_ctime(now);
|
||||
drop(old_meta);
|
||||
drop(self_dir);
|
||||
|
||||
let now = now();
|
||||
let mut self_meta = self.metadata.lock();
|
||||
self_meta.set_mtime(now);
|
||||
self_meta.set_ctime(now);
|
||||
self_meta.inc_size();
|
||||
drop(self_meta);
|
||||
|
||||
let mut old_meta = old.metadata.lock();
|
||||
old_meta.inc_nlinks();
|
||||
old_meta.set_ctime(now);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,6 +119,19 @@ pub struct Metadata {
|
|||
pub self_dev_id: Option<DeviceId>,
|
||||
}
|
||||
|
||||
/// Describes whether an inode may get new hard links.
|
||||
///
|
||||
/// This does not control symbolic links. A symbolic link is a separate inode
|
||||
/// whose target is a pathname string, so creating one does not link the target
|
||||
/// inode directly.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum HardLinkability {
|
||||
/// Allows creating hard links to the inode.
|
||||
Linkable,
|
||||
/// Prevents creating hard links to the inode.
|
||||
Unlinkable,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
pub fn new_dir(ino: u64, mode: InodeMode, blk_size: usize, container_dev_id: DeviceId) -> Self {
|
||||
let now = RealTimeCoarseClock::get().read_time();
|
||||
|
|
@ -365,6 +378,14 @@ pub trait Inode: Any + FileOps + Send + Sync {
|
|||
Err(Error::new(Errno::ENOTDIR))
|
||||
}
|
||||
|
||||
fn create_tmpfile(
|
||||
&self,
|
||||
mode: InodeMode,
|
||||
hard_linkability: HardLinkability,
|
||||
) -> Result<Arc<dyn Inode>> {
|
||||
Err(Error::new(Errno::EOPNOTSUPP))
|
||||
}
|
||||
|
||||
fn mknod(&self, name: &str, mode: InodeMode, type_: MknodType) -> Result<Arc<dyn Inode>> {
|
||||
Err(Error::new(Errno::ENOTDIR))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,7 +215,6 @@ impl Dentry {
|
|||
/// Creates a new anonymous `Dentry` with the given inode and parent.
|
||||
///
|
||||
/// See the [`Dentry`] type-level documentation for what "anonymous" means.
|
||||
#[expect(dead_code, reason = "constructed by the upcoming O_TMPFILE support")]
|
||||
pub(super) fn new_anonymous(inode: Arc<dyn Inode>, parent: Arc<Dentry>) -> Arc<Self> {
|
||||
Self::new(inode, DentryOptions::Anonymous { parent })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use crate::{
|
|||
pseudofs::NsInode,
|
||||
vfs::{
|
||||
file_system::{FileSystem, FsFlags},
|
||||
inode::{Inode, Metadata, MknodType},
|
||||
inode::{HardLinkability, Inode, Metadata, MknodType},
|
||||
xattr::{XattrName, XattrNamespace, XattrSetFlags},
|
||||
},
|
||||
},
|
||||
|
|
@ -76,6 +76,28 @@ impl Path {
|
|||
Ok(Self::new(self.mount.clone(), new_child_dentry))
|
||||
}
|
||||
|
||||
/// Creates a new `Path` to represent an unnamed temporary file.
|
||||
///
|
||||
/// The returned inode has no directory entry and is invisible to `readdir`.
|
||||
/// If created hard-linkable, it may later be given a name via a link
|
||||
/// operation; otherwise it can never be linked.
|
||||
pub fn create_tmpfile(
|
||||
&self,
|
||||
mode: InodeMode,
|
||||
hard_linkability: HardLinkability,
|
||||
) -> Result<Self> {
|
||||
if self
|
||||
.inode()
|
||||
.check_permission(Permission::MAY_WRITE)
|
||||
.is_err()
|
||||
{
|
||||
return_errno!(Errno::EACCES);
|
||||
}
|
||||
let tmp_inode = self.inode().create_tmpfile(mode, hard_linkability)?;
|
||||
let tmp_dentry = Dentry::new_anonymous(tmp_inode, self.dentry.clone());
|
||||
Ok(Self::new(self.mount.clone(), tmp_dentry))
|
||||
}
|
||||
|
||||
/// Creates a new pseudo `Path`.
|
||||
pub(in crate::fs) fn new_pseudo(
|
||||
mount: Arc<Mount>,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ use crate::{
|
|||
StatusFlags,
|
||||
file_table::{FdFlags, RawFileDesc},
|
||||
},
|
||||
vfs::path::{AT_FDCWD, EmptyPathStr, FsPath, LookupResult, PathResolver},
|
||||
vfs::{
|
||||
inode::HardLinkability,
|
||||
path::{AT_FDCWD, EmptyPathStr, FsPath, LookupResult, PathResolver},
|
||||
},
|
||||
},
|
||||
prelude::*,
|
||||
syscall::constants::MAX_FILENAME_LEN,
|
||||
|
|
@ -82,6 +85,10 @@ fn do_open(
|
|||
) -> Result<Arc<dyn FileLike>> {
|
||||
let open_args = OpenArgs::from_flags_and_mode(flags, mode)?;
|
||||
|
||||
if open_args.is_tmpfile() {
|
||||
return do_open_tmpfile(path_resolver, fs_path, &open_args);
|
||||
}
|
||||
|
||||
let lookup_res = if open_args.follow_tail_link() {
|
||||
path_resolver.lookup_unresolved(fs_path)?
|
||||
} else {
|
||||
|
|
@ -119,3 +126,37 @@ fn do_open(
|
|||
|
||||
Ok(file_handle)
|
||||
}
|
||||
|
||||
fn do_open_tmpfile(
|
||||
path_resolver: &PathResolver,
|
||||
fs_path: &FsPath,
|
||||
open_args: &OpenArgs,
|
||||
) -> Result<Arc<dyn FileLike>> {
|
||||
let dir_path = if open_args.follow_tail_link() {
|
||||
path_resolver.lookup(fs_path)?
|
||||
} else {
|
||||
path_resolver.lookup_no_follow(fs_path)?
|
||||
};
|
||||
if dir_path.type_() != InodeType::Dir {
|
||||
return_errno_with_message!(
|
||||
Errno::ENOTDIR,
|
||||
"O_TMPFILE requires the path to be a directory"
|
||||
);
|
||||
}
|
||||
|
||||
// `O_EXCL` with `O_TMPFILE` is allowed by Linux, but it prevents the tmpfile
|
||||
// from being linked later by `linkat(..., AT_EMPTY_PATH)`.
|
||||
// Reference: <https://man7.org/linux/man-pages/man2/open.2.html>.
|
||||
let hard_linkability = if open_args.creation_flags.contains(CreationFlags::O_EXCL) {
|
||||
HardLinkability::Unlinkable
|
||||
} else {
|
||||
HardLinkability::Linkable
|
||||
};
|
||||
let tmpfile_path = dir_path.create_tmpfile(open_args.inode_mode, hard_linkability)?;
|
||||
|
||||
Ok(Arc::new(InodeHandle::new_unchecked_access(
|
||||
tmpfile_path,
|
||||
open_args.access_mode,
|
||||
open_args.status_flags,
|
||||
)?))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ SUBDIRS := \
|
|||
procfs \
|
||||
pseudofs \
|
||||
symlink \
|
||||
tmpfile \
|
||||
utimensat \
|
||||
|
||||
include ../common/Makefile
|
||||
|
|
|
|||
|
|
@ -131,4 +131,6 @@ echo "All mount bind file test passed."
|
|||
|
||||
./symlink/symlink
|
||||
|
||||
./tmpfile/tmpfile
|
||||
|
||||
./utimensat/utimensat
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
include ../../common/Makefile
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
/* SPDX-License-Identifier: MPL-2.0 */
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../../common/test.h"
|
||||
|
||||
#define TEST_DIR "/tmp/aster_tmpfile_test"
|
||||
#define CROSS_LINK_DIR "/ext2"
|
||||
#define CROSS_LINK_NAME "cross_mount_tmpfile"
|
||||
#define LINKED_NAME "linked_file"
|
||||
#define LINKED_SECOND_NAME "linked_second_file"
|
||||
#define LINKED_O_EXCL_NAME "linked_o_excl_file"
|
||||
#define SYMLINK_NAME "tmpfile_symlink"
|
||||
#define DATA "hello from tmpfile"
|
||||
#define DATA_LEN (sizeof(DATA) - 1)
|
||||
|
||||
#define RAW_O_TMPFILE 020000000
|
||||
|
||||
/* O_TMPFILE may not be defined on older glibc. */
|
||||
#ifndef __O_TMPFILE
|
||||
#define __O_TMPFILE RAW_O_TMPFILE
|
||||
#endif
|
||||
|
||||
#ifndef O_TMPFILE
|
||||
#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
|
||||
#endif
|
||||
|
||||
#ifndef AT_EMPTY_PATH
|
||||
#define AT_EMPTY_PATH 0x1000
|
||||
#endif
|
||||
|
||||
static void cleanup_test_files(void)
|
||||
{
|
||||
unlink(TEST_DIR "/" LINKED_NAME);
|
||||
unlink(TEST_DIR "/" LINKED_SECOND_NAME);
|
||||
unlink(TEST_DIR "/" LINKED_O_EXCL_NAME);
|
||||
unlink(TEST_DIR "/" SYMLINK_NAME);
|
||||
unlink(CROSS_LINK_DIR "/" CROSS_LINK_NAME);
|
||||
rmdir(TEST_DIR);
|
||||
}
|
||||
|
||||
static int timespec_equal(struct timespec left, struct timespec right)
|
||||
{
|
||||
return left.tv_sec == right.tv_sec && left.tv_nsec == right.tv_nsec;
|
||||
}
|
||||
|
||||
static int dir_is_unavailable_or_same_mount(const char *source_path,
|
||||
const char *target_path)
|
||||
{
|
||||
struct stat source_stat;
|
||||
struct stat target_stat;
|
||||
|
||||
if (stat(source_path, &source_stat) < 0 ||
|
||||
stat(target_path, &target_stat) < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return source_stat.st_dev == target_stat.st_dev;
|
||||
}
|
||||
|
||||
FN_SETUP(prepare)
|
||||
{
|
||||
cleanup_test_files();
|
||||
CHECK(mkdir(TEST_DIR, 0755));
|
||||
}
|
||||
END_SETUP()
|
||||
|
||||
FN_TEST(tmpfile_open_succeeds)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = TEST_SUCC(open(TEST_DIR, O_TMPFILE | O_RDWR, 0666));
|
||||
TEST_SUCC(close(fd));
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_open_write_only_succeeds)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = TEST_SUCC(open(TEST_DIR, O_TMPFILE | O_WRONLY, 0666));
|
||||
TEST_SUCC(close(fd));
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_open_read_only_returns_einval)
|
||||
{
|
||||
TEST_ERRNO(open(TEST_DIR, O_TMPFILE | O_RDONLY, 0666), EINVAL);
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_open_without_o_directory_returns_einval)
|
||||
{
|
||||
TEST_ERRNO(open(TEST_DIR, RAW_O_TMPFILE | O_RDWR, 0666), EINVAL);
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_open_with_o_creat_returns_einval)
|
||||
{
|
||||
TEST_ERRNO(open(TEST_DIR, O_TMPFILE | O_RDWR | O_CREAT, 0666), EINVAL);
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_open_with_o_path_yields_path_fd)
|
||||
{
|
||||
int fd;
|
||||
char buf[1];
|
||||
|
||||
fd = TEST_SUCC(open(TEST_DIR, O_TMPFILE | O_PATH | O_RDWR, 0666));
|
||||
TEST_ERRNO(read(fd, buf, sizeof(buf)), EBADF);
|
||||
|
||||
TEST_SUCC(close(fd));
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
// FIXME: Add `O_TMPFILE` support for ext2.
|
||||
#ifdef __asterinas__
|
||||
FN_TEST(tmpfile_open_on_ext2_returns_eopnotsupp)
|
||||
{
|
||||
TEST_ERRNO(open(CROSS_LINK_DIR, O_TMPFILE | O_RDWR, 0666), EOPNOTSUPP);
|
||||
}
|
||||
END_TEST()
|
||||
#endif
|
||||
|
||||
FN_TEST(tmpfile_open_with_o_excl_succeeds)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = TEST_SUCC(open(TEST_DIR, O_TMPFILE | O_RDWR | O_EXCL, 0666));
|
||||
TEST_SUCC(close(fd));
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_open_symlink_with_o_nofollow_returns_enotdir)
|
||||
{
|
||||
TEST_SUCC(symlink(TEST_DIR, TEST_DIR "/" SYMLINK_NAME));
|
||||
|
||||
TEST_ERRNO(open(TEST_DIR "/" SYMLINK_NAME,
|
||||
O_TMPFILE | O_RDWR | O_NOFOLLOW, 0666),
|
||||
ENOTDIR);
|
||||
|
||||
TEST_SUCC(unlink(TEST_DIR "/" SYMLINK_NAME));
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_open_non_dir_returns_enotdir)
|
||||
{
|
||||
TEST_ERRNO(open("/dev/null", O_TMPFILE | O_RDWR, 0666), ENOTDIR);
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_open_does_not_update_parent_timestamps)
|
||||
{
|
||||
struct stat before;
|
||||
struct stat after;
|
||||
int fd;
|
||||
|
||||
TEST_SUCC(stat(TEST_DIR, &before));
|
||||
fd = TEST_SUCC(open(TEST_DIR, O_TMPFILE | O_RDWR, 0666));
|
||||
TEST_SUCC(close(fd));
|
||||
TEST_SUCC(stat(TEST_DIR, &after));
|
||||
|
||||
TEST_RES(timespec_equal(after.st_mtim, before.st_mtim), _ret);
|
||||
TEST_RES(timespec_equal(after.st_ctim, before.st_ctim), _ret);
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_invisible_in_readdir)
|
||||
{
|
||||
DIR *dir;
|
||||
int fd;
|
||||
int found;
|
||||
struct dirent *entry;
|
||||
|
||||
fd = TEST_SUCC(open(TEST_DIR, O_TMPFILE | O_RDWR, 0666));
|
||||
|
||||
dir = TEST_RES(opendir(TEST_DIR), _ret != NULL);
|
||||
|
||||
found = 0;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(entry->d_name, ".") != 0 &&
|
||||
strcmp(entry->d_name, "..") != 0) {
|
||||
found++;
|
||||
}
|
||||
}
|
||||
TEST_RES(found, found == 0);
|
||||
|
||||
TEST_SUCC(closedir(dir));
|
||||
TEST_SUCC(close(fd));
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_write_and_linkat)
|
||||
{
|
||||
DIR *dir;
|
||||
char buf[DATA_LEN];
|
||||
int dirfd;
|
||||
int fd;
|
||||
int found;
|
||||
int linked_fd;
|
||||
struct dirent *entry;
|
||||
struct stat stat_after_link;
|
||||
struct stat stat_after_second_link;
|
||||
|
||||
fd = TEST_SUCC(open(TEST_DIR, O_TMPFILE | O_RDWR, 0666));
|
||||
|
||||
TEST_RES(write(fd, DATA, DATA_LEN), _ret == DATA_LEN);
|
||||
|
||||
TEST_RES(pread(fd, buf, sizeof(buf), 0), _ret == DATA_LEN);
|
||||
TEST_RES(memcmp(buf, DATA, DATA_LEN), _ret == 0);
|
||||
|
||||
dirfd = TEST_SUCC(open(TEST_DIR, O_RDONLY | O_DIRECTORY));
|
||||
TEST_SUCC(linkat(fd, "", dirfd, LINKED_NAME, AT_EMPTY_PATH));
|
||||
|
||||
TEST_RES(fstat(fd, &stat_after_link), stat_after_link.st_nlink == 1);
|
||||
|
||||
dir = TEST_RES(opendir(TEST_DIR), _ret != NULL);
|
||||
found = 0;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
found += strcmp(entry->d_name, LINKED_NAME) == 0;
|
||||
}
|
||||
TEST_RES(found, found == 1);
|
||||
TEST_SUCC(closedir(dir));
|
||||
|
||||
TEST_SUCC(link(TEST_DIR "/" LINKED_NAME,
|
||||
TEST_DIR "/" LINKED_SECOND_NAME));
|
||||
TEST_RES(fstat(fd, &stat_after_second_link),
|
||||
stat_after_second_link.st_nlink == 2);
|
||||
|
||||
linked_fd = TEST_SUCC(open(TEST_DIR "/" LINKED_NAME, O_RDONLY));
|
||||
TEST_RES(pread(linked_fd, buf, sizeof(buf), 0), _ret == DATA_LEN);
|
||||
TEST_RES(memcmp(buf, DATA, DATA_LEN), _ret == 0);
|
||||
|
||||
TEST_SUCC(close(linked_fd));
|
||||
TEST_SUCC(close(dirfd));
|
||||
TEST_SUCC(close(fd));
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_open_with_o_excl_cannot_be_linked)
|
||||
{
|
||||
int fd;
|
||||
int dirfd;
|
||||
|
||||
fd = TEST_SUCC(open(TEST_DIR, O_TMPFILE | O_RDWR | O_EXCL, 0666));
|
||||
dirfd = TEST_SUCC(open(TEST_DIR, O_RDONLY | O_DIRECTORY));
|
||||
|
||||
TEST_ERRNO(linkat(fd, "", dirfd, LINKED_O_EXCL_NAME, AT_EMPTY_PATH),
|
||||
ENOENT);
|
||||
|
||||
TEST_SUCC(close(dirfd));
|
||||
TEST_SUCC(close(fd));
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_TEST(tmpfile_linkat_cross_mount_returns_exdev)
|
||||
{
|
||||
int fd;
|
||||
int tmpfd;
|
||||
|
||||
#ifdef __asterinas__
|
||||
TEST_RES(dir_is_unavailable_or_same_mount(TEST_DIR, CROSS_LINK_DIR),
|
||||
_ret == 0);
|
||||
#else
|
||||
SKIP_TEST_IF(
|
||||
dir_is_unavailable_or_same_mount(TEST_DIR, CROSS_LINK_DIR));
|
||||
#endif
|
||||
|
||||
fd = TEST_SUCC(open(TEST_DIR, O_TMPFILE | O_RDWR, 0666));
|
||||
tmpfd = TEST_SUCC(open(CROSS_LINK_DIR, O_RDONLY | O_DIRECTORY));
|
||||
|
||||
TEST_ERRNO(linkat(fd, "", tmpfd, CROSS_LINK_NAME, AT_EMPTY_PATH),
|
||||
EXDEV);
|
||||
|
||||
TEST_SUCC(close(tmpfd));
|
||||
TEST_SUCC(close(fd));
|
||||
}
|
||||
END_TEST()
|
||||
|
||||
FN_SETUP(cleanup)
|
||||
{
|
||||
cleanup_test_files();
|
||||
}
|
||||
END_SETUP()
|
||||
Loading…
Reference in New Issue