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);
197207static int CFNetOpenDir (CFNetOptions * opts , const char * hostname , char * * args );
198208static int CFNetMulti (const char * server );
199209static 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+
9791294static int CFNetMulti (const char * server )
9801295{
9811296 time_t start ;
0 commit comments