r339: The menu key bindings are now only saved if they actually changed.
[rox-filer.git] / ROX-Filer / src / find.c
blobe8d491d1d0249f6d0ea16ae4e2e95f33092e7814
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@users.sourceforge.net>.
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_is(guchar **expression);
53 static Eval *parse_eval(guchar **expression);
54 static Eval *parse_variable(guchar **expression);
56 static gboolean match(guchar **expression, guchar *word);
58 typedef enum {
59 IS_DIR,
60 IS_REG,
61 IS_LNK,
62 IS_FIFO,
63 IS_SOCK,
64 IS_CHR,
65 IS_BLK,
66 IS_DEV,
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 long (*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(guchar *string)
144 FindCondition *cond;
145 guchar **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 g_return_if_fail(condition != NULL);
175 condition->free(condition);
178 /****************************************************************
179 * INTERNAL FUNCTIONS *
180 ****************************************************************/
182 /* Call this when you've just eaten '('. Returns the string upto the
183 * matching ')' (and eats that bracket), or NULL on failure.
184 * Brackets within the string may be quoted or escaped.
186 static guchar *get_bracketed_string(guchar **expression)
188 GString *str;
189 int opens = 1;
190 guchar quote = '\0';
192 str = g_string_new(NULL);
194 while (NEXT != '\0')
196 guchar c = NEXT;
198 EAT;
200 if (quote == '\0')
202 if (c == '\'' || c == '"')
203 quote = c; /* Start quoted section */
204 else if (c == '\\' && (NEXT == '(' || NEXT == ')'))
206 c = NEXT;
207 EAT;
209 else if (c == ')')
211 opens--;
212 if (opens < 1)
214 guchar *retval = str->str;
216 g_string_free(str, FALSE);
217 return retval;
220 else if (c == '(')
221 opens++;
223 else if (c == quote)
224 quote = '\0'; /* End quoted section */
225 else if (c == '\\' && NEXT == quote)
227 g_string_append_c(str, c);
228 c = NEXT;
229 EAT;
232 g_string_append_c(str, c);
235 g_string_free(str, TRUE);
237 return NULL;
241 /* TESTING CODE */
243 static gboolean test_prune(FindCondition *condition, FindInfo *info)
245 info->prune = TRUE;
246 return FALSE;
249 static gboolean test_leaf(FindCondition *condition, FindInfo *info)
251 return fnmatch(condition->data1, info->leaf, 0) == 0;
254 static gboolean test_path(FindCondition *condition, FindInfo *info)
256 return fnmatch(condition->data1, info->fullpath, FNM_PATHNAME) == 0;
259 static gboolean test_system(FindCondition *condition, FindInfo *info)
261 guchar *command = (guchar *) condition->data1;
262 guchar *start = command;
263 GString *to_sys = NULL;
264 guchar *perc;
265 int retcode;
267 to_sys = g_string_new(NULL);
269 while ((perc = strchr(command, '%')))
271 if (perc > start && perc[-1] == '\\')
272 perc--;
274 while (command < perc)
275 g_string_append_c(to_sys, *(command++));
277 if (*perc == '%')
278 g_string_append(to_sys, info->fullpath);
279 else
281 g_string_append_c(to_sys, '%');
282 perc++;
285 command = perc + 1;
288 g_string_append(to_sys, command);
290 retcode = system(to_sys->str);
292 g_string_free(to_sys, TRUE);
294 return retcode == 0;
297 static gboolean test_OR(FindCondition *condition, FindInfo *info)
299 FindCondition *first = (FindCondition *) condition->data1;
300 FindCondition *second = (FindCondition *) condition->data2;
302 return first->test(first, info) || second->test(second, info);
305 static gboolean test_AND(FindCondition *condition, FindInfo *info)
307 FindCondition *first = (FindCondition *) condition->data1;
308 FindCondition *second = (FindCondition *) condition->data2;
310 return first->test(first, info) && second->test(second, info);
313 static gboolean test_neg(FindCondition *condition, FindInfo *info)
315 FindCondition *first = (FindCondition *) condition->data1;
317 return !first->test(first, info);
320 static gboolean test_is(FindCondition *condition, FindInfo *info)
322 mode_t mode = info->stats.st_mode;
324 switch ((IsTest) condition->value)
326 case IS_DIR:
327 return S_ISDIR(mode);
328 case IS_REG:
329 return S_ISREG(mode);
330 case IS_LNK:
331 return S_ISLNK(mode);
332 case IS_FIFO:
333 return S_ISFIFO(mode);
334 case IS_SOCK:
335 return S_ISSOCK(mode);
336 case IS_CHR:
337 return S_ISCHR(mode);
338 case IS_BLK:
339 return S_ISBLK(mode);
340 case IS_DEV:
341 return S_ISCHR(mode)
342 || S_ISBLK(mode);
343 case IS_SUID:
344 return (mode & S_ISUID) != 0;
345 case IS_SGID:
346 return (mode & S_ISGID) != 0;
347 case IS_STICKY:
348 return (mode & S_ISVTX) != 0;
349 /* NOTE: access() uses uid, not euid. Shouldn't matter? */
350 case IS_READABLE:
351 return access(info->fullpath, R_OK) == 0;
352 case IS_WRITEABLE:
353 return access(info->fullpath, W_OK) == 0;
354 case IS_EXEC:
355 return access(info->fullpath, X_OK) == 0;
356 case IS_EMPTY:
357 return info->stats.st_size == 0;
358 case IS_MINE:
359 return info->stats.st_uid == euid;
362 return FALSE;
365 static gboolean test_comp(FindCondition *condition, FindInfo *info)
367 Eval *first = (Eval *) condition->data1;
368 Eval *second = (Eval *) condition->data2;
369 long a, b;
371 a = first->calc(first, info);
372 b = second->calc(second, info);
374 switch ((CompType) condition->value)
376 case COMP_LT:
377 return a < b;
378 case COMP_LE:
379 return a <= b;
380 case COMP_EQ:
381 return a == b;
382 case COMP_NE:
383 return a != b;
384 case COMP_GE:
385 return a >= b;
386 case COMP_GT:
387 return a > b;
390 return FALSE;
393 /* FREEING CODE */
395 /* Frees the structure and g_free()s both data items (NULL is OK) */
396 static void free_simple(FindCondition *condition)
398 g_return_if_fail(condition != NULL);
400 g_free(condition->data1);
401 g_free(condition->data2);
402 g_free(condition);
405 /* Treats data1 and data2 as conditions (or NULL) and frees recursively */
406 static void free_branch(FindCondition *condition)
408 FindCondition *first = (FindCondition *) condition->data1;
409 FindCondition *second = (FindCondition *) condition->data2;
411 if (first)
412 first->free(first);
413 if (second)
414 second->free(second);
415 g_free(condition);
418 /* Treats data1 and data2 as evals (or NULL) and frees recursively */
419 static void free_comp(FindCondition *condition)
421 Eval *first = (Eval *) condition->data1;
422 Eval *second = (Eval *) condition->data2;
424 if (first)
425 first->free(first);
426 if (second)
427 second->free(second);
428 g_free(condition);
431 /* PARSING CODE */
433 /* These all work in the same way - you give them an expression and the
434 * parse as much as they can, returning a condition for everything that
435 * was parsed and updating the expression pointer to the first unknown
436 * token. NULL indicates an error.
440 /* An expression is a series of comma-separated cases, any of which
441 * may match.
443 static FindCondition *parse_expression(guchar **expression)
445 FindCondition *first, *second, *cond;
447 first = parse_case(expression);
448 if (!first)
449 return NULL;
451 SKIP;
452 if (NEXT != ',')
453 return first;
454 EAT;
456 second = parse_expression(expression);
457 if (!second)
459 first->free(first);
460 return NULL;
463 cond = g_new(FindCondition, 1);
464 cond->test = &test_OR;
465 cond->free = &free_branch;
466 cond->data1 = first;
467 cond->data2 = second;
469 return cond;
472 static FindCondition *parse_case(guchar **expression)
474 FindCondition *first, *second, *cond;
476 first = parse_condition(expression);
477 if (!first)
478 return NULL;
480 SKIP;
481 if (NEXT == '\0' || NEXT == ',' || NEXT == ')')
482 return first;
484 (void) MATCH(_("And"));
486 second = parse_case(expression);
487 if (!second)
489 first->free(first);
490 return NULL;
493 cond = g_new(FindCondition, 1);
494 cond->test = &test_AND;
495 cond->free = &free_branch;
496 cond->data1 = first;
497 cond->data2 = second;
499 return cond;
502 static FindCondition *parse_condition(guchar **expression)
504 FindCondition *cond = NULL;
506 SKIP;
508 if (NEXT == '!' || MATCH(_("Not")))
510 FindCondition *operand;
512 EAT;
514 operand = parse_condition(expression);
515 if (!operand)
516 return NULL;
517 cond = g_new(FindCondition, 1);
518 cond->test = test_neg;
519 cond->free = free_branch;
520 cond->data1 = operand;
521 cond->data2 = NULL;
522 return cond;
525 if (NEXT == '(')
527 FindCondition *subcond;
529 EAT;
531 subcond = parse_expression(expression);
532 if (!subcond)
533 return NULL;
534 SKIP;
535 if (NEXT != ')')
537 subcond->free(subcond);
538 return NULL;
541 EAT;
542 return subcond;
545 if (NEXT == '\'')
547 EAT;
548 return parse_match(expression);
551 if (MATCH(_("system")))
553 SKIP;
554 if (NEXT != '(')
555 return NULL;
556 EAT;
557 return parse_system(expression);
559 else if (MATCH(_("prune")))
561 cond = g_new(FindCondition, 1);
562 cond->test = test_prune;
563 cond->free = (FindFree) g_free;
564 cond->data1 = NULL;
565 cond->data2 = NULL;
567 return cond;
570 cond = parse_is(expression);
571 if (cond)
572 return cond;
574 return parse_comparison(expression);
577 /* Call this when you've just eaten 'system(' */
578 static FindCondition *parse_system(guchar **expression)
580 FindCondition *cond = NULL;
581 guchar *command_string;
583 command_string = get_bracketed_string(expression);
584 if (!command_string)
585 return NULL;
587 cond = g_new(FindCondition, 1);
588 cond->test = test_system;
589 cond->free = &free_simple;
590 cond->data1 = command_string;
591 cond->data2 = NULL;
593 return cond;
596 static FindCondition *parse_comparison(guchar **expression)
598 FindCondition *cond = NULL;
599 Eval *first;
600 Eval *second;
601 CompType comp;
603 SKIP;
605 first = parse_eval(expression);
606 if (!first)
607 return NULL;
609 SKIP;
610 if (NEXT == '=')
612 comp = COMP_EQ;
613 EAT;
615 else if (NEXT == '>')
617 EAT;
618 if (NEXT == '=')
620 EAT;
621 comp = COMP_GE;
623 else
624 comp = COMP_GT;
626 else if (NEXT == '<')
628 EAT;
629 if (NEXT == '=')
631 EAT;
632 comp = COMP_LE;
634 else
635 comp = COMP_LT;
637 else if (NEXT == '!' && (*expression)[1] == '=')
639 EAT;
640 EAT;
641 comp = COMP_NE;
643 else if (MATCH(_("After")))
644 comp = COMP_GT;
645 else if (MATCH(_("Before")))
646 comp = COMP_LT;
647 else
648 return NULL;
650 SKIP;
651 second = parse_eval(expression);
652 if (!second)
654 first->free(first);
655 return NULL;
658 cond = g_new(FindCondition, 1);
659 cond->test = &test_comp;
660 cond->free = (FindFree) &free_comp;
661 cond->data1 = first;
662 cond->data2 = second;
663 cond->value = comp;
665 return cond;
668 /* Returns NULL if expression is not an is-expression */
669 static FindCondition *parse_is(guchar **expression)
671 FindCondition *cond;
672 IsTest test;
674 if (MATCH(_("IsReg")))
675 test = IS_REG;
676 else if (MATCH(_("IsLink")))
677 test = IS_LNK;
678 else if (MATCH(_("IsDir")))
679 test = IS_DIR;
680 else if (MATCH(_("IsChar")))
681 test = IS_CHR;
682 else if (MATCH(_("IsBlock")))
683 test = IS_BLK;
684 else if (MATCH(_("IsDev")))
685 test = IS_DEV;
686 else if (MATCH(_("IsPipe")))
687 test = IS_FIFO;
688 else if (MATCH(_("IsSocket")))
689 test = IS_SOCK;
690 else if (MATCH(_("IsSUID")))
691 test = IS_SUID;
692 else if (MATCH(_("IsSGID")))
693 test = IS_SGID;
694 else if (MATCH(_("IsSticky")))
695 test = IS_STICKY;
696 else if (MATCH(_("IsReadable")))
697 test = IS_READABLE;
698 else if (MATCH(_("IsWriteable")))
699 test = IS_WRITEABLE;
700 else if (MATCH(_("IsExecutable")))
701 test = IS_EXEC;
702 else if (MATCH(_("IsEmpty")))
703 test = IS_EMPTY;
704 else if (MATCH(_("IsMine")))
705 test = IS_MINE;
706 else
707 return NULL;
709 cond = g_new(FindCondition, 1);
710 cond->test = &test_is;
711 cond->free = (FindFree) &g_free;
712 cond->data1 = NULL;
713 cond->data2 = NULL;
714 cond->value = test;
716 return cond;
719 /* Call this just after reading a ' */
720 static FindCondition *parse_match(guchar **expression)
722 FindCondition *cond = NULL;
723 GString *str;
724 FindTest test = &test_leaf;
725 str = g_string_new(NULL);
727 while (NEXT != '\'')
729 guchar c = NEXT;
731 if (c == '\0')
732 goto out;
733 EAT;
735 if (c == '\\' && NEXT == '\'')
737 c = NEXT;
738 EAT;
741 if (c == '/')
742 test = &test_path;
744 g_string_append_c(str, c);
746 EAT;
748 cond = g_new(FindCondition, 1);
749 cond->test = test;
750 cond->free = &free_simple;
751 cond->data1 = str->str;
752 cond->data2 = NULL;
754 out:
755 g_string_free(str, cond ? FALSE : TRUE);
757 return cond;
760 /* NUMERIC EXPRESSIONS */
762 /* CALCULATIONS */
764 static long get_constant(Eval *eval, FindInfo *info)
766 long value = *((long *) (eval->data1));
767 int flags = (int) (eval->data2);
769 if (flags & FLAG_AGO)
770 value = info->now - value;
771 else if (flags & FLAG_HENCE)
772 value = info->now + value;
774 return value;
777 static long get_var(Eval *eval, FindInfo *info)
779 switch ((VarType) eval->data1)
781 case V_ATIME:
782 return info->stats.st_atime;
783 case V_CTIME:
784 return info->stats.st_ctime;
785 case V_MTIME:
786 return info->stats.st_mtime;
787 case V_SIZE:
788 return info->stats.st_size;
789 case V_INODE:
790 return info->stats.st_ino;
791 case V_NLINKS:
792 return info->stats.st_nlink;
793 case V_UID:
794 return info->stats.st_uid;
795 case V_GID:
796 return info->stats.st_gid;
797 case V_BLOCKS:
798 return info->stats.st_blocks;
801 return 0L;
804 /* FREEING */
806 static void free_constant(Eval *eval)
808 g_free(eval->data1);
809 g_free(eval);
812 /* PARSING */
814 /* Parse something that evaluates to a number.
815 * This function tried to get a constant - if it fails then it tries
816 * interpreting the next token as a variable.
818 static Eval *parse_eval(guchar **expression)
820 char *start, *end;
821 long value;
822 Eval *eval;
823 int flags = 0;
825 SKIP;
826 start = *expression;
827 value = strtol(start, &end, 0);
829 if (end == start)
831 if (MATCH(_("Now")))
833 value = 0;
834 flags |= FLAG_HENCE;
836 else
837 return parse_variable(expression);
839 else
840 *expression = end;
842 SKIP;
844 if (MATCH(_("Byte")) || MATCH(_("Bytes")))
846 else if (MATCH("Kb"))
847 value <<= 10;
848 else if (MATCH("Mb"))
849 value <<= 20;
850 else if (MATCH("Gb"))
851 value <<= 30;
852 else if (MATCH(_("Sec")) || MATCH(_("Secs")))
854 else if (MATCH(_("Min")) || MATCH(_("Mins")))
855 value *= 60;
856 else if (MATCH(_("Hour")) || MATCH(_("Hours")))
857 value *= 60 * 60;
858 else if (MATCH(_("Day")) || MATCH(_("Days")))
859 value *= 60 * 60 * 24;
860 else if (MATCH(_("Week")) || MATCH(_("Weeks")))
861 value *= 60 * 60 * 24 * 7;
862 else if (MATCH(_("Year")) || MATCH(_("Years")))
863 value *= 60 * 60 * 24 * 7 * 365.25;
865 eval = g_new(Eval, 1);
866 eval->calc = &get_constant;
867 eval->free = &free_constant;
868 eval->data1 = g_memdup(&value, sizeof(value));
870 SKIP;
871 if (MATCH(_("Ago")))
872 flags |= FLAG_AGO;
873 else if (MATCH(_("Hence")))
874 flags |= FLAG_HENCE;
876 eval->data2 = (gpointer) flags;
878 return eval;
881 static Eval *parse_variable(guchar **expression)
883 Eval *eval;
884 VarType var;
886 SKIP;
888 if (MATCH(_("atime")))
889 var = V_ATIME;
890 else if (MATCH(_("ctime")))
891 var = V_CTIME;
892 else if (MATCH(_("mtime")))
893 var = V_MTIME;
894 else if (MATCH(_("size")))
895 var = V_SIZE;
896 else if (MATCH(_("inode")))
897 var = V_INODE;
898 else if (MATCH(_("nlinks")))
899 var = V_NLINKS;
900 else if (MATCH(_("uid")))
901 var = V_UID;
902 else if (MATCH(_("gid")))
903 var = V_GID;
904 else if (MATCH(_("blocks")))
905 var = V_BLOCKS;
906 else
907 return NULL;
909 eval = g_new(Eval, 1);
910 eval->calc = &get_var;
911 eval->free = (EvalFree) &g_free;
912 eval->data1 = (gpointer) var;
913 eval->data2 = NULL;
915 return eval;
918 static gboolean match(guchar **expression, guchar *word)
920 int len;
922 len = strlen(word);
923 if (g_strncasecmp(*expression, word, len))
924 return FALSE;
926 if (isalpha(*(*expression + len)))
927 return FALSE;
929 (*expression) += len;
931 return TRUE;