Implement renameat, renameat2
Like the other "at" functions, renameat renames files with respect with a directory descriptor. "renameat2" is Linux specific but really nice - it adds a flag which supports atomically swapping two files or failing if the target already exists. The latter is easily supported in relibc. The former requires redoxfs support. Besides the impl itself, I refactored "cap_path_at" into what it really is: a mini openat2-like function.
This commit is contained in:
@@ -24,6 +24,16 @@ pub const _IOFBF: c_int = 0;
|
||||
pub const _IOLBF: c_int = 1;
|
||||
pub const _IONBF: c_int = 2;
|
||||
|
||||
// renameat2
|
||||
// (uint instead of int is intentional)
|
||||
/// Rename but don't replace the target if it exists.
|
||||
pub const RENAME_NOREPLACE: c_uint = 0x01;
|
||||
/// Atomically swap two files.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const RENAME_EXCHANGE: c_uint = 0x02;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const RENAME_WHITEOUT: c_uint = 0x04;
|
||||
|
||||
// /dev/tty + nul
|
||||
pub const L_ctermid: usize = 9;
|
||||
// form of name is /XXXXXX, so 7
|
||||
|
||||
@@ -1034,6 +1034,35 @@ pub unsafe extern "C" fn rename(oldpath: *const c_char, newpath: *const c_char)
|
||||
.or_minus_one_errno()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn renameat(
|
||||
old_dir: c_int,
|
||||
old_path: *const c_char,
|
||||
new_dir: c_int,
|
||||
new_path: *const c_char,
|
||||
) -> c_int {
|
||||
let old_path = CStr::from_ptr(old_path);
|
||||
let new_path = CStr::from_ptr(new_path);
|
||||
Sys::renameat(old_dir, old_path, new_dir, new_path)
|
||||
.map(|()| 0)
|
||||
.or_minus_one_errno()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn renameat2(
|
||||
old_dir: c_int,
|
||||
old_path: *const c_char,
|
||||
new_dir: c_int,
|
||||
new_path: *const c_char,
|
||||
flags: c_uint,
|
||||
) -> c_int {
|
||||
let old_path = CStr::from_ptr(old_path);
|
||||
let new_path = CStr::from_ptr(new_path);
|
||||
Sys::renameat2(old_dir, old_path, new_dir, new_path, flags)
|
||||
.map(|()| 0)
|
||||
.or_minus_one_errno()
|
||||
}
|
||||
|
||||
/// Rewind `stream` back to the beginning of it
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rewind(stream: *mut FILE) {
|
||||
|
||||
@@ -686,6 +686,39 @@ impl Pal for Sys {
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn renameat(old_dir: c_int, old_path: CStr, new_dir: c_int, new_path: CStr) -> Result<()> {
|
||||
e_raw(unsafe {
|
||||
syscall!(
|
||||
RENAMEAT,
|
||||
old_dir,
|
||||
old_path.as_ptr(),
|
||||
new_dir,
|
||||
new_path.as_ptr()
|
||||
)
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn renameat2(
|
||||
old_dir: c_int,
|
||||
old_path: CStr,
|
||||
new_dir: c_int,
|
||||
new_path: CStr,
|
||||
flags: c_uint,
|
||||
) -> Result<()> {
|
||||
e_raw(unsafe {
|
||||
syscall!(
|
||||
RENAMEAT2,
|
||||
old_dir,
|
||||
old_path.as_ptr(),
|
||||
new_dir,
|
||||
new_path.as_ptr(),
|
||||
flags
|
||||
)
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn rmdir(path: CStr) -> Result<()> {
|
||||
e_raw(unsafe { syscall!(UNLINKAT, AT_FDCWD, path.as_ptr(), AT_REMOVEDIR) }).map(|_| ())
|
||||
}
|
||||
|
||||
@@ -232,6 +232,14 @@ pub trait Pal {
|
||||
fn readlinkat(dirfd: c_int, pathname: CStr, out: &mut [u8]) -> Result<usize>;
|
||||
|
||||
fn rename(old: CStr, new: CStr) -> Result<()>;
|
||||
fn renameat(old_dir: c_int, old_path: CStr, new_dir: c_int, new_path: CStr) -> Result<()>;
|
||||
fn renameat2(
|
||||
old_dir: c_int,
|
||||
old_path: CStr,
|
||||
new_dir: c_int,
|
||||
new_path: CStr,
|
||||
flags: c_uint,
|
||||
) -> Result<()>;
|
||||
|
||||
fn rmdir(path: CStr) -> Result<()>;
|
||||
|
||||
|
||||
+49
-14
@@ -14,6 +14,11 @@ use syscall::{
|
||||
dirent::{DirentHeader, DirentKind},
|
||||
};
|
||||
|
||||
use self::{
|
||||
exec::Executable,
|
||||
path::{canonicalize, openat2, openat2_path},
|
||||
};
|
||||
use super::{ERRNO, Pal, Read, types::*};
|
||||
use crate::{
|
||||
c_str::{CStr, CString},
|
||||
error::{self, Errno, Result, ResultExt},
|
||||
@@ -21,13 +26,14 @@ use crate::{
|
||||
header::{
|
||||
dirent::dirent,
|
||||
errno::{
|
||||
EBADF, EBADFD, EBADR, EFAULT, EINTR, EINVAL, EIO, ENAMETOOLONG, ENOENT, ENOMEM, ENOSYS,
|
||||
EOPNOTSUPP, EPERM, ERANGE,
|
||||
EBADF, EBADFD, EBADR, EEXIST, EFAULT, EINTR, EINVAL, EIO, ENAMETOOLONG, ENOENT, ENOMEM,
|
||||
ENOSYS, EOPNOTSUPP, EPERM, ERANGE,
|
||||
},
|
||||
fcntl::{self, AT_FDCWD, O_CREAT, O_RDONLY, O_RDWR},
|
||||
fcntl::{self, AT_FDCWD, AT_SYMLINK_NOFOLLOW, O_CREAT, O_RDONLY, O_RDWR},
|
||||
limits,
|
||||
pthread::{pthread_cancel, pthread_create, pthread_self},
|
||||
pthread::{pthread_cancel, pthread_create},
|
||||
signal::{NSIG, SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD, SIGRTMIN, sigevent},
|
||||
stdio::RENAME_NOREPLACE,
|
||||
sys_mman::{MAP_ANONYMOUS, MAP_FAILED, PROT_READ, PROT_WRITE},
|
||||
sys_random,
|
||||
sys_resource::{RLIM_INFINITY, rlimit, rusage},
|
||||
@@ -50,8 +56,6 @@ use crate::{
|
||||
|
||||
pub use redox_rt::proc::FdGuard;
|
||||
|
||||
use super::{ERRNO, Pal, Read, types::*};
|
||||
|
||||
static mut BRK_CUR: *mut c_void = ptr::null_mut();
|
||||
static mut BRK_END: *mut c_void = ptr::null_mut();
|
||||
|
||||
@@ -93,11 +97,6 @@ macro_rules! path_from_c_str {
|
||||
}};
|
||||
}
|
||||
|
||||
use self::{
|
||||
exec::Executable,
|
||||
path::{canonicalize, cap_path_at},
|
||||
};
|
||||
|
||||
static CLONE_LOCK: RwLock<()> = RwLock::new(());
|
||||
|
||||
/// Redox syscall implementation of the platform abstraction layer.
|
||||
@@ -273,7 +272,7 @@ impl Pal for Sys {
|
||||
let path = path
|
||||
.and_then(|cs| str::from_utf8(cs.to_bytes()).ok())
|
||||
.ok_or(Errno(ENOENT))?;
|
||||
let file = cap_path_at(dirfd, path, flags, 0)?;
|
||||
let file = openat2(dirfd, path, flags, 0)?;
|
||||
syscall::fchmod(*file as usize, mode as u16)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -316,7 +315,7 @@ impl Pal for Sys {
|
||||
let path = path
|
||||
.and_then(|cs| str::from_utf8(cs.to_bytes()).ok())
|
||||
.ok_or(Errno(ENOENT))?;
|
||||
let file = cap_path_at(dirfd, path, flags, 0)?;
|
||||
let file = openat2(dirfd, path, flags, 0)?;
|
||||
Sys::fstat(*file, buf)
|
||||
}
|
||||
|
||||
@@ -923,7 +922,7 @@ impl Pal for Sys {
|
||||
|
||||
fn readlinkat(dirfd: c_int, path: CStr, out: &mut [u8]) -> Result<usize> {
|
||||
let path = str::from_utf8(path.to_bytes()).map_err(|_| Errno(ENOENT))?;
|
||||
let file = cap_path_at(dirfd, path, 0, fcntl::O_SYMLINK)?;
|
||||
let file = openat2(dirfd, path, 0, fcntl::O_RDONLY | fcntl::O_SYMLINK)?;
|
||||
Sys::read(*file, out)
|
||||
}
|
||||
|
||||
@@ -939,6 +938,42 @@ impl Pal for Sys {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn renameat(old_dir: c_int, old_path: CStr, new_dir: c_int, new_path: CStr) -> Result<()> {
|
||||
Sys::renameat2(old_dir, old_path, new_dir, new_path, 0)
|
||||
}
|
||||
|
||||
fn renameat2(
|
||||
old_dir: c_int,
|
||||
old_path: CStr,
|
||||
new_dir: c_int,
|
||||
new_path: CStr,
|
||||
flags: c_uint,
|
||||
) -> Result<()> {
|
||||
const MASK: c_uint = !RENAME_NOREPLACE;
|
||||
if MASK & flags != 0 {
|
||||
return Err(Errno(EOPNOTSUPP));
|
||||
}
|
||||
|
||||
let new_path = new_path.to_str().map_err(|_| Errno(EINVAL))?;
|
||||
let target = openat2_path(new_dir, new_path, 0)?;
|
||||
// Fail if the target exists with RENAME_NOREPLACE.
|
||||
if flags & RENAME_NOREPLACE != 0
|
||||
&& let Ok(fd) =
|
||||
libredox::open(&target, fcntl::O_PATH | fcntl::O_CLOEXEC, 0).map(FdGuard::new)
|
||||
{
|
||||
return Err(Errno(EEXIST));
|
||||
}
|
||||
|
||||
let old_path = old_path.to_str().map_err(|_| Errno(EINVAL))?;
|
||||
// oflags are the same as Sys::rename above.
|
||||
let source = openat2(old_dir, old_path, 0, fcntl::O_NOFOLLOW | fcntl::O_PATH)?;
|
||||
|
||||
// I'm avoiding Sys::rename to avoid reallocating a CString from a String.
|
||||
syscall::frename(*source as usize, target)
|
||||
.map(|_| ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn rmdir(path: CStr) -> Result<()> {
|
||||
let path = path.to_str().map_err(|_| Errno(EINVAL))?;
|
||||
let canon = canonicalize(path)?;
|
||||
|
||||
+44
-17
@@ -210,46 +210,73 @@ fn get_parent_path(path: &str) -> Option<&str> {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Fold into openat or something.
|
||||
pub fn cap_path_at(
|
||||
dirfd: c_int,
|
||||
path: &str,
|
||||
at_flags: c_int,
|
||||
oflags: c_int,
|
||||
) -> Result<File, Errno> {
|
||||
/// Resolve `path` under `dirfd`.
|
||||
///
|
||||
/// See [`openat2`] for more information.
|
||||
pub(super) fn openat2_path(dirfd: c_int, path: &str, at_flags: c_int) -> Result<String, Errno> {
|
||||
// Ideally, the function calling this fn would check AT_EMPTY_PATH and just call fstat or
|
||||
// whatever with the fd.
|
||||
if path.is_empty() && at_flags & fcntl::AT_EMPTY_PATH != fcntl::AT_EMPTY_PATH {
|
||||
return Err(Errno(ENOENT));
|
||||
}
|
||||
|
||||
let oflags = if at_flags & fcntl::AT_SYMLINK_NOFOLLOW == fcntl::AT_SYMLINK_NOFOLLOW {
|
||||
fcntl::O_CLOEXEC | fcntl::O_NOFOLLOW | fcntl::O_PATH | fcntl::O_SYMLINK | oflags
|
||||
} else {
|
||||
fcntl::O_CLOEXEC | fcntl::O_RDONLY | oflags
|
||||
};
|
||||
|
||||
// Absolute paths are passed without processing unless RESOLVE_BENEATH is used.
|
||||
// canonicalize_using_cwd checks that path is absolute so a third branch that does so here
|
||||
// isn't needed.
|
||||
let path = if dirfd == fcntl::AT_FDCWD {
|
||||
if dirfd == fcntl::AT_FDCWD {
|
||||
// The special constant AT_FDCWD indicates that we should use the cwd.
|
||||
let mut buf = [0; limits::PATH_MAX];
|
||||
let len = getcwd(Out::from_mut(&mut buf)).ok_or(Errno(ENAMETOOLONG))?;
|
||||
// SAFETY: Redox's cwd is stored as a str.
|
||||
let cwd = unsafe { str::from_utf8_unchecked(&buf[..len]) };
|
||||
|
||||
canonicalize_using_cwd(Some(cwd), path).ok_or(Errno(EBADF))?
|
||||
canonicalize_using_cwd(Some(cwd), path).ok_or(Errno(EBADF))
|
||||
} else {
|
||||
let mut buf = [0; limits::PATH_MAX];
|
||||
let len = Sys::fpath(dirfd, &mut buf)?;
|
||||
// SAFETY: fpath checks then copies valid UTF8.
|
||||
let dir = unsafe { str::from_utf8_unchecked(&buf[..len]) };
|
||||
|
||||
canonicalize_using_cwd(Some(dir), path).ok_or(Errno(EBADF))?
|
||||
};
|
||||
canonicalize_using_cwd(Some(dir), path).ok_or(Errno(EBADF))
|
||||
}
|
||||
}
|
||||
|
||||
/// Canonicalize and open `path` with respect to `dirfd`.
|
||||
///
|
||||
/// This unexported openat2 is similar to the Linux syscall but with a different interface. The
|
||||
/// naming is mostly for convenience - it's not a drop in replacement for openat2.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `dirfd` is a directory descriptor to which `path` is resolved.
|
||||
/// * `path` is a relative or absolute path. Relative paths are resolved in relation to `dirfd`
|
||||
/// while absolute paths skip `dirfd`.
|
||||
/// * `at_flags` constrains how `path` is resolved.
|
||||
/// * `oflags` are flags that are passed to open.
|
||||
///
|
||||
/// # Constants
|
||||
/// `at_flags`:
|
||||
/// * AT_EMPTY_PATH returns the path at `dirfd` itself if `path` is empty. If `path` is not
|
||||
/// empty, it's resolved w.r.t `dirfd` like normal.
|
||||
///
|
||||
/// `dirfd`:
|
||||
/// `AT_FDCWD` is a special constant for `dirfd` that resolves `path` under the current working
|
||||
/// directory.
|
||||
pub(super) fn openat2(
|
||||
dirfd: c_int,
|
||||
path: &str,
|
||||
at_flags: c_int,
|
||||
oflags: c_int,
|
||||
) -> Result<File, Errno> {
|
||||
let path = openat2_path(dirfd, path, at_flags)?;
|
||||
let path = CString::new(path).map_err(|_| Errno(ENOENT))?;
|
||||
|
||||
// Translate at flags into open flags; openat will do this on its own most likely.
|
||||
let oflags = if at_flags & fcntl::AT_SYMLINK_NOFOLLOW == fcntl::AT_SYMLINK_NOFOLLOW {
|
||||
fcntl::O_CLOEXEC | fcntl::O_NOFOLLOW | fcntl::O_PATH | fcntl::O_SYMLINK | oflags
|
||||
} else {
|
||||
fcntl::O_CLOEXEC | oflags
|
||||
};
|
||||
|
||||
// TODO:
|
||||
// * Switch open to openat.
|
||||
File::open(path.as_c_str().into(), oflags)
|
||||
|
||||
@@ -64,6 +64,7 @@ EXPECT_NAMES=\
|
||||
stdio/popen \
|
||||
stdio/printf \
|
||||
stdio/rename \
|
||||
stdio/renameat \
|
||||
stdio/scanf \
|
||||
stdio/setvbuf \
|
||||
stdio/sprintf \
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int temp_file(
|
||||
const char dir[],
|
||||
size_t dlen,
|
||||
char buf[PATH_MAX],
|
||||
const char name[],
|
||||
size_t nlen,
|
||||
bool create
|
||||
) {
|
||||
memcpy(buf, dir, dlen);
|
||||
buf[dlen] = '/';
|
||||
memcpy(&buf[dlen + 1], name, nlen);
|
||||
|
||||
if (create) {
|
||||
int fd = open(buf, O_CREAT);
|
||||
if (fd == -1) {
|
||||
perror("open (file A)");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int result = EXIT_FAILURE;
|
||||
|
||||
char dir_template[] = "/tmp/mvattest.XXXXXX";
|
||||
size_t dlen = sizeof(dir_template) - 1;
|
||||
if (!mkdtemp(dir_template)) {
|
||||
perror("mkdtemp");
|
||||
goto bye;
|
||||
}
|
||||
int dirfd = open(dir_template, O_DIRECTORY | O_PATH);
|
||||
if (dirfd == -1) {
|
||||
perror("open (temp directory)");
|
||||
goto bye;
|
||||
}
|
||||
|
||||
char dir_template2[] = "/tmp/mvattest2.XXXXXX";
|
||||
size_t dlen2 = sizeof(dir_template2) - 1;
|
||||
if (!mkdtemp(dir_template2)) {
|
||||
perror("mkdtemp");
|
||||
goto close_dir1;
|
||||
}
|
||||
int dirfd2 = open(dir_template2, O_DIRECTORY | O_PATH);
|
||||
if (dirfd2 == -1) {
|
||||
perror("open (temp directory)");
|
||||
goto close_dir1;
|
||||
}
|
||||
|
||||
// File A
|
||||
const char file_a[] = "power";
|
||||
char file_a_path[PATH_MAX] = {0};
|
||||
if (temp_file(dir_template, dlen, file_a_path, file_a, sizeof(file_a), true) == -1) {
|
||||
goto close_dir2;
|
||||
}
|
||||
|
||||
// File B
|
||||
const char file_b[] = "nyanko";
|
||||
char file_b_path[PATH_MAX] = {0};
|
||||
if (
|
||||
temp_file(
|
||||
dir_template,
|
||||
dlen,
|
||||
file_b_path,
|
||||
file_b,
|
||||
sizeof(file_b),
|
||||
false
|
||||
) == -1
|
||||
) {
|
||||
goto remove_ab;
|
||||
}
|
||||
|
||||
// File A but in directory 2
|
||||
char file_a_dir2[PATH_MAX] = {0};
|
||||
if (
|
||||
temp_file(
|
||||
dir_template2,
|
||||
dlen2,
|
||||
file_a_dir2,
|
||||
file_a,
|
||||
sizeof(file_a),
|
||||
false
|
||||
) == -1
|
||||
) {
|
||||
goto remove_all;
|
||||
}
|
||||
|
||||
// Move file A to file B normally (same dir)
|
||||
if (renameat(dirfd, file_a, dirfd, file_b) == -1) {
|
||||
perror("renameat (A -> B, basic)");
|
||||
goto remove_all;
|
||||
}
|
||||
if (access(file_b_path, F_OK) == -1) {
|
||||
perror("access (file B, basic)");
|
||||
goto remove_all;
|
||||
}
|
||||
|
||||
// Move file B to A (absolute path; same dir)
|
||||
if (renameat(dirfd, file_b_path, dirfd, file_a) == -1) {
|
||||
perror("renameat (B -> A, absolute path)");
|
||||
goto remove_all;
|
||||
}
|
||||
if (access(file_a_path, F_OK) == -1) {
|
||||
perror("access (file A, absolute path)");
|
||||
goto remove_all;
|
||||
}
|
||||
|
||||
// Move A to B (both absolute)
|
||||
if (renameat(dirfd, file_a_path, dirfd, file_b_path) == -1) {
|
||||
perror("renameat (A -> B, both absolute)");
|
||||
goto remove_all;
|
||||
}
|
||||
if (access(file_b_path, F_OK) == -1) {
|
||||
perror("access (file B, both absolute)");
|
||||
goto remove_all;
|
||||
}
|
||||
|
||||
// Move B to B
|
||||
if (renameat(dirfd, file_b, dirfd, file_b) == -1) {
|
||||
perror("renameat (B -> B)");
|
||||
goto remove_all;
|
||||
}
|
||||
if (access(file_b_path, F_OK) == -1) {
|
||||
perror("access (file B)");
|
||||
goto remove_all;
|
||||
}
|
||||
|
||||
// Move file B to A (AT_FDCWD)
|
||||
char cwd[PATH_MAX] = {0};
|
||||
if (!getcwd(cwd, PATH_MAX)) {
|
||||
perror("getcwd");
|
||||
goto remove_all;
|
||||
}
|
||||
if (chdir(dir_template) == -1) {
|
||||
perror("chdir");
|
||||
goto remove_all;
|
||||
}
|
||||
|
||||
if (renameat(AT_FDCWD, file_b, dirfd, file_a) == -1) {
|
||||
perror("renameat (B -> A, AT_FDCWD)");
|
||||
goto remove_all;
|
||||
}
|
||||
if (access(file_a_path, F_OK) == -1) {
|
||||
perror("access (file A, AT_FDCWD)");
|
||||
goto remove_all;
|
||||
}
|
||||
|
||||
// Reset, though it doesn't really matter.
|
||||
if (chdir(cwd) == -1) {
|
||||
perror("chdir");
|
||||
goto remove_all;
|
||||
}
|
||||
|
||||
// Move to different directory
|
||||
if (renameat(dirfd, file_a, dirfd2, file_a) == -1) {
|
||||
perror("renameat (A -> B, different dirs)");
|
||||
goto remove_all;
|
||||
}
|
||||
if (access(file_a_dir2, F_OK) == -1) {
|
||||
perror("access (file A in dir2)");
|
||||
goto remove_all;
|
||||
}
|
||||
|
||||
// Move non-existing file
|
||||
if (renameat(dirfd, "aki", dirfd, "denji") == 0) {
|
||||
// Wut?
|
||||
fputs("renameat succeeded at moving a non-existing file\n", stderr);
|
||||
goto remove_all;
|
||||
}
|
||||
|
||||
// RENAME_NOREPLACE
|
||||
// Create file B in dir 1 first because we moved it earlier.
|
||||
int fd = open(file_b_path, O_CREAT);
|
||||
if (fd == -1) {
|
||||
perror("open (file B in dir 1)");
|
||||
close(fd);
|
||||
goto remove_all;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
if (renameat2(dirfd, file_b, dirfd2, file_a, RENAME_NOREPLACE) == 0) {
|
||||
fputs("renameat2 (B -> A, noreplace) succeeded\n", stderr);
|
||||
goto remove_all;
|
||||
}
|
||||
if (access(file_a_dir2, F_OK) == -1) {
|
||||
fputs("RENAME_NOREPLACE ate file A in dir 2\n", stderr);
|
||||
goto remove_all;
|
||||
}
|
||||
if (access(file_b_path, F_OK) == -1) {
|
||||
fputs("RENAME_NOREPLACE ate file B in dir 1\n", stderr);
|
||||
goto remove_all;
|
||||
}
|
||||
|
||||
// TODO: RENAME_EXCHANGE (Needs frename support in Redox)
|
||||
// Notes for later:
|
||||
// * Write a message to both files
|
||||
// * Swap
|
||||
// * Check if the files swapped correctly by strcmp messages
|
||||
|
||||
result = EXIT_SUCCESS;
|
||||
remove_all:
|
||||
remove(file_a_dir2);
|
||||
remove_ab:
|
||||
remove(file_a_path);
|
||||
remove(file_b_path);
|
||||
rmdir(dir_template);
|
||||
rmdir(dir_template2);
|
||||
close_dir2:
|
||||
close(dirfd2);
|
||||
close_dir1:
|
||||
close(dirfd);
|
||||
bye:
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user