Skip to content

Commit 5b55d16

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 0130e1d commit 5b55d16

File tree

1 file changed

+229
-0
lines changed

1 file changed

+229
-0
lines changed

cf-net/cf-net.c

Lines changed: 229 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"},
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:
@@ -976,6 +983,228 @@ static int CFNetOpenDir(ARG_UNUSED CFNetOptions *opts, const char *hostname, cha
976983
return 0;
977984
}
978985

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+
assert(conn != NULL && remote_path != NULL && local_path != NULL);
991+
992+
struct stat perms;
993+
if (!ProtocolStat(conn, remote_path, &perms)) {
994+
Log(LOG_LEVEL_ERR, "Failed to stat remote file: %s:%s",
995+
conn->this_server, remote_path);
996+
return false;
997+
}
998+
999+
if (!ProtocolGet(conn, remote_path, local_path, perms.st_size, perms.st_mode, print_stats)) {
1000+
Log(LOG_LEVEL_ERR, "Failed to get remote file: %s:%s",
1001+
conn->this_server, remote_path);
1002+
return false;
1003+
}
1004+
1005+
return true;
1006+
}
1007+
1008+
// Helper: Create local directory path
1009+
static bool create_local_dir(const char *local_base, const char *subdir,
1010+
bool has_output_path)
1011+
{
1012+
char path[PATH_MAX];
1013+
int written;
1014+
1015+
if (has_output_path) {
1016+
written = snprintf(path, sizeof(path), "%s/%s/", local_base, subdir);
1017+
} else {
1018+
char cwd[PATH_MAX];
1019+
if (!getcwd(cwd, sizeof(cwd))) {
1020+
Log(LOG_LEVEL_ERR, "Failed to get current working directory");
1021+
return false;
1022+
}
1023+
written = snprintf(path, sizeof(path), "%s/%s/%s/", cwd, local_base, subdir);
1024+
}
1025+
1026+
if (written < 0 || written >= sizeof(path)) {
1027+
Log(LOG_LEVEL_ERR, "Path too long for directory: %s", subdir);
1028+
return false;
1029+
}
1030+
1031+
MakeParentDirectory(path, false, NULL);
1032+
return true;
1033+
}
1034+
1035+
// Helper: Recursively process directory entries
1036+
static void process_dir_recursive(AgentConnection *conn,
1037+
const char *remote_path,
1038+
const char *local_path,
1039+
bool has_output_path,
1040+
bool print_stats)
1041+
{
1042+
int written;
1043+
Seq *items = ProtocolOpenDir(conn, remote_path);
1044+
if (!items) {
1045+
Log(LOG_LEVEL_ERR, "Failed to open directory: %s", remote_path);
1046+
return;
1047+
}
1048+
1049+
for (size_t i = 0; i < SeqLength(items); i++)
1050+
{
1051+
char *item = SeqAt(items, i);
1052+
1053+
if (strcmp(".", item) == 0 || strcmp("..", item) == 0) {
1054+
continue;
1055+
}
1056+
1057+
char remote_full[PATH_MAX];
1058+
written = snprintf(remote_full, sizeof(remote_full), "%s/%s", remote_path, item);
1059+
if (written < 0 || written >= sizeof(remote_full)) {
1060+
Log(LOG_LEVEL_ERR, "Path too long for full remote path: %s",remote_full);
1061+
continue;
1062+
}
1063+
1064+
char local_full[PATH_MAX];
1065+
written = snprintf(local_full, sizeof(local_full), "%s/%s", local_path, item);
1066+
if (written < 0 || written >= sizeof(local_full)) {
1067+
Log(LOG_LEVEL_ERR, "Path too long for full local path: %s",local_full);
1068+
continue;
1069+
}
1070+
1071+
struct stat sb;
1072+
if (!ProtocolStat(conn, remote_full, &sb)) {
1073+
Log(LOG_LEVEL_ERR, "Could not stat: %s", remote_full);
1074+
continue;
1075+
}
1076+
1077+
if (S_ISDIR(sb.st_mode)) { // Is directory
1078+
if (!create_local_dir(local_path, item, has_output_path)) {
1079+
continue;
1080+
}
1081+
1082+
process_dir_recursive(conn, remote_full, local_full,
1083+
has_output_path, print_stats);
1084+
} else {
1085+
CFNetGetWithPerms(conn, remote_full, local_full, print_stats);
1086+
}
1087+
}
1088+
1089+
SeqDestroy(items);
1090+
}
1091+
1092+
static int CFNetGetDir( CFNetOptions *opts, const char *hostname, char **args)
1093+
{
1094+
assert(opts != NULL);
1095+
assert(hostname != NULL);
1096+
assert(args != NULL);
1097+
char *local_dir = NULL;
1098+
1099+
int argc = 0;
1100+
while (args[argc] != NULL)
1101+
{
1102+
++argc;
1103+
}
1104+
1105+
static struct option longopts[] = {
1106+
{ "output", required_argument, NULL, 'o' },
1107+
{ NULL, 0, NULL, 0 }
1108+
};
1109+
if (argc <= 1)
1110+
{
1111+
return invalid_command("getdir");
1112+
}
1113+
extern int optind;
1114+
optind = 0;
1115+
extern char *optarg;
1116+
int c = 0;
1117+
const char *optstr = "o:";
1118+
bool specified_path = false;
1119+
while ((c = getopt_long(argc, args, optstr, longopts, NULL))
1120+
!= -1)
1121+
{
1122+
switch (c)
1123+
{
1124+
case 'o':
1125+
{
1126+
if (local_dir != NULL)
1127+
{
1128+
Log(LOG_LEVEL_INFO,
1129+
"Warning: multiple occurrences of -o in command, "\
1130+
"only last one will be used.");
1131+
free(local_dir);
1132+
}
1133+
local_dir = xstrdup(optarg);
1134+
specified_path = true;
1135+
break;
1136+
}
1137+
case ':':
1138+
case '?':
1139+
{
1140+
return invalid_command("getdir");
1141+
}
1142+
default:
1143+
{
1144+
printf("Default optarg = '%s', c = '%c' = %i\n",
1145+
optarg, c, (int)c);
1146+
break;
1147+
}
1148+
}
1149+
}
1150+
args = &(args[optind]);
1151+
argc -= optind;
1152+
char *remote_dir = args[0];
1153+
if (specified_path)
1154+
{
1155+
size_t len = strlen(local_dir) + strlen(basename(remote_dir)) + 2; // / and '\0'
1156+
char *temp = malloc(len);
1157+
if (temp == NULL)
1158+
{
1159+
free(local_dir);
1160+
return -1;
1161+
}
1162+
int written = snprintf(temp, len, "%s/%s", local_dir, basename(remote_dir));
1163+
if (written < 0 || written >= len) {
1164+
Log(LOG_LEVEL_ERR, "Path too long for local path: %s", temp);
1165+
free(local_dir);
1166+
return -1;
1167+
}
1168+
free(local_dir);
1169+
local_dir = temp;
1170+
}
1171+
1172+
if (local_dir == NULL)
1173+
{
1174+
char *base = basename(remote_dir);
1175+
local_dir = xstrdup(base);
1176+
}
1177+
1178+
AgentConnection *conn = CFNetOpenConnection(hostname, opts->use_protocol_version);
1179+
if (conn == NULL)
1180+
{
1181+
free(local_dir);
1182+
return -1;
1183+
}
1184+
struct stat sb;
1185+
bool ret = ProtocolStat(conn, remote_dir, &sb);
1186+
if (!ret)
1187+
{
1188+
printf("Could not stat: '%s'\n", remote_dir);
1189+
free(local_dir);
1190+
CFNetDisconnect(conn);
1191+
return -1;
1192+
}
1193+
if (!S_ISDIR(sb.st_mode))
1194+
{
1195+
printf("'%s' is not a directory, use 'get' for single file download\n", remote_dir);
1196+
free(local_dir);
1197+
CFNetDisconnect(conn);
1198+
return -1;
1199+
}
1200+
1201+
process_dir_recursive(conn, remote_dir, local_dir, specified_path, opts->print_stats);
1202+
1203+
free(local_dir);
1204+
CFNetDisconnect(conn);
1205+
return 0;
1206+
}
1207+
9791208
static int CFNetMulti(const char *server)
9801209
{
9811210
time_t start;

0 commit comments

Comments
 (0)