Line data Source code
1 : // SPDX-License-Identifier: GPL-3.0-or-later
2 : // Copyright (C) 2013 Andrea Mazzoleni
3 :
4 : #include "portable.h"
5 :
6 : #include "support.h"
7 : #include "elem.h"
8 : #include "state.h"
9 :
10 : struct snapraid_pool {
11 : char file[PATH_MAX];
12 : char linkto[PATH_MAX];
13 : int64_t mtime_sec;
14 : int mtime_nsec;
15 :
16 : /* nodes for data structures */
17 : tommy_hashdyn_node node;
18 : };
19 :
20 5751 : struct snapraid_pool* pool_alloc(const char* dir, const char* name, const char* linkto, const struct stat* st)
21 : {
22 : struct snapraid_pool* pool;
23 :
24 5751 : pool = malloc_nofail(sizeof(struct snapraid_pool));
25 5751 : pathprint(pool->file, sizeof(pool->file), "%s%s", dir, name);
26 5751 : pathcpy(pool->linkto, sizeof(pool->linkto), linkto);
27 5751 : pool->mtime_sec = st->st_mtime;
28 5751 : pool->mtime_nsec = STAT_NSEC(st);
29 :
30 5751 : return pool;
31 : }
32 :
33 23340 : static inline tommy_uint32_t pool_hash(const char* file)
34 : {
35 23340 : return tommy_hash_u32(0, file, strlen(file));
36 : }
37 :
38 5751 : void pool_free(struct snapraid_pool* pool)
39 : {
40 5751 : free(pool);
41 5751 : }
42 :
43 5690 : int pool_compare(const void* void_arg, const void* void_data)
44 : {
45 5690 : const char* arg = void_arg;
46 5690 : const struct snapraid_pool* pool = void_data;
47 :
48 5690 : return strcmp(arg, pool->file);
49 : }
50 :
51 : /**
52 : * Remove empty dir.
53 : * Return == 0 if the directory is empty, and it can be removed
54 : */
55 7 : static int clean_dir(const char* dir)
56 : {
57 : DIR* d;
58 7 : int full = 0;
59 :
60 7 : d = opendir(dir);
61 7 : if (!d) {
62 : /* LCOV_EXCL_START */
63 : log_fatal(errno, "Error opening pool directory '%s'. %s.\n", dir, strerror(errno));
64 : exit(EXIT_FAILURE);
65 : /* LCOV_EXCL_STOP */
66 : }
67 :
68 17086 : while (1) {
69 : char path_next[PATH_MAX];
70 : struct stat st;
71 : const char* name;
72 : struct dirent* dd;
73 :
74 : /* clear errno to detect erroneous conditions */
75 17093 : errno = 0;
76 17093 : dd = readdir(d);
77 17093 : if (dd == 0 && errno != 0) {
78 : /* LCOV_EXCL_START */
79 : log_fatal(errno, "Error reading pool directory '%s'. %s.\n", dir, strerror(errno));
80 : exit(EXIT_FAILURE);
81 : /* LCOV_EXCL_STOP */
82 : }
83 17093 : if (dd == 0 && errno == 0) {
84 7 : break; /* finished */
85 : }
86 :
87 : /* skip "." and ".." files */
88 17086 : name = dd->d_name;
89 17086 : if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
90 14 : continue;
91 :
92 17072 : pathprint(path_next, sizeof(path_next), "%s%s", dir, name);
93 :
94 : #if HAVE_STRUCT_DIRENT_D_STAT
95 : /* convert dirent to lstat result */
96 : dirent_lstat(dd, &st);
97 :
98 : /*
99 : * If the st_mode field is missing, takes care to fill it using normal lstat()
100 : * at now this can happen only in Windows
101 : */
102 : if (st.st_mode == 0) {
103 : if (lstat(path_next, &st) != 0) {
104 : /* LCOV_EXCL_START */
105 : log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno));
106 : exit(EXIT_FAILURE);
107 : /* LCOV_EXCL_STOP */
108 : }
109 : }
110 : #else
111 : /* get lstat info about the file */
112 17072 : if (lstat(path_next, &st) != 0) {
113 : /* LCOV_EXCL_START */
114 : log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno));
115 : exit(EXIT_FAILURE);
116 : /* LCOV_EXCL_STOP */
117 : }
118 : #endif
119 :
120 17072 : if (S_ISDIR(st.st_mode)) {
121 : /* recurse */
122 4 : pathslash(path_next, sizeof(path_next));
123 4 : if (clean_dir(path_next) == 0) {
124 : int ret;
125 :
126 : /* directory is empty, try to remove it */
127 0 : ret = rmdir(path_next);
128 0 : if (ret < 0) {
129 : #ifdef _WIN32
130 : if (errno == EACCES) {
131 : /*
132 : * In Windows just ignore EACCES errors removing directories
133 : * because it could happen that the directory is in use
134 : * and it cannot be removed
135 : */
136 : log_fatal(errno, "Directory '%s' not removed because it's in use.\n", path_next);
137 : full = 1;
138 : } else
139 : #endif
140 : {
141 : /* LCOV_EXCL_START */
142 : log_fatal(errno, "Error removing pool directory '%s'. %s.\n", path_next, strerror(errno));
143 : exit(EXIT_FAILURE);
144 : /* LCOV_EXCL_STOP */
145 : }
146 : }
147 : } else {
148 : /* something is present */
149 4 : full = 1;
150 : }
151 : } else {
152 : /* something is present */
153 17068 : full = 1;
154 : }
155 : }
156 :
157 7 : if (closedir(d) != 0) {
158 : /* LCOV_EXCL_START */
159 : log_fatal(errno, "Error closing pool directory '%s'. %s.\n", dir, strerror(errno));
160 : exit(EXIT_FAILURE);
161 : /* LCOV_EXCL_STOP */
162 : }
163 :
164 7 : return full;
165 : }
166 :
167 : /**
168 : * Read all the links in a directory tree.
169 : */
170 5 : static void read_dir(tommy_hashdyn* poolset, const char* base_dir, const char* sub_dir)
171 : {
172 : char dir[PATH_MAX];
173 : DIR* d;
174 :
175 5 : pathprint(dir, sizeof(dir), "%s%s", base_dir, sub_dir);
176 5 : d = opendir(dir);
177 5 : if (!d) {
178 : /* LCOV_EXCL_START */
179 : log_fatal(errno, "Error opening pool directory '%s'. %s.\n", dir, strerror(errno));
180 : exit(EXIT_FAILURE);
181 : /* LCOV_EXCL_STOP */
182 : }
183 :
184 5763 : while (1) {
185 : char path_next[PATH_MAX];
186 : struct stat st;
187 : const char* name;
188 : struct dirent* dd;
189 :
190 : /* clear errno to detect erroneous conditions */
191 5768 : errno = 0;
192 5768 : dd = readdir(d);
193 5768 : if (dd == 0 && errno != 0) {
194 : /* LCOV_EXCL_START */
195 : log_fatal(errno, "Error reading pool directory '%s'. %s.\n", dir, strerror(errno));
196 : exit(EXIT_FAILURE);
197 : /* LCOV_EXCL_STOP */
198 : }
199 5768 : if (dd == 0 && errno == 0) {
200 5 : break; /* finished */
201 : }
202 :
203 : /* skip "." and ".." files */
204 5763 : name = dd->d_name;
205 5763 : if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
206 10 : continue;
207 :
208 5753 : pathprint(path_next, sizeof(path_next), "%s%s", dir, name);
209 :
210 : #if HAVE_STRUCT_DIRENT_D_STAT
211 : /* convert dirent to lstat result */
212 : dirent_lstat(dd, &st);
213 :
214 : /*
215 : * If the st_mode field is missing, takes care to fill it using normal lstat()
216 : * at now this can happen only in Windows
217 : */
218 : if (st.st_mode == 0) {
219 : if (lstat(path_next, &st) != 0) {
220 : /* LCOV_EXCL_START */
221 : log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno));
222 : exit(EXIT_FAILURE);
223 : /* LCOV_EXCL_STOP */
224 : }
225 : }
226 : #else
227 : /* get lstat info about the file */
228 5753 : if (lstat(path_next, &st) != 0) {
229 : /* LCOV_EXCL_START */
230 : log_fatal(errno, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno));
231 : exit(EXIT_FAILURE);
232 : /* LCOV_EXCL_STOP */
233 : }
234 : #endif
235 :
236 5753 : if (S_ISLNK(st.st_mode)) {
237 : struct snapraid_pool* pool;
238 : char linkto[PATH_MAX];
239 : int ret;
240 :
241 5751 : ret = readlink(path_next, linkto, sizeof(linkto));
242 5751 : if (ret < 0 || ret >= PATH_MAX) {
243 : /* LCOV_EXCL_START */
244 : log_fatal(errno, "Error in readlink symlink '%s'. %s.\n", path_next, strerror(errno));
245 : exit(EXIT_FAILURE);
246 : /* LCOV_EXCL_STOP */
247 : }
248 5751 : linkto[ret] = 0;
249 :
250 : /* store the link info */
251 5751 : pool = pool_alloc(sub_dir, name, linkto, &st);
252 :
253 5751 : tommy_hashdyn_insert(poolset, &pool->node, pool, pool_hash(pool->file));
254 :
255 2 : } else if (S_ISDIR(st.st_mode)) {
256 2 : pathprint(path_next, sizeof(path_next), "%s%s/", sub_dir, name);
257 :
258 2 : read_dir(poolset, base_dir, path_next);
259 : } else {
260 0 : msg_verbose("Ignoring pool file '%s'\n", path_next);
261 : }
262 : }
263 :
264 5 : if (closedir(d) != 0) {
265 : /* LCOV_EXCL_START */
266 : log_fatal(errno, "Error closing pool directory '%s'. %s.\n", dir, strerror(errno));
267 : exit(EXIT_FAILURE);
268 : /* LCOV_EXCL_STOP */
269 : }
270 5 : }
271 :
272 : /**
273 : * Remove the link
274 : */
275 61 : static void remove_link(void* void_arg, void* void_pool)
276 : {
277 : char path[PATH_MAX];
278 61 : const char* arg = void_arg;
279 61 : struct snapraid_pool* pool = void_pool;
280 : int ret;
281 :
282 61 : pathprint(path, sizeof(path), "%s%s", arg, pool->file);
283 :
284 : /* delete the link */
285 61 : ret = remove(path);
286 61 : if (ret < 0) {
287 : /* LCOV_EXCL_START */
288 : log_fatal(errno, "Error removing symlink '%s'. %s.\n", path, strerror(errno));
289 : exit(EXIT_FAILURE);
290 : /* LCOV_EXCL_STOP */
291 : }
292 61 : }
293 :
294 : /**
295 : * Create a link to the specified disk link.
296 : */
297 17589 : static void make_link(tommy_hashdyn* poolset, const char* pool_dir, const char* share_dir, struct snapraid_disk* disk, const char* sub, int64_t mtime_sec, int mtime_nsec)
298 : {
299 : char path[PATH_MAX];
300 : char linkto[PATH_MAX];
301 : char linkto_exported[PATH_MAX];
302 : struct snapraid_pool* found;
303 : int ret;
304 :
305 : /* make the source path */
306 17589 : pathprint(path, sizeof(path), "%s%s", pool_dir, sub);
307 :
308 : /* make the linkto path */
309 17589 : if (share_dir[0] != 0) {
310 : /* with a shared directory, use it */
311 17589 : pathprint(linkto, sizeof(linkto), "%s%s/%s", share_dir, disk->name, sub);
312 : } else {
313 : /* without a share directory, use the local disk paths */
314 0 : pathprint(linkto, sizeof(linkto), "%s%s", disk->dir, sub);
315 : }
316 :
317 : /* search for the sub path */
318 17589 : found = tommy_hashdyn_search(poolset, pool_compare, sub, pool_hash(sub));
319 17589 : if (found) {
320 : /* remove from the set */
321 5690 : tommy_hashdyn_remove_existing(poolset, &found->node);
322 :
323 : /* check if the info match */
324 5690 : if (found->mtime_sec == mtime_sec
325 5406 : && found->mtime_nsec == mtime_nsec
326 5385 : && strcmp(found->linkto, linkto) == 0
327 : ) {
328 : /* nothing to do */
329 5355 : pool_free(found);
330 5355 : return;
331 : }
332 :
333 : /* delete the link */
334 335 : ret = remove(path);
335 335 : if (ret < 0) {
336 : /* LCOV_EXCL_START */
337 : log_fatal(errno, "Error removing symlink '%s'. %s.\n", path, strerror(errno));
338 : exit(EXIT_FAILURE);
339 : /* LCOV_EXCL_STOP */
340 : }
341 :
342 335 : pool_free(found);
343 : }
344 :
345 : /* create the ancestor directories */
346 12234 : ret = mkancestor(path);
347 12234 : if (ret != 0) {
348 : /* LCOV_EXCL_START */
349 : exit(EXIT_FAILURE);
350 : /* LCOV_EXCL_STOP */
351 : }
352 :
353 : /* convert back slashes */
354 12234 : pathexport(linkto_exported, sizeof(linkto_exported), linkto);
355 :
356 : /* create the symlink */
357 12234 : ret = symlink(linkto_exported, path);
358 12234 : if (ret != 0) {
359 521 : if (errno == EEXIST) {
360 521 : log_fatal(errno, "WARNING! Duplicate pooling for '%s'\n", path);
361 : #ifdef _WIN32
362 : } else if (errno == EPERM) {
363 : /* LCOV_EXCL_START */
364 : log_fatal(errno, "You must run as Administrator to be able to create symlinks.\n");
365 : exit(EXIT_FAILURE);
366 : /* LCOV_EXCL_STOP */
367 : #endif
368 : } else {
369 : /* LCOV_EXCL_START */
370 : log_fatal(errno, "Error writing symlink '%s'. %s.\n", path, strerror(errno));
371 : exit(EXIT_FAILURE);
372 : /* LCOV_EXCL_STOP */
373 : }
374 : }
375 :
376 12234 : if (mtime_sec) {
377 11645 : ret = lmtime(path, mtime_sec, mtime_nsec);
378 11645 : if (ret != 0) {
379 : /* LCOV_EXCL_START */
380 : log_fatal(errno, "Error setting time to symlink '%s'. %s.\n", path, strerror(errno));
381 : exit(EXIT_FAILURE);
382 : /* LCOV_EXCL_STOP */
383 : }
384 : }
385 : }
386 :
387 3 : void state_pool(struct snapraid_state* state)
388 : {
389 : tommy_hashdyn poolset;
390 : tommy_node* i;
391 : char pool_dir[PATH_MAX];
392 : char share_dir[PATH_MAX];
393 : unsigned count;
394 :
395 3 : tommy_hashdyn_init(&poolset);
396 :
397 3 : if (state->pool[0] == 0) {
398 : /* LCOV_EXCL_START */
399 : log_fatal(EUSER, "To use the 'pool' command you must set the pool directory in the configuration file\n");
400 : exit(EXIT_FAILURE);
401 : /* LCOV_EXCL_STOP */
402 : }
403 :
404 3 : msg_progress("Reading...\n");
405 :
406 : /* pool directory with final slash */
407 3 : pathprint(pool_dir, sizeof(pool_dir), "%s", state->pool);
408 3 : pathslash(pool_dir, sizeof(pool_dir));
409 :
410 : /* share directory with final slash */
411 3 : pathprint(share_dir, sizeof(share_dir), "%s", state->share);
412 3 : pathslash(share_dir, sizeof(share_dir));
413 :
414 : /* first read the previous pool tree */
415 3 : read_dir(&poolset, pool_dir, "");
416 :
417 3 : msg_progress("Writing...\n");
418 :
419 : /* for each disk */
420 3 : count = 0;
421 21 : for (i = state->disklist; i != 0; i = i->next) {
422 : tommy_node* j;
423 18 : struct snapraid_disk* disk = i->data;
424 :
425 : /* for each file */
426 17018 : for (j = disk->filelist; j != 0; j = j->next) {
427 17000 : struct snapraid_file* file = j->data;
428 17000 : make_link(&poolset, pool_dir, share_dir, disk, file->sub, file->mtime_sec, file->mtime_nsec);
429 17000 : ++count;
430 : }
431 :
432 : /* for each link */
433 607 : for (j = disk->linklist; j != 0; j = j->next) {
434 589 : struct snapraid_link* slink = j->data;
435 589 : make_link(&poolset, pool_dir, share_dir, disk, slink->sub, 0, 0);
436 589 : ++count;
437 : }
438 :
439 : /* we ignore empty dirs in disk->dir */
440 : }
441 :
442 3 : msg_progress("Cleaning...\n");
443 :
444 : /* delete all the remaining links */
445 3 : tommy_hashdyn_foreach_arg(&poolset, (tommy_foreach_arg_func*)remove_link, pool_dir);
446 :
447 : /* delete empty dirs */
448 3 : clean_dir(pool_dir);
449 :
450 3 : tommy_hashdyn_foreach(&poolset, (tommy_foreach_func*)pool_free);
451 3 : tommy_hashdyn_done(&poolset);
452 :
453 3 : if (count)
454 2 : msg_status("%u links\n", count);
455 : else
456 1 : msg_status("No link\n");
457 :
458 3 : log_tag("summary:link_count::%u\n", count);
459 3 : log_tag("summary:exit:ok\n");
460 3 : log_flush();
461 3 : }
462 :
|