Skip to content

Commit f76cdff

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 f76cdff

1 file changed

Lines changed: 315 additions & 0 deletions

File tree

cf-net/cf-net.c

Lines changed: 315 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) \
@@ -163,6 +167,12 @@ static const char *command_strings[] =
163167
};
164168

165169

170+
//*******************************************************************
171+
// MACRO DECLARATIONS:
172+
//*******************************************************************
173+
#define RECURSIONLIMIT 50 // GetDir
174+
175+
166176
//*******************************************************************
167177
// FUNCTION DECLARATIONS:
168178
//*******************************************************************
@@ -197,6 +207,7 @@ static int CFNetGet(CFNetOptions *opts, const char *hostname, char **args);
197207
static int CFNetOpenDir(CFNetOptions *opts, const char *hostname, char **args);
198208
static int CFNetMulti(const char *server);
199209
static int CFNetMultiTLS(const char *server, const char *use_protocol_version);
210+
static int CFNetGetDir(CFNetOptions *opts, const char *hostname, char **args);
200211

201212

202213
//*******************************************************************
@@ -411,6 +422,8 @@ static int CFNetCommandSwitch(CFNetOptions *opts, const char *hostname,
411422
return CFNetGet(opts, hostname, args);
412423
case CFNET_CMD_OPENDIR:
413424
return CFNetOpenDir(opts, hostname, args);
425+
case CFNET_CMD_GETDIR:
426+
return CFNetGetDir(opts, hostname, args);
414427
case CFNET_CMD_MULTI:
415428
return CFNetMulti(hostname);
416429
case CFNET_CMD_MULTITLS:
@@ -591,6 +604,16 @@ static int CFNetHelpTopic(const char *topic)
591604
"\nbasename in current working directory (cwd). Override this"
592605
"\nusing the -o filename option (-o - for stdout).\n");
593606
}
607+
else if (strcmp("getdir", topic) == 0)
608+
{
609+
printf("\ncf-net getdir recursively downloads a directory from a remote host."
610+
"\nIt uses OPENDIR to list contents, STAT to check file types, and GET"
611+
"\nto download files. By default the directory is saved with its basename"
612+
"\nin the current working directory (cwd). Override the destination using"
613+
"\nthe -o path option."
614+
"\n\nUsage: cf-net getdir [-o output_path] <remote_directory>"
615+
"\n\nExample: cf-net getdir -o /tmp/backup masterfiles/\n");
616+
}
594617
else
595618
{
596619
if (found == false)
@@ -976,6 +999,298 @@ static int CFNetOpenDir(ARG_UNUSED CFNetOptions *opts, const char *hostname, cha
976999
return 0;
9771000
}
9781001

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

0 commit comments

Comments
 (0)