Line data Source code
1 : // Copyright (c) 2018-2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : /** \file
20 : * \brief Verify that the rm_r() function works.
21 : *
22 : * This file implements tests for the rm_r() function.
23 : */
24 :
25 : // self
26 : //
27 : #include <snapdev/rm_r.h>
28 :
29 : #include <snapdev/as_root.h>
30 : #include <snapdev/raii_generic_deleter.h>
31 : #include <snapdev/mkdir_p.h>
32 : #include <snapdev/pathinfo.h>
33 : #include <snapdev/version.h>
34 :
35 : #include "catch_main.h"
36 :
37 :
38 : // C
39 : //
40 : #include <sys/socket.h>
41 : #include <sys/sysmacros.h>
42 : #include <sys/un.h>
43 : #include <unistd.h>
44 :
45 :
46 : // last include
47 : //
48 : #include <snapdev/poison.h>
49 :
50 :
51 : namespace
52 : {
53 :
54 :
55 : // gcc on 22.04 generates this warning thinking that an strncpy() will fail
56 : // but according to the bug report it is not an issue
57 : // https://github.com/eworm-de/mkinitcpio-ykfde/issues/25
58 : // the warning was removed in future versions of gcc, unfortunately, we'll
59 : // be on 22.04 for a while...
60 : #if SNAPDEV_CHECK_GCC_VERSION(11, 3, 0)
61 : #pragma GCC diagnostic push
62 : #pragma GCC diagnostic ignored "-Wstringop-truncation"
63 : #endif
64 7 : void create_files(std::string const & folder, int depth, int max_depth, bool special_files, std::vector<int> & sockets)
65 : {
66 7 : int r(0);
67 :
68 : // first create this directory (unless it exists, then leave it alone)
69 : //
70 7 : struct stat st;
71 7 : if(stat(folder.c_str(), &st) == 0)
72 : {
73 : // if it already exists and it is a directory, go on
74 : //
75 0 : CATCH_REQUIRE(S_ISDIR(st.st_mode));
76 : }
77 : else
78 : {
79 7 : r = mkdir(folder.c_str(), 0700);
80 7 : CATCH_REQUIRE(r == 0);
81 : }
82 :
83 : // second create a few text files in this folder
84 : //
85 7 : int const max(rand() % 5 + 1);
86 24 : for(int i(1); i <= max; ++i)
87 : {
88 17 : std::string filename(folder);
89 17 : filename += '/';
90 17 : filename += "file";
91 17 : filename += std::to_string(i);
92 17 : filename += ".txt";
93 :
94 17 : std::ofstream out(filename);
95 17 : out << SNAP_CATCH2_NAMESPACE::random_string(10, 100);
96 17 : CATCH_REQUIRE(!!out);
97 17 : }
98 :
99 7 : if(special_files)
100 : {
101 : // also create special files
102 : //
103 4 : std::string fifo(folder);
104 4 : fifo += "/file.fifo";
105 4 : r = mkfifo(fifo.c_str(), 0700);
106 4 : CATCH_REQUIRE(r == 0);
107 :
108 4 : std::string socket_name(folder);
109 4 : socket_name += "/file.socket";
110 4 : struct sockaddr_un addr = {};
111 4 : addr.sun_family = AF_UNIX;
112 4 : CATCH_REQUIRE(socket_name.length() < sizeof(addr.sun_path));
113 4 : strncpy(addr.sun_path, socket_name.c_str(), sizeof(addr.sun_path));
114 4 : int s = socket(AF_UNIX, SOCK_STREAM, 0);
115 4 : CATCH_REQUIRE(s != -1);
116 4 : sockets.push_back(s);
117 4 : r = bind(s, reinterpret_cast<sockaddr const *>(&addr), sizeof(addr));
118 4 : CATCH_REQUIRE(r == 0);
119 :
120 : // creating device files is a root matter
121 : //
122 4 : snapdev::as_root root;
123 4 : if(root.is_switched())
124 : {
125 0 : std::string char_dev(folder);
126 0 : char_dev += "/char.dev";
127 0 : r = mknod(char_dev.c_str(), 0700 | S_IFCHR, makedev(1, 5)); // like /dev/zero
128 0 : CATCH_REQUIRE(r == 0);
129 :
130 0 : std::string block_dev(folder);
131 0 : block_dev += "/block.dev";
132 0 : r = mknod(block_dev.c_str(), 0700 | S_IFBLK, makedev(7, 1)); // like /dev/loop1
133 0 : CATCH_REQUIRE(r == 0);
134 0 : }
135 : else
136 : {
137 : static bool message_shown(false);
138 4 : if(!message_shown)
139 : {
140 1 : message_shown = true;
141 :
142 : std::cout << "--- note that the special file test cannot create block and character devices.\n"
143 : << "--- if you want to test those, make sure to run as root:\n"
144 1 : << "--- sudo unittest ... rm_r\n";
145 : }
146 : }
147 4 : }
148 :
149 : // third create sub-directories and recursively create files/dirs
150 : // unless we already reached the maximum depth
151 : //
152 7 : if(depth >= max_depth)
153 : {
154 5 : return;
155 : }
156 2 : int const max_foo(rand() % 3 + 2);
157 7 : for(int foo(1); foo <= max_foo; ++foo)
158 : {
159 5 : std::string sub_folder(folder);
160 5 : sub_folder += "/foo";
161 5 : sub_folder += std::to_string(foo);
162 :
163 5 : create_files(sub_folder, depth + 1, max_depth, special_files, sockets);
164 5 : }
165 : }
166 : #if SNAPDEV_CHECK_GCC_VERSION(11, 3, 0)
167 : #pragma GCC diagnostic pop
168 : #endif
169 :
170 :
171 :
172 : }
173 :
174 :
175 5 : CATCH_TEST_CASE("rm_r", "[os]")
176 : {
177 5 : int r(0);
178 :
179 5 : CATCH_START_SECTION("rm_r: missing")
180 : {
181 1 : std::string path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
182 1 : path += "/missing";
183 :
184 1 : r = snapdev::rm_r(path);
185 1 : CATCH_REQUIRE(r == 0);
186 1 : }
187 5 : CATCH_END_SECTION()
188 :
189 5 : CATCH_START_SECTION("rm_r: empty directory")
190 : {
191 1 : std::string path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
192 1 : path += "/empty";
193 :
194 1 : struct stat st;
195 1 : if(stat(path.c_str(), &st) == 0)
196 : {
197 : // if it already exists and it is a directory, go on
198 : //
199 0 : CATCH_REQUIRE(S_ISDIR(st.st_mode));
200 : }
201 : else
202 : {
203 1 : r = mkdir(path.c_str(), 0700);
204 1 : CATCH_REQUIRE(r == 0);
205 : }
206 :
207 1 : r = snapdev::rm_r(path);
208 1 : CATCH_REQUIRE(r == 0);
209 1 : }
210 5 : CATCH_END_SECTION()
211 :
212 5 : CATCH_START_SECTION("rm_r: directory with a few files")
213 : {
214 1 : std::string path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
215 1 : path += "/directory";
216 :
217 1 : struct stat st;
218 1 : if(stat(path.c_str(), &st) == 0)
219 : {
220 : // if it already exists and it is a directory, go on
221 : //
222 0 : CATCH_REQUIRE(S_ISDIR(st.st_mode));
223 : }
224 : else
225 : {
226 1 : r = mkdir(path.c_str(), 0700);
227 1 : CATCH_REQUIRE(r == 0);
228 : }
229 :
230 1 : int const max(rand() % 5 + 1);
231 4 : for(int i(1); i <= max; ++i)
232 : {
233 3 : std::string filename(path);
234 3 : filename += '/';
235 3 : filename += "file";
236 3 : filename += std::to_string(i);
237 3 : filename += ".txt";
238 :
239 3 : std::ofstream out(filename);
240 3 : out << SNAP_CATCH2_NAMESPACE::random_string(10, 100);
241 3 : }
242 :
243 1 : r = snapdev::rm_r(path);
244 1 : CATCH_REQUIRE(r == 0);
245 1 : }
246 5 : CATCH_END_SECTION()
247 :
248 5 : CATCH_START_SECTION("rm_r: directory tree")
249 : {
250 1 : std::string path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
251 1 : path += "/tree";
252 :
253 1 : std::vector<int> sockets;
254 1 : int const max_depth(rand() % 3 + 1);
255 1 : create_files(path, 1, max_depth, false, sockets);
256 : // ignore the sockets since we do not create special files
257 :
258 1 : r = snapdev::rm_r(path);
259 1 : CATCH_REQUIRE(r == 0);
260 1 : }
261 5 : CATCH_END_SECTION()
262 :
263 5 : CATCH_START_SECTION("rm_r: directory tree with special files")
264 : {
265 1 : std::string path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
266 1 : path += "/special";
267 :
268 1 : std::vector<int> sockets;
269 1 : int const max_depth(rand() % 3 + 1);
270 1 : create_files(path, 1, max_depth, true, sockets);
271 :
272 : // deleting as is must fail
273 : // (i.e. we detect special files early and don't delete anything)
274 : //
275 1 : r = snapdev::rm_r(path);
276 1 : if(r == 0)
277 : {
278 0 : CATCH_REQUIRE(r != 0);
279 : }
280 1 : CATCH_REQUIRE(errno == EPERM);
281 :
282 : // try with SPECIAL_FILE_IGNORE
283 : // (i.e. delete everything exception special files)
284 : //
285 1 : r = snapdev::rm_r(path, snapdev::special_file_t::SPECIAL_FILE_IGNORE);
286 1 : CATCH_REQUIRE(r == 0);
287 :
288 : // try with SPECIAL_FILE_KEEP_DEVICES
289 : // (i.e. delete everything exception devices)
290 : //
291 1 : r = snapdev::rm_r(path, snapdev::special_file_t::SPECIAL_FILE_KEEP_DEVICES);
292 1 : CATCH_REQUIRE(r == 0);
293 :
294 : // try with SPECIAL_FILE_REMOVE
295 : // (i.e. delete absolutely everything)
296 : //
297 : // note: this requires root permissions (we do not create if we
298 : // can't become root)
299 : {
300 1 : snapdev::as_root root;
301 1 : r = snapdev::rm_r(path, snapdev::special_file_t::SPECIAL_FILE_REMOVE);
302 1 : CATCH_REQUIRE(r == 0);
303 1 : }
304 :
305 : // now close all the sockets (ignore errors)
306 : //
307 5 : for(auto const & s : sockets)
308 : {
309 4 : close(s);
310 : }
311 :
312 : // finally make sure the directory is gone
313 : //
314 1 : struct stat st;
315 1 : CATCH_REQUIRE(stat(path.c_str(), &st) != 0);
316 1 : }
317 5 : CATCH_END_SECTION()
318 5 : }
319 :
320 :
321 :
322 : // vim: ts=4 sw=4 et
|