Add fcntl's subcommands: F_GETFL and F_SETFL

* Modify fcntl system call to support F_GETFL and F_SETFL
* Separate OpenFlags to CreationsFlags, AccessMode and StatusFlags
This commit is contained in:
LI Qing 2019-12-04 08:13:53 +00:00 committed by Tate, Hongliang Tian
parent ebc158fe6c
commit daed89007a
9 changed files with 351 additions and 171 deletions

76
src/libos/src/fs/fcntl.rs Normal file

@ -0,0 +1,76 @@
use super::*;
use process::FileTableRef;
#[derive(Debug)]
pub enum FcntlCmd {
/// Duplicate the file descriptor fd using the lowest-numbered available
/// file descriptor greater than or equal to arg.
DupFd(FileDesc),
/// As for `DupFd`, but additionally set the close-on-exec flag for the
/// duplicate file descriptor.
DupFdCloexec(FileDesc),
/// Return (as the function result) the file descriptor flags
GetFd(),
/// Set the file descriptor to be close-on-exec or not
SetFd(u32),
/// Get the file status flags
GetFl(),
/// Set the file status flags
SetFl(u32),
}
impl FcntlCmd {
#[deny(unreachable_patterns)]
pub fn from_raw(cmd: u32, arg: u64) -> Result<FcntlCmd> {
Ok(match cmd as c_int {
libc::F_DUPFD => FcntlCmd::DupFd(arg as FileDesc),
libc::F_DUPFD_CLOEXEC => FcntlCmd::DupFdCloexec(arg as FileDesc),
libc::F_GETFD => FcntlCmd::GetFd(),
libc::F_SETFD => FcntlCmd::SetFd(arg as u32),
libc::F_GETFL => FcntlCmd::GetFl(),
libc::F_SETFL => FcntlCmd::SetFl(arg as u32),
_ => return_errno!(EINVAL, "unsupported command"),
})
}
}
pub fn do_fcntl(file_table_ref: &FileTableRef, fd: FileDesc, cmd: &FcntlCmd) -> Result<isize> {
let mut file_table = file_table_ref.lock().unwrap();
let ret = match cmd {
FcntlCmd::DupFd(min_fd) => {
let dup_fd = file_table.dup(fd, *min_fd, false)?;
dup_fd as isize
}
FcntlCmd::DupFdCloexec(min_fd) => {
let dup_fd = file_table.dup(fd, *min_fd, true)?;
dup_fd as isize
}
FcntlCmd::GetFd() => {
let entry = file_table.get_entry(fd)?;
let fd_flags = if entry.is_close_on_spawn() {
libc::FD_CLOEXEC
} else {
0
};
fd_flags as isize
}
FcntlCmd::SetFd(fd_flags) => {
let entry = file_table.get_entry_mut(fd)?;
entry.set_close_on_spawn((fd_flags & libc::FD_CLOEXEC as u32) != 0);
0
}
FcntlCmd::GetFl() => {
let file = file_table.get(fd)?;
let status_flags = file.get_status_flags()?;
let access_mode = file.get_access_mode()?;
(status_flags.bits() | access_mode as u32) as isize
}
FcntlCmd::SetFl(flags) => {
let file = file_table.get(fd)?;
let status_flags = StatusFlags::from_bits_truncate(*flags);
file.set_status_flags(status_flags)?;
0
}
};
Ok(ret)
}

@ -71,6 +71,18 @@ pub trait File: Debug + Sync + Send + Any {
return_op_unsupported_error!("ioctl")
}
fn get_access_mode(&self) -> Result<AccessMode> {
return_op_unsupported_error!("get_access_mode")
}
fn get_status_flags(&self) -> Result<StatusFlags> {
return_op_unsupported_error!("get_status_flags")
}
fn set_status_flags(&self, new_status_flags: StatusFlags) -> Result<()> {
return_op_unsupported_error!("set_status_flags")
}
fn as_any(&self) -> &Any;
}

@ -0,0 +1,102 @@
use super::*;
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
#[repr(u8)]
pub enum AccessMode {
/// read only
O_RDONLY = 0,
/// write only
O_WRONLY = 1,
/// read write
O_RDWR = 2,
}
impl AccessMode {
pub fn readable(&self) -> bool {
match *self {
AccessMode::O_RDONLY | AccessMode::O_RDWR => true,
_ => false,
}
}
pub fn writable(&self) -> bool {
match *self {
AccessMode::O_WRONLY | AccessMode::O_RDWR => true,
_ => false,
}
}
}
impl AccessMode {
pub fn from_u32(flags: u32) -> Result<Self> {
let bits = flags & 0b11;
if bits > AccessMode::O_RDWR as u32 {
return_errno!(EINVAL, "invalid bits for access mode")
}
Ok(unsafe { core::mem::transmute(bits as u8) })
}
}
bitflags! {
pub struct CreationFlags: u32 {
/// create file if it does not exist
const O_CREAT = 1 << 6;
/// error if CREATE and the file exists
const O_EXCL = 1 << 7;
/// not become the process's controlling terminal
const O_NOCTTY = 1 << 8;
/// truncate file upon open
const O_TRUNC = 1 << 9;
/// file is a directory
const O_DIRECTORY = 1 << 16;
/// pathname is not a symbolic link
const O_NOFOLLOW = 1 << 17;
/// close on exec
const O_CLOEXEC = 1 << 19;
/// create an unnamed temporary regular file
const _O_TMPFILE = 1 << 22;
}
}
impl CreationFlags {
pub fn must_close_on_spawn(&self) -> bool {
self.contains(CreationFlags::O_CLOEXEC)
}
pub fn can_create(&self) -> bool {
self.contains(CreationFlags::O_CREAT)
}
pub fn is_exclusive(&self) -> bool {
self.contains(CreationFlags::O_EXCL)
}
}
bitflags! {
pub struct StatusFlags: u32 {
/// append on each write
const O_APPEND = 1 << 10;
/// non block
const O_NONBLOCK = 1 << 11;
/// synchronized I/O, data
const O_DSYNC = 1 << 12;
/// signal-driven I/O
const O_ASYNC = 1 << 13;
/// direct I/O
const O_DIRECT = 1 << 14;
/// on x86_64, O_LARGEFILE is 0
/// not update st_atime
const O_NOATIME = 1 << 18;
/// synchronized I/O, data and metadata
const _O_SYNC = 1 << 20;
/// equivalent of POSIX.1's O_EXEC
const O_PATH = 1 << 21;
}
}
impl StatusFlags {
pub fn always_append(&self) -> bool {
self.contains(StatusFlags::O_APPEND)
}
}

@ -6,20 +6,13 @@ use std::fmt;
pub struct INodeFile {
inode: Arc<INode>,
offset: SgxMutex<usize>,
options: OpenOptions,
}
#[derive(Debug, Clone)]
pub struct OpenOptions {
pub read: bool,
pub write: bool,
/// Before each write, the file offset is positioned at the end of the file.
pub append: bool,
access_mode: AccessMode,
status_flags: SgxRwLock<StatusFlags>,
}
impl File for INodeFile {
fn read(&self, buf: &mut [u8]) -> Result<usize> {
if !self.options.read {
if !self.access_mode.readable() {
return_errno!(EBADF, "File not readable");
}
let mut offset = self.offset.lock().unwrap();
@ -29,11 +22,11 @@ impl File for INodeFile {
}
fn write(&self, buf: &[u8]) -> Result<usize> {
if !self.options.write {
if !self.access_mode.writable() {
return_errno!(EBADF, "File not writable");
}
let mut offset = self.offset.lock().unwrap();
if self.options.append {
if self.status_flags.read().unwrap().always_append() {
let info = self.inode.metadata()?;
*offset = info.size;
}
@ -43,7 +36,7 @@ impl File for INodeFile {
}
fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize> {
if !self.options.read {
if !self.access_mode.readable() {
return_errno!(EBADF, "File not readable");
}
let len = self.inode.read_at(offset, buf)?;
@ -51,7 +44,7 @@ impl File for INodeFile {
}
fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize> {
if !self.options.write {
if !self.access_mode.writable() {
return_errno!(EBADF, "File not writable");
}
let len = self.inode.write_at(offset, buf)?;
@ -59,7 +52,7 @@ impl File for INodeFile {
}
fn readv(&self, bufs: &mut [&mut [u8]]) -> Result<usize> {
if !self.options.read {
if !self.access_mode.readable() {
return_errno!(EBADF, "File not readable");
}
let mut offset = self.offset.lock().unwrap();
@ -78,11 +71,11 @@ impl File for INodeFile {
}
fn writev(&self, bufs: &[&[u8]]) -> Result<usize> {
if !self.options.write {
if !self.access_mode.writable() {
return_errno!(EBADF, "File not writable");
}
let mut offset = self.offset.lock().unwrap();
if self.options.append {
if self.status_flags.read().unwrap().always_append() {
let info = self.inode.metadata()?;
*offset = info.size;
}
@ -116,7 +109,7 @@ impl File for INodeFile {
}
fn set_len(&self, len: u64) -> Result<()> {
if !self.options.write {
if !self.access_mode.writable() {
return_errno!(EBADF, "File not writable. Can't set len.");
}
self.inode.resize(len as usize)?;
@ -134,7 +127,7 @@ impl File for INodeFile {
}
fn read_entry(&self) -> Result<String> {
if !self.options.read {
if !self.access_mode.readable() {
return_errno!(EBADF, "File not readable. Can't read entry.");
}
let mut offset = self.offset.lock().unwrap();
@ -143,24 +136,48 @@ impl File for INodeFile {
Ok(name)
}
fn get_access_mode(&self) -> Result<AccessMode> {
Ok(self.access_mode.clone())
}
fn get_status_flags(&self) -> Result<StatusFlags> {
let status_flags = self.status_flags.read().unwrap();
Ok(status_flags.clone())
}
fn set_status_flags(&self, new_status_flags: StatusFlags) -> Result<()> {
let mut status_flags = self.status_flags.write().unwrap();
// Currently, F_SETFL can change only the O_APPEND,
// O_ASYNC, O_NOATIME, and O_NONBLOCK flags
let valid_flags_mask = StatusFlags::O_APPEND
| StatusFlags::O_ASYNC
| StatusFlags::O_NOATIME
| StatusFlags::O_NONBLOCK;
status_flags.remove(valid_flags_mask);
status_flags.insert(new_status_flags & valid_flags_mask);
Ok(())
}
fn as_any(&self) -> &Any {
self
}
}
impl INodeFile {
pub fn open(inode: Arc<INode>, options: OpenOptions) -> Result<Self> {
if (options.read && !inode.allow_read()?) {
pub fn open(inode: Arc<INode>, flags: u32) -> Result<Self> {
let access_mode = AccessMode::from_u32(flags)?;
if (access_mode.readable() && !inode.allow_read()?) {
return_errno!(EBADF, "File not readable");
}
if (options.write && !inode.allow_write()?) {
if (access_mode.writable() && !inode.allow_write()?) {
return_errno!(EBADF, "File not writable");
}
let status_flags = StatusFlags::from_bits_truncate(flags);
Ok(INodeFile {
inode,
offset: SgxMutex::new(0),
options,
access_mode,
status_flags: SgxRwLock::new(status_flags),
})
}
}
@ -169,9 +186,10 @@ impl Debug for INodeFile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"INodeFile {{ inode: ???, pos: {}, options: {:?} }}",
"INodeFile {{ inode: ???, pos: {}, access_mode: {:?}, status_flags: {:#o} }}",
*self.offset.lock().unwrap(),
self.options
self.access_mode,
*self.status_flags.read().unwrap()
)
}
}

@ -12,9 +12,10 @@ use self::dev_null::DevNull;
use self::dev_random::DevRandom;
use self::dev_sgx::DevSgx;
use self::dev_zero::DevZero;
pub use self::fcntl::FcntlCmd;
pub use self::file::{File, FileRef, SgxFile, StdinFile, StdoutFile};
pub use self::file_flags::{AccessMode, CreationFlags, StatusFlags};
pub use self::file_table::{FileDesc, FileTable};
use self::inode_file::OpenOptions;
pub use self::inode_file::{INodeExt, INodeFile};
pub use self::io_multiplexing::*;
pub use self::ioctl::*;
@ -30,7 +31,9 @@ mod dev_null;
mod dev_random;
mod dev_sgx;
mod dev_zero;
mod fcntl;
mod file;
mod file_flags;
mod file_table;
mod hostfs;
mod inode_file;
@ -42,9 +45,8 @@ mod sgx_impl;
mod unix_socket;
pub fn do_open(path: &str, flags: u32, mode: u32) -> Result<FileDesc> {
let flags = OpenFlags::from_bits_truncate(flags);
info!(
"open: path: {:?}, flags: {:?}, mode: {:#o}",
"open: path: {:?}, flags: {:#o}, mode: {:#o}",
path, flags, mode
);
@ -55,11 +57,11 @@ pub fn do_open(path: &str, flags: u32, mode: u32) -> Result<FileDesc> {
let file_ref: Arc<Box<File>> = Arc::new(file);
let fd = {
let close_on_spawn = flags.contains(OpenFlags::CLOEXEC);
let creation_flags = CreationFlags::from_bits_truncate(flags);
proc.get_files()
.lock()
.unwrap()
.put(file_ref, close_on_spawn)
.put(file_ref, creation_flags.must_close_on_spawn())
};
Ok(fd)
}
@ -238,14 +240,14 @@ pub fn do_ioctl(fd: FileDesc, cmd: &mut IoctlCmd) -> Result<()> {
pub fn do_pipe2(flags: u32) -> Result<[FileDesc; 2]> {
info!("pipe2: flags: {:#x}", flags);
let flags = OpenFlags::from_bits_truncate(flags);
let creation_flags = CreationFlags::from_bits_truncate(flags);
let current_ref = process::get_current();
let current = current_ref.lock().unwrap();
let pipe = Pipe::new()?;
let file_table_ref = current.get_files();
let mut file_table = file_table_ref.lock().unwrap();
let close_on_spawn = flags.contains(OpenFlags::CLOEXEC);
let close_on_spawn = creation_flags.must_close_on_spawn();
let reader_fd = file_table.put(Arc::new(Box::new(pipe.reader)), close_on_spawn);
let writer_fd = file_table.put(Arc::new(Box::new(pipe.writer)), close_on_spawn);
info!("pipe2: reader_fd: {}, writer_fd: {}", reader_fd, writer_fd);
@ -275,7 +277,7 @@ pub fn do_dup2(old_fd: FileDesc, new_fd: FileDesc) -> Result<FileDesc> {
}
pub fn do_dup3(old_fd: FileDesc, new_fd: FileDesc, flags: u32) -> Result<FileDesc> {
let flags = OpenFlags::from_bits_truncate(flags);
let creation_flags = CreationFlags::from_bits_truncate(flags);
let current_ref = process::get_current();
let current = current_ref.lock().unwrap();
let file_table_ref = current.get_files();
@ -284,8 +286,7 @@ pub fn do_dup3(old_fd: FileDesc, new_fd: FileDesc, flags: u32) -> Result<FileDes
if old_fd == new_fd {
return_errno!(EINVAL, "old_fd must not be equal to new_fd");
}
let close_on_spawn = flags.contains(OpenFlags::CLOEXEC);
file_table.put_at(new_fd, file, close_on_spawn);
file_table.put_at(new_fd, file, creation_flags.must_close_on_spawn());
Ok(new_fd)
}
@ -439,7 +440,7 @@ extern "C" {
impl Process {
/// Open a file on the process. But DO NOT add it to file table.
pub fn open_file(&self, path: &str, flags: OpenFlags, mode: u32) -> Result<Box<File>> {
pub fn open_file(&self, path: &str, flags: u32, mode: u32) -> Result<Box<File>> {
if path == "/dev/null" {
return Ok(Box::new(DevNull));
}
@ -452,12 +453,13 @@ impl Process {
if path == "/dev/sgx" {
return Ok(Box::new(DevSgx));
}
let inode = if flags.contains(OpenFlags::CREATE) {
let creation_flags = CreationFlags::from_bits_truncate(flags);
let inode = if creation_flags.can_create() {
let (dir_path, file_name) = split_path(&path);
let dir_inode = self.lookup_inode(dir_path)?;
match dir_inode.find(file_name) {
Ok(file_inode) => {
if flags.contains(OpenFlags::EXCLUSIVE) {
if creation_flags.is_exclusive() {
return_errno!(EEXIST, "file exists");
}
file_inode
@ -473,7 +475,7 @@ impl Process {
} else {
self.lookup_inode(&path)?
};
Ok(Box::new(INodeFile::open(inode, flags.to_options())?))
Ok(Box::new(INodeFile::open(inode, flags)?))
}
/// Lookup INode from the cwd of the process
@ -504,47 +506,6 @@ fn split_path(path: &str) -> (&str, &str) {
(dir_path, file_name)
}
bitflags! {
pub struct OpenFlags: u32 {
/// read only
const RDONLY = 0;
/// write only
const WRONLY = 1;
/// read write
const RDWR = 2;
/// create file if it does not exist
const CREATE = 1 << 6;
/// error if CREATE and the file exists
const EXCLUSIVE = 1 << 7;
/// truncate file upon open
const TRUNCATE = 1 << 9;
/// append on each write
const APPEND = 1 << 10;
/// non block
const NONBLOCK = 1 << 11;
/// close on exec
const CLOEXEC = 1 << 19;
}
}
impl OpenFlags {
fn readable(&self) -> bool {
let b = self.bits() & 0b11;
b == OpenFlags::RDONLY.bits() || b == OpenFlags::RDWR.bits()
}
fn writable(&self) -> bool {
let b = self.bits() & 0b11;
b == OpenFlags::WRONLY.bits() || b == OpenFlags::RDWR.bits()
}
fn to_options(&self) -> OpenOptions {
OpenOptions {
read: self.readable(),
write: self.writable(),
append: self.contains(OpenFlags::APPEND),
}
}
}
#[repr(packed)] // Don't use 'C'. Or its size will align up to 8 bytes.
pub struct LinuxDirent64 {
/// Inode number
@ -731,93 +692,13 @@ pub unsafe fn write_cstr(ptr: *mut u8, s: &str) {
ptr.add(s.len()).write(0);
}
#[derive(Debug)]
pub enum FcntlCmd {
/// Duplicate the file descriptor fd using the lowest-numbered available
/// file descriptor greater than or equal to arg.
DupFd(FileDesc),
/// As for `DupFd`, but additionally set the close-on-exec flag for the
/// duplicate file descriptor.
DupFdCloexec(FileDesc),
/// Return (as the function result) the file descriptor flags
GetFd(),
/// Set the file descriptor to be close-on-exec or not
SetFd(u32),
/// Get the file status flags
GetFl(),
/// Set the file status flags
SetFl(u32),
}
impl FcntlCmd {
#[deny(unreachable_patterns)]
pub fn from_raw(cmd: u32, arg: u64) -> Result<FcntlCmd> {
Ok(match cmd as c_int {
libc::F_DUPFD => FcntlCmd::DupFd(arg as FileDesc),
libc::F_DUPFD_CLOEXEC => FcntlCmd::DupFdCloexec(arg as FileDesc),
libc::F_GETFD => FcntlCmd::GetFd(),
libc::F_SETFD => FcntlCmd::SetFd(arg as u32),
libc::F_GETFL => FcntlCmd::GetFl(),
libc::F_SETFL => FcntlCmd::SetFl(arg as u32),
_ => return_errno!(EINVAL, "invalid command"),
})
}
}
pub fn do_fcntl(fd: FileDesc, cmd: &FcntlCmd) -> Result<isize> {
info!("fcntl: fd: {:?}, cmd: {:?}", &fd, cmd);
let current_ref = process::get_current();
let mut current = current_ref.lock().unwrap();
let files_ref = current.get_files();
let mut files = files_ref.lock().unwrap();
Ok(match cmd {
FcntlCmd::DupFd(min_fd) => {
let dup_fd = files.dup(fd, *min_fd, false)?;
dup_fd as isize
}
FcntlCmd::DupFdCloexec(min_fd) => {
let dup_fd = files.dup(fd, *min_fd, true)?;
dup_fd as isize
}
FcntlCmd::GetFd() => {
let entry = files.get_entry(fd)?;
let fd_flags = if entry.is_close_on_spawn() {
libc::FD_CLOEXEC
} else {
0
};
fd_flags as isize
}
FcntlCmd::SetFd(fd_flags) => {
let entry = files.get_entry_mut(fd)?;
entry.set_close_on_spawn((fd_flags & libc::FD_CLOEXEC as u32) != 0);
0
}
FcntlCmd::GetFl() => {
let file = files.get(fd)?;
if let Ok(socket) = file.as_socket() {
let ret = try_libc!(libc::ocall::fcntl_arg0(socket.fd(), libc::F_GETFL));
ret as isize
} else {
warn!("fcntl.getfl is unimplemented");
0
}
}
FcntlCmd::SetFl(flags) => {
let file = files.get(fd)?;
if let Ok(socket) = file.as_socket() {
let ret = try_libc!(libc::ocall::fcntl_arg1(
socket.fd(),
libc::F_SETFL,
*flags as c_int
));
ret as isize
} else {
warn!("fcntl.setfl is unimplemented");
0
}
}
})
let file_table_ref = current.get_files();
let ret = fcntl::do_fcntl(file_table_ref, fd, cmd)?;
Ok(ret)
}
pub fn do_readlink(path: &str, buf: &mut [u8]) -> Result<usize> {

@ -5,7 +5,7 @@ use std::path::Path;
use std::sgxfs::SgxFile;
use super::fs::{
File, FileDesc, FileTable, INodeExt, OpenFlags, StdinFile, StdoutFile, ROOT_INODE,
CreationFlags, File, FileDesc, FileTable, INodeExt, StdinFile, StdoutFile, ROOT_INODE,
};
use super::misc::ResourceLimitsRef;
use super::vm::{ProcessVM, ProcessVMBuilder};
@ -117,12 +117,10 @@ fn init_files(parent_ref: &ProcessRef, file_actions: &[FileAction]) -> Result<Fi
oflag,
fd,
} => {
let flags = OpenFlags::from_bits_truncate(oflag);
let file = parent.open_file(path.as_str(), flags, mode)?;
let file = parent.open_file(path.as_str(), oflag, mode)?;
let file_ref: Arc<Box<File>> = Arc::new(file);
let close_on_spawn = flags.contains(OpenFlags::CLOEXEC);
cloned_file_table.put_at(fd, file_ref, close_on_spawn);
let creation_flags = CreationFlags::from_bits_truncate(oflag);
cloned_file_table.put_at(fd, file_ref, creation_flags.must_close_on_spawn());
}
&FileAction::Dup2(old_fd, new_fd) => {
let file = cloned_file_table.get(old_fd)?;

@ -8,7 +8,7 @@ TEST_DEPS := client data_sink
TESTS := empty env hello_world malloc mmap file fs_perms getpid spawn sched pipe time \
truncate readdir mkdir link tls pthread uname rlimit server \
server_epoll unix_socket cout hostfs cpuid rdtsc device sleep exit_group \
ioctl
ioctl fcntl
# Benchmarks: need to be compiled and run by bench-% target
BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput

5
test/fcntl/Makefile Normal file

@ -0,0 +1,5 @@
include ../test_common.mk
EXTRA_C_FLAGS :=
EXTRA_LINK_FLAGS :=
BIN_ARGS :=

88
test/fcntl/main.c Normal file

@ -0,0 +1,88 @@
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include "test.h"
// ============================================================================
// Helper macro
// ============================================================================
#define CREATION_FLAGS_MASK (O_CLOEXEC | O_CREAT| O_DIRECTORY | O_EXCL | \
O_NOCTTY | O_NOFOLLOW | O_TMPFILE | O_TRUNC)
// ============================================================================
// Test cases for fcntl
// ============================================================================
static int __fcntl_getfl(int fd, int open_flags) {
int actual_flags;
actual_flags = fcntl(fd, F_GETFL);
open_flags &= ~CREATION_FLAGS_MASK;
open_flags |= O_LARGEFILE;
if (open_flags != actual_flags) {
THROW_ERROR("check getfl failed");
}
return 0;
}
static int __fcntl_setfl(int fd, int open_flags) {
int ret, actual_flags;
ret = fcntl(fd, F_SETFL, open_flags & ~O_APPEND);
if (ret < 0) {
THROW_ERROR("failed to call setfl");
}
actual_flags = fcntl(fd, F_GETFL);
if ((actual_flags & O_APPEND) != 0) {
THROW_ERROR("failed to check getfl after setfl");
}
return 0;
}
typedef int(*test_fcntl_func_t)(int fd, int open_flags);
static int test_fcntl_framework(test_fcntl_func_t fn) {
const char *file_path = "/root/test_fcntl_file.txt";
int open_flags = O_RDWR | O_CREAT | O_TRUNC | O_APPEND;
int mode = 00666;
int fd, ret;
fd = open(file_path, open_flags, mode);
if (fd < 0) {
THROW_ERROR("failed to open & create file");
}
if (fn(fd, open_flags) < 0)
return -1;
close(fd);
ret = unlink(file_path);
if (ret < 0) {
THROW_ERROR("failed to unlink the created file");
}
return 0;
}
static int test_fcntl_getfl() {
return test_fcntl_framework(__fcntl_getfl);
}
static int test_fcntl_setfl() {
return test_fcntl_framework(__fcntl_setfl);
}
// ============================================================================
// Test suite
// ============================================================================
static test_case_t test_cases[] = {
TEST_CASE(test_fcntl_getfl),
TEST_CASE(test_fcntl_setfl),
};
int main() {
return test_suite_run(test_cases, ARRAY_SIZE(test_cases));
}