The short answer to the headline question is no, as far as I can tell.
However there are adequate native functions available to implement the fs_ext__swap function. Here's how I did it:
#include <fcntl.h>
#include <stdint.h>
#include <uv.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include "../include/fs-ext.h"
#include "platform.h"
void random_string_len10(char *str){
// Creates a 10 char long random alpha-numeric string (with in the input char array). Note the input array *str must have length of 11 to allow for 10 chars and a '\0' termination.
const char charset[] = "0123456789abcdefghijklmnopqrstuvwxyz";
size_t charset_length = strlen(charset);
size_t str_length = 10;
for (size_t i = 0; i < str_length; ++i){
str[i] = charset[arc4random_uniform(charset_length -1)];
}
str[str_length] = '\0';
}
int append_file_to_path_string(char *path, char *filename, char *result)
{
// function to append a filename to directory name. Directory name must have a slash at the end.
size_t path_length = strlen(path);
size_t filename_length = strlen(filename);
size_t result_size = path_length + filename_length + 1; // +1 for null terminator
if (result_size > PATH_MAX ) {
fprintf(stderr, "Cannot append file name to to path; result size is too large!\n");
return EXIT_FAILURE;
}
strncpy(result, path, path_length);
strncpy(result + path_length, filename, filename_length);
result[result_size - 1] = '\0'; // Ensure null termination
return EXIT_SUCCESS;
}
void append_slash(char *str) {
// appends a slash to an input string
size_t len = strlen(str);
if (len > 0 && str[len -1] != '/' && len < PATH_MAX-1) {
str[len] = '/';
str[len+1] = '\0';
}
}
int
swap_directories (const char *from, const char *to) {
// *****************
// Prep temporary directory with same underlying path as from
char temp_dir[PATH_MAX];
snprintf(temp_dir,sizeof(temp_dir), "%s.swap_temp", from);
// *****************
// Perform series of rename operations to swap to and from directories.
// 1. Rename fromdir to tempdir
if( renameat( AT_FDCWD, from, AT_FDCWD, temp_dir) == -1) {
printf("Renameat from - temp failed.\n");
return uv_translate_sys_error(errno);
} else {
printf("Renameat from - to worked!\n");
}
// 2. Rename todir to fromdir
if( renameat( AT_FDCWD, to, AT_FDCWD, from) == -1) {
printf("Renameat to - from failed.\n");
return uv_translate_sys_error(errno);
} else {
printf("Renameat to - from worked!\n");
}
// 3. Rename temp_dir(now original fromdir) to todir
if( renameat( AT_FDCWD, temp_dir, AT_FDCWD, to) == -1) {
printf("Renameat temp - to failed.\n");
if( renameat( AT_FDCWD, from, AT_FDCWD, to) == -1) { printf("Rollback failed.\n");}
return uv_translate_sys_error(errno);
} else {
printf("Renameat temp - to worked!\n");
}
return 0;
}
int
swap_files (const char *from, const char *to) {
// *****************
// Prep temporary files with random names. Must share path of input files to avoid cross file system error.
char temp_nameA[11];
char temp_nameB[11];
random_string_len10(temp_nameA);
random_string_len10(temp_nameB);
char *to_path = dirname(strdup(to));
size_t pathlen = strlen(to_path);
if ( pathlen + 1 < PATH_MAX){
append_slash(to_path);
} else {
return -1;
}
char temp_fileA[PATH_MAX];
char temp_fileB[PATH_MAX];
append_file_to_path_string(to_path,temp_nameA,temp_fileA);
append_file_to_path_string(to_path,temp_nameB,temp_fileB);
// *****************
// Perform series of linking and unlinking operations to swap to and from file. "TO-file" and "FROM-file" in the comments denote the original underlying file objects.
// 1. LINK (from,tempA) tempA and from can access FROM-file
if( linkat( AT_FDCWD, from, AT_FDCWD, temp_fileA,0)== -1) {
printf("link(from tempA) failed.\n");
return uv_translate_sys_error(errno);
}
else {
printf("link(from tempA) worked!\n");
}
// 2. LINK (to,tempB) tempB and to can access TO-file
if( linkat( AT_FDCWD, to, AT_FDCWD, temp_fileB,0)== -1) {
printf("link(to tempB) failed.\n");
return uv_translate_sys_error(errno);
} else {
printf("link(to tempB) worked!\n");
}
// 3. UNLINK (from) only tempA can access FROM-file
if( unlinkat( AT_FDCWD, from,0)== -1) {
printf("unlink(from) failed.\n");
return uv_translate_sys_error(errno);
} else {
printf("unlink(from) worked!\n");
}
// 4. UNLINK (to) only tempB can access TO-file
if( unlinkat( AT_FDCWD, to,0)== -1) {
printf("unlink(to) failed.\n");
return uv_translate_sys_error(errno);
} else {
printf("unlink(to) worked!\n");
}
// 5. LINK (tempA,to) tempA and to can access FROM-file
if( linkat( AT_FDCWD, temp_fileA, AT_FDCWD, to,0)== -1) {
printf("link(tempB to) failed.\n");
return uv_translate_sys_error(errno);
} else {
printf("link(to tempB) worked!\n");
}
// 6. LINK (tempB,from) tempB and from can access TO-file
if( linkat( AT_FDCWD, temp_fileB, AT_FDCWD, from,0)== -1) {
printf("link(tempB from) failed.\n");
return uv_translate_sys_error(errno);
} else {
printf("link(tempB,from) worked!\n");
}
// 7. UNLINK (tempA) only to can access FROM-file
if( unlinkat( AT_FDCWD, temp_fileA,0)== -1) {
printf("unlink(tempA) failed.\n");
return uv_translate_sys_error(errno);
} else {
printf("unlink(tempA) worked!\n");
}
// 8. UNLINK (tempB) only from can access TO-file
if( unlinkat( AT_FDCWD, temp_fileB,0)== -1) {
printf("unlink(tempB) failed.\n");
return uv_translate_sys_error(errno);
} else {
printf("unlink(tempB) worked!\n");
}
return 0;
}
int
fs_ext__swap (const char *from, const char *to) {
// use sys/stat.h to determine if from and to are files or directories
struct stat st_from, st_to;
int from_is_dir = stat(from, &st_from) == 0 && S_ISDIR(st_from.st_mode);
int to_is_dir = stat(to, &st_to) == 0 && S_ISDIR(st_to.st_mode);
// Call swap_files or swap_directories dependendent whether inputs are files or directories:
switch (from_is_dir * 2 + to_is_dir) {
case 0: // Both are files
if( swap_files(from,to)!=0){ return uv_translate_sys_error(errno);} // swap files
return 0;
case 1: // from is file to is dir: file dir-swap seems to work with case 3 code, so no return statements included.
case 2: // from is dir to is file
case 3: // Both are dirs
if (swap_directories(from,to)!=0){ return uv_translate_sys_error(errno);}
return 0;
default: // something else: ERR
return -1;
}
}
renameat2(RENAME_EXCHANGE)
of Linux.(un)linkat()
. Looks like you need 3 renames with a temporary name (rename(from, tmp); rename(to, from); rename(tmp, to)
+ error checking and recovery).test
doesn't appear its written in C, but it looks like the paths may not be passed down correctly to thatfs_ext__swap
C function. In any case, you'd likely want the temp name to be in the same directory as that ofto
to minimise the risk of it failing and it being hidden (start with a.
) and have a random name, and check for error and take recovery action if needed at every point.from
is/some/dir/file
andto
is/some/other/dir/file
,temp
should likely be/some/other/dir/.something_random
.temp_name
or./something_random
would move the entry to the current working directory of the caller which may well not be writable or not be on the same filesystem