Skip to content

Commit 1b062de

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 1b062de

1 file changed

Lines changed: 319 additions & 0 deletions

File tree

cf-net/cf-net.c

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

0 commit comments

Comments
 (0)