Skip to content

Commit 62eb2df

Browse files
Added getdir command to cf-net, recursively copies directory
Ticket: CFE-2986 Changelog: Title Signed-off-by: Simon Halvorsen <simon.halvorsen@northern.tech>
1 parent 7789b21 commit 62eb2df

1 file changed

Lines changed: 314 additions & 0 deletions

File tree

cf-net/cf-net.c

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include <cleanup.h>
4545
#include <protocol.h>
4646
#include <sequence.h>
47+
#include <files_lib.h>
4748

4849
#define ARG_UNUSED __attribute__((unused))
4950

@@ -100,6 +101,8 @@ static const Description COMMANDS[] =
100101
"\t\t\t(%d can be used in both the remote and output file paths when '-j' is used)"},
101102
{"opendir", "List files and folders in a directory",
102103
"cf-net opendir masterfiles"},
104+
{"getdir", "Recursively downloads files and folders in a directory",
105+
"cf-net getdir masterfiles/ -o /tmp/ "},
103106
{NULL, NULL, NULL}
104107
};
105108

@@ -144,6 +147,7 @@ static const char *const HINTS[] =
144147
generator_macro(STAT) \
145148
generator_macro(GET) \
146149
generator_macro(OPENDIR) \
150+
generator_macro(GETDIR) \
147151
generator_macro(MULTI) \
148152
generator_macro(MULTITLS) \
149153
generator_macro(HELP) \
@@ -197,6 +201,7 @@ static int CFNetGet(CFNetOptions *opts, const char *hostname, char **args);
197201
static int CFNetOpenDir(CFNetOptions *opts, const char *hostname, char **args);
198202
static int CFNetMulti(const char *server);
199203
static int CFNetMultiTLS(const char *server, const char *use_protocol_version);
204+
static int CFNetGetDir(CFNetOptions *opts, const char *hostname, char **args);
200205

201206

202207
//*******************************************************************
@@ -411,6 +416,8 @@ static int CFNetCommandSwitch(CFNetOptions *opts, const char *hostname,
411416
return CFNetGet(opts, hostname, args);
412417
case CFNET_CMD_OPENDIR:
413418
return CFNetOpenDir(opts, hostname, args);
419+
case CFNET_CMD_GETDIR:
420+
return CFNetGetDir(opts, hostname, args);
414421
case CFNET_CMD_MULTI:
415422
return CFNetMulti(hostname);
416423
case CFNET_CMD_MULTITLS:
@@ -591,6 +598,16 @@ static int CFNetHelpTopic(const char *topic)
591598
"\nbasename in current working directory (cwd). Override this"
592599
"\nusing the -o filename option (-o - for stdout).\n");
593600
}
601+
else if (strcmp("getdir", topic) == 0)
602+
{
603+
printf("\ncf-net getdir recursively downloads a directory from a remote host."
604+
"\nIt uses OPENDIR to list contents, STAT to check file types, and GET"
605+
"\nto download files. By default the directory is saved with its basename"
606+
"\nin the current working directory (cwd). Override the destination using"
607+
"\nthe -o path option."
608+
"\n\nUsage: cf-net getdir [-o output_path] <remote_directory>"
609+
"\n\nExample: cf-net getdir -o /tmp/backup masterfiles/\n");
610+
}
594611
else
595612
{
596613
if (found == false)
@@ -976,6 +993,303 @@ static int CFNetOpenDir(ARG_UNUSED CFNetOptions *opts, const char *hostname, cha
976993
return 0;
977994
}
978995

996+
// Helper: Get a single file with permissions
997+
static bool CFNetGetWithPerms(AgentConnection *conn, const char *remote_path,
998+
const char *local_path, bool print_stats)
999+
{
1000+
assert(conn != NULL);
1001+
assert(remote_path != NULL);
1002+
assert(local_path != NULL);
1003+
1004+
struct stat perms;
1005+
if (!ProtocolStat(conn, remote_path, &perms))
1006+
{
1007+
Log(LOG_LEVEL_ERR, "Failed to stat remote file: %s:%s",
1008+
conn->this_server, remote_path);
1009+
return false;
1010+
}
1011+
1012+
if (!ProtocolGet(conn, remote_path, local_path, perms.st_size, perms.st_mode, print_stats))
1013+
{
1014+
Log(LOG_LEVEL_ERR, "Failed to get remote file: %s:%s",
1015+
conn->this_server, remote_path);
1016+
return false;
1017+
}
1018+
1019+
return true;
1020+
}
1021+
1022+
/**
1023+
* @brief Creates a local directory path with specified permissions
1024+
*
1025+
* This helper function constructs and creates a directory path either relative
1026+
* to a provided base path or relative to the current working directory. It
1027+
* handles path construction, validates length constraints, and creates parent
1028+
* directories as needed.
1029+
*
1030+
* @param local_base Base directory path for the new directory
1031+
* @param subdir Subdirectory name to create
1032+
* @param has_output_path Flag indicating whether to use local_base as absolute
1033+
* path (true) or relative to current working directory (false)
1034+
* @param perms Permission mode bits to apply to created directories
1035+
*
1036+
* @return true if the directory path was successfully created, false otherwise
1037+
*
1038+
* @note If has_output_path is false, the function prepends the current working
1039+
* directory to the path construction.
1040+
*/
1041+
static bool create_local_dir(const char *local_base, const char *subdir,
1042+
bool has_output_path, mode_t perms)
1043+
{
1044+
char path[PATH_MAX];
1045+
int written;
1046+
1047+
if (has_output_path)
1048+
{
1049+
written = snprintf(path, sizeof(path), "%s/%s/", local_base, subdir);
1050+
}
1051+
else
1052+
{
1053+
char cwd[PATH_MAX];
1054+
if (!getcwd(cwd, sizeof(cwd)))
1055+
{
1056+
Log(LOG_LEVEL_ERR, "Failed to get current working directory");
1057+
return false;
1058+
}
1059+
written = snprintf(path, sizeof(path), "%s/%s/%s/", cwd, local_base, subdir);
1060+
}
1061+
1062+
if (written < 0 || (size_t) written >= sizeof(path))
1063+
{
1064+
Log(LOG_LEVEL_ERR, "Path too long for new directory: %s", subdir);
1065+
return false;
1066+
}
1067+
1068+
bool force = false;
1069+
MakeParentDirectoryPerms(path, force, NULL, perms);
1070+
return true;
1071+
}
1072+
1073+
// Helper: Recursively process directory entries
1074+
static int process_dir_recursive(AgentConnection *conn,
1075+
const char *remote_path,
1076+
const char *local_path,
1077+
bool has_output_path,
1078+
bool print_stats,
1079+
long limit)
1080+
{
1081+
int ret = 0;
1082+
if (limit <= 0)
1083+
{
1084+
Log(LOG_LEVEL_ERR, "Recursion limit reached");
1085+
return -2;
1086+
}
1087+
1088+
int written;
1089+
Seq *items = ProtocolOpenDir(conn, remote_path);
1090+
if (items == NULL)
1091+
{
1092+
return -1;
1093+
}
1094+
1095+
for (size_t i = 0; i < SeqLength(items); i++)
1096+
{
1097+
char *item = SeqAt(items, i);
1098+
1099+
if (strcmp(".", item) == 0 || strcmp("..", item) == 0)
1100+
{
1101+
continue;
1102+
}
1103+
1104+
char remote_full[PATH_MAX];
1105+
written = snprintf(remote_full, sizeof(remote_full), "%s/%s", remote_path, item);
1106+
if (written < 0 || (size_t) written >= sizeof(remote_full))
1107+
{
1108+
Log(LOG_LEVEL_ERR,
1109+
"Path too long for building full remote path: %s and %s",
1110+
remote_path, item);
1111+
SeqDestroy(items);
1112+
return -1;
1113+
}
1114+
1115+
char local_full[PATH_MAX];
1116+
written = snprintf(local_full, sizeof(local_full), "%s/%s", local_path, item);
1117+
if (written < 0 || (size_t) written >= sizeof(local_full))
1118+
{
1119+
Log(LOG_LEVEL_ERR,
1120+
"Path too long for building full local path: %s and %s",
1121+
local_path, item);
1122+
SeqDestroy(items);
1123+
return -1;
1124+
}
1125+
1126+
struct stat sb;
1127+
if (!ProtocolStat(conn, remote_full, &sb))
1128+
{
1129+
Log(LOG_LEVEL_ERR, "Could not stat: %s", remote_full);
1130+
SeqDestroy(items);
1131+
return -1;
1132+
}
1133+
1134+
if (S_ISDIR(sb.st_mode)) // Is directory
1135+
{
1136+
if (!create_local_dir(local_path, item, has_output_path, sb.st_mode))
1137+
{
1138+
// Error already logged
1139+
SeqDestroy(items);
1140+
return -1;
1141+
}
1142+
ret = process_dir_recursive(conn, remote_full, local_full, has_output_path, print_stats, (limit - 1));
1143+
}
1144+
else
1145+
{
1146+
if (!CFNetGetWithPerms(conn, remote_full, local_full, print_stats))
1147+
{
1148+
SeqDestroy(items);
1149+
return -1;
1150+
}
1151+
}
1152+
1153+
if (ret != 0)
1154+
{
1155+
SeqDestroy(items);
1156+
return ret;
1157+
}
1158+
}
1159+
1160+
SeqDestroy(items);
1161+
return ret;
1162+
}
1163+
1164+
static int CFNetGetDir(CFNetOptions *opts, const char *hostname, char **args)
1165+
{
1166+
assert(opts != NULL);
1167+
assert(hostname != NULL);
1168+
assert(args != NULL);
1169+
char *local_dir = NULL;
1170+
unsigned long limit = 50;
1171+
1172+
int argc = 0;
1173+
while (args[argc] != NULL)
1174+
{
1175+
++argc;
1176+
}
1177+
1178+
static struct option longopts[] = {
1179+
{ "output", required_argument, NULL, 'o' },
1180+
{ NULL, 0, NULL, 0 }
1181+
};
1182+
if (argc <= 1)
1183+
{
1184+
return invalid_command("getdir");
1185+
}
1186+
extern int optind;
1187+
optind = 0;
1188+
extern char *optarg;
1189+
int c = 0;
1190+
const char *optstr = "o:";
1191+
bool specified_path = false;
1192+
while ((c = getopt_long(argc, args, optstr, longopts, NULL))
1193+
!= -1)
1194+
{
1195+
switch (c)
1196+
{
1197+
case 'o':
1198+
{
1199+
if (local_dir != NULL)
1200+
{
1201+
Log(LOG_LEVEL_INFO,
1202+
"Warning: multiple occurrences of -o in command, "\
1203+
"only last one will be used.");
1204+
free(local_dir);
1205+
}
1206+
local_dir = xstrdup(optarg);
1207+
specified_path = true;
1208+
break;
1209+
}
1210+
case ':':
1211+
case '?':
1212+
{
1213+
return invalid_command("getdir");
1214+
}
1215+
default:
1216+
{
1217+
printf("Default optarg = '%s', c = '%c' = %i\n",
1218+
optarg, c, (int)c);
1219+
break;
1220+
}
1221+
}
1222+
}
1223+
1224+
args = &(args[optind]);
1225+
argc -= optind;
1226+
char *remote_dir = args[0];
1227+
char *tmp = xstrdup(remote_dir);
1228+
char *base = xstrdup(basename(tmp));
1229+
free(tmp);
1230+
1231+
if (specified_path)
1232+
{
1233+
char temp[PATH_MAX];
1234+
1235+
int written = snprintf(temp, sizeof(temp), "%s/%s", local_dir, base);
1236+
if (written < 0 || (size_t) written >= sizeof(temp))
1237+
{
1238+
Log(LOG_LEVEL_ERR, "Path too long for local path: %s/%s", local_dir, base);
1239+
free(local_dir);
1240+
return -1;
1241+
}
1242+
free(local_dir);
1243+
local_dir = xstrdup(temp);
1244+
}
1245+
1246+
if (local_dir == NULL)
1247+
{
1248+
local_dir = xstrdup(base);
1249+
}
1250+
1251+
AgentConnection *conn = CFNetOpenConnection(hostname, opts->use_protocol_version);
1252+
if (conn == NULL)
1253+
{
1254+
free(local_dir);
1255+
free(base);
1256+
return -1;
1257+
}
1258+
struct stat sb;
1259+
if (!ProtocolStat(conn, remote_dir, &sb))
1260+
{
1261+
printf("Could not stat: '%s'\n", remote_dir);
1262+
free(local_dir);
1263+
free(base);
1264+
CFNetDisconnect(conn);
1265+
return -1;
1266+
}
1267+
if (!S_ISDIR(sb.st_mode))
1268+
{
1269+
printf("'%s' is not a directory, use 'get' for single file download\n", remote_dir);
1270+
free(local_dir);
1271+
free(base);
1272+
CFNetDisconnect(conn);
1273+
return -1;
1274+
}
1275+
1276+
int ret = process_dir_recursive(conn, remote_dir, local_dir, specified_path, opts->print_stats, limit);
1277+
if (ret == -2)
1278+
{
1279+
// Log(LOG_LEVEL_INFO, "Recursion limit(%d) reached", limit);
1280+
// already logged
1281+
}
1282+
else if (ret == -1)
1283+
{
1284+
Log(LOG_LEVEL_INFO, "Failed to copy all contents of %s", remote_dir);
1285+
}
1286+
1287+
free(local_dir);
1288+
free(base);
1289+
CFNetDisconnect(conn);
1290+
return (ret == 0) ? 0 : -1;
1291+
}
1292+
9791293
static int CFNetMulti(const char *server)
9801294
{
9811295
time_t start;

0 commit comments

Comments
 (0)