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