r232: Added 'prune' and 'system' find commands.
[rox-filer.git] / ROX-Filer / src / find.c
blob03e8cbc92b2a2f154d997986b4124af1d1fb309c
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 typedef enum {
55 IS_DIR,
56 IS_REG,
57 IS_LNK,
58 IS_FIFO,
59 IS_SOCK,
60 IS_CHR,
61 IS_BLK,
62 IS_DEV,
63 IS_SUID,
64 IS_SGID,
65 IS_STICKY,
66 IS_READABLE,
67 IS_WRITEABLE,
68 IS_EXEC,
69 IS_EMPTY,
70 IS_MINE,
71 } IsTest;
73 typedef enum {
74 COMP_LT,
75 COMP_LE,
76 COMP_EQ,
77 COMP_NE,
78 COMP_GE,
79 COMP_GT,
80 } CompType;
82 typedef enum {
83 V_ATIME,
84 V_CTIME,
85 V_MTIME,
86 V_SIZE,
87 V_INODE,
88 V_NLINKS,
89 V_UID,
90 V_GID,
91 V_BLOCKS,
92 } VarType;
94 enum
96 FLAG_AGO = 1 << 0,
97 FLAG_HENCE = 1 << 1,
100 typedef long (*EvalCalc)(Eval *eval, FindInfo *info);
101 typedef void (*EvalFree)(Eval *eval);
103 struct _Eval
105 EvalCalc calc;
106 EvalFree free;
107 gpointer data1;
108 gpointer data2;
112 #define EAT ((*expression)++)
113 #define NEXT (**expression)
114 #define SKIP while (NEXT == ' ' || NEXT == '\t') EAT
115 #define MATCH(word) (g_strncasecmp(*expression, word, sizeof(word) - 1) == 0 \
116 && (isalpha((*expression)[sizeof(word) - 1]) == 0) \
117 && ((*expression) += sizeof(word) - 1))
119 #ifndef S_ISVTX
120 # define S_ISVTX 0x0001000
121 #endif
123 /****************************************************************
124 * EXTERNAL INTERFACE *
125 ****************************************************************/
127 /* Take a string and parse it, returning a condition object which
128 * can be passed to find_test_condition() later. NULL if the string
129 * is not a valid expression.
131 FindCondition *find_compile(guchar *string)
133 FindCondition *cond;
134 guchar **expression = &string;
136 g_return_val_if_fail(string != NULL, NULL);
138 cond = parse_expression(expression);
139 if (!cond)
140 return NULL;
142 SKIP;
143 if (NEXT != '\0')
145 cond->free(cond);
146 cond = NULL;
149 return cond;
152 gboolean find_test_condition(FindCondition *condition, FindInfo *info)
154 g_return_val_if_fail(condition != NULL, FALSE);
155 g_return_val_if_fail(info != NULL, FALSE);
157 return condition->test(condition, info);
160 void find_condition_free(FindCondition *condition)
162 g_return_if_fail(condition != NULL);
164 condition->free(condition);
167 /****************************************************************
168 * INTERNAL FUNCTIONS *
169 ****************************************************************/
171 /* Call this when you've just eaten '('. Returns the string upto the
172 * matching ')' (and eats that bracket), or NULL on failure.
173 * Brackets within the string may be quoted or escaped.
175 static guchar *get_bracketed_string(guchar **expression)
177 GString *str;
178 int opens = 1;
179 guchar quote = '\0';
181 str = g_string_new(NULL);
183 while (NEXT != '\0')
185 guchar c = NEXT;
187 EAT;
189 if (quote == '\0')
191 if (c == '\'' || c == '"')
192 quote = c; /* Start quoted section */
193 else if (c == '\\' && (NEXT == '(' || NEXT == ')'))
195 c = NEXT;
196 EAT;
198 else if (c == ')')
200 opens--;
201 if (opens < 1)
203 guchar *retval = str->str;
205 g_string_free(str, FALSE);
206 return retval;
209 else if (c == '(')
210 opens++;
212 else if (c == quote)
213 quote = '\0'; /* End quoted section */
214 else if (c == '\\' && NEXT == quote)
216 g_string_append_c(str, c);
217 c = NEXT;
218 EAT;
221 g_string_append_c(str, c);
224 g_string_free(str, TRUE);
226 return NULL;
230 /* TESTING CODE */
232 static gboolean test_prune(FindCondition *condition, FindInfo *info)
234 info->prune = TRUE;
235 return FALSE;
238 static gboolean test_leaf(FindCondition *condition, FindInfo *info)
240 return fnmatch(condition->data1, info->leaf, 0) == 0;
243 static gboolean test_path(FindCondition *condition, FindInfo *info)
245 return fnmatch(condition->data1, info->fullpath, FNM_PATHNAME) == 0;
248 static gboolean test_system(FindCondition *condition, FindInfo *info)
250 guchar *command = (guchar *) condition->data1;
251 guchar *start = command;
252 GString *to_sys = NULL;
253 guchar *perc;
254 int retcode;
256 to_sys = g_string_new(NULL);
258 while ((perc = strchr(command, '%')))
260 if (perc > start && perc[-1] == '\\')
261 perc--;
263 while (command < perc)
264 g_string_append_c(to_sys, *(command++));
266 if (*perc == '%')
267 g_string_append(to_sys, info->fullpath);
268 else
270 g_string_append_c(to_sys, '%');
271 perc++;
274 command = perc + 1;
277 g_string_append(to_sys, command);
279 retcode = system(to_sys->str);
281 g_string_free(to_sys, TRUE);
283 return retcode == 0;
286 static gboolean test_OR(FindCondition *condition, FindInfo *info)
288 FindCondition *first = (FindCondition *) condition->data1;
289 FindCondition *second = (FindCondition *) condition->data2;
291 return first->test(first, info) || second->test(second, info);
294 static gboolean test_AND(FindCondition *condition, FindInfo *info)
296 FindCondition *first = (FindCondition *) condition->data1;
297 FindCondition *second = (FindCondition *) condition->data2;
299 return first->test(first, info) && second->test(second, info);
302 static gboolean test_neg(FindCondition *condition, FindInfo *info)
304 FindCondition *first = (FindCondition *) condition->data1;
306 return !first->test(first, info);
309 static gboolean test_is(FindCondition *condition, FindInfo *info)
311 mode_t mode = info->stats.st_mode;
313 switch ((IsTest) condition->value)
315 case IS_DIR:
316 return S_ISDIR(mode);
317 case IS_REG:
318 return S_ISREG(mode);
319 case IS_LNK:
320 return S_ISLNK(mode);
321 case IS_FIFO:
322 return S_ISFIFO(mode);
323 case IS_SOCK:
324 return S_ISSOCK(mode);
325 case IS_CHR:
326 return S_ISCHR(mode);
327 case IS_BLK:
328 return S_ISBLK(mode);
329 case IS_DEV:
330 return S_ISCHR(mode)
331 || S_ISBLK(mode);
332 case IS_SUID:
333 return (mode & S_ISUID) != 0;
334 case IS_SGID:
335 return (mode & S_ISGID) != 0;
336 case IS_STICKY:
337 return (mode & S_ISVTX) != 0;
338 /* NOTE: access() uses uid, not euid. Shouldn't matter? */
339 case IS_READABLE:
340 return access(info->fullpath, R_OK) == 0;
341 case IS_WRITEABLE:
342 return access(info->fullpath, W_OK) == 0;
343 case IS_EXEC:
344 return access(info->fullpath, X_OK) == 0;
345 case IS_EMPTY:
346 return info->stats.st_size == 0;
347 case IS_MINE:
348 return info->stats.st_uid == euid;
351 return FALSE;
354 static gboolean test_comp(FindCondition *condition, FindInfo *info)
356 Eval *first = (Eval *) condition->data1;
357 Eval *second = (Eval *) condition->data2;
358 long a, b;
360 a = first->calc(first, info);
361 b = second->calc(second, info);
363 switch ((CompType) condition->value)
365 case COMP_LT:
366 return a < b;
367 case COMP_LE:
368 return a <= b;
369 case COMP_EQ:
370 return a == b;
371 case COMP_NE:
372 return a != b;
373 case COMP_GE:
374 return a >= b;
375 case COMP_GT:
376 return a > b;
379 return FALSE;
382 /* FREEING CODE */
384 /* Frees the structure and g_free()s both data items (NULL is OK) */
385 static void free_simple(FindCondition *condition)
387 g_return_if_fail(condition != NULL);
389 g_free(condition->data1);
390 g_free(condition->data2);
391 g_free(condition);
394 /* Treats data1 and data2 as conditions (or NULL) and frees recursively */
395 static void free_branch(FindCondition *condition)
397 FindCondition *first = (FindCondition *) condition->data1;
398 FindCondition *second = (FindCondition *) condition->data2;
400 if (first)
401 first->free(first);
402 if (second)
403 second->free(second);
404 g_free(condition);
407 /* Treats data1 and data2 as evals (or NULL) and frees recursively */
408 static void free_comp(FindCondition *condition)
410 Eval *first = (Eval *) condition->data1;
411 Eval *second = (Eval *) condition->data2;
413 if (first)
414 first->free(first);
415 if (second)
416 second->free(second);
417 g_free(condition);
420 /* PARSING CODE */
422 /* These all work in the same way - you give them an expression and the
423 * parse as much as they can, returning a condition for everything that
424 * was parsed and updating the expression pointer to the first unknown
425 * token. NULL indicates an error.
429 /* An expression is a series of comma-separated cases, any of which
430 * may match.
432 static FindCondition *parse_expression(guchar **expression)
434 FindCondition *first, *second, *cond;
436 first = parse_case(expression);
437 if (!first)
438 return NULL;
440 SKIP;
441 if (NEXT != ',')
442 return first;
443 EAT;
445 second = parse_expression(expression);
446 if (!second)
448 first->free(first);
449 return NULL;
452 cond = g_new(FindCondition, 1);
453 cond->test = &test_OR;
454 cond->free = &free_branch;
455 cond->data1 = first;
456 cond->data2 = second;
458 return cond;
461 static FindCondition *parse_case(guchar **expression)
463 FindCondition *first, *second, *cond;
465 first = parse_condition(expression);
466 if (!first)
467 return NULL;
469 SKIP;
470 if (NEXT == '\0' || NEXT == ',' || NEXT == ')')
471 return first;
473 (void) MATCH("And");
475 second = parse_case(expression);
476 if (!second)
478 first->free(first);
479 return NULL;
482 cond = g_new(FindCondition, 1);
483 cond->test = &test_AND;
484 cond->free = &free_branch;
485 cond->data1 = first;
486 cond->data2 = second;
488 return cond;
491 static FindCondition *parse_condition(guchar **expression)
493 FindCondition *cond = NULL;
495 SKIP;
497 if (NEXT == '!' || MATCH("Not"))
499 FindCondition *operand;
501 EAT;
503 operand = parse_condition(expression);
504 if (!operand)
505 return NULL;
506 cond = g_new(FindCondition, 1);
507 cond->test = test_neg;
508 cond->free = free_branch;
509 cond->data1 = operand;
510 cond->data2 = NULL;
511 return cond;
514 if (NEXT == '(')
516 FindCondition *subcond;
518 EAT;
520 subcond = parse_expression(expression);
521 if (!subcond)
522 return NULL;
523 SKIP;
524 if (NEXT != ')')
526 subcond->free(subcond);
527 return NULL;
530 EAT;
531 return subcond;
534 if (NEXT == '\'')
536 EAT;
537 return parse_match(expression);
540 if (MATCH("system"))
542 SKIP;
543 if (NEXT != '(')
544 return NULL;
545 EAT;
546 return parse_system(expression);
548 else if (MATCH("prune"))
550 cond = g_new(FindCondition, 1);
551 cond->test = test_prune;
552 cond->free = (FindFree) g_free;
553 cond->data1 = NULL;
554 cond->data2 = NULL;
556 return cond;
558 else if (g_strncasecmp(*expression, "Is", 2) == 0)
560 EAT;
561 EAT;
562 return parse_is(expression);
565 return parse_comparison(expression);
568 /* Call this when you've just eaten 'system(' */
569 static FindCondition *parse_system(guchar **expression)
571 FindCondition *cond = NULL;
572 guchar *command_string;
574 command_string = get_bracketed_string(expression);
575 if (!command_string)
576 return NULL;
578 cond = g_new(FindCondition, 1);
579 cond->test = test_system;
580 cond->free = &free_simple;
581 cond->data1 = command_string;
582 cond->data2 = NULL;
584 return cond;
587 static FindCondition *parse_comparison(guchar **expression)
589 FindCondition *cond = NULL;
590 Eval *first;
591 Eval *second;
592 CompType comp;
594 SKIP;
596 first = parse_eval(expression);
597 if (!first)
598 return NULL;
600 SKIP;
601 if (NEXT == '=')
603 comp = COMP_EQ;
604 EAT;
606 else if (NEXT == '>')
608 EAT;
609 if (NEXT == '=')
611 EAT;
612 comp = COMP_GE;
614 else
615 comp = COMP_GT;
617 else if (NEXT == '<')
619 EAT;
620 if (NEXT == '=')
622 EAT;
623 comp = COMP_LE;
625 else
626 comp = COMP_LT;
628 else if (NEXT == '!' && (*expression)[1] == '=')
630 EAT;
631 EAT;
632 comp = COMP_NE;
634 else if (MATCH("After"))
635 comp = COMP_GT;
636 else if (MATCH("Before"))
637 comp = COMP_LT;
638 else
639 return NULL;
641 SKIP;
642 second = parse_eval(expression);
643 if (!second)
645 first->free(first);
646 return NULL;
649 cond = g_new(FindCondition, 1);
650 cond->test = &test_comp;
651 cond->free = (FindFree) &free_comp;
652 cond->data1 = first;
653 cond->data2 = second;
654 cond->value = comp;
656 return cond;
659 static FindCondition *parse_is(guchar **expression)
661 FindCondition *cond;
662 IsTest test;
664 if (MATCH("Reg"))
665 test = IS_REG;
666 else if (MATCH("Link"))
667 test = IS_LNK;
668 else if (MATCH("Dir"))
669 test = IS_DIR;
670 else if (MATCH("Char"))
671 test = IS_CHR;
672 else if (MATCH("Block"))
673 test = IS_BLK;
674 else if (MATCH("Dev"))
675 test = IS_DEV;
676 else if (MATCH("Pipe"))
677 test = IS_FIFO;
678 else if (MATCH("Socket"))
679 test = IS_SOCK;
680 else if (MATCH("SUID"))
681 test = IS_SUID;
682 else if (MATCH("SGID"))
683 test = IS_SGID;
684 else if (MATCH("Sticky"))
685 test = IS_STICKY;
686 else if (MATCH("Readable"))
687 test = IS_READABLE;
688 else if (MATCH("Writeable"))
689 test = IS_WRITEABLE;
690 else if (MATCH("Executable"))
691 test = IS_EXEC;
692 else if (MATCH("Empty"))
693 test = IS_EMPTY;
694 else if (MATCH("Mine"))
695 test = IS_MINE;
696 else
697 return NULL;
699 cond = g_new(FindCondition, 1);
700 cond->test = &test_is;
701 cond->free = (FindFree) &g_free;
702 cond->data1 = NULL;
703 cond->data2 = NULL;
704 cond->value = test;
706 return cond;
709 /* Call this just after reading a ' */
710 static FindCondition *parse_match(guchar **expression)
712 FindCondition *cond = NULL;
713 GString *str;
714 FindTest test = &test_leaf;
715 str = g_string_new(NULL);
717 while (NEXT != '\'')
719 guchar c = NEXT;
721 if (c == '\0')
722 goto out;
723 EAT;
725 if (c == '\\' && NEXT == '\'')
727 c = NEXT;
728 EAT;
731 if (c == '/')
732 test = &test_path;
734 g_string_append_c(str, c);
736 EAT;
738 cond = g_new(FindCondition, 1);
739 cond->test = test;
740 cond->free = &free_simple;
741 cond->data1 = str->str;
742 cond->data2 = NULL;
744 out:
745 g_string_free(str, cond ? FALSE : TRUE);
747 return cond;
750 /* NUMERIC EXPRESSIONS */
752 /* CALCULATIONS */
754 static long get_constant(Eval *eval, FindInfo *info)
756 long value = *((long *) (eval->data1));
757 int flags = (int) (eval->data2);
759 if (flags & FLAG_AGO)
760 value = info->now - value;
761 else if (flags & FLAG_HENCE)
762 value = info->now + value;
764 return value;
767 static long get_var(Eval *eval, FindInfo *info)
769 switch ((VarType) eval->data1)
771 case V_ATIME:
772 return info->stats.st_atime;
773 case V_CTIME:
774 return info->stats.st_ctime;
775 case V_MTIME:
776 return info->stats.st_mtime;
777 case V_SIZE:
778 return info->stats.st_size;
779 case V_INODE:
780 return info->stats.st_ino;
781 case V_NLINKS:
782 return info->stats.st_nlink;
783 case V_UID:
784 return info->stats.st_uid;
785 case V_GID:
786 return info->stats.st_gid;
787 case V_BLOCKS:
788 return info->stats.st_blocks;
791 return 0L;
794 /* FREEING */
796 static void free_constant(Eval *eval)
798 g_free(eval->data1);
799 g_free(eval);
802 /* PARSING */
804 /* Parse something that evaluates to a number.
805 * This function tried to get a constant - if it fails then it tries
806 * interpreting the next token as a variable.
808 static Eval *parse_eval(guchar **expression)
810 char *start, *end;
811 long value;
812 Eval *eval;
813 int flags = 0;
815 SKIP;
816 start = *expression;
817 value = strtol(start, &end, 0);
819 if (end == start)
821 if (MATCH("Now"))
823 value = 0;
824 flags |= FLAG_HENCE;
826 else
827 return parse_variable(expression);
829 else
830 *expression = end;
832 SKIP;
834 if (MATCH("Byte") || MATCH("Bytes"))
836 else if (MATCH("Kb"))
837 value <<= 10;
838 else if (MATCH("Mb"))
839 value <<= 20;
840 else if (MATCH("Gb"))
841 value <<= 30;
842 else if (MATCH("Sec") || MATCH("Secs"))
844 else if (MATCH("Min") || MATCH("Mins"))
845 value *= 60;
846 else if (MATCH("Hour") || MATCH("Hours"))
847 value *= 60 * 60;
848 else if (MATCH("Day") || MATCH("Days"))
849 value *= 60 * 60 * 24;
850 else if (MATCH("Week") || MATCH("Weeks"))
851 value *= 60 * 60 * 24 * 7;
852 else if (MATCH("Year") || MATCH("Years"))
853 value *= 60 * 60 * 24 * 7 * 365.25;
855 eval = g_new(Eval, 1);
856 eval->calc = &get_constant;
857 eval->free = &free_constant;
858 eval->data1 = g_memdup(&value, sizeof(value));
860 SKIP;
861 if (MATCH("Ago"))
862 flags |= FLAG_AGO;
863 else if (MATCH("Hence"))
864 flags |= FLAG_HENCE;
866 eval->data2 = (gpointer) flags;
868 return eval;
871 static Eval *parse_variable(guchar **expression)
873 Eval *eval;
874 VarType var;
876 SKIP;
878 if (MATCH("atime"))
879 var = V_ATIME;
880 else if (MATCH("ctime"))
881 var = V_CTIME;
882 else if (MATCH("mtime"))
883 var = V_MTIME;
884 else if (MATCH("size"))
885 var = V_SIZE;
886 else if (MATCH("inode"))
887 var = V_INODE;
888 else if (MATCH("nlinks"))
889 var = V_NLINKS;
890 else if (MATCH("uid"))
891 var = V_UID;
892 else if (MATCH("gid"))
893 var = V_GID;
894 else if (MATCH("blocks"))
895 var = V_BLOCKS;
896 else
897 return NULL;
899 eval = g_new(Eval, 1);
900 eval->calc = &get_var;
901 eval->free = (EvalFree) &g_free;
902 eval->data1 = (gpointer) var;
903 eval->data2 = NULL;
905 return eval;