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