1 /*
2 * Copyright (c) 2008 Vreixo Formoso
3 *
4 * This file is part of the libisofs project; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License version 2
6 * or later as published by the Free Software Foundation.
7 * See COPYING file for details.
8 */
9
10 #ifdef HAVE_CONFIG_H
11 #include "../config.h"
12 #endif
13
14 #include "libisofs.h"
15 #include "node.h"
16
17 #include <fnmatch.h>
18 #include <string.h>
19
20 struct iso_find_condition
21 {
22 /*
23 * Check whether the given node matches this condition.
24 *
25 * @param cond
26 * The condition to check
27 * @param node
28 * The node that should be checked
29 * @return
30 * 1 if the node matches the condition, 0 if not
31 */
32 int (*matches)(IsoFindCondition *cond, IsoNode *node);
33
34 /**
35 * Free condition specific data
36 */
37 void (*free)(IsoFindCondition*);
38
39 /** condition specific data */
40 void *data;
41 };
42
43 struct find_iter_data
44 {
45 IsoDir *dir; /**< original dir of the iterator */
46 IsoDirIter *iter;
47 IsoDirIter *itersec; /**< iterator to deal with child dirs */
48 IsoFindCondition *cond;
49 int err; /**< error? */
50 IsoNode *current; /**< node to be returned next */
51 IsoNode *prev; /**< last returned node, needed for removal */
52 int free_cond; /**< whether to free cond on iter_free */
53 };
54
55 static
get_next(struct find_iter_data * iter,IsoNode ** n)56 int get_next(struct find_iter_data *iter, IsoNode **n)
57 {
58 int ret;
59
60 if (iter->itersec != NULL) {
61 ret = iso_dir_iter_next(iter->itersec, n);
62 if (ret <= 0) {
63 /* secondary item no more needed */
64 iso_dir_iter_free(iter->itersec);
65 iter->itersec = NULL;
66 }
67 if (ret != 0) {
68 /* success or error */
69 return ret;
70 }
71 }
72
73 /*
74 * we reach here if:
75 * - no secondary item is present
76 * - secondary item has no more items
77 */
78
79 while ((ret = iso_dir_iter_next(iter->iter, n)) == 1) {
80 if (iter->cond->matches(iter->cond, *n)) {
81 return ISO_SUCCESS;
82 } else if (ISO_NODE_IS_DIR(*n)) {
83 /* recurse on child dir */
84 struct find_iter_data *data;
85 ret = iso_dir_find_children((IsoDir*)*n, iter->cond,
86 &iter->itersec);
87 if (ret < 0) {
88 return ret;
89 }
90 data = iter->itersec->data;
91 data->free_cond = 0; /* we don't need sec iter to free cond */
92 return get_next(iter, n);
93 }
94 }
95 return ret;
96 }
97
98 static
update_next(IsoDirIter * iter)99 void update_next(IsoDirIter *iter)
100 {
101 int ret;
102 IsoNode *n;
103 struct find_iter_data *data = iter->data;
104
105 if (data->prev) {
106 iso_node_unref(data->prev);
107 }
108 data->prev = data->current;
109
110 if (data->itersec == NULL && data->current != NULL
111 && ISO_NODE_IS_DIR(data->current)) {
112
113 /* we need to recurse on child dir */
114 struct find_iter_data *data2;
115 ret = iso_dir_find_children((IsoDir*)data->current, data->cond,
116 &data->itersec);
117 if (ret < 0) {
118 data->current = NULL;
119 data->err = ret;
120 return;
121 }
122 data2 = data->itersec->data;
123 data2->free_cond = 0; /* we don't need sec iter to free cond */
124 }
125
126 ret = get_next(data, &n);
127 iso_node_unref((IsoNode*)iter->dir);
128 if (ret == 1) {
129 data->current = n;
130 iso_node_ref(n);
131 data->err = 0;
132 iter->dir = n->parent;
133 } else {
134 data->current = NULL;
135 data->err = ret;
136 iter->dir = data->dir;
137 }
138 iso_node_ref((IsoNode*)iter->dir);
139 }
140
141 static
find_iter_next(IsoDirIter * iter,IsoNode ** node)142 int find_iter_next(IsoDirIter *iter, IsoNode **node)
143 {
144 struct find_iter_data *data;
145
146 if (iter == NULL || node == NULL) {
147 return ISO_NULL_POINTER;
148 }
149 data = iter->data;
150
151 if (data->err < 0) {
152 return data->err;
153 }
154 *node = data->current;
155 update_next(iter);
156 return (*node == NULL) ? 0 : ISO_SUCCESS;
157 }
158
159 static
find_iter_has_next(IsoDirIter * iter)160 int find_iter_has_next(IsoDirIter *iter)
161 {
162 struct find_iter_data *data = iter->data;
163
164 return (data->current != NULL);
165 }
166
167 static
find_iter_free(IsoDirIter * iter)168 void find_iter_free(IsoDirIter *iter)
169 {
170 struct find_iter_data *data = iter->data;
171 if (data->free_cond) {
172 data->cond->free(data->cond);
173 free(data->cond);
174 }
175
176 iso_node_unref((IsoNode*)data->dir);
177
178 /* free refs to nodes */
179 if (data->prev) {
180 iso_node_unref(data->prev);
181 }
182 if (data->current) {
183 iso_node_unref(data->current);
184 }
185
186 /* free underlying iter */
187 iso_dir_iter_free(data->iter);
188 free(iter->data);
189 }
190
191 static
find_iter_take(IsoDirIter * iter)192 int find_iter_take(IsoDirIter *iter)
193 {
194 struct find_iter_data *data = iter->data;
195
196 if (data->prev == NULL) {
197 return ISO_ERROR; /* next not called or end of dir */
198 }
199 return iso_node_take(data->prev);
200 }
201
202 static
find_iter_remove(IsoDirIter * iter)203 int find_iter_remove(IsoDirIter *iter)
204 {
205 struct find_iter_data *data = iter->data;
206
207 if (data->prev == NULL) {
208 return ISO_ERROR; /* next not called or end of dir */
209 }
210 return iso_node_remove(data->prev);
211 }
212
find_notify_child_taken(IsoDirIter * iter,IsoNode * node)213 void find_notify_child_taken(IsoDirIter *iter, IsoNode *node)
214 {
215 struct find_iter_data *data = iter->data;
216
217 if (data->prev == node) {
218 /* free our ref */
219 iso_node_unref(node);
220 data->prev = NULL;
221 } else if (data->current == node) {
222 iso_node_unref(node);
223 data->current = NULL;
224 update_next(iter);
225 }
226 }
227
228 static
229 struct iso_dir_iter_iface find_iter_class = {
230 find_iter_next,
231 find_iter_has_next,
232 find_iter_free,
233 find_iter_take,
234 find_iter_remove,
235 find_notify_child_taken
236 };
237
iso_dir_find_children(IsoDir * dir,IsoFindCondition * cond,IsoDirIter ** iter)238 int iso_dir_find_children(IsoDir* dir, IsoFindCondition *cond,
239 IsoDirIter **iter)
240 {
241 int ret;
242 IsoDirIter *children;
243 IsoDirIter *it;
244 struct find_iter_data *data;
245
246 if (dir == NULL || cond == NULL || iter == NULL) {
247 return ISO_NULL_POINTER;
248 }
249 it = malloc(sizeof(IsoDirIter));
250 if (it == NULL) {
251 return ISO_OUT_OF_MEM;
252 }
253 data = malloc(sizeof(struct find_iter_data));
254 if (data == NULL) {
255 free(it);
256 return ISO_OUT_OF_MEM;
257 }
258 ret = iso_dir_get_children(dir, &children);
259 if (ret < 0) {
260 free(it);
261 free(data);
262 return ret;
263 }
264
265 it->class = &find_iter_class;
266 it->dir = (IsoDir*)dir;
267 data->iter = children;
268 data->itersec = NULL;
269 data->cond = cond;
270 data->free_cond = 1;
271 data->err = 0;
272 data->prev = data->current = NULL;
273 it->data = data;
274
275 if (iso_dir_iter_register(it) < 0) {
276 free(it);
277 return ISO_OUT_OF_MEM;
278 }
279
280 iso_node_ref((IsoNode*)dir);
281
282 /* take another ref to the original dir */
283 data->dir = (IsoDir*)dir;
284 iso_node_ref((IsoNode*)dir);
285
286 update_next(it);
287
288 *iter = it;
289 return ISO_SUCCESS;
290 }
291
292 /*************** find by name wildcard condition *****************/
293
294 static
cond_name_matches(IsoFindCondition * cond,IsoNode * node)295 int cond_name_matches(IsoFindCondition *cond, IsoNode *node)
296 {
297 char *pattern = (char*) cond->data;
298 int ret = fnmatch(pattern, node->name, 0);
299 return ret == 0 ? 1 : 0;
300 }
301
302 static
cond_name_free(IsoFindCondition * cond)303 void cond_name_free(IsoFindCondition *cond)
304 {
305 free(cond->data);
306 }
307
308 /**
309 * Create a new condition that checks if the node name matches the given
310 * wildcard.
311 *
312 * @param wildcard
313 * @result
314 * The created IsoFindCondition, NULL on error.
315 *
316 * @since 0.6.4
317 */
iso_new_find_conditions_name(const char * wildcard)318 IsoFindCondition *iso_new_find_conditions_name(const char *wildcard)
319 {
320 IsoFindCondition *cond;
321 if (wildcard == NULL) {
322 return NULL;
323 }
324 cond = malloc(sizeof(IsoFindCondition));
325 if (cond == NULL) {
326 return NULL;
327 }
328 cond->data = strdup(wildcard);
329 cond->free = cond_name_free;
330 cond->matches = cond_name_matches;
331 return cond;
332 }
333
334 /*************** find by mode condition *****************/
335
336 static
cond_mode_matches(IsoFindCondition * cond,IsoNode * node)337 int cond_mode_matches(IsoFindCondition *cond, IsoNode *node)
338 {
339 mode_t *mask = (mode_t*) cond->data;
340 return node->mode & *mask ? 1 : 0;
341 }
342
343 static
cond_mode_free(IsoFindCondition * cond)344 void cond_mode_free(IsoFindCondition *cond)
345 {
346 free(cond->data);
347 }
348
349 /**
350 * Create a new condition that checks the node mode against a mode mask. It
351 * can be used to check both file type and permissions.
352 *
353 * For example:
354 *
355 * iso_new_find_conditions_mode(S_IFREG) : search for regular files
356 * iso_new_find_conditions_mode(S_IFCHR | S_IWUSR) : search for character
357 * devices where owner has write permissions.
358 *
359 * @param mask
360 * Mode mask to AND against node mode.
361 * @result
362 * The created IsoFindCondition, NULL on error.
363 *
364 * @since 0.6.4
365 */
iso_new_find_conditions_mode(mode_t mask)366 IsoFindCondition *iso_new_find_conditions_mode(mode_t mask)
367 {
368 IsoFindCondition *cond;
369 mode_t *data;
370 cond = malloc(sizeof(IsoFindCondition));
371 if (cond == NULL) {
372 return NULL;
373 }
374 data = malloc(sizeof(mode_t));
375 if (data == NULL) {
376 free(cond);
377 return NULL;
378 }
379 *data = mask;
380 cond->data = data;
381 cond->free = cond_mode_free;
382 cond->matches = cond_mode_matches;
383 return cond;
384 }
385
386 /*************** find by gid condition *****************/
387
388 static
cond_gid_matches(IsoFindCondition * cond,IsoNode * node)389 int cond_gid_matches(IsoFindCondition *cond, IsoNode *node)
390 {
391 gid_t *gid = (gid_t*) cond->data;
392 return node->gid == *gid ? 1 : 0;
393 }
394
395 static
cond_gid_free(IsoFindCondition * cond)396 void cond_gid_free(IsoFindCondition *cond)
397 {
398 free(cond->data);
399 }
400
401 /**
402 * Create a new condition that checks the node gid.
403 *
404 * @param gid
405 * Desired Group Id.
406 * @result
407 * The created IsoFindCondition, NULL on error.
408 *
409 * @since 0.6.4
410 */
iso_new_find_conditions_gid(gid_t gid)411 IsoFindCondition *iso_new_find_conditions_gid(gid_t gid)
412 {
413 IsoFindCondition *cond;
414 gid_t *data;
415 cond = malloc(sizeof(IsoFindCondition));
416 if (cond == NULL) {
417 return NULL;
418 }
419 data = malloc(sizeof(gid_t));
420 if (data == NULL) {
421 free(cond);
422 return NULL;
423 }
424 *data = gid;
425 cond->data = data;
426 cond->free = cond_gid_free;
427 cond->matches = cond_gid_matches;
428 return cond;
429 }
430
431 /*************** find by uid condition *****************/
432
433 static
cond_uid_matches(IsoFindCondition * cond,IsoNode * node)434 int cond_uid_matches(IsoFindCondition *cond, IsoNode *node)
435 {
436 uid_t *uid = (uid_t*) cond->data;
437 return node->uid == *uid ? 1 : 0;
438 }
439
440 static
cond_uid_free(IsoFindCondition * cond)441 void cond_uid_free(IsoFindCondition *cond)
442 {
443 free(cond->data);
444 }
445
446 /**
447 * Create a new condition that checks the node uid.
448 *
449 * @param uid
450 * Desired User Id.
451 * @result
452 * The created IsoFindCondition, NULL on error.
453 *
454 * @since 0.6.4
455 */
iso_new_find_conditions_uid(uid_t uid)456 IsoFindCondition *iso_new_find_conditions_uid(uid_t uid)
457 {
458 IsoFindCondition *cond;
459 uid_t *data;
460 cond = malloc(sizeof(IsoFindCondition));
461 if (cond == NULL) {
462 return NULL;
463 }
464 data = malloc(sizeof(uid_t));
465 if (data == NULL) {
466 free(cond);
467 return NULL;
468 }
469 *data = uid;
470 cond->data = data;
471 cond->free = cond_uid_free;
472 cond->matches = cond_uid_matches;
473 return cond;
474 }
475
476 /*************** find by timestamps condition *****************/
477
478 struct cond_times
479 {
480 time_t time;
481 int what_time; /* 0 atime, 1 mtime, 2 ctime */
482 enum iso_find_comparisons comparison;
483 };
484
485 static
cond_time_matches(IsoFindCondition * cond,IsoNode * node)486 int cond_time_matches(IsoFindCondition *cond, IsoNode *node)
487 {
488 time_t node_time;
489 struct cond_times *data = cond->data;
490
491 switch (data->what_time) {
492 case 0: node_time = node->atime; break;
493 case 1: node_time = node->mtime; break;
494 default: node_time = node->ctime; break;
495 }
496
497 switch (data->comparison) {
498 case ISO_FIND_COND_GREATER:
499 return node_time > data->time ? 1 : 0;
500 case ISO_FIND_COND_GREATER_OR_EQUAL:
501 return node_time >= data->time ? 1 : 0;
502 case ISO_FIND_COND_EQUAL:
503 return node_time == data->time ? 1 : 0;
504 case ISO_FIND_COND_LESS:
505 return node_time < data->time ? 1 : 0;
506 case ISO_FIND_COND_LESS_OR_EQUAL:
507 return node_time <= data->time ? 1 : 0;
508 }
509 /* should never happen */
510 return 0;
511 }
512
513 static
cond_time_free(IsoFindCondition * cond)514 void cond_time_free(IsoFindCondition *cond)
515 {
516 free(cond->data);
517 }
518
519 /**
520 * Create a new condition that checks the time of last access.
521 *
522 * @param time
523 * Time to compare against IsoNode atime.
524 * @param comparison
525 * Comparison to be done between IsoNode atime and submitted time.
526 * Note that ISO_FIND_COND_GREATER, for example, is true if the node
527 * time is greater than the submitted time.
528 * @result
529 * The created IsoFindCondition, NULL on error.
530 *
531 * @since 0.6.4
532 */
iso_new_find_conditions_atime(time_t time,enum iso_find_comparisons comparison)533 IsoFindCondition *iso_new_find_conditions_atime(time_t time,
534 enum iso_find_comparisons comparison)
535 {
536 IsoFindCondition *cond;
537 struct cond_times *data;
538 cond = malloc(sizeof(IsoFindCondition));
539 if (cond == NULL) {
540 return NULL;
541 }
542 data = malloc(sizeof(struct cond_times));
543 if (data == NULL) {
544 free(cond);
545 return NULL;
546 }
547 data->time = time;
548 data->comparison = comparison;
549 data->what_time = 0; /* atime */
550 cond->data = data;
551 cond->free = cond_time_free;
552 cond->matches = cond_time_matches;
553 return cond;
554 }
555
556 /**
557 * Create a new condition that checks the time of last modification.
558 *
559 * @param time
560 * Time to compare against IsoNode mtime.
561 * @param comparison
562 * Comparison to be done between IsoNode mtime and submitted time.
563 * Note that ISO_FIND_COND_GREATER, for example, is true if the node
564 * time is greater than the submitted time.
565 * @result
566 * The created IsoFindCondition, NULL on error.
567 *
568 * @since 0.6.4
569 */
iso_new_find_conditions_mtime(time_t time,enum iso_find_comparisons comparison)570 IsoFindCondition *iso_new_find_conditions_mtime(time_t time,
571 enum iso_find_comparisons comparison)
572 {
573 IsoFindCondition *cond;
574 struct cond_times *data;
575 cond = malloc(sizeof(IsoFindCondition));
576 if (cond == NULL) {
577 return NULL;
578 }
579 data = malloc(sizeof(struct cond_times));
580 if (data == NULL) {
581 free(cond);
582 return NULL;
583 }
584 data->time = time;
585 data->comparison = comparison;
586 data->what_time = 1; /* mtime */
587 cond->data = data;
588 cond->free = cond_time_free;
589 cond->matches = cond_time_matches;
590 return cond;
591 }
592
593 /**
594 * Create a new condition that checks the time of last status change.
595 *
596 * @param time
597 * Time to compare against IsoNode ctime.
598 * @param comparison
599 * Comparison to be done between IsoNode ctime and submitted time.
600 * Note that ISO_FIND_COND_GREATER, for example, is true if the node
601 * time is greater than the submitted time.
602 * @result
603 * The created IsoFindCondition, NULL on error.
604 *
605 * @since 0.6.4
606 */
iso_new_find_conditions_ctime(time_t time,enum iso_find_comparisons comparison)607 IsoFindCondition *iso_new_find_conditions_ctime(time_t time,
608 enum iso_find_comparisons comparison)
609 {
610 IsoFindCondition *cond;
611 struct cond_times *data;
612 cond = malloc(sizeof(IsoFindCondition));
613 if (cond == NULL) {
614 return NULL;
615 }
616 data = malloc(sizeof(struct cond_times));
617 if (data == NULL) {
618 free(cond);
619 return NULL;
620 }
621 data->time = time;
622 data->comparison = comparison;
623 data->what_time = 2; /* ctime */
624 cond->data = data;
625 cond->free = cond_time_free;
626 cond->matches = cond_time_matches;
627 return cond;
628 }
629
630 /*************** logical operations on conditions *****************/
631
632 struct logical_binary_conditions {
633 IsoFindCondition *a;
634 IsoFindCondition *b;
635 };
636
637 static
cond_logical_binary_free(IsoFindCondition * cond)638 void cond_logical_binary_free(IsoFindCondition *cond)
639 {
640 struct logical_binary_conditions *data;
641 data = cond->data;
642 data->a->free(data->a);
643 free(data->a);
644 data->b->free(data->b);
645 free(data->b);
646 free(cond->data);
647 }
648
649 static
cond_logical_and_matches(IsoFindCondition * cond,IsoNode * node)650 int cond_logical_and_matches(IsoFindCondition *cond, IsoNode *node)
651 {
652 struct logical_binary_conditions *data = cond->data;
653 return data->a->matches(data->a, node) && data->b->matches(data->b, node);
654 }
655
656 /**
657 * Create a new condition that check if the two given conditions are
658 * valid.
659 *
660 * @param a
661 * @param b
662 * IsoFindCondition to compare
663 * @result
664 * The created IsoFindCondition, NULL on error.
665 *
666 * @since 0.6.4
667 */
iso_new_find_conditions_and(IsoFindCondition * a,IsoFindCondition * b)668 IsoFindCondition *iso_new_find_conditions_and(IsoFindCondition *a,
669 IsoFindCondition *b)
670 {
671 IsoFindCondition *cond;
672 struct logical_binary_conditions *data;
673 cond = malloc(sizeof(IsoFindCondition));
674 if (cond == NULL) {
675 return NULL;
676 }
677 data = malloc(sizeof(struct logical_binary_conditions));
678 if (data == NULL) {
679 free(cond);
680 return NULL;
681 }
682 data->a = a;
683 data->b = b;
684 cond->data = data;
685 cond->free = cond_logical_binary_free;
686 cond->matches = cond_logical_and_matches;
687 return cond;
688 }
689
690 static
cond_logical_or_matches(IsoFindCondition * cond,IsoNode * node)691 int cond_logical_or_matches(IsoFindCondition *cond, IsoNode *node)
692 {
693 struct logical_binary_conditions *data = cond->data;
694 return data->a->matches(data->a, node) || data->b->matches(data->b, node);
695 }
696
697 /**
698 * Create a new condition that check if at least one the two given conditions
699 * is valid.
700 *
701 * @param a
702 * @param b
703 * IsoFindCondition to compare
704 * @result
705 * The created IsoFindCondition, NULL on error.
706 *
707 * @since 0.6.4
708 */
iso_new_find_conditions_or(IsoFindCondition * a,IsoFindCondition * b)709 IsoFindCondition *iso_new_find_conditions_or(IsoFindCondition *a,
710 IsoFindCondition *b)
711 {
712 IsoFindCondition *cond;
713 struct logical_binary_conditions *data;
714 cond = malloc(sizeof(IsoFindCondition));
715 if (cond == NULL) {
716 return NULL;
717 }
718 data = malloc(sizeof(struct logical_binary_conditions));
719 if (data == NULL) {
720 free(cond);
721 return NULL;
722 }
723 data->a = a;
724 data->b = b;
725 cond->data = data;
726 cond->free = cond_logical_binary_free;
727 cond->matches = cond_logical_or_matches;
728 return cond;
729 }
730
731 static
cond_not_free(IsoFindCondition * cond)732 void cond_not_free(IsoFindCondition *cond)
733 {
734 IsoFindCondition *negate = cond->data;
735 negate->free(negate);
736 free(negate);
737 }
738
739 static
cond_not_matches(IsoFindCondition * cond,IsoNode * node)740 int cond_not_matches(IsoFindCondition *cond, IsoNode *node)
741 {
742 IsoFindCondition *negate = cond->data;
743 return !(negate->matches(negate, node));
744 }
745
746 /**
747 * Create a new condition that check if the given conditions is false.
748 *
749 * @param negate
750 * @result
751 * The created IsoFindCondition, NULL on error.
752 *
753 * @since 0.6.4
754 */
iso_new_find_conditions_not(IsoFindCondition * negate)755 IsoFindCondition *iso_new_find_conditions_not(IsoFindCondition *negate)
756 {
757 IsoFindCondition *cond;
758 cond = malloc(sizeof(IsoFindCondition));
759 if (cond == NULL) {
760 return NULL;
761 }
762 cond->data = negate;
763 cond->free = cond_not_free;
764 cond->matches = cond_not_matches;
765 return cond;
766 }
767
768