|
44 | 44 | #include <cleanup.h> |
45 | 45 | #include <protocol.h> |
46 | 46 | #include <sequence.h> |
| 47 | +#include <files_lib.h> |
47 | 48 |
|
48 | 49 | #define ARG_UNUSED __attribute__((unused)) |
49 | 50 |
|
@@ -100,6 +101,8 @@ static const Description COMMANDS[] = |
100 | 101 | "\t\t\t(%d can be used in both the remote and output file paths when '-j' is used)"}, |
101 | 102 | {"opendir", "List files and folders in a directory", |
102 | 103 | "cf-net opendir masterfiles"}, |
| 104 | + {"getdir", "Recursively downloads files and folders in a directory", |
| 105 | + "cf-net getdir masterfiles"}, |
103 | 106 | {NULL, NULL, NULL} |
104 | 107 | }; |
105 | 108 |
|
@@ -144,6 +147,7 @@ static const char *const HINTS[] = |
144 | 147 | generator_macro(STAT) \ |
145 | 148 | generator_macro(GET) \ |
146 | 149 | generator_macro(OPENDIR) \ |
| 150 | + generator_macro(GETDIR) \ |
147 | 151 | generator_macro(MULTI) \ |
148 | 152 | generator_macro(MULTITLS) \ |
149 | 153 | generator_macro(HELP) \ |
@@ -197,6 +201,7 @@ static int CFNetGet(CFNetOptions *opts, const char *hostname, char **args); |
197 | 201 | static int CFNetOpenDir(CFNetOptions *opts, const char *hostname, char **args); |
198 | 202 | static int CFNetMulti(const char *server); |
199 | 203 | static int CFNetMultiTLS(const char *server, const char *use_protocol_version); |
| 204 | +static int CFNetGetDir(CFNetOptions *opts, const char *hostname, char **args); |
200 | 205 |
|
201 | 206 |
|
202 | 207 | //******************************************************************* |
@@ -411,6 +416,8 @@ static int CFNetCommandSwitch(CFNetOptions *opts, const char *hostname, |
411 | 416 | return CFNetGet(opts, hostname, args); |
412 | 417 | case CFNET_CMD_OPENDIR: |
413 | 418 | return CFNetOpenDir(opts, hostname, args); |
| 419 | + case CFNET_CMD_GETDIR: |
| 420 | + return CFNetGetDir(opts, hostname, args); |
414 | 421 | case CFNET_CMD_MULTI: |
415 | 422 | return CFNetMulti(hostname); |
416 | 423 | case CFNET_CMD_MULTITLS: |
@@ -976,6 +983,226 @@ static int CFNetOpenDir(ARG_UNUSED CFNetOptions *opts, const char *hostname, cha |
976 | 983 | return 0; |
977 | 984 | } |
978 | 985 |
|
| 986 | +// Helper: Get a single file with permissions |
| 987 | +static bool CFNetGetWithPerms(AgentConnection *conn, const char *remote_path, |
| 988 | + const char *local_path, bool print_stats) |
| 989 | +{ |
| 990 | + struct stat perms; |
| 991 | + if (!ProtocolStat(conn, remote_path, &perms)) { |
| 992 | + Log(LOG_LEVEL_ERR, "Failed to stat remote file: %s:%s", |
| 993 | + conn->this_server, remote_path); |
| 994 | + return false; |
| 995 | + } |
| 996 | + |
| 997 | + if (!ProtocolGet(conn, remote_path, local_path, perms.st_size, perms.st_mode, print_stats)) { |
| 998 | + Log(LOG_LEVEL_ERR, "Failed to get remote file: %s:%s", |
| 999 | + conn->this_server, remote_path); |
| 1000 | + return false; |
| 1001 | + } |
| 1002 | + |
| 1003 | + return true; |
| 1004 | +} |
| 1005 | + |
| 1006 | +// Helper: Create local directory path |
| 1007 | +static bool create_local_dir(const char *local_base, const char *subdir, |
| 1008 | + bool has_output_path) |
| 1009 | +{ |
| 1010 | + char path[PATH_MAX]; |
| 1011 | + int written; |
| 1012 | + |
| 1013 | + if (has_output_path) { |
| 1014 | + written = snprintf(path, sizeof(path), "%s/%s/", local_base, subdir); |
| 1015 | + } else { |
| 1016 | + char cwd[PATH_MAX]; |
| 1017 | + if (!getcwd(cwd, sizeof(cwd))) { |
| 1018 | + Log(LOG_LEVEL_ERR, "Failed to get current working directory"); |
| 1019 | + return false; |
| 1020 | + } |
| 1021 | + written = snprintf(path, sizeof(path), "%s/%s/%s/", cwd, local_base, subdir); |
| 1022 | + } |
| 1023 | + |
| 1024 | + if (written < 0 || written >= sizeof(path)) { |
| 1025 | + Log(LOG_LEVEL_ERR, "Path too long for directory: %s", subdir); |
| 1026 | + return false; |
| 1027 | + } |
| 1028 | + |
| 1029 | + MakeParentDirectory(path, false, NULL); |
| 1030 | + return true; |
| 1031 | +} |
| 1032 | + |
| 1033 | +// Helper: Recursively process directory entries |
| 1034 | +static void process_dir_recursive(AgentConnection *conn, |
| 1035 | + const char *remote_path, |
| 1036 | + const char *local_path, |
| 1037 | + bool has_output_path, |
| 1038 | + bool print_stats) |
| 1039 | +{ |
| 1040 | + int written; |
| 1041 | + Seq *items = ProtocolOpenDir(conn, remote_path); |
| 1042 | + if (!items) { |
| 1043 | + Log(LOG_LEVEL_ERR, "Failed to open directory: %s", remote_path); |
| 1044 | + return; |
| 1045 | + } |
| 1046 | + |
| 1047 | + for (size_t i = 0; i < SeqLength(items); i++) |
| 1048 | + { |
| 1049 | + char *item = SeqAt(items, i); |
| 1050 | + |
| 1051 | + if (strcmp(".", item) == 0 || strcmp("..", item) == 0) { |
| 1052 | + continue; |
| 1053 | + } |
| 1054 | + |
| 1055 | + char remote_full[PATH_MAX]; |
| 1056 | + written = snprintf(remote_full, sizeof(remote_full), "%s/%s", remote_path, item); |
| 1057 | + if (written < 0 || written >= sizeof(remote_full)) { |
| 1058 | + Log(LOG_LEVEL_ERR, "Path too long for full remote path: %s",remote_full); |
| 1059 | + continue; |
| 1060 | + } |
| 1061 | + |
| 1062 | + char local_full[PATH_MAX]; |
| 1063 | + written = snprintf(local_full, sizeof(local_full), "%s/%s", local_path, item); |
| 1064 | + if (written < 0 || written >= sizeof(local_full)) { |
| 1065 | + Log(LOG_LEVEL_ERR, "Path too long for full local path: %s",local_full); |
| 1066 | + continue; |
| 1067 | + } |
| 1068 | + |
| 1069 | + struct stat sb; |
| 1070 | + if (!ProtocolStat(conn, remote_full, &sb)) { |
| 1071 | + Log(LOG_LEVEL_ERR, "Could not stat: %s", remote_full); |
| 1072 | + continue; |
| 1073 | + } |
| 1074 | + |
| 1075 | + if (S_ISDIR(sb.st_mode)) { // Is directory |
| 1076 | + if (!create_local_dir(local_path, item, has_output_path)) { |
| 1077 | + continue; |
| 1078 | + } |
| 1079 | + |
| 1080 | + process_dir_recursive(conn, remote_full, local_full, |
| 1081 | + has_output_path, print_stats); |
| 1082 | + } else { |
| 1083 | + CFNetGetWithPerms(conn, remote_full, local_full, print_stats); |
| 1084 | + } |
| 1085 | + } |
| 1086 | + |
| 1087 | + SeqDestroy(items); |
| 1088 | +} |
| 1089 | + |
| 1090 | +static int CFNetGetDir( CFNetOptions *opts, const char *hostname, char **args) |
| 1091 | +{ |
| 1092 | + assert(opts); |
| 1093 | + assert(hostname); |
| 1094 | + assert(args); |
| 1095 | + char *local_dir = NULL; |
| 1096 | + |
| 1097 | + int argc = 0; |
| 1098 | + while (args[argc] != NULL) |
| 1099 | + { |
| 1100 | + ++argc; |
| 1101 | + } |
| 1102 | + |
| 1103 | + static struct option longopts[] = { |
| 1104 | + { "output", required_argument, NULL, 'o' }, |
| 1105 | + { NULL, 0, NULL, 0 } |
| 1106 | + }; |
| 1107 | + if (argc <= 1) |
| 1108 | + { |
| 1109 | + return invalid_command("getdir"); |
| 1110 | + } |
| 1111 | + extern int optind; |
| 1112 | + optind = 0; |
| 1113 | + extern char *optarg; |
| 1114 | + int c = 0; |
| 1115 | + const char *optstr = "o:"; |
| 1116 | + bool specified_path = false; |
| 1117 | + while ((c = getopt_long(argc, args, optstr, longopts, NULL)) |
| 1118 | + != -1) |
| 1119 | + { |
| 1120 | + switch (c) |
| 1121 | + { |
| 1122 | + case 'o': |
| 1123 | + { |
| 1124 | + if (local_dir != NULL) |
| 1125 | + { |
| 1126 | + Log(LOG_LEVEL_INFO, |
| 1127 | + "Warning: multiple occurrences of -o in command, "\ |
| 1128 | + "only last one will be used."); |
| 1129 | + free(local_dir); |
| 1130 | + } |
| 1131 | + local_dir = xstrdup(optarg); |
| 1132 | + specified_path = true; |
| 1133 | + break; |
| 1134 | + } |
| 1135 | + case ':': |
| 1136 | + case '?': |
| 1137 | + { |
| 1138 | + return invalid_command("getdir"); |
| 1139 | + } |
| 1140 | + default: |
| 1141 | + { |
| 1142 | + printf("Default optarg = '%s', c = '%c' = %i\n", |
| 1143 | + optarg, c, (int)c); |
| 1144 | + break; |
| 1145 | + } |
| 1146 | + } |
| 1147 | + } |
| 1148 | + args = &(args[optind]); |
| 1149 | + argc -= optind; |
| 1150 | + char *remote_dir = args[0]; |
| 1151 | + if (specified_path) |
| 1152 | + { |
| 1153 | + size_t len = strlen(local_dir) + strlen(basename(remote_dir)) + 2; // / and '\0' |
| 1154 | + char *temp = malloc(len); |
| 1155 | + if (temp == NULL) |
| 1156 | + { |
| 1157 | + free(local_dir); |
| 1158 | + return -1; |
| 1159 | + } |
| 1160 | + int written = snprintf(temp, len, "%s/%s", local_dir, basename(remote_dir)); |
| 1161 | + if (written < 0 || written >= len) { |
| 1162 | + Log(LOG_LEVEL_ERR, "Path too long for local path: %s", temp); |
| 1163 | + free(local_dir); |
| 1164 | + return -1; |
| 1165 | + } |
| 1166 | + free(local_dir); |
| 1167 | + local_dir = temp; |
| 1168 | + } |
| 1169 | + |
| 1170 | + if (local_dir == NULL) |
| 1171 | + { |
| 1172 | + char *base = basename(remote_dir); |
| 1173 | + local_dir = xstrdup(base); |
| 1174 | + } |
| 1175 | + |
| 1176 | + AgentConnection *conn = CFNetOpenConnection(hostname, opts->use_protocol_version); |
| 1177 | + if (conn == NULL) |
| 1178 | + { |
| 1179 | + free(local_dir); |
| 1180 | + return -1; |
| 1181 | + } |
| 1182 | + struct stat sb; |
| 1183 | + bool ret = ProtocolStat(conn, remote_dir, &sb); |
| 1184 | + if (!ret) |
| 1185 | + { |
| 1186 | + printf("Could not stat: '%s'\n", remote_dir); |
| 1187 | + free(local_dir); |
| 1188 | + CFNetDisconnect(conn); |
| 1189 | + return -1; |
| 1190 | + } |
| 1191 | + if (!S_ISDIR(sb.st_mode)) |
| 1192 | + { |
| 1193 | + printf("'%s' is not a directory, use 'get' for single file download\n", remote_dir); |
| 1194 | + free(local_dir); |
| 1195 | + CFNetDisconnect(conn); |
| 1196 | + return -1; |
| 1197 | + } |
| 1198 | + |
| 1199 | + process_dir_recursive(conn, remote_dir, local_dir, specified_path, opts->print_stats); |
| 1200 | + |
| 1201 | + free(local_dir); |
| 1202 | + CFNetDisconnect(conn); |
| 1203 | + return 0; |
| 1204 | +} |
| 1205 | + |
979 | 1206 | static int CFNetMulti(const char *server) |
980 | 1207 | { |
981 | 1208 | time_t start; |
|
0 commit comments