-
Notifications
You must be signed in to change notification settings - Fork 197
CFE-2986: Added getdir command to cf-net, copies directory and files #6049
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,6 +44,7 @@ | |
| #include <cleanup.h> | ||
| #include <protocol.h> | ||
| #include <sequence.h> | ||
| #include <files_lib.h> | ||
|
|
||
| #define ARG_UNUSED __attribute__((unused)) | ||
|
|
||
|
|
@@ -100,6 +101,8 @@ | |
| "\t\t\t(%d can be used in both the remote and output file paths when '-j' is used)"}, | ||
| {"opendir", "List files and folders in a directory", | ||
| "cf-net opendir masterfiles"}, | ||
| {"getdir", "Recursively downloads files and folders in a directory", | ||
| "cf-net getdir masterfiles"}, | ||
| {NULL, NULL, NULL} | ||
| }; | ||
|
|
||
|
|
@@ -144,6 +147,7 @@ | |
| generator_macro(STAT) \ | ||
| generator_macro(GET) \ | ||
| generator_macro(OPENDIR) \ | ||
| generator_macro(GETDIR) \ | ||
| generator_macro(MULTI) \ | ||
| generator_macro(MULTITLS) \ | ||
| generator_macro(HELP) \ | ||
|
|
@@ -197,6 +201,7 @@ | |
| static int CFNetOpenDir(CFNetOptions *opts, const char *hostname, char **args); | ||
| static int CFNetMulti(const char *server); | ||
| static int CFNetMultiTLS(const char *server, const char *use_protocol_version); | ||
| static int CFNetGetDir(CFNetOptions *opts, const char *hostname, char **args); | ||
|
|
||
|
|
||
| //******************************************************************* | ||
|
|
@@ -411,6 +416,8 @@ | |
| return CFNetGet(opts, hostname, args); | ||
| case CFNET_CMD_OPENDIR: | ||
| return CFNetOpenDir(opts, hostname, args); | ||
| case CFNET_CMD_GETDIR: | ||
| return CFNetGetDir(opts, hostname, args); | ||
| case CFNET_CMD_MULTI: | ||
| return CFNetMulti(hostname); | ||
| case CFNET_CMD_MULTITLS: | ||
|
|
@@ -875,7 +882,7 @@ | |
| { | ||
| snprintf(threads[i]->data->local_file, PATH_MAX, local_file, i); | ||
| } | ||
| else | ||
Check failureCode scanning / CodeQL Non-constant format string Critical
The format string argument to
snprintf Error loading related location Loading The format string argument to snprintf Error loading related location Loading The format string argument to snprintf Error loading related location Loading The format string argument to snprintf Error loading related location Loading |
||
| { | ||
| snprintf(threads[i]->data->local_file, PATH_MAX, "%s.%d", local_file, i); | ||
| } | ||
|
|
@@ -976,6 +983,228 @@ | |
| return 0; | ||
| } | ||
|
|
||
| // Helper: Get a single file with permissions | ||
| static bool CFNetGetWithPerms(AgentConnection *conn, const char *remote_path, | ||
| const char *local_path, bool print_stats) | ||
| { | ||
| assert(conn != NULL && remote_path != NULL && local_path != NULL); | ||
|
|
||
| struct stat perms; | ||
| if (!ProtocolStat(conn, remote_path, &perms)) { | ||
| Log(LOG_LEVEL_ERR, "Failed to stat remote file: %s:%s", | ||
| conn->this_server, remote_path); | ||
| return false; | ||
| } | ||
|
|
||
| if (!ProtocolGet(conn, remote_path, local_path, perms.st_size, perms.st_mode, print_stats)) { | ||
| Log(LOG_LEVEL_ERR, "Failed to get remote file: %s:%s", | ||
| conn->this_server, remote_path); | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| // Helper: Create local directory path | ||
| static bool create_local_dir(const char *local_base, const char *subdir, | ||
Check noticeCode scanning / CodeQL Pointer argument is dereferenced without checking for NULL Note
Parameter conn in CFNetGetWithPerms() is dereferenced without an explicit null-check
|
||
| bool has_output_path) | ||
| { | ||
| char path[PATH_MAX]; | ||
| int written; | ||
|
|
||
| if (has_output_path) { | ||
| written = snprintf(path, sizeof(path), "%s/%s/", local_base, subdir); | ||
| } else { | ||
| char cwd[PATH_MAX]; | ||
| if (!getcwd(cwd, sizeof(cwd))) { | ||
| Log(LOG_LEVEL_ERR, "Failed to get current working directory"); | ||
| return false; | ||
| } | ||
| written = snprintf(path, sizeof(path), "%s/%s/%s/", cwd, local_base, subdir); | ||
| } | ||
|
|
||
| if (written < 0 || written >= sizeof(path)) { | ||
| Log(LOG_LEVEL_ERR, "Path too long for directory: %s", subdir); | ||
| return false; | ||
| } | ||
|
|
||
| MakeParentDirectory(path, false, NULL); | ||
| return true; | ||
| } | ||
|
|
||
| // Helper: Recursively process directory entries | ||
| static void process_dir_recursive(AgentConnection *conn, | ||
| const char *remote_path, | ||
| const char *local_path, | ||
| bool has_output_path, | ||
| bool print_stats) | ||
| { | ||
| int written; | ||
| Seq *items = ProtocolOpenDir(conn, remote_path); | ||
| if (!items) { | ||
| Log(LOG_LEVEL_ERR, "Failed to open directory: %s", remote_path); | ||
| return; | ||
| } | ||
|
|
||
| for (size_t i = 0; i < SeqLength(items); i++) | ||
| { | ||
| char *item = SeqAt(items, i); | ||
|
|
||
| if (strcmp(".", item) == 0 || strcmp("..", item) == 0) { | ||
| continue; | ||
| } | ||
|
|
||
| char remote_full[PATH_MAX]; | ||
| written = snprintf(remote_full, sizeof(remote_full), "%s/%s", remote_path, item); | ||
| if (written < 0 || written >= sizeof(remote_full)) { | ||
| Log(LOG_LEVEL_ERR, "Path too long for full remote path: %s",remote_full); | ||
| continue; | ||
| } | ||
|
|
||
| char local_full[PATH_MAX]; | ||
| written = snprintf(local_full, sizeof(local_full), "%s/%s", local_path, item); | ||
| if (written < 0 || written >= sizeof(local_full)) { | ||
| Log(LOG_LEVEL_ERR, "Path too long for full local path: %s",local_full); | ||
| continue; | ||
| } | ||
|
|
||
| struct stat sb; | ||
| if (!ProtocolStat(conn, remote_full, &sb)) { | ||
| Log(LOG_LEVEL_ERR, "Could not stat: %s", remote_full); | ||
| continue; | ||
| } | ||
|
|
||
| if (S_ISDIR(sb.st_mode)) { // Is directory | ||
| if (!create_local_dir(local_path, item, has_output_path)) { | ||
| continue; | ||
| } | ||
|
|
||
| process_dir_recursive(conn, remote_full, local_full, | ||
| has_output_path, print_stats); | ||
| } else { | ||
| CFNetGetWithPerms(conn, remote_full, local_full, print_stats); | ||
| } | ||
| } | ||
|
|
||
| SeqDestroy(items); | ||
| } | ||
|
|
||
| static int CFNetGetDir( CFNetOptions *opts, const char *hostname, char **args) | ||
Check warningCode scanning / CodeQL Poorly documented large function Warning
Poorly documented function: fewer than 2% comments for a function of 115 lines.
|
||
| { | ||
| assert(opts != NULL); | ||
| assert(hostname != NULL); | ||
| assert(args != NULL); | ||
| char *local_dir = NULL; | ||
|
|
||
| int argc = 0; | ||
| while (args[argc] != NULL) | ||
|
||
| { | ||
| ++argc; | ||
| } | ||
|
|
||
| static struct option longopts[] = { | ||
| { "output", required_argument, NULL, 'o' }, | ||
| { NULL, 0, NULL, 0 } | ||
| }; | ||
| if (argc <= 1) | ||
| { | ||
| return invalid_command("getdir"); | ||
| } | ||
| extern int optind; | ||
| optind = 0; | ||
| extern char *optarg; | ||
| int c = 0; | ||
| const char *optstr = "o:"; | ||
| bool specified_path = false; | ||
| while ((c = getopt_long(argc, args, optstr, longopts, NULL)) | ||
| != -1) | ||
| { | ||
| switch (c) | ||
| { | ||
| case 'o': | ||
| { | ||
| if (local_dir != NULL) | ||
| { | ||
| Log(LOG_LEVEL_INFO, | ||
| "Warning: multiple occurrences of -o in command, "\ | ||
| "only last one will be used."); | ||
| free(local_dir); | ||
| } | ||
| local_dir = xstrdup(optarg); | ||
| specified_path = true; | ||
| break; | ||
| } | ||
| case ':': | ||
| case '?': | ||
| { | ||
| return invalid_command("getdir"); | ||
| } | ||
| default: | ||
| { | ||
| printf("Default optarg = '%s', c = '%c' = %i\n", | ||
| optarg, c, (int)c); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| args = &(args[optind]); | ||
| argc -= optind; | ||
| char *remote_dir = args[0]; | ||
| if (specified_path) | ||
| { | ||
| size_t len = strlen(local_dir) + strlen(basename(remote_dir)) + 2; // / and '\0' | ||
| char *temp = malloc(len); | ||
| if (temp == NULL) | ||
| { | ||
|
Comment on lines
+1153
to
+1158
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some inconsistent indentation here |
||
| free(local_dir); | ||
| return -1; | ||
| } | ||
| int written = snprintf(temp, len, "%s/%s", local_dir, basename(remote_dir)); | ||
| if (written < 0 || written >= len) { | ||
| Log(LOG_LEVEL_ERR, "Path too long for local path: %s", temp); | ||
| free(local_dir); | ||
| return -1; | ||
| } | ||
| free(local_dir); | ||
| local_dir = temp; | ||
| } | ||
|
|
||
| if (local_dir == NULL) | ||
| { | ||
| char *base = basename(remote_dir); | ||
| local_dir = xstrdup(base); | ||
| } | ||
|
|
||
| AgentConnection *conn = CFNetOpenConnection(hostname, opts->use_protocol_version); | ||
| if (conn == NULL) | ||
| { | ||
| free(local_dir); | ||
| return -1; | ||
| } | ||
| struct stat sb; | ||
| bool ret = ProtocolStat(conn, remote_dir, &sb); | ||
| if (!ret) | ||
|
||
| { | ||
| printf("Could not stat: '%s'\n", remote_dir); | ||
| free(local_dir); | ||
| CFNetDisconnect(conn); | ||
| return -1; | ||
| } | ||
| if (!S_ISDIR(sb.st_mode)) | ||
| { | ||
| printf("'%s' is not a directory, use 'get' for single file download\n", remote_dir); | ||
| free(local_dir); | ||
| CFNetDisconnect(conn); | ||
| return -1; | ||
| } | ||
|
|
||
| process_dir_recursive(conn, remote_dir, local_dir, specified_path, opts->print_stats); | ||
|
|
||
| free(local_dir); | ||
| CFNetDisconnect(conn); | ||
| return 0; | ||
| } | ||
|
|
||
| static int CFNetMulti(const char *server) | ||
| { | ||
|
||
| time_t start; | ||
|
|
||
Check notice
Code scanning / CodeQL
Pointer argument is dereferenced without checking for NULL Note