Rmtree command (#440)
* Added --rmtree command for recursive directory deletion. Generalized the read_dir function to invoke the callback both before and after processing the contents of a directory. Tidied up indentation in copy_file_callback.
* Added missing call to check_error.
diff --git a/src/ios-deploy/ios-deploy.m b/src/ios-deploy/ios-deploy.m
index 4c0e96b..4b5f2f5 100644
--- a/src/ios-deploy/ios-deploy.m
+++ b/src/ios-deploy/ios-deploy.m
@@ -1246,9 +1246,10 @@
return bundle_id;
}
+typedef enum { READ_DIR_FILE, READ_DIR_BEFORE_DIR, READ_DIR_AFTER_DIR } read_dir_cb_reason;
void read_dir(AFCConnectionRef afc_conn_p, const char* dir,
- void(*callback)(AFCConnectionRef conn, const char *dir, int file))
+ void(*callback)(AFCConnectionRef conn, const char *dir, read_dir_cb_reason reason))
{
char *dir_ent;
@@ -1271,13 +1272,7 @@
AFCKeyValueClose(afc_dict_p);
if (not_dir) {
- NSLogOut(@"%@", [NSString stringWithUTF8String:dir]);
- } else {
- NSLogOut(@"%@/", [NSString stringWithUTF8String:dir]);
- }
-
- if (not_dir) {
- if (callback) (*callback)(afc_conn_p, dir, not_dir);
+ if (callback) (*callback)(afc_conn_p, dir, READ_DIR_FILE);
return;
}
@@ -1287,9 +1282,13 @@
if (err != 0) {
// Couldn't open dir - was probably a file
return;
- } else {
- if (callback) (*callback)(afc_conn_p, dir, not_dir);
}
+
+ // Call the callback on the directory before processing its
+ // contents. This is used by copy file callback, which needs to
+ // create the directory on the host before attempting to copy
+ // files into it.
+ if (callback) (*callback)(afc_conn_p, dir, READ_DIR_BEFORE_DIR);
while(true) {
err = AFCDirectoryRead(afc_conn_p, afc_dir_p, &dir_ent);
@@ -1310,6 +1309,11 @@
}
AFCDirectoryClose(afc_conn_p, afc_dir_p);
+
+ // Call the callback on the directory after processing its
+ // contents. This is used by the rmtree callback because it needs
+ // to delete the directory's contents before the directory itself
+ if (callback) (*callback)(afc_conn_p, dir, READ_DIR_AFTER_DIR);
}
@@ -1388,11 +1392,21 @@
fclose(fd);
return content;
}
+
+void list_files_callback(AFCConnectionRef conn, const char *name, read_dir_cb_reason reason)
+{
+ if (reason == READ_DIR_FILE) {
+ NSLogOut(@"%@", [NSString stringWithUTF8String:name]);
+ } else if (reason == READ_DIR_BEFORE_DIR) {
+ NSLogOut(@"%@/", [NSString stringWithUTF8String:name]);
+ }
+}
+
void list_files(AMDeviceRef device)
{
AFCConnectionRef afc_conn_p = start_house_arrest_service(device);
assert(afc_conn_p);
- read_dir(afc_conn_p, list_root?list_root:"/", NULL);
+ read_dir(afc_conn_p, list_root?list_root:"/", list_files_callback);
check_error(AFCConnectionClose(afc_conn_p));
}
@@ -1467,7 +1481,7 @@
check_error(AMDeviceDisconnect(device));
}
-void copy_file_callback(AFCConnectionRef afc_conn_p, const char *name,int file)
+void copy_file_callback(AFCConnectionRef afc_conn_p, const char *name, read_dir_cb_reason reason)
{
const char *local_name=name;
@@ -1475,39 +1489,44 @@
if (*local_name=='\0') return;
- if (file) {
- afc_file_ref fref;
- int err = AFCFileRefOpen(afc_conn_p,name,1,&fref);
+ if (reason == READ_DIR_FILE) {
+ NSLogOut(@"%@", [NSString stringWithUTF8String:name]);
+ afc_file_ref fref;
+ int err = AFCFileRefOpen(afc_conn_p,name,1,&fref);
- if (err) {
- fprintf(stderr,"AFCFileRefOpen(\"%s\") failed: %d\n",name,err);
- return;
- }
+ if (err) {
+ fprintf(stderr,"AFCFileRefOpen(\"%s\") failed: %d\n",name,err);
+ return;
+ }
- FILE *fp = fopen(local_name,"w");
+ FILE *fp = fopen(local_name,"w");
- if (fp==NULL) {
- fprintf(stderr,"fopen(\"%s\",\"w\") failer: %s\n",local_name,strerror(errno));
+ if (fp==NULL) {
+ fprintf(stderr,"fopen(\"%s\",\"w\") failer: %s\n",local_name,strerror(errno));
+ AFCFileRefClose(afc_conn_p,fref);
+ return;
+ }
+
+ char buf[4096];
+ size_t sz=sizeof(buf);
+
+ while (AFCFileRefRead(afc_conn_p,fref,buf,&sz)==0 && sz) {
+ fwrite(buf,sz,1,fp);
+ sz = sizeof(buf);
+ }
+
AFCFileRefClose(afc_conn_p,fref);
- return;
- }
+ fclose(fp);
- char buf[4096];
- size_t sz=sizeof(buf);
-
- while (AFCFileRefRead(afc_conn_p,fref,buf,&sz)==0 && sz) {
- fwrite(buf,sz,1,fp);
- sz = sizeof(buf);
- }
-
- AFCFileRefClose(afc_conn_p,fref);
- fclose(fp);
- } else {
- if (mkdir(local_name,0777) && errno!=EEXIST)
- fprintf(stderr,"mkdir(\"%s\") failed: %s\n",local_name,strerror(errno));
+ } else if (reason == READ_DIR_BEFORE_DIR) {
+ NSLogOut(@"%@/", [NSString stringWithUTF8String:name]);
+ if (mkdir(local_name,0777) && errno!=EEXIST) {
+ fprintf(stderr,"mkdir(\"%s\") failed: %s\n",local_name,strerror(errno));
+ }
}
}
+
void download_tree(AMDeviceRef device)
{
AFCConnectionRef afc_conn_p = start_house_arrest_service(device);
@@ -1547,7 +1566,6 @@
{
AFCConnectionRef afc_conn_p = start_house_arrest_service(device);
assert(afc_conn_p);
- // read_dir(houseFd, NULL, "/", NULL);
if (!target_filename)
{
@@ -1578,8 +1596,6 @@
afc_file_ref file_ref;
- // read_dir(houseFd, NULL, "/", NULL);
-
size_t file_size;
void* file_content = read_file_to_memory([sourcePath fileSystemRepresentation], &file_size);
@@ -1643,6 +1659,23 @@
check_error(AFCConnectionClose(afc_conn_p));
}
+// Handles the READ_DIR_AFTER_DIR callback so that we delete the contents of the
+// directory before the directory itself
+void rmtree_callback(AFCConnectionRef conn, const char *name, read_dir_cb_reason reason)
+{
+ if (reason == READ_DIR_FILE || reason == READ_DIR_AFTER_DIR) {
+ NSLogVerbose(@"Deleting %s", name);
+ check_error(AFCRemovePath(conn, name));
+ }
+}
+
+void rmtree(AMDeviceRef device) {
+ AFCConnectionRef afc_conn_p = start_house_arrest_service(device);
+ assert(afc_conn_p);
+ read_dir(afc_conn_p, target_filename, rmtree_callback);
+ check_error(AFCConnectionClose(afc_conn_p));
+}
+
void uninstall_app(AMDeviceRef device) {
CFRetain(device); // don't know if this is necessary?
@@ -1722,6 +1755,8 @@
make_directory(device);
} else if (strcmp("rm", command) == 0) {
remove_path(device);
+ } else if (strcmp("rmtree", command) == 0) {
+ rmtree(device);
} else if (strcmp("exists", command) == 0) {
exit(app_exists(device));
} else if (strcmp("uninstall_only", command) == 0) {
@@ -1968,6 +2003,7 @@
@" -2, --to <target pathname> use together with up/download file/tree. specify target\n"
@" -D, --mkdir <dir> make directory on device\n"
@" -R, --rm <path> remove file or directory on device (directories must be empty)\n"
+ @" -X, --rmtree <path> remove directory and all contained files recursively on device\n"
@" -V, --version print the executable version \n"
@" -e, --exists check if the app with given bundle_id is installed or not \n"
@" -B, --list_bundle_id list bundle_id \n"
@@ -2020,6 +2056,7 @@
{ "to", required_argument, NULL, '2'},
{ "mkdir", required_argument, NULL, 'D'},
{ "rm", required_argument, NULL, 'R'},
+ { "rmtree",required_argument, NULL, 'X'},
{ "exists", no_argument, NULL, 'e'},
{ "list_bundle_id", no_argument, NULL, 'B'},
{ "no-wifi", no_argument, NULL, 'W'},
@@ -2033,7 +2070,7 @@
};
int ch;
- while ((ch = getopt_long(argc, argv, "VmcdvunrILeD:R:i:b:a:t:p:1:2:o:l:w:9BWjNs:OE:CA:", longopts, NULL)) != -1)
+ while ((ch = getopt_long(argc, argv, "VmcdvunrILeD:R:X:i:b:a:t:p:1:2:o:l:w:9BWjNs:OE:CA:", longopts, NULL)) != -1)
{
switch (ch) {
case 'm':
@@ -2128,6 +2165,11 @@
target_filename = optarg;
command = "rm";
break;
+ case 'X':
+ command_only = true;
+ target_filename = optarg;
+ command = "rmtree";
+ break;
case 'e':
command_only = true;
command = "exists";