1 #include "libfilezilla/recursive_remove.hpp"
2 #include "libfilezilla/local_filesys.hpp"
3 
4 #ifndef FZ_WINDOWS
5 #include <unistd.h>
6 #endif
7 
8 namespace fz {
9 
remove(const native_string & path)10 bool recursive_remove::remove(const native_string& path)
11 {
12 	std::list<native_string> paths;
13 	paths.push_back(path);
14 	return remove(paths);
15 }
16 
remove(std::list<native_string> dirsToVisit)17 bool recursive_remove::remove(std::list<native_string> dirsToVisit)
18 {
19 	bool success = true;
20 
21 	// Under Windows use SHFileOperation to delete files and directories.
22 	// Under other systems, we have to recurse into subdirectories manually
23 	// to delete all contents.
24 
25 #ifdef FZ_WINDOWS
26 	// SHFileOperation accepts a list of null-terminated strings. Go through all
27 	// paths to get the required buffer length
28 
29 	size_t len = 1; // String list terminated by empty string
30 
31 	for (auto const& dir : dirsToVisit) {
32 		len += dir.size() + 1;
33 	}
34 
35 	// Allocate memory
36 	native_string::value_type* pBuffer = new native_string::value_type[len];
37 	native_string::value_type* p = pBuffer;
38 
39 	for (auto& dir : dirsToVisit) {
40 		if (!dir.empty() && local_filesys::is_separator(dir.back())) {
41 			dir.pop_back();
42 		}
43 		if (local_filesys::get_file_type(dir) == local_filesys::unknown) {
44 			continue;
45 		}
46 
47 		wcscpy(p, dir.c_str());
48 		p += dir.size() + 1;
49 	}
50 	if (p != pBuffer) {
51 		*p = 0;
52 
53 		// Now we can delete the files in the buffer
54 		SHFILEOPSTRUCT op{};
55 		op.wFunc = FO_DELETE;
56 		op.pFrom = pBuffer;
57 
58 		adjust_shfileop(op);
59 
60 		if (SHFileOperation(&op) != 0) {
61 			success = false;
62 		}
63 	}
64 	delete [] pBuffer;
65 #else
66 	if (!confirm()) {
67 		return false;
68 	}
69 
70 	for (auto& dir : dirsToVisit) {
71 		if (dir.size() > 1 && dir.back() == '/') {
72 			dir.pop_back();
73 		}
74 	}
75 
76 	// Remember the directories to delete after recursing into them
77 	std::list<native_string> dirsToDelete;
78 
79 	local_filesys fs;
80 
81 	// Process all dirctories that have to be visited
82 	while (!dirsToVisit.empty()) {
83 		auto const iter = dirsToVisit.begin();
84 		native_string const& path = *iter;
85 
86 		if (path.empty()) {
87 			dirsToVisit.erase(iter);
88 			continue;
89 		}
90 
91 		if (fs.get_file_type(path) != local_filesys::dir) {
92 			if (unlink(path.c_str()) != 0) {
93 				success = false;
94 			}
95 			dirsToVisit.erase(iter);
96 			continue;
97 		}
98 
99 		dirsToDelete.splice(dirsToDelete.begin(), dirsToVisit, iter);
100 
101 		if (!fs.begin_find_files(path, false)) {
102 			continue;
103 		}
104 
105 		// Depending on underlying platform, wxDir does not handle
106 		// changes to the directory contents very well.
107 		// See https://trac.filezilla-project.org/ticket/3482
108 		// To work around this, delete files after enumerating everything in current directory
109 		std::list<native_string> filesToDelete;
110 
111 		native_string file;
112 		while (fs.get_next_file(file)) {
113 			if (file.empty()) {
114 				continue;
115 			}
116 
117 			native_string const fullName = path + fzT("/") + file;
118 
119 			if (local_filesys::get_file_type(fullName) == local_filesys::dir) {
120 				dirsToVisit.push_back(fullName);
121 			}
122 			else {
123 				filesToDelete.push_back(fullName);
124 			}
125 		}
126 		fs.end_find_files();
127 
128 		// Delete all files and links in current directory enumerated before
129 		for (auto const& filename : filesToDelete) {
130 			if (unlink(filename.c_str()) != 0) {
131 				success = false;
132 			}
133 		}
134 	}
135 
136 	// Delete the now empty directories
137 	for (auto const& dir : dirsToDelete) {
138 		if (rmdir(dir.c_str()) != 0) {
139 			success = false;
140 		}
141 	}
142 #endif
143 
144 	return success;
145 }
146 
147 #ifdef FZ_WINDOWS
adjust_shfileop(SHFILEOPSTRUCT & op)148 void recursive_remove::adjust_shfileop(SHFILEOPSTRUCT & op)
149 {
150 	op.fFlags = FOF_NO_UI | FOF_ALLOWUNDO;
151 }
152 #endif
153 
154 }
155