r1066: Fixed some compiler warnings (Vincent Lef�vre).
[rox-filer.git] / ROX-Filer / src / find.c
blob3dadee3f5a531740b8d59c064a369acd0268f901
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* find.c - processes the find conditions
24 * A Condition is a tree structure. Each node has a test() fn which
25 * can be used to see whether the current file matches, and a free() fn
26 * which frees it. Both will recurse down the tree as needed.
29 #include "config.h"
31 #include <string.h>
32 #include <fnmatch.h>
33 #include <ctype.h>
34 #include <unistd.h>
35 #include <stdlib.h>
36 #include <time.h>
38 #include "global.h"
40 #include "main.h"
41 #include "find.h"
43 typedef struct _Eval Eval;
45 /* Static prototypes */
46 static FindCondition *parse_expression(guchar **expression);
47 static FindCondition *parse_case(guchar **expression);
48 static FindCondition *parse_system(guchar **expression);
49 static FindCondition *parse_condition(guchar **expression);
50 static FindCondition *parse_match(guchar **expression);
51 static FindCondition *parse_comparison(guchar **expression);
52 static FindCondition *parse_dash(guchar **expression);
53 static FindCondition *parse_is(guchar **expression);
54 static Eval *parse_eval(guchar **expression);
55 static Eval *parse_variable(guchar **expression);
57 static gboolean match(guchar **expression, guchar *word);
59 typedef enum {
60 IS_DIR,
61 IS_REG,
62 IS_LNK,
63 IS_FIFO,
64 IS_SOCK,
65 IS_CHR,
66 IS_BLK,
67 IS_DEV,
68 IS_SUID,
69 IS_SGID,
70 IS_STICKY,
71 IS_READABLE,
72 IS_WRITEABLE,
73 IS_EXEC,
74 IS_EMPTY,
75 IS_MINE,
76 } IsTest;
78 typedef enum {
79 COMP_LT,
80 COMP_LE,
81 COMP_EQ,
82 COMP_NE,
83 COMP_GE,
84 COMP_GT,
85 } CompType;
87 typedef enum {
88 V_ATIME,
89 V_CTIME,
90 V_MTIME,
91 V_SIZE,
92 V_INODE,
93 V_NLINKS,
94 V_UID,
95 V_GID,
96 V_BLOCKS,
97 } VarType;
99 enum
101 FLAG_AGO = 1 << 0,
102 FLAG_HENCE = 1 << 1,
105 typedef long (*EvalCalc)(Eval *eval, FindInfo *info);
106 typedef void (*EvalFree)(Eval *eval);
108 struct _FindCondition
110 FindTest test;
111 FindFree free;
112 /* These next three depend on the first two... */
113 gpointer data1;
114 gpointer data2;
115 gint value;
118 struct _Eval
120 EvalCalc calc;
121 EvalFree free;
122 gpointer data1;
123 gpointer data2;
126 #define EAT ((*expression)++)
127 #define NEXT (**expression)
128 #define SKIP while (NEXT == ' ' || NEXT == '\t') EAT
129 #define MATCH(word) (match(expression, word))
131 #ifndef S_ISVTX
132 # define S_ISVTX 0x0001000
133 #endif
135 /****************************************************************
136 * EXTERNAL INTERFACE *
137 ****************************************************************/
139 /* Take a string and parse it, returning a condition object which
140 * can be passed to find_test_condition() later. NULL if the string
141 * is not a valid expression.
143 FindCondition *find_compile(guchar *string)
145 FindCondition *cond;
146 guchar **expression = &string;
148 g_return_val_if_fail(string != NULL, NULL);
150 cond = parse_expression(expression);
151 if (!cond)
152 return NULL;
154 SKIP;
155 if (NEXT != '\0')
157 cond->free(cond);
158 cond = NULL;
161 return cond;
164 gboolean find_test_condition(FindCondition *condition, FindInfo *info)
166 g_return_val_if_fail(condition != NULL, FALSE);
167 g_return_val_if_fail(info != NULL, FALSE);
169 return condition->test(condition, info);
172 void find_condition_free(FindCondition *condition)
174 g_return_if_fail(condition != NULL);
176 condition->free(condition);
179 /****************************************************************
180 * INTERNAL FUNCTIONS *
181 ****************************************************************/
183 /* Call this when you've just eaten '('. Returns the string upto the
184 * matching ')' (and eats that bracket), or NULL on failure.
185 * Brackets within the string may be quoted or escaped.
187 static guchar *get_bracketed_string(guchar **expression)
189 GString *str;
190 int opens = 1;
191 guchar quote = '\0';
193 str = g_string_new(NULL);
195 while (NEXT != '\0')
197 guchar c = NEXT;
199 EAT;
201 if (quote == '\0')
203 if (c == '\'' || c == '"')
204 quote = c; /* Start quoted section */
205 else if (c == '\\' && (NEXT == '(' || NEXT == ')'))
207 c = NEXT;
208 EAT;
210 else if (c == ')')
212 opens--;
213 if (opens < 1)
215 guchar *retval = str->str;
217 g_string_free(str, FALSE);
218 return retval;
221 else if (c == '(')
222 opens++;
224 else if (c == quote)
225 quote = '\0'; /* End quoted section */
226 else if (c == '\\' && NEXT == quote)
228 g_string_append_c(str, c);
229 c = NEXT;
230 EAT;
233 g_string_append_c(str, c);
236 g_string_free(str, TRUE);
238 return NULL;
242 /* TESTING CODE */
244 static gboolean test_prune(FindCondition *condition, FindInfo *info)
246 info->prune = TRUE;
247 return FALSE;
250 static gboolean test_leaf(FindCondition *condition, FindInfo *info)
252 return fnmatch(condition->data1, info->leaf, 0) == 0;
255 static gboolean test_path(FindCondition *condition, FindInfo *info)
257 return fnmatch(condition->data1, info->fullpath, FNM_PATHNAME) == 0;
260 static gboolean test_system(FindCondition *condition, FindInfo *info)
262 guchar *command = (guchar *) condition->data1;
263 guchar *start = command;
264 GString *to_sys = NULL;
265 guchar *perc;
266 int retcode;
268 to_sys = g_string_new(NULL);
270 while ((perc = strchr(command, '%')))
272 if (perc > start && perc[-1] == '\\')
273 perc--;
275 while (command < perc)
276 g_string_append_c(to_sys, *(command++));
278 if (*perc == '%')
279 g_string_append(to_sys, info->fullpath);
280 else
282 g_string_append_c(to_sys, '%');
283 perc++;
286 command = perc + 1;
289 g_string_append(to_sys, command);
291 retcode = system(to_sys->str);
293 g_string_free(to_sys, TRUE);
295 return retcode == 0;
298 static gboolean test_OR(FindCondition *condition, FindInfo *info)
300 FindCondition *first = (FindCondition *) condition->data1;
301 FindCondition *second = (FindCondition *) condition->data2;
303 return first->test(first, info) || second->test(second, info);
306 static gboolean test_AND(FindCondition *condition, FindInfo *info)
308 FindCondition *first = (FindCondition *) condition->data1;
309 FindCondition *second = (FindCondition *) condition->data2;
311 return first->test(first, info) && second->test(second, info);
314 static gboolean test_neg(FindCondition *condition, FindInfo *info)
316 FindCondition *first = (FindCondition *) condition->data1;
318 return !first->test(first, info);
321 static gboolean test_is(FindCondition *condition, FindInfo *info)
323 mode_t mode = info->stats.st_mode;
325 switch ((IsTest) condition->value)
327 case IS_DIR:
328 return S_ISDIR(mode);
329 case IS_REG:
330 return S_ISREG(mode);
331 case IS_LNK:
332 return S_ISLNK(mode);
333 case IS_FIFO:
334 return S_ISFIFO(mode);
335 case IS_SOCK:
336 return S_ISSOCK(mode);
337 case IS_CHR:
338 return S_ISCHR(mode);
339 case IS_BLK:
340 return S_ISBLK(mode);
341 case IS_DEV:
342 return S_ISCHR(mode)
343 || S_ISBLK(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 long 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(guchar **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(guchar **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(guchar **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(guchar **expression)
585 FindCondition *cond = NULL;
586 guchar *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(guchar **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(guchar **expression)
675 guchar *exp = *expression;
676 FindCondition *cond, *retval = NULL;
677 IsTest test;
678 int i = 1;
680 if (NEXT != '-')
681 return NULL;
683 while (exp[i] && !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 'u': test = IS_SUID; break;;
696 case 'g': test = IS_SGID; break;;
697 case 'k': test = IS_STICKY; break;;
698 case 'r': test = IS_READABLE; break;;
699 case 'w': test = IS_WRITEABLE; break;;
700 case 'x': test = IS_EXEC; break;;
701 case 'o': test = IS_MINE; break;;
702 case 'z': test = IS_EMPTY; break;;
703 default:
704 if (retval)
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(guchar **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(_("IsSUID")))
761 test = IS_SUID;
762 else if (MATCH(_("IsSGID")))
763 test = IS_SGID;
764 else if (MATCH(_("IsSticky")))
765 test = IS_STICKY;
766 else if (MATCH(_("IsReadable")))
767 test = IS_READABLE;
768 else if (MATCH(_("IsWriteable")))
769 test = IS_WRITEABLE;
770 else if (MATCH(_("IsExecutable")))
771 test = IS_EXEC;
772 else if (MATCH(_("IsEmpty")))
773 test = IS_EMPTY;
774 else if (MATCH(_("IsMine")))
775 test = IS_MINE;
776 else
777 return NULL;
779 cond = g_new(FindCondition, 1);
780 cond->test = &test_is;
781 cond->free = (FindFree) &g_free;
782 cond->data1 = NULL;
783 cond->data2 = NULL;
784 cond->value = test;
786 return cond;
789 /* Call this just after reading a ' */
790 static FindCondition *parse_match(guchar **expression)
792 FindCondition *cond = NULL;
793 GString *str;
794 FindTest test = &test_leaf;
795 str = g_string_new(NULL);
797 while (NEXT != '\'')
799 guchar c = NEXT;
801 if (c == '\0')
802 goto out;
803 EAT;
805 if (c == '\\' && NEXT == '\'')
807 c = NEXT;
808 EAT;
811 if (c == '/')
812 test = &test_path;
814 g_string_append_c(str, c);
816 EAT;
818 cond = g_new(FindCondition, 1);
819 cond->test = test;
820 cond->free = &free_simple;
821 cond->data1 = str->str;
822 cond->data2 = NULL;
824 out:
825 g_string_free(str, cond ? FALSE : TRUE);
827 return cond;
830 /* NUMERIC EXPRESSIONS */
832 /* CALCULATIONS */
834 static long get_constant(Eval *eval, FindInfo *info)
836 long value = *((long *) (eval->data1));
837 gint flags = GPOINTER_TO_INT(eval->data2);
839 if (flags & FLAG_AGO)
840 value = info->now - value;
841 else if (flags & FLAG_HENCE)
842 value = info->now + value;
844 return value;
847 static long get_var(Eval *eval, FindInfo *info)
849 switch ((VarType) eval->data1)
851 case V_ATIME:
852 return info->stats.st_atime;
853 case V_CTIME:
854 return info->stats.st_ctime;
855 case V_MTIME:
856 return info->stats.st_mtime;
857 case V_SIZE:
858 return info->stats.st_size;
859 case V_INODE:
860 return info->stats.st_ino;
861 case V_NLINKS:
862 return info->stats.st_nlink;
863 case V_UID:
864 return info->stats.st_uid;
865 case V_GID:
866 return info->stats.st_gid;
867 case V_BLOCKS:
868 return info->stats.st_blocks;
871 return 0L;
874 /* FREEING */
876 static void free_constant(Eval *eval)
878 g_free(eval->data1);
879 g_free(eval);
882 /* PARSING */
884 /* Parse something that evaluates to a number.
885 * This function tries to get a constant - if it fails then it tries
886 * interpreting the next token as a variable.
888 static Eval *parse_eval(guchar **expression)
890 char *start, *end;
891 long value;
892 Eval *eval;
893 gint flags = 0;
895 SKIP;
896 start = *expression;
897 value = strtol(start, &end, 0);
899 if (end == start)
901 if (MATCH(_("Now")))
903 value = 0;
904 flags |= FLAG_HENCE;
906 else
907 return parse_variable(expression);
909 else
910 *expression = end;
912 SKIP;
914 if (MATCH(_("Byte")) || MATCH(_("Bytes")))
916 else if (MATCH("Kb"))
917 value <<= 10;
918 else if (MATCH("Mb"))
919 value <<= 20;
920 else if (MATCH("Gb"))
921 value <<= 30;
922 else if (MATCH(_("Sec")) || MATCH(_("Secs")))
924 else if (MATCH(_("Min")) || MATCH(_("Mins")))
925 value *= 60;
926 else if (MATCH(_("Hour")) || MATCH(_("Hours")))
927 value *= 60 * 60;
928 else if (MATCH(_("Day")) || MATCH(_("Days")))
929 value *= 60 * 60 * 24;
930 else if (MATCH(_("Week")) || MATCH(_("Weeks")))
931 value *= 60 * 60 * 24 * 7;
932 else if (MATCH(_("Year")) || MATCH(_("Years")))
933 value *= 60 * 60 * 24 * 7 * 365.25;
935 eval = g_new(Eval, 1);
936 eval->calc = &get_constant;
937 eval->free = &free_constant;
938 eval->data1 = g_memdup(&value, sizeof(value));
940 SKIP;
941 if (MATCH(_("Ago")))
942 flags |= FLAG_AGO;
943 else if (MATCH(_("Hence")))
944 flags |= FLAG_HENCE;
946 eval->data2 = GINT_TO_POINTER(flags);
948 return eval;
951 static Eval *parse_variable(guchar **expression)
953 Eval *eval;
954 VarType var;
956 SKIP;
958 if (MATCH(_("atime")))
959 var = V_ATIME;
960 else if (MATCH(_("ctime")))
961 var = V_CTIME;
962 else if (MATCH(_("mtime")))
963 var = V_MTIME;
964 else if (MATCH(_("size")))
965 var = V_SIZE;
966 else if (MATCH(_("inode")))
967 var = V_INODE;
968 else if (MATCH(_("nlinks")))
969 var = V_NLINKS;
970 else if (MATCH(_("uid")))
971 var = V_UID;
972 else if (MATCH(_("gid")))
973 var = V_GID;
974 else if (MATCH(_("blocks")))
975 var = V_BLOCKS;
976 else
977 return NULL;
979 eval = g_new(Eval, 1);
980 eval->calc = &get_var;
981 eval->free = (EvalFree) &g_free;
982 eval->data1 = (gpointer) var;
983 eval->data2 = NULL;
985 return eval;
988 static gboolean match(guchar **expression, guchar *word)
990 int len;
992 len = strlen(word);
993 if (g_strncasecmp(*expression, word, len))
994 return FALSE;
996 if (isalpha(*(*expression + len)))
997 return FALSE;
999 (*expression) += len;
1001 return TRUE;