r4716: Fixes for the filter directories option: Initialize properly, inherit from
[rox-filer.git] / ROX-Filer / src / find.c
blobe31d42e03414c159afc780bd89e1ce862d697e8b
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* find.c - processes the find conditions
22 * A Condition is a tree structure. Each node has a test() fn which
23 * can be used to see whether the current file matches, and a free() fn
24 * which frees it. Both will recurse down the tree as needed.
27 #include "config.h"
29 #include <string.h>
30 #include <fnmatch.h>
31 #include <ctype.h>
32 #include <unistd.h>
33 #include <stdlib.h>
34 #include <time.h>
36 #include "global.h"
38 #include "main.h"
39 #include "find.h"
41 typedef struct _Eval Eval;
43 /* Static prototypes */
44 static FindCondition *parse_expression(const gchar **expression);
45 static FindCondition *parse_case(const gchar **expression);
46 static FindCondition *parse_system(const gchar **expression);
47 static FindCondition *parse_condition(const gchar **expression);
48 static FindCondition *parse_match(const gchar **expression);
49 static FindCondition *parse_comparison(const gchar **expression);
50 static FindCondition *parse_dash(const gchar **expression);
51 static FindCondition *parse_is(const gchar **expression);
52 static Eval *parse_eval(const gchar **expression);
53 static Eval *parse_variable(const gchar **expression);
55 static gboolean match(const gchar **expression, const gchar *word);
57 typedef enum {
58 IS_DIR,
59 IS_REG,
60 IS_LNK,
61 IS_FIFO,
62 IS_SOCK,
63 IS_CHR,
64 IS_BLK,
65 IS_DEV,
66 IS_DOOR,
67 IS_SUID,
68 IS_SGID,
69 IS_STICKY,
70 IS_READABLE,
71 IS_WRITEABLE,
72 IS_EXEC,
73 IS_EMPTY,
74 IS_MINE,
75 } IsTest;
77 typedef enum {
78 COMP_LT,
79 COMP_LE,
80 COMP_EQ,
81 COMP_NE,
82 COMP_GE,
83 COMP_GT,
84 } CompType;
86 typedef enum {
87 V_ATIME,
88 V_CTIME,
89 V_MTIME,
90 V_SIZE,
91 V_INODE,
92 V_NLINKS,
93 V_UID,
94 V_GID,
95 V_BLOCKS,
96 } VarType;
98 enum
100 FLAG_AGO = 1 << 0,
101 FLAG_HENCE = 1 << 1,
104 typedef double (*EvalCalc)(Eval *eval, FindInfo *info);
105 typedef void (*EvalFree)(Eval *eval);
107 struct _FindCondition
109 FindTest test;
110 FindFree free;
111 /* These next three depend on the first two... */
112 gpointer data1;
113 gpointer data2;
114 gint value;
117 struct _Eval
119 EvalCalc calc;
120 EvalFree free;
121 gpointer data1;
122 gpointer data2;
125 #define EAT ((*expression)++)
126 #define NEXT (**expression)
127 #define SKIP while (NEXT == ' ' || NEXT == '\t') EAT
128 #define MATCH(word) (match(expression, word))
130 #ifndef S_ISVTX
131 # define S_ISVTX 0x0001000
132 #endif
134 /****************************************************************
135 * EXTERNAL INTERFACE *
136 ****************************************************************/
138 /* Take a string and parse it, returning a condition object which
139 * can be passed to find_test_condition() later. NULL if the string
140 * is not a valid expression.
142 FindCondition *find_compile(const gchar *string)
144 FindCondition *cond;
145 const gchar **expression = &string;
147 g_return_val_if_fail(string != NULL, NULL);
149 cond = parse_expression(expression);
150 if (!cond)
151 return NULL;
153 SKIP;
154 if (NEXT != '\0')
156 cond->free(cond);
157 cond = NULL;
160 return cond;
163 gboolean find_test_condition(FindCondition *condition, FindInfo *info)
165 g_return_val_if_fail(condition != NULL, FALSE);
166 g_return_val_if_fail(info != NULL, FALSE);
168 return condition->test(condition, info);
171 void find_condition_free(FindCondition *condition)
173 if (condition)
174 condition->free(condition);
177 /****************************************************************
178 * INTERNAL FUNCTIONS *
179 ****************************************************************/
181 /* Call this when you've just eaten '('. Returns the string upto the
182 * matching ')' (and eats that bracket), or NULL on failure.
183 * Brackets within the string may be quoted or escaped.
185 static gchar *get_bracketed_string(const gchar **expression)
187 GString *str;
188 int opens = 1;
189 gchar quote = '\0';
191 str = g_string_new(NULL);
193 while (NEXT != '\0')
195 gchar c = NEXT;
197 EAT;
199 if (quote == '\0')
201 if (c == '\'' || c == '"')
202 quote = c; /* Start quoted section */
203 else if (c == '\\' && (NEXT == '(' || NEXT == ')'))
205 c = NEXT;
206 EAT;
208 else if (c == ')')
210 opens--;
211 if (opens < 1)
213 gchar *retval = str->str;
215 g_string_free(str, FALSE);
216 return retval;
219 else if (c == '(')
220 opens++;
222 else if (c == quote)
223 quote = '\0'; /* End quoted section */
224 else if (c == '\\' && NEXT == quote)
226 g_string_append_c(str, c);
227 c = NEXT;
228 EAT;
231 g_string_append_c(str, c);
234 g_string_free(str, TRUE);
236 return NULL;
240 /* TESTING CODE */
242 static gboolean test_prune(FindCondition *condition, FindInfo *info)
244 info->prune = TRUE;
245 return FALSE;
248 static gboolean test_leaf(FindCondition *condition, FindInfo *info)
250 return fnmatch(condition->data1, info->leaf, 0) == 0;
253 static gboolean test_path(FindCondition *condition, FindInfo *info)
255 return fnmatch(condition->data1, info->fullpath, FNM_PATHNAME) == 0;
258 static gboolean test_system(FindCondition *condition, FindInfo *info)
260 gchar *command = (gchar *) condition->data1;
261 gchar *start = command;
262 GString *to_sys = NULL;
263 gchar *perc;
264 int retcode;
266 to_sys = g_string_new(NULL);
268 while ((perc = strchr(command, '%')))
270 if (perc > start && perc[-1] == '\\')
271 perc--;
273 while (command < perc)
274 g_string_append_c(to_sys, *(command++));
276 if (*perc == '%')
277 g_string_append(to_sys, info->fullpath);
278 else
280 g_string_append_c(to_sys, '%');
281 perc++;
284 command = perc + 1;
287 g_string_append(to_sys, command);
289 retcode = system(to_sys->str);
291 g_string_free(to_sys, TRUE);
293 return retcode == 0;
296 static gboolean test_OR(FindCondition *condition, FindInfo *info)
298 FindCondition *first = (FindCondition *) condition->data1;
299 FindCondition *second = (FindCondition *) condition->data2;
301 return first->test(first, info) || second->test(second, info);
304 static gboolean test_AND(FindCondition *condition, FindInfo *info)
306 FindCondition *first = (FindCondition *) condition->data1;
307 FindCondition *second = (FindCondition *) condition->data2;
309 return first->test(first, info) && second->test(second, info);
312 static gboolean test_neg(FindCondition *condition, FindInfo *info)
314 FindCondition *first = (FindCondition *) condition->data1;
316 return !first->test(first, info);
319 static gboolean test_is(FindCondition *condition, FindInfo *info)
321 mode_t mode = info->stats.st_mode;
323 switch ((IsTest) condition->value)
325 case IS_DIR:
326 return S_ISDIR(mode);
327 case IS_REG:
328 return S_ISREG(mode);
329 case IS_LNK:
330 return S_ISLNK(mode);
331 case IS_FIFO:
332 return S_ISFIFO(mode);
333 case IS_SOCK:
334 return S_ISSOCK(mode);
335 case IS_CHR:
336 return S_ISCHR(mode);
337 case IS_BLK:
338 return S_ISBLK(mode);
339 case IS_DEV:
340 return S_ISCHR(mode)
341 || S_ISBLK(mode);
342 case IS_DOOR:
343 return S_ISDOOR(mode);
344 case IS_SUID:
345 return (mode & S_ISUID) != 0;
346 case IS_SGID:
347 return (mode & S_ISGID) != 0;
348 case IS_STICKY:
349 return (mode & S_ISVTX) != 0;
350 /* NOTE: access() uses uid, not euid. Shouldn't matter? */
351 case IS_READABLE:
352 return access(info->fullpath, R_OK) == 0;
353 case IS_WRITEABLE:
354 return access(info->fullpath, W_OK) == 0;
355 case IS_EXEC:
356 return access(info->fullpath, X_OK) == 0;
357 case IS_EMPTY:
358 return info->stats.st_size == 0;
359 case IS_MINE:
360 return info->stats.st_uid == euid;
363 return FALSE;
366 static gboolean test_comp(FindCondition *condition, FindInfo *info)
368 Eval *first = (Eval *) condition->data1;
369 Eval *second = (Eval *) condition->data2;
370 double a, b;
372 a = first->calc(first, info);
373 b = second->calc(second, info);
375 switch ((CompType) condition->value)
377 case COMP_LT:
378 return a < b;
379 case COMP_LE:
380 return a <= b;
381 case COMP_EQ:
382 return a == b;
383 case COMP_NE:
384 return a != b;
385 case COMP_GE:
386 return a >= b;
387 case COMP_GT:
388 return a > b;
391 return FALSE;
394 /* FREEING CODE */
396 /* Frees the structure and g_free()s both data items (NULL is OK) */
397 static void free_simple(FindCondition *condition)
399 g_return_if_fail(condition != NULL);
401 g_free(condition->data1);
402 g_free(condition->data2);
403 g_free(condition);
406 /* Treats data1 and data2 as conditions (or NULL) and frees recursively */
407 static void free_branch(FindCondition *condition)
409 FindCondition *first = (FindCondition *) condition->data1;
410 FindCondition *second = (FindCondition *) condition->data2;
412 if (first)
413 first->free(first);
414 if (second)
415 second->free(second);
416 g_free(condition);
419 /* Treats data1 and data2 as evals (or NULL) and frees recursively */
420 static void free_comp(FindCondition *condition)
422 Eval *first = (Eval *) condition->data1;
423 Eval *second = (Eval *) condition->data2;
425 if (first)
426 first->free(first);
427 if (second)
428 second->free(second);
429 g_free(condition);
432 /* PARSING CODE */
434 /* These all work in the same way - you give them an expression and the
435 * parse as much as they can, returning a condition for everything that
436 * was parsed and updating the expression pointer to the first unknown
437 * token. NULL indicates an error.
441 /* An expression is a series of comma-separated cases, any of which
442 * may match.
444 static FindCondition *parse_expression(const gchar **expression)
446 FindCondition *first, *second, *cond;
448 first = parse_case(expression);
449 if (!first)
450 return NULL;
452 SKIP;
453 if (NEXT != ',')
454 return first;
455 EAT;
457 second = parse_expression(expression);
458 if (!second)
460 first->free(first);
461 return NULL;
464 cond = g_new(FindCondition, 1);
465 cond->test = &test_OR;
466 cond->free = &free_branch;
467 cond->data1 = first;
468 cond->data2 = second;
470 return cond;
473 static FindCondition *parse_case(const gchar **expression)
475 FindCondition *first, *second, *cond;
477 first = parse_condition(expression);
478 if (!first)
479 return NULL;
481 SKIP;
482 if (NEXT == '\0' || NEXT == ',' || NEXT == ')')
483 return first;
485 (void) MATCH(_("And"));
487 second = parse_case(expression);
488 if (!second)
490 first->free(first);
491 return NULL;
494 cond = g_new(FindCondition, 1);
495 cond->test = &test_AND;
496 cond->free = &free_branch;
497 cond->data1 = first;
498 cond->data2 = second;
500 return cond;
503 static FindCondition *parse_condition(const gchar **expression)
505 FindCondition *cond = NULL;
507 SKIP;
509 if (NEXT == '!' || MATCH(_("Not")))
511 FindCondition *operand;
513 EAT;
515 operand = parse_condition(expression);
516 if (!operand)
517 return NULL;
518 cond = g_new(FindCondition, 1);
519 cond->test = test_neg;
520 cond->free = free_branch;
521 cond->data1 = operand;
522 cond->data2 = NULL;
523 return cond;
526 if (NEXT == '(')
528 FindCondition *subcond;
530 EAT;
532 subcond = parse_expression(expression);
533 if (!subcond)
534 return NULL;
535 SKIP;
536 if (NEXT != ')')
538 subcond->free(subcond);
539 return NULL;
542 EAT;
543 return subcond;
546 if (NEXT == '\'')
548 EAT;
549 return parse_match(expression);
552 if (MATCH(_("system")))
554 SKIP;
555 if (NEXT != '(')
556 return NULL;
557 EAT;
558 return parse_system(expression);
560 else if (MATCH(_("prune")))
562 cond = g_new(FindCondition, 1);
563 cond->test = test_prune;
564 cond->free = (FindFree) g_free;
565 cond->data1 = NULL;
566 cond->data2 = NULL;
568 return cond;
571 cond = parse_dash(expression);
572 if (cond)
573 return cond;
575 cond = parse_is(expression);
576 if (cond)
577 return cond;
579 return parse_comparison(expression);
582 /* Call this when you've just eaten 'system(' */
583 static FindCondition *parse_system(const gchar **expression)
585 FindCondition *cond = NULL;
586 gchar *command_string;
588 command_string = get_bracketed_string(expression);
589 if (!command_string)
590 return NULL;
592 cond = g_new(FindCondition, 1);
593 cond->test = test_system;
594 cond->free = &free_simple;
595 cond->data1 = command_string;
596 cond->data2 = NULL;
598 return cond;
601 static FindCondition *parse_comparison(const gchar **expression)
603 FindCondition *cond = NULL;
604 Eval *first;
605 Eval *second;
606 CompType comp;
608 SKIP;
610 first = parse_eval(expression);
611 if (!first)
612 return NULL;
614 SKIP;
615 if (NEXT == '=')
617 comp = COMP_EQ;
618 EAT;
620 else if (NEXT == '>')
622 EAT;
623 if (NEXT == '=')
625 EAT;
626 comp = COMP_GE;
628 else
629 comp = COMP_GT;
631 else if (NEXT == '<')
633 EAT;
634 if (NEXT == '=')
636 EAT;
637 comp = COMP_LE;
639 else
640 comp = COMP_LT;
642 else if (NEXT == '!' && (*expression)[1] == '=')
644 EAT;
645 EAT;
646 comp = COMP_NE;
648 else if (MATCH(_("After")))
649 comp = COMP_GT;
650 else if (MATCH(_("Before")))
651 comp = COMP_LT;
652 else
653 return NULL;
655 SKIP;
656 second = parse_eval(expression);
657 if (!second)
659 first->free(first);
660 return NULL;
663 cond = g_new(FindCondition, 1);
664 cond->test = &test_comp;
665 cond->free = (FindFree) &free_comp;
666 cond->data1 = first;
667 cond->data2 = second;
668 cond->value = comp;
670 return cond;
673 static FindCondition *parse_dash(const gchar **expression)
675 const gchar *exp = *expression;
676 FindCondition *cond, *retval = NULL;
677 IsTest test;
678 int i = 1;
680 if (NEXT != '-')
681 return NULL;
683 while (exp[i] && !g_ascii_isspace(exp[i]))
685 switch (exp[i])
687 case 'f': test = IS_REG; break;
688 case 'l': test = IS_LNK; break;
689 case 'd': test = IS_DIR; break;
690 case 'b': test = IS_BLK; break;
691 case 'c': test = IS_CHR; break;
692 case 'D': test = IS_DEV; break;
693 case 'p': test = IS_FIFO; break;
694 case 'S': test = IS_SOCK; break;
695 case 'O': test = IS_DOOR; break;
696 case 'u': test = IS_SUID; break;
697 case 'g': test = IS_SGID; break;
698 case 'k': test = IS_STICKY; break;
699 case 'r': test = IS_READABLE; break;
700 case 'w': test = IS_WRITEABLE; break;
701 case 'x': test = IS_EXEC; break;
702 case 'o': test = IS_MINE; break;
703 case 'z': test = IS_EMPTY; break;
704 default:
705 find_condition_free(retval);
706 return NULL;
708 i++;
710 cond = g_new(FindCondition, 1);
711 cond->test = &test_is;
712 cond->free = (FindFree) &g_free;
713 cond->data1 = NULL;
714 cond->data2 = NULL;
715 cond->value = test;
717 if (retval)
719 FindCondition *new;
721 new = g_new(FindCondition, 1);
722 new->test = &test_AND;
723 new->free = &free_branch;
724 new->data1 = retval;
725 new->data2 = cond;
727 retval = new;
729 else
730 retval = cond;
733 (*expression) += i;
735 return retval;
738 /* Returns NULL if expression is not an is-expression */
739 static FindCondition *parse_is(const gchar **expression)
741 FindCondition *cond;
742 IsTest test;
744 if (MATCH(_("IsReg")))
745 test = IS_REG;
746 else if (MATCH(_("IsLink")))
747 test = IS_LNK;
748 else if (MATCH(_("IsDir")))
749 test = IS_DIR;
750 else if (MATCH(_("IsChar")))
751 test = IS_CHR;
752 else if (MATCH(_("IsBlock")))
753 test = IS_BLK;
754 else if (MATCH(_("IsDev")))
755 test = IS_DEV;
756 else if (MATCH(_("IsPipe")))
757 test = IS_FIFO;
758 else if (MATCH(_("IsSocket")))
759 test = IS_SOCK;
760 else if (MATCH(_("IsDoor")))
761 test = IS_DOOR;
762 else if (MATCH(_("IsSUID")))
763 test = IS_SUID;
764 else if (MATCH(_("IsSGID")))
765 test = IS_SGID;
766 else if (MATCH(_("IsSticky")))
767 test = IS_STICKY;
768 else if (MATCH(_("IsReadable")))
769 test = IS_READABLE;
770 else if (MATCH(_("IsWriteable")))
771 test = IS_WRITEABLE;
772 else if (MATCH(_("IsExecutable")))
773 test = IS_EXEC;
774 else if (MATCH(_("IsEmpty")))
775 test = IS_EMPTY;
776 else if (MATCH(_("IsMine")))
777 test = IS_MINE;
778 else
779 return NULL;
781 cond = g_new(FindCondition, 1);
782 cond->test = &test_is;
783 cond->free = (FindFree) &g_free;
784 cond->data1 = NULL;
785 cond->data2 = NULL;
786 cond->value = test;
788 return cond;
791 /* Call this just after reading a ' */
792 static FindCondition *parse_match(const gchar **expression)
794 FindCondition *cond = NULL;
795 GString *str;
796 FindTest test = &test_leaf;
797 str = g_string_new(NULL);
799 while (NEXT != '\'')
801 gchar c = NEXT;
803 if (c == '\0')
804 goto out;
805 EAT;
807 if (c == '\\' && NEXT == '\'')
809 c = NEXT;
810 EAT;
813 if (c == '/')
814 test = &test_path;
816 g_string_append_c(str, c);
818 EAT;
820 cond = g_new(FindCondition, 1);
821 cond->test = test;
822 cond->free = &free_simple;
823 cond->data1 = str->str;
824 cond->data2 = NULL;
826 out:
827 g_string_free(str, cond ? FALSE : TRUE);
829 return cond;
832 /* NUMERIC EXPRESSIONS */
834 /* CALCULATIONS */
836 static double get_constant(Eval *eval, FindInfo *info)
838 double value = *((double *) (eval->data1));
839 gint flags = GPOINTER_TO_INT(eval->data2);
841 if (flags & FLAG_AGO)
842 value = info->now - value;
843 else if (flags & FLAG_HENCE)
844 value = info->now + value;
846 return value;
849 static double get_var(Eval *eval, FindInfo *info)
851 switch ((VarType) eval->data1)
853 case V_ATIME:
854 return info->stats.st_atime;
855 case V_CTIME:
856 return info->stats.st_ctime;
857 case V_MTIME:
858 return info->stats.st_mtime;
859 case V_SIZE:
860 return info->stats.st_size;
861 case V_INODE:
862 return info->stats.st_ino;
863 case V_NLINKS:
864 return info->stats.st_nlink;
865 case V_UID:
866 return info->stats.st_uid;
867 case V_GID:
868 return info->stats.st_gid;
869 case V_BLOCKS:
870 return info->stats.st_blocks;
873 return 0;
876 /* FREEING */
878 static void free_constant(Eval *eval)
880 g_free(eval->data1);
881 g_free(eval);
884 /* PARSING */
886 /* Parse something that evaluates to a number.
887 * This function tries to get a constant - if it fails then it tries
888 * interpreting the next token as a variable.
890 static Eval *parse_eval(const gchar **expression)
892 const char *start;
893 char *end;
894 double value;
895 Eval *eval;
896 gint flags = 0;
898 SKIP;
899 start = *expression;
900 value = strtol(start, &end, 0);
902 if (end == start)
904 if (MATCH(_("Now")))
906 value = 0;
907 flags |= FLAG_HENCE;
909 else
910 return parse_variable(expression);
912 else
913 *expression = end;
915 SKIP;
917 if (MATCH(_("Byte")) || MATCH(_("Bytes")))
919 else if (MATCH(_("Kb")) || MATCH(_("K")))
920 value *= 1<<10;
921 else if (MATCH(_("Mb")) || MATCH(_("M")))
922 value *= 1<<20;
923 else if (MATCH(_("Gb")) || MATCH(_("G")))
924 value *= 1<<30;
925 else if (MATCH(_("Sec")) || MATCH(_("Secs")))
927 else if (MATCH(_("Min")) || MATCH(_("Mins")))
928 value *= 60;
929 else if (MATCH(_("Hour")) || MATCH(_("Hours")))
930 value *= 60 * 60;
931 else if (MATCH(_("Day")) || MATCH(_("Days")))
932 value *= 60 * 60 * 24;
933 else if (MATCH(_("Week")) || MATCH(_("Weeks")))
934 value *= 60 * 60 * 24 * 7;
935 else if (MATCH(_("Year")) || MATCH(_("Years")))
936 value *= 60 * 60 * 24 * 7 * 365.25;
938 eval = g_new(Eval, 1);
939 eval->calc = &get_constant;
940 eval->free = &free_constant;
941 eval->data1 = g_memdup(&value, sizeof(value));
943 SKIP;
944 if (MATCH(_("Ago")))
945 flags |= FLAG_AGO;
946 else if (MATCH(_("Hence")))
947 flags |= FLAG_HENCE;
949 eval->data2 = GINT_TO_POINTER(flags);
951 return eval;
954 static Eval *parse_variable(const gchar **expression)
956 Eval *eval;
957 VarType var;
959 SKIP;
961 if (MATCH(_("atime")))
962 var = V_ATIME;
963 else if (MATCH(_("ctime")))
964 var = V_CTIME;
965 else if (MATCH(_("mtime")))
966 var = V_MTIME;
967 else if (MATCH(_("size")))
968 var = V_SIZE;
969 else if (MATCH(_("inode")))
970 var = V_INODE;
971 else if (MATCH(_("nlinks")))
972 var = V_NLINKS;
973 else if (MATCH(_("uid")))
974 var = V_UID;
975 else if (MATCH(_("gid")))
976 var = V_GID;
977 else if (MATCH(_("blocks")))
978 var = V_BLOCKS;
979 else
980 return NULL;
982 eval = g_new(Eval, 1);
983 eval->calc = &get_var;
984 eval->free = (EvalFree) &g_free;
985 eval->data1 = (gpointer) var;
986 eval->data2 = NULL;
988 return eval;
991 static gboolean match(const gchar **expression, const gchar *word)
993 int len;
995 len = strlen(word);
996 if (g_strncasecmp(*expression, word, len))
997 return FALSE;
999 if (g_ascii_isalpha(*(*expression + len)))
1000 return FALSE;
1002 (*expression) += len;
1004 return TRUE;