diff --git a/cf-net/cf-net.c b/cf-net/cf-net.c index 1573847e15..098bd27a45 100644 --- a/cf-net/cf-net.c +++ b/cf-net/cf-net.c @@ -44,6 +44,7 @@ #include #include #include +#include #define ARG_UNUSED __attribute__((unused)) @@ -100,6 +101,8 @@ static const Description COMMANDS[] = "\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 @@ static const char *const HINTS[] = 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 CFNetGet(CFNetOptions *opts, const char *hostname, char **args); 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 @@ static int CFNetCommandSwitch(CFNetOptions *opts, const char *hostname, 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: @@ -976,6 +983,225 @@ static int CFNetOpenDir(ARG_UNUSED CFNetOptions *opts, const char *hostname, cha return 0; } +static int CFNetGetDir( CFNetOptions *opts, const char *hostname, char **args) +{ + assert(opts); + assert(hostname); + assert(args); + 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 } + }; + assert(opts != NULL); + 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 occurences of -o in command, "\ + "only last one will be used."); + free(local_dir); + } + local_dir = xstrdup(optarg); + specified_path = true; + break; + } + case ':': + { + return invalid_command("getdir"); + break; + } + case '?': + { + return invalid_command("getdir"); + break; + } + 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) + { + free(local_dir); + return -1; + } + snprintf(temp, len, "%s/%s", local_dir, basename(remote_dir)); + 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; + } + + Seq *seq = ProtocolOpenDir(conn, remote_dir); + if (seq == NULL) + { + free(local_dir); + CFNetDisconnect(conn); + return -1; + } + + for (size_t i = 0; i < SeqLength(seq); i++) + { + char dir_entry[PATH_MAX]; + char *curr = SeqAt(seq, i); + + if (strcmp(".", curr) == 0 || strcmp("..", curr) == 0) + { + continue; + } + + snprintf(dir_entry, sizeof(dir_entry), "%s/%s", remote_dir, curr); + + if (!ProtocolStat(conn, dir_entry, &sb)) + { + continue; + } + + if (S_ISDIR(sb.st_mode)) + { + bool force = false; + char path[PATH_MAX]; + int written; + + if (specified_path) + { + written = snprintf(path, sizeof(path), "%s/%s/", local_dir, curr); + } + else + { + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) != NULL) + { + written = snprintf(path, sizeof(path), "%s/%s/%s/", cwd, local_dir, curr); + } + else + { + Log(LOG_LEVEL_ERR, "Failed to get current working directory"); + continue; + } + } + if (written < 0 || written >= sizeof(path)) + { + Log(LOG_LEVEL_ERR, "Path too long for directory: %s", curr); + continue; + } + MakeParentDirectory(path, force, NULL); + + Seq *items = ProtocolOpenDir(conn, dir_entry); + if (items == NULL) + { + Log(LOG_LEVEL_ERR, "Failed to open subdir: %s", dir_entry); + continue; + } + + for (size_t j = 0; j < SeqLength(items); j++) + { + char *item = SeqAt(items, j); + if (strcmp(".", item) == 0 || strcmp("..", item) == 0) + { + continue; + } + + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s/%s", curr, item); + SeqAppend(seq, xstrdup(buf)); + } + SeqDestroy(items); + continue; + } + + char curr_dir[PATH_MAX]; + snprintf(curr_dir, sizeof(curr_dir), "%s/%s", local_dir, curr); + + GetFileData *filedata = calloc(1, sizeof(GetFileData)); + if (filedata == NULL) + { + continue; + } + + snprintf(filedata->local_file, PATH_MAX, "%s", curr_dir); + snprintf(filedata->remote_file, PATH_MAX, "%s", dir_entry); + filedata->hostname = hostname; + filedata->use_protocol_version = opts->use_protocol_version; + filedata->print_stats = opts->print_stats; + + filedata->ret = ProtocolStatGet(conn, filedata->remote_file, + filedata->local_file, 0644, filedata->print_stats); + if (!filedata->ret) + { + Log(LOG_LEVEL_ERR,"Failed to get remote file: %s:%s", conn->this_server, filedata->remote_file); + } + + free(filedata); + } + + SeqDestroy(seq); + free(local_dir); + CFNetDisconnect(conn); + return 0; +} + static int CFNetMulti(const char *server) { time_t start;