From d738fb7a973367f57d9480cba9b2b63717920374 Mon Sep 17 00:00:00 2001 From: Tony Hutter Date: Tue, 7 Jul 2020 12:40:19 -0700 Subject: [PATCH] Use temporary file name when transferring This patch will add an temporary AXL extension to the file while it is copying. The extension is removed after the file copy is complete. For example, if you're copying to /tmp/file1, AXL will actually copy to /tmp/file1._AXL, and then rename to /tmp/file1 at the end of the transfer. Additionally, a BB API transfer will also encode the transfer handle number into the extension like: /tmp/file1._AXL13456 That way we can later recover the transfer handle number. Fixes: #66 --- src/axl.c | 143 ++++++++++++++++++++++++++++++++++++++++-- src/axl_async_bbapi.c | 24 +++++++ src/axl_async_bbapi.h | 2 + src/axl_internal.h | 5 ++ src/axl_util.c | 60 ++++++++++++++++++ 5 files changed, 229 insertions(+), 5 deletions(-) diff --git a/src/axl.c b/src/axl.c index 63415fb..ccbef3d 100644 --- a/src/axl.c +++ b/src/axl.c @@ -44,6 +44,9 @@ typedef enum { AXL_XFER_STATE_CANCELED, /* transfer was AXL_Cancel'd */ } axl_xfer_state_t; +/* Temporary extension added onto files while they're being transferred. */ +#define AXL_EXTENSION "._AXL" + /* ========================================= Global Variables @@ -331,6 +334,23 @@ static int path_type(const char *path) { return PATH_UNKNOWN; } +/* + * Given a path name to a file ('path'), add on an extra AXL temporary extension + * in the form of: path._AXL[extra]. So if path = '/tmp/file1' and + * extra = "1234", then the new name is /tmp/file1._AXL1234. The new name is + * allocated and returned as a new string (which must be freed). + * + * 'extra' can be optionally used to store additional information about the + * transfer, such as the BB API transfer handle number, or it can be left NULL. + */ +static char * axl_add_extension(const char *path, const char *extra) +{ + char *tmp = NULL; + asprintf(&tmp, "%s%s%s", path, AXL_EXTENSION, extra ? extra : ""); + return tmp; +} + + /* * Add a file to an existing transfer handle. No directories. * @@ -341,6 +361,8 @@ static int __AXL_Add (int id, const char* src, const char* dest) { kvtree* file_list = NULL; + char *newdest = NULL; + char *extra = NULL; axl_xfer_t xtype = AXL_XFER_NULL; axl_xfer_state_t xstate = AXL_XFER_STATE_NULL; @@ -365,7 +387,32 @@ __AXL_Add (int id, const char* src, const char* dest) * STATUS * SOURCE */ kvtree* src_hash = kvtree_set_kv(file_list, AXL_KEY_FILES, src); - kvtree_util_set_str(src_hash, AXL_KEY_FILE_DEST, dest); + +#ifdef HAVE_BBAPI + /* + * Special case: For BB API we includes the transfer handle in the temporary + * file extension. That way, we can later use it to lookup the transfer + * handle for old transfers and cancel them. + */ + if (xtype == AXL_XFER_ASYNC_BBAPI) { + uint64_t thandle; + if (axl_async_get_bbapi_handle(id, &thandle) !=0 ) { + AXL_ERR("Couldn't get BB API transfer handle"); + return AXL_FAILURE; + } + + asprintf(&extra, "%lu", thandle); + } +#endif + + newdest = axl_add_extension(dest, extra); + +#ifdef HAVE_BBAPI + free(extra); +#endif + + kvtree_util_set_str(src_hash, AXL_KEY_FILE_DEST, newdest); + free(newdest); kvtree_util_set_int(src_hash, AXL_KEY_STATUS, AXL_STATUS_SOURCE); /* add file to transfer data structure, depending on its type */ @@ -610,6 +657,74 @@ int AXL_Dispatch (int id) return rc; } +/* + * Given a path with an AXL temporary extension, allocate an return a new + * string with the extension removed. Also, if extra is specified, return + * a pointer to the offset in 'path_with_extension' where the 'extra' field + * is. + * + * Returns the new allocated path string on success, or NULL on error. + */ +static char * axl_remove_extension(char *path_with_extension, char **extra) +{ + int i; + size_t size = strlen(path_with_extension); + char *ext = AXL_EXTENSION; + size_t ext_len = sizeof(AXL_EXTENSION) - 1; /* -1 for '\0' */ + + /* path should at the very least be a one char file name + extension */ + if (size < 1 + ext_len) { + return NULL; + } + + /* + * Look backwards from the end of the string for the start of the + * extension. + */ + for (i = size - ext_len; i >= 0; i--) { + if (memcmp(&path_with_extension[i], ext, strlen(AXL_EXTENSION)) == 0) { + /* Match! */ + if (extra) + *extra = &path_with_extension[i] + ext_len; + return strndup(path_with_extension, i); + } + } + return NULL; +} + +/* + * When you do an AXL transfer, it actually transfers to a temporary file + * behind the scenes. It's only after the transfer is finished that the file + * is renamed to its final name. + * + * This function renames all the temporary files to their final names. We + * assume you're calling this after all the transfers have been successfully + * transferred. + * + * TODO: Make the file renames multithreaded + */ +static void axl_rename_files_to_final_names(int id) +{ + char *dst; + kvtree_elem *elem = NULL; + char *newdst; + char *extra; + + while ((elem = axl_get_next_path(id, elem, NULL, &dst))) { + extra = NULL; + newdst = NULL; + newdst = axl_remove_extension(dst, &extra); + if (!newdst) { + AXL_ERR("Couldn't remove extension, this shouldn't happen"); + /* Nothing we can do... */ + free(newdst); + continue; + } + rename(dst, newdst); + free(newdst); + } +} + /* Test if a transfer has completed * Returns AXL_SUCCESS if the transfer has completed */ int AXL_Test (int id) @@ -618,6 +733,8 @@ int AXL_Test (int id) kvtree* file_list = NULL; axl_xfer_t xtype = AXL_XFER_NULL; axl_xfer_state_t xstate = AXL_XFER_STATE_NULL; + int rc; + if (axl_get_info(id, &file_list, &xtype, &xstate) != AXL_SUCCESS) { AXL_ERR("Could not find transfer info for UID %d", id); return AXL_FAILURE; @@ -632,7 +749,8 @@ int AXL_Test (int id) int status; kvtree_util_get_int(file_list, AXL_KEY_STATUS, &status); if (status == AXL_STATUS_DEST) { - return AXL_SUCCESS; + rc = AXL_SUCCESS; + goto end; } else if (status == AXL_STATUS_ERROR) { /* we return success since it's done, even on error, * caller must call wait to determine whether it was successful */ @@ -642,7 +760,7 @@ int AXL_Test (int id) return AXL_FAILURE; } /* else (status == AXL_STATUS_INPROG) send to XFER interfaces */ - int rc = AXL_SUCCESS; + rc = AXL_SUCCESS; switch (xtype) { case AXL_XFER_SYNC: rc = axl_sync_test(id); @@ -665,6 +783,12 @@ int AXL_Test (int id) break; } +end: + + if (rc == AXL_SUCCESS) { + axl_rename_files_to_final_names(id); + } + return rc; } @@ -676,6 +800,8 @@ int AXL_Wait (int id) kvtree* file_list = NULL; axl_xfer_t xtype = AXL_XFER_NULL; axl_xfer_state_t xstate = AXL_XFER_STATE_NULL; + int rc; + if (axl_get_info(id, &file_list, &xtype, &xstate) != AXL_SUCCESS) { AXL_ERR("Could not find transfer info for UID %d", id); return AXL_FAILURE; @@ -694,7 +820,8 @@ int AXL_Wait (int id) if (status == AXL_STATUS_DEST) { kvtree_util_set_int(file_list, AXL_KEY_STATE, (int)AXL_XFER_STATE_COMPLETED); - return AXL_SUCCESS; + rc = AXL_SUCCESS; + goto end; } else if (status == AXL_STATUS_ERROR) { return AXL_FAILURE; } else if (status == AXL_STATUS_SOURCE) { @@ -703,7 +830,7 @@ int AXL_Wait (int id) } /* else (status == AXL_STATUS_INPROG) send to XFER interfaces */ /* if not done, call vendor API to wait */ - int rc = AXL_SUCCESS; + rc = AXL_SUCCESS; switch (xtype) { case AXL_XFER_SYNC: rc = axl_sync_wait(id); @@ -725,6 +852,12 @@ int AXL_Wait (int id) rc = AXL_FAILURE; break; } + +end: + if (rc == AXL_SUCCESS) { + axl_rename_files_to_final_names(id); + } + kvtree_util_set_int(file_list, AXL_KEY_STATE, (int)AXL_XFER_STATE_COMPLETED); /* write data to file if we have one */ diff --git a/src/axl_async_bbapi.c b/src/axl_async_bbapi.c index 2bde51e..39b67fa 100644 --- a/src/axl_async_bbapi.c +++ b/src/axl_async_bbapi.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "axl_internal.h" #include "axl_async_bbapi.h" #include "axl_pthread.h" @@ -287,6 +288,29 @@ int axl_async_create_bbapi(int id) { return AXL_FAILURE; } +/* + * Return the BBTransferHandle_t (which is just a uint64_t) for a given AXL id. + * + * You can only call this function after axl_async_create_bbapi(id) has been + * called. + * + * Returns 0 on success, 1 on error. On success, thandle contains the transfer + * handle value. + */ +int axl_async_get_bbapi_handle(int id, uint64_t *thandle) +{ +#ifdef HAVE_BBAPI + kvtree* file_list = kvtree_get_kv_int(axl_file_lists, AXL_KEY_HANDLE_UID, id); + + if (kvtree_util_get_unsigned_long(file_list, AXL_BBAPI_KEY_TRANSFERHANDLE, + thandle) != KVTREE_SUCCESS) + return 1; + + return 0; +#endif + return AXL_FAILURE; +} + /* Called from AXL_Add * Adds file source/destination to BBTransferDef */ int axl_async_add_bbapi (int id, const char* source, const char* dest) { diff --git a/src/axl_async_bbapi.h b/src/axl_async_bbapi.h index 1e5988c..1876e85 100644 --- a/src/axl_async_bbapi.h +++ b/src/axl_async_bbapi.h @@ -1,5 +1,6 @@ #ifndef AXL_ASYNC_BBAPI_H #define AXL_ASYNC_BBAPI_H +#include #define AXL_BBAPI_KEY_TRANSFERHANDLE ("BB_TransferHandle") #define AXL_BBAPI_KEY_TRANSFERDEF ("BB_TransferDef") @@ -15,6 +16,7 @@ int axl_async_init_bbapi(void); int axl_async_finalize_bbapi(void); int axl_async_create_bbapi(int id); int axl_async_add_bbapi(int id, const char* source, const char* destination); +int axl_async_get_bbapi_handle(int id, uint64_t *thandle); int axl_async_start_bbapi(int id); int axl_async_test_bbapi(int id); int axl_async_wait_bbapi(int id); diff --git a/src/axl_internal.h b/src/axl_internal.h index abd7348..fc68041 100644 --- a/src/axl_internal.h +++ b/src/axl_internal.h @@ -5,6 +5,8 @@ #include "config.h" #include +#include + #include "kvtree.h" #include "kvtree_util.h" @@ -150,6 +152,9 @@ void axl_free(void* p); kvtree_elem * axl_get_next_path(int id, kvtree_elem *elem, char **src, char **dst); +/* Clone of apsrintf(). See the standard asprintf() man page for details */ +int asprintf(char** strp, const char* fmt, ...); + /* given a source file, record its current uid/gid, permissions, * and timestamps, record them in provided kvtree */ int axl_meta_encode(const char* file, kvtree* meta); diff --git a/src/axl_util.c b/src/axl_util.c index 913ca4b..b0586fb 100644 --- a/src/axl_util.c +++ b/src/axl_util.c @@ -1,5 +1,11 @@ #include #include +#include +#include +#include +#include +#include + #include #include "axl_internal.h" @@ -31,6 +37,60 @@ void axl_free(void* p) { } } +/* Clone of apsrintf(). See the standard asprintf() man page for details */ +int asprintf(char** strp, const char* fmt, ...) +{ + /* + * This code is taken from the vmalloc(3) man page and modified slightly. + */ + int n; + int size = 100; /* Guess we need no more than 100 bytes */ + char* p; + char* np; + va_list ap; + + p = malloc(size); + if (p == NULL) { + *strp = NULL; + return -ENOMEM; + } + + while (1) { + /* Try to print in the allocated space */ + + va_start(ap, fmt); + n = vsnprintf(p, size, fmt, ap); + va_end(ap); + + /* Check error code */ + + if (n < 0) { + *strp = NULL; + return -1; + } + + /* If that worked, return the string */ + if (n < size) { + *strp = p; + return n; + } + + /* Else try again with more space */ + + size = n + 1; /* Precisely what is needed */ + + np = realloc(p, size); + if (np == NULL) { + *strp = NULL; + free(p); + return -ENOMEM; + } else { + p = np; + } + } +} + + /* * This is an helper function to iterate though a file list for a given * AXL ID. Usage: