r252: Fixed a slight bug in the Find code; expressions couldn't be parsed due
[rox-filer/dt.git] / ROX-Filer / src / find.c
blob12606a58e726c4a7e0d3d1c756f30c949a134760
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
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 "main.h"
39 #include "find.h"
41 typedef struct _Eval Eval;
43 /* Static prototypes */
44 static FindCondition *parse_expression(guchar **expression);
45 static FindCondition *parse_case(guchar **expression);
46 static FindCondition *parse_system(guchar **expression);
47 static FindCondition *parse_condition(guchar **expression);
48 static FindCondition *parse_match(guchar **expression);
49 static FindCondition *parse_comparison(guchar **expression);
50 static FindCondition *parse_is(guchar **expression);
51 static Eval *parse_eval(guchar **expression);
52 static Eval *parse_variable(guchar **expression);
54 static gboolean match(guchar **expression, guchar *word);
56 typedef enum {
57 IS_DIR,
58 IS_REG,
59 IS_LNK,
60 IS_FIFO,
61 IS_SOCK,
62 IS_CHR,
63 IS_BLK,
64 IS_DEV,
65 IS_SUID,
66 IS_SGID,
67 IS_STICKY,
68 IS_READABLE,
69 IS_WRITEABLE,
70 IS_EXEC,
71 IS_EMPTY,
72 IS_MINE,
73 } IsTest;
75 typedef enum {
76 COMP_LT,
77 COMP_LE,
78 COMP_EQ,
79 COMP_NE,
80 COMP_GE,
81 COMP_GT,
82 } CompType;
84 typedef enum {
85 V_ATIME,
86 V_CTIME,
87 V_MTIME,
88 V_SIZE,
89 V_INODE,
90 V_NLINKS,
91 V_UID,
92 V_GID,
93 V_BLOCKS,
94 } VarType;
96 enum
98 FLAG_AGO = 1 << 0,
99 FLAG_HENCE = 1 << 1,
102 typedef long (*EvalCalc)(Eval *eval, FindInfo *info);
103 typedef void (*EvalFree)(Eval *eval);
105 struct _Eval
107 EvalCalc calc;
108 EvalFree free;
109 gpointer data1;
110 gpointer data2;
113 #define EAT ((*expression)++)
114 #define NEXT (**expression)
115 #define SKIP while (NEXT == ' ' || NEXT == '\t') EAT
116 #define MATCH(word) (match(expression, word))
118 #ifndef S_ISVTX
119 # define S_ISVTX 0x0001000
120 #endif
122 /****************************************************************
123 * EXTERNAL INTERFACE *
124 ****************************************************************/
126 /* Take a string and parse it, returning a condition object which
127 * can be passed to find_test_condition() later. NULL if the string
128 * is not a valid expression.
130 FindCondition *find_compile(guchar *string)
132 FindCondition *cond;
133 guchar **expression = &string;
135 g_return_val_if_fail(string != NULL, NULL);
137 cond = parse_expression(expression);
138 if (!cond)
139 return NULL;
141 SKIP;
142 if (NEXT != '\0')
144 cond->free(cond);
145 cond = NULL;
148 return cond;
151 gboolean find_test_condition(FindCondition *condition, FindInfo *info)
153 g_return_val_if_fail(condition != NULL, FALSE);
154 g_return_val_if_fail(info != NULL, FALSE);
156 return condition->test(condition, info);
159 void find_condition_free(FindCondition *condition)
161 g_return_if_fail(condition != NULL);
163 condition->free(condition);
166 /****************************************************************
167 * INTERNAL FUNCTIONS *
168 ****************************************************************/
170 /* Call this when you've just eaten '('. Returns the string upto the
171 * matching ')' (and eats that bracket), or NULL on failure.
172 * Brackets within the string may be quoted or escaped.
174 static guchar *get_bracketed_string(guchar **expression)
176 GString *str;
177 int opens = 1;
178 guchar quote = '\0';
180 str = g_string_new(NULL);
182 while (NEXT != '\0')
184 guchar c = NEXT;
186 EAT;
188 if (quote == '\0')
190 if (c == '\'' || c == '"')
191 quote = c; /* Start quoted section */
192 else if (c == '\\' && (NEXT == '(' || NEXT == ')'))
194 c = NEXT;
195 EAT;
197 else if (c == ')')
199 opens--;
200 if (opens < 1)
202 guchar *retval = str->str;
204 g_string_free(str, FALSE);
205 return retval;
208 else if (c == '(')
209 opens++;
211 else if (c == quote)
212 quote = '\0'; /* End quoted section */
213 else if (c == '\\' && NEXT == quote)
215 g_string_append_c(str, c);
216 c = NEXT;
217 EAT;
220 g_string_append_c(str, c);
223 g_string_free(str, TRUE);
225 return NULL;
229 /* TESTING CODE */
231 static gboolean test_prune(FindCondition *condition, FindInfo *info)
233 info->prune = TRUE;
234 return FALSE;
237 static gboolean test_leaf(FindCondition *condition, FindInfo *info)
239 return fnmatch(condition->data1, info->leaf, 0) == 0;
242 static gboolean test_path(FindCondition *condition, FindInfo *info)
244 return fnmatch(condition->data1, info->fullpath, FNM_PATHNAME) == 0;
247 static gboolean test_system(FindCondition *condition, FindInfo *info)
249 guchar *command = (guchar *) condition->data1;
250 guchar *start = command;
251 GString *to_sys = NULL;
252 guchar *perc;
253 int retcode;
255 to_sys = g_string_new(NULL);
257 while ((perc = strchr(command, '%')))
259 if (perc > start && perc[-1] == '\\')
260 perc--;
262 while (command < perc)
263 g_string_append_c(to_sys, *(command++));
265 if (*perc == '%')
266 g_string_append(to_sys, info->fullpath);
267 else
269 g_string_append_c(to_sys, '%');
270 perc++;
273 command = perc + 1;
276 g_string_append(to_sys, command);
278 retcode = system(to_sys->str);
280 g_string_free(to_sys, TRUE);
282 return retcode == 0;
285 static gboolean test_OR(FindCondition *condition, FindInfo *info)
287 FindCondition *first = (FindCondition *) condition->data1;
288 FindCondition *second = (FindCondition *) condition->data2;
290 return first->test(first, info) || second->test(second, info);
293 static gboolean test_AND(FindCondition *condition, FindInfo *info)
295 FindCondition *first = (FindCondition *) condition->data1;
296 FindCondition *second = (FindCondition *) condition->data2;
298 return first->test(first, info) && second->test(second, info);
301 static gboolean test_neg(FindCondition *condition, FindInfo *info)
303 FindCondition *first = (FindCondition *) condition->data1;
305 return !first->test(first, info);
308 static gboolean test_is(FindCondition *condition, FindInfo *info)
310 mode_t mode = info->stats.st_mode;
312 switch ((IsTest) condition->value)
314 case IS_DIR:
315 return S_ISDIR(mode);
316 case IS_REG:
317 return S_ISREG(mode);
318 case IS_LNK:
319 return S_ISLNK(mode);
320 case IS_FIFO:
321 return S_ISFIFO(mode);
322 case IS_SOCK:
323 return S_ISSOCK(mode);
324 case IS_CHR:
325 return S_ISCHR(mode);
326 case IS_BLK:
327 return S_ISBLK(mode);
328 case IS_DEV:
329 return S_ISCHR(mode)
330 || S_ISBLK(mode);
331 case IS_SUID:
332 return (mode & S_ISUID) != 0;
333 case IS_SGID:
334 return (mode & S_ISGID) != 0;
335 case IS_STICKY:
336 return (mode & S_ISVTX) != 0;
337 /* NOTE: access() uses uid, not euid. Shouldn't matter? */
338 case IS_READABLE:
339 return access(info->fullpath, R_OK) == 0;
340 case IS_WRITEABLE:
341 return access(info->fullpath, W_OK) == 0;
342 case IS_EXEC:
343 return access(info->fullpath, X_OK) == 0;
344 case IS_EMPTY:
345 return info->stats.st_size == 0;
346 case IS_MINE:
347 return info->stats.st_uid == euid;
350 return FALSE;
353 static gboolean test_comp(FindCondition *condition, FindInfo *info)
355 Eval *first = (Eval *) condition->data1;
356 Eval *second = (Eval *) condition->data2;
357 long a, b;
359 a = first->calc(first, info);
360 b = second->calc(second, info);
362 switch ((CompType) condition->value)
364 case COMP_LT:
365 return a < b;
366 case COMP_LE:
367 return a <= b;
368 case COMP_EQ:
369 return a == b;
370 case COMP_NE:
371 return a != b;
372 case COMP_GE:
373 return a >= b;
374 case COMP_GT:
375 return a > b;
378 return FALSE;
381 /* FREEING CODE */
383 /* Frees the structure and g_free()s both data items (NULL is OK) */
384 static void free_simple(FindCondition *condition)
386 g_return_if_fail(condition != NULL);
388 g_free(condition->data1);
389 g_free(condition->data2);
390 g_free(condition);
393 /* Treats data1 and data2 as conditions (or NULL) and frees recursively */
394 static void free_branch(FindCondition *condition)
396 FindCondition *first = (FindCondition *) condition->data1;
397 FindCondition *second = (FindCondition *) condition->data2;
399 if (first)
400 first->free(first);
401 if (second)
402 second->free(second);
403 g_free(condition);
406 /* Treats data1 and data2 as evals (or NULL) and frees recursively */
407 static void free_comp(FindCondition *condition)
409 Eval *first = (Eval *) condition->data1;
410 Eval *second = (Eval *) condition->data2;
412 if (first)
413 first->free(first);
414 if (second)
415 second->free(second);
416 g_free(condition);
419 /* PARSING CODE */
421 /* These all work in the same way - you give them an expression and the
422 * parse as much as they can, returning a condition for everything that
423 * was parsed and updating the expression pointer to the first unknown
424 * token. NULL indicates an error.
428 /* An expression is a series of comma-separated cases, any of which
429 * may match.
431 static FindCondition *parse_expression(guchar **expression)
433 FindCondition *first, *second, *cond;
435 first = parse_case(expression);
436 if (!first)
437 return NULL;
439 SKIP;
440 if (NEXT != ',')
441 return first;
442 EAT;
444 second = parse_expression(expression);
445 if (!second)
447 first->free(first);
448 return NULL;
451 cond = g_new(FindCondition, 1);
452 cond->test = &test_OR;
453 cond->free = &free_branch;
454 cond->data1 = first;
455 cond->data2 = second;
457 return cond;
460 static FindCondition *parse_case(guchar **expression)
462 FindCondition *first, *second, *cond;
464 first = parse_condition(expression);
465 if (!first)
466 return NULL;
468 SKIP;
469 if (NEXT == '\0' || NEXT == ',' || NEXT == ')')
470 return first;
472 (void) MATCH(_("And"));
474 second = parse_case(expression);
475 if (!second)
477 first->free(first);
478 return NULL;
481 cond = g_new(FindCondition, 1);
482 cond->test = &test_AND;
483 cond->free = &free_branch;
484 cond->data1 = first;
485 cond->data2 = second;
487 return cond;
490 static FindCondition *parse_condition(guchar **expression)
492 FindCondition *cond = NULL;
494 SKIP;
496 if (NEXT == '!' || MATCH(_("Not")))
498 FindCondition *operand;
500 EAT;
502 operand = parse_condition(expression);
503 if (!operand)
504 return NULL;
505 cond = g_new(FindCondition, 1);
506 cond->test = test_neg;
507 cond->free = free_branch;
508 cond->data1 = operand;
509 cond->data2 = NULL;
510 return cond;
513 if (NEXT == '(')
515 FindCondition *subcond;
517 EAT;
519 subcond = parse_expression(expression);
520 if (!subcond)
521 return NULL;
522 SKIP;
523 if (NEXT != ')')
525 subcond->free(subcond);
526 return NULL;
529 EAT;
530 return subcond;
533 if (NEXT == '\'')
535 EAT;
536 return parse_match(expression);
539 if (MATCH(_("system")))
541 SKIP;
542 if (NEXT != '(')
543 return NULL;
544 EAT;
545 return parse_system(expression);
547 else if (MATCH(_("prune")))
549 cond = g_new(FindCondition, 1);
550 cond->test = test_prune;
551 cond->free = (FindFree) g_free;
552 cond->data1 = NULL;
553 cond->data2 = NULL;
555 return cond;
558 cond = parse_is(expression);
559 if (cond)
560 return cond;
562 return parse_comparison(expression);
565 /* Call this when you've just eaten 'system(' */
566 static FindCondition *parse_system(guchar **expression)
568 FindCondition *cond = NULL;
569 guchar *command_string;
571 command_string = get_bracketed_string(expression);
572 if (!command_string)
573 return NULL;
575 cond = g_new(FindCondition, 1);
576 cond->test = test_system;
577 cond->free = &free_simple;
578 cond->data1 = command_string;
579 cond->data2 = NULL;
581 return cond;
584 static FindCondition *parse_comparison(guchar **expression)
586 FindCondition *cond = NULL;
587 Eval *first;
588 Eval *second;
589 CompType comp;
591 SKIP;
593 first = parse_eval(expression);
594 if (!first)
595 return NULL;
597 SKIP;
598 if (NEXT == '=')
600 comp = COMP_EQ;
601 EAT;
603 else if (NEXT == '>')
605 EAT;
606 if (NEXT == '=')
608 EAT;
609 comp = COMP_GE;
611 else
612 comp = COMP_GT;
614 else if (NEXT == '<')
616 EAT;
617 if (NEXT == '=')
619 EAT;
620 comp = COMP_LE;
622 else
623 comp = COMP_LT;
625 else if (NEXT == '!' && (*expression)[1] == '=')
627 EAT;
628 EAT;
629 comp = COMP_NE;
631 else if (MATCH(_("After")))
632 comp = COMP_GT;
633 else if (MATCH(_("Before")))
634 comp = COMP_LT;
635 else
636 return NULL;
638 SKIP;
639 second = parse_eval(expression);
640 if (!second)
642 first->free(first);
643 return NULL;
646 cond = g_new(FindCondition, 1);
647 cond->test = &test_comp;
648 cond->free = (FindFree) &free_comp;
649 cond->data1 = first;
650 cond->data2 = second;
651 cond->value = comp;
653 return cond;
656 /* Returns NULL if expression is not an is-expression */
657 static FindCondition *parse_is(guchar **expression)
659 FindCondition *cond;
660 IsTest test;
662 if (MATCH(_("IsReg")))
663 test = IS_REG;
664 else if (MATCH(_("IsLink")))
665 test = IS_LNK;
666 else if (MATCH(_("IsDir")))
667 test = IS_DIR;
668 else if (MATCH(_("IsChar")))
669 test = IS_CHR;
670 else if (MATCH(_("IsBlock")))
671 test = IS_BLK;
672 else if (MATCH(_("IsDev")))
673 test = IS_DEV;
674 else if (MATCH(_("IsPipe")))
675 test = IS_FIFO;
676 else if (MATCH(_("IsSocket")))
677 test = IS_SOCK;
678 else if (MATCH(_("IsSUID")))
679 test = IS_SUID;
680 else if (MATCH(_("IsSGID")))
681 test = IS_SGID;
682 else if (MATCH(_("IsSticky")))
683 test = IS_STICKY;
684 else if (MATCH(_("IsReadable")))
685 test = IS_READABLE;
686 else if (MATCH(_("IsWriteable")))
687 test = IS_WRITEABLE;
688 else if (MATCH(_("IsExecutable")))
689 test = IS_EXEC;
690 else if (MATCH(_("IsEmpty")))
691 test = IS_EMPTY;
692 else if (MATCH(_("IsMine")))
693 test = IS_MINE;
694 else
695 return NULL;
697 cond = g_new(FindCondition, 1);
698 cond->test = &test_is;
699 cond->free = (FindFree) &g_free;
700 cond->data1 = NULL;
701 cond->data2 = NULL;
702 cond->value = test;
704 return cond;
707 /* Call this just after reading a ' */
708 static FindCondition *parse_match(guchar **expression)
710 FindCondition *cond = NULL;
711 GString *str;
712 FindTest test = &test_leaf;
713 str = g_string_new(NULL);
715 while (NEXT != '\'')
717 guchar c = NEXT;
719 if (c == '\0')
720 goto out;
721 EAT;
723 if (c == '\\' && NEXT == '\'')
725 c = NEXT;
726 EAT;
729 if (c == '/')
730 test = &test_path;
732 g_string_append_c(str, c);
734 EAT;
736 cond = g_new(FindCondition, 1);
737 cond->test = test;
738 cond->free = &free_simple;
739 cond->data1 = str->str;
740 cond->data2 = NULL;
742 out:
743 g_string_free(str, cond ? FALSE : TRUE);
745 return cond;
748 /* NUMERIC EXPRESSIONS */
750 /* CALCULATIONS */
752 static long get_constant(Eval *eval, FindInfo *info)
754 long value = *((long *) (eval->data1));
755 int flags = (int) (eval->data2);
757 if (flags & FLAG_AGO)
758 value = info->now - value;
759 else if (flags & FLAG_HENCE)
760 value = info->now + value;
762 return value;
765 static long get_var(Eval *eval, FindInfo *info)
767 switch ((VarType) eval->data1)
769 case V_ATIME:
770 return info->stats.st_atime;
771 case V_CTIME:
772 return info->stats.st_ctime;
773 case V_MTIME:
774 return info->stats.st_mtime;
775 case V_SIZE:
776 return info->stats.st_size;
777 case V_INODE:
778 return info->stats.st_ino;
779 case V_NLINKS:
780 return info->stats.st_nlink;
781 case V_UID:
782 return info->stats.st_uid;
783 case V_GID:
784 return info->stats.st_gid;
785 case V_BLOCKS:
786 return info->stats.st_blocks;
789 return 0L;
792 /* FREEING */
794 static void free_constant(Eval *eval)
796 g_free(eval->data1);
797 g_free(eval);
800 /* PARSING */
802 /* Parse something that evaluates to a number.
803 * This function tried to get a constant - if it fails then it tries
804 * interpreting the next token as a variable.
806 static Eval *parse_eval(guchar **expression)
808 char *start, *end;
809 long value;
810 Eval *eval;
811 int flags = 0;
813 SKIP;
814 start = *expression;
815 value = strtol(start, &end, 0);
817 if (end == start)
819 if (MATCH(_("Now")))
821 value = 0;
822 flags |= FLAG_HENCE;
824 else
825 return parse_variable(expression);
827 else
828 *expression = end;
830 SKIP;
832 if (MATCH(_("Byte")) || MATCH(_("Bytes")))
834 else if (MATCH("Kb"))
835 value <<= 10;
836 else if (MATCH("Mb"))
837 value <<= 20;
838 else if (MATCH("Gb"))
839 value <<= 30;
840 else if (MATCH(_("Sec")) || MATCH(_("Secs")))
842 else if (MATCH(_("Min")) || MATCH(_("Mins")))
843 value *= 60;
844 else if (MATCH(_("Hour")) || MATCH(_("Hours")))
845 value *= 60 * 60;
846 else if (MATCH(_("Day")) || MATCH(_("Days")))
847 value *= 60 * 60 * 24;
848 else if (MATCH(_("Week")) || MATCH(_("Weeks")))
849 value *= 60 * 60 * 24 * 7;
850 else if (MATCH(_("Year")) || MATCH(_("Years")))
851 value *= 60 * 60 * 24 * 7 * 365.25;
853 eval = g_new(Eval, 1);
854 eval->calc = &get_constant;
855 eval->free = &free_constant;
856 eval->data1 = g_memdup(&value, sizeof(value));
858 SKIP;
859 if (MATCH(_("Ago")))
860 flags |= FLAG_AGO;
861 else if (MATCH(_("Hence")))
862 flags |= FLAG_HENCE;
864 eval->data2 = (gpointer) flags;
866 return eval;
869 static Eval *parse_variable(guchar **expression)
871 Eval *eval;
872 VarType var;
874 SKIP;
876 if (MATCH(_("atime")))
877 var = V_ATIME;
878 else if (MATCH(_("ctime")))
879 var = V_CTIME;
880 else if (MATCH(_("mtime")))
881 var = V_MTIME;
882 else if (MATCH(_("size")))
883 var = V_SIZE;
884 else if (MATCH(_("inode")))
885 var = V_INODE;
886 else if (MATCH(_("nlinks")))
887 var = V_NLINKS;
888 else if (MATCH(_("uid")))
889 var = V_UID;
890 else if (MATCH(_("gid")))
891 var = V_GID;
892 else if (MATCH(_("blocks")))
893 var = V_BLOCKS;
894 else
895 return NULL;
897 eval = g_new(Eval, 1);
898 eval->calc = &get_var;
899 eval->free = (EvalFree) &g_free;
900 eval->data1 = (gpointer) var;
901 eval->data2 = NULL;
903 return eval;
906 static gboolean match(guchar **expression, guchar *word)
908 int len;
910 len = strlen(word);
911 if (g_strncasecmp(*expression, word, len))
912 return FALSE;
914 if (isalpha(*(*expression + len)))
915 return FALSE;
917 (*expression) += len;
919 return TRUE;