r2596: Updated for new version.
[rox-filer.git] / ROX-Filer / src / find.c
blob463f145fff3eb648228bd4871566ecdaf4f45879
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2003, 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(const gchar **expression);
47 static FindCondition *parse_case(const gchar **expression);
48 static FindCondition *parse_system(const gchar **expression);
49 static FindCondition *parse_condition(const gchar **expression);
50 static FindCondition *parse_match(const gchar **expression);
51 static FindCondition *parse_comparison(const gchar **expression);
52 static FindCondition *parse_dash(const gchar **expression);
53 static FindCondition *parse_is(const gchar **expression);
54 static Eval *parse_eval(const gchar **expression);
55 static Eval *parse_variable(const gchar **expression);
57 static gboolean match(const gchar **expression, const gchar *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_DOOR,
69 IS_SUID,
70 IS_SGID,
71 IS_STICKY,
72 IS_READABLE,
73 IS_WRITEABLE,
74 IS_EXEC,
75 IS_EMPTY,
76 IS_MINE,
77 } IsTest;
79 typedef enum {
80 COMP_LT,
81 COMP_LE,
82 COMP_EQ,
83 COMP_NE,
84 COMP_GE,
85 COMP_GT,
86 } CompType;
88 typedef enum {
89 V_ATIME,
90 V_CTIME,
91 V_MTIME,
92 V_SIZE,
93 V_INODE,
94 V_NLINKS,
95 V_UID,
96 V_GID,
97 V_BLOCKS,
98 } VarType;
100 enum
102 FLAG_AGO = 1 << 0,
103 FLAG_HENCE = 1 << 1,
106 typedef double (*EvalCalc)(Eval *eval, FindInfo *info);
107 typedef void (*EvalFree)(Eval *eval);
109 struct _FindCondition
111 FindTest test;
112 FindFree free;
113 /* These next three depend on the first two... */
114 gpointer data1;
115 gpointer data2;
116 gint value;
119 struct _Eval
121 EvalCalc calc;
122 EvalFree free;
123 gpointer data1;
124 gpointer data2;
127 #define EAT ((*expression)++)
128 #define NEXT (**expression)
129 #define SKIP while (NEXT == ' ' || NEXT == '\t') EAT
130 #define MATCH(word) (match(expression, word))
132 #ifndef S_ISVTX
133 # define S_ISVTX 0x0001000
134 #endif
136 /****************************************************************
137 * EXTERNAL INTERFACE *
138 ****************************************************************/
140 /* Take a string and parse it, returning a condition object which
141 * can be passed to find_test_condition() later. NULL if the string
142 * is not a valid expression.
144 FindCondition *find_compile(const gchar *string)
146 FindCondition *cond;
147 const gchar **expression = &string;
149 g_return_val_if_fail(string != NULL, NULL);
151 cond = parse_expression(expression);
152 if (!cond)
153 return NULL;
155 SKIP;
156 if (NEXT != '\0')
158 cond->free(cond);
159 cond = NULL;
162 return cond;
165 gboolean find_test_condition(FindCondition *condition, FindInfo *info)
167 g_return_val_if_fail(condition != NULL, FALSE);
168 g_return_val_if_fail(info != NULL, FALSE);
170 return condition->test(condition, info);
173 void find_condition_free(FindCondition *condition)
175 if (condition)
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 gchar *get_bracketed_string(const gchar **expression)
189 GString *str;
190 int opens = 1;
191 gchar quote = '\0';
193 str = g_string_new(NULL);
195 while (NEXT != '\0')
197 gchar 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 gchar *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 gchar *command = (gchar *) condition->data1;
263 gchar *start = command;
264 GString *to_sys = NULL;
265 gchar *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_DOOR:
345 return S_ISDOOR(mode);
346 case IS_SUID:
347 return (mode & S_ISUID) != 0;
348 case IS_SGID:
349 return (mode & S_ISGID) != 0;
350 case IS_STICKY:
351 return (mode & S_ISVTX) != 0;
352 /* NOTE: access() uses uid, not euid. Shouldn't matter? */
353 case IS_READABLE:
354 return access(info->fullpath, R_OK) == 0;
355 case IS_WRITEABLE:
356 return access(info->fullpath, W_OK) == 0;
357 case IS_EXEC:
358 return access(info->fullpath, X_OK) == 0;
359 case IS_EMPTY:
360 return info->stats.st_size == 0;
361 case IS_MINE:
362 return info->stats.st_uid == euid;
365 return FALSE;
368 static gboolean test_comp(FindCondition *condition, FindInfo *info)
370 Eval *first = (Eval *) condition->data1;
371 Eval *second = (Eval *) condition->data2;
372 double a, b;
374 a = first->calc(first, info);
375 b = second->calc(second, info);
377 switch ((CompType) condition->value)
379 case COMP_LT:
380 return a < b;
381 case COMP_LE:
382 return a <= b;
383 case COMP_EQ:
384 return a == b;
385 case COMP_NE:
386 return a != b;
387 case COMP_GE:
388 return a >= b;
389 case COMP_GT:
390 return a > b;
393 return FALSE;
396 /* FREEING CODE */
398 /* Frees the structure and g_free()s both data items (NULL is OK) */
399 static void free_simple(FindCondition *condition)
401 g_return_if_fail(condition != NULL);
403 g_free(condition->data1);
404 g_free(condition->data2);
405 g_free(condition);
408 /* Treats data1 and data2 as conditions (or NULL) and frees recursively */
409 static void free_branch(FindCondition *condition)
411 FindCondition *first = (FindCondition *) condition->data1;
412 FindCondition *second = (FindCondition *) condition->data2;
414 if (first)
415 first->free(first);
416 if (second)
417 second->free(second);
418 g_free(condition);
421 /* Treats data1 and data2 as evals (or NULL) and frees recursively */
422 static void free_comp(FindCondition *condition)
424 Eval *first = (Eval *) condition->data1;
425 Eval *second = (Eval *) condition->data2;
427 if (first)
428 first->free(first);
429 if (second)
430 second->free(second);
431 g_free(condition);
434 /* PARSING CODE */
436 /* These all work in the same way - you give them an expression and the
437 * parse as much as they can, returning a condition for everything that
438 * was parsed and updating the expression pointer to the first unknown
439 * token. NULL indicates an error.
443 /* An expression is a series of comma-separated cases, any of which
444 * may match.
446 static FindCondition *parse_expression(const gchar **expression)
448 FindCondition *first, *second, *cond;
450 first = parse_case(expression);
451 if (!first)
452 return NULL;
454 SKIP;
455 if (NEXT != ',')
456 return first;
457 EAT;
459 second = parse_expression(expression);
460 if (!second)
462 first->free(first);
463 return NULL;
466 cond = g_new(FindCondition, 1);
467 cond->test = &test_OR;
468 cond->free = &free_branch;
469 cond->data1 = first;
470 cond->data2 = second;
472 return cond;
475 static FindCondition *parse_case(const gchar **expression)
477 FindCondition *first, *second, *cond;
479 first = parse_condition(expression);
480 if (!first)
481 return NULL;
483 SKIP;
484 if (NEXT == '\0' || NEXT == ',' || NEXT == ')')
485 return first;
487 (void) MATCH(_("And"));
489 second = parse_case(expression);
490 if (!second)
492 first->free(first);
493 return NULL;
496 cond = g_new(FindCondition, 1);
497 cond->test = &test_AND;
498 cond->free = &free_branch;
499 cond->data1 = first;
500 cond->data2 = second;
502 return cond;
505 static FindCondition *parse_condition(const gchar **expression)
507 FindCondition *cond = NULL;
509 SKIP;
511 if (NEXT == '!' || MATCH(_("Not")))
513 FindCondition *operand;
515 EAT;
517 operand = parse_condition(expression);
518 if (!operand)
519 return NULL;
520 cond = g_new(FindCondition, 1);
521 cond->test = test_neg;
522 cond->free = free_branch;
523 cond->data1 = operand;
524 cond->data2 = NULL;
525 return cond;
528 if (NEXT == '(')
530 FindCondition *subcond;
532 EAT;
534 subcond = parse_expression(expression);
535 if (!subcond)
536 return NULL;
537 SKIP;
538 if (NEXT != ')')
540 subcond->free(subcond);
541 return NULL;
544 EAT;
545 return subcond;
548 if (NEXT == '\'')
550 EAT;
551 return parse_match(expression);
554 if (MATCH(_("system")))
556 SKIP;
557 if (NEXT != '(')
558 return NULL;
559 EAT;
560 return parse_system(expression);
562 else if (MATCH(_("prune")))
564 cond = g_new(FindCondition, 1);
565 cond->test = test_prune;
566 cond->free = (FindFree) g_free;
567 cond->data1 = NULL;
568 cond->data2 = NULL;
570 return cond;
573 cond = parse_dash(expression);
574 if (cond)
575 return cond;
577 cond = parse_is(expression);
578 if (cond)
579 return cond;
581 return parse_comparison(expression);
584 /* Call this when you've just eaten 'system(' */
585 static FindCondition *parse_system(const gchar **expression)
587 FindCondition *cond = NULL;
588 gchar *command_string;
590 command_string = get_bracketed_string(expression);
591 if (!command_string)
592 return NULL;
594 cond = g_new(FindCondition, 1);
595 cond->test = test_system;
596 cond->free = &free_simple;
597 cond->data1 = command_string;
598 cond->data2 = NULL;
600 return cond;
603 static FindCondition *parse_comparison(const gchar **expression)
605 FindCondition *cond = NULL;
606 Eval *first;
607 Eval *second;
608 CompType comp;
610 SKIP;
612 first = parse_eval(expression);
613 if (!first)
614 return NULL;
616 SKIP;
617 if (NEXT == '=')
619 comp = COMP_EQ;
620 EAT;
622 else if (NEXT == '>')
624 EAT;
625 if (NEXT == '=')
627 EAT;
628 comp = COMP_GE;
630 else
631 comp = COMP_GT;
633 else if (NEXT == '<')
635 EAT;
636 if (NEXT == '=')
638 EAT;
639 comp = COMP_LE;
641 else
642 comp = COMP_LT;
644 else if (NEXT == '!' && (*expression)[1] == '=')
646 EAT;
647 EAT;
648 comp = COMP_NE;
650 else if (MATCH(_("After")))
651 comp = COMP_GT;
652 else if (MATCH(_("Before")))
653 comp = COMP_LT;
654 else
655 return NULL;
657 SKIP;
658 second = parse_eval(expression);
659 if (!second)
661 first->free(first);
662 return NULL;
665 cond = g_new(FindCondition, 1);
666 cond->test = &test_comp;
667 cond->free = (FindFree) &free_comp;
668 cond->data1 = first;
669 cond->data2 = second;
670 cond->value = comp;
672 return cond;
675 static FindCondition *parse_dash(const gchar **expression)
677 const gchar *exp = *expression;
678 FindCondition *cond, *retval = NULL;
679 IsTest test;
680 int i = 1;
682 if (NEXT != '-')
683 return NULL;
685 while (exp[i] && !isspace(exp[i]))
687 switch (exp[i])
689 case 'f': test = IS_REG; break;
690 case 'l': test = IS_LNK; break;
691 case 'd': test = IS_DIR; break;
692 case 'b': test = IS_BLK; break;
693 case 'c': test = IS_CHR; break;
694 case 'D': test = IS_DEV; break;
695 case 'p': test = IS_FIFO; break;
696 case 'S': test = IS_SOCK; break;
697 case 'O': test = IS_DOOR; break;
698 case 'u': test = IS_SUID; break;
699 case 'g': test = IS_SGID; break;
700 case 'k': test = IS_STICKY; break;
701 case 'r': test = IS_READABLE; break;
702 case 'w': test = IS_WRITEABLE; break;
703 case 'x': test = IS_EXEC; break;
704 case 'o': test = IS_MINE; break;
705 case 'z': test = IS_EMPTY; break;
706 default:
707 find_condition_free(retval);
708 return NULL;
710 i++;
712 cond = g_new(FindCondition, 1);
713 cond->test = &test_is;
714 cond->free = (FindFree) &g_free;
715 cond->data1 = NULL;
716 cond->data2 = NULL;
717 cond->value = test;
719 if (retval)
721 FindCondition *new;
723 new = g_new(FindCondition, 1);
724 new->test = &test_AND;
725 new->free = &free_branch;
726 new->data1 = retval;
727 new->data2 = cond;
729 retval = new;
731 else
732 retval = cond;
735 (*expression) += i;
737 return retval;
740 /* Returns NULL if expression is not an is-expression */
741 static FindCondition *parse_is(const gchar **expression)
743 FindCondition *cond;
744 IsTest test;
746 if (MATCH(_("IsReg")))
747 test = IS_REG;
748 else if (MATCH(_("IsLink")))
749 test = IS_LNK;
750 else if (MATCH(_("IsDir")))
751 test = IS_DIR;
752 else if (MATCH(_("IsChar")))
753 test = IS_CHR;
754 else if (MATCH(_("IsBlock")))
755 test = IS_BLK;
756 else if (MATCH(_("IsDev")))
757 test = IS_DEV;
758 else if (MATCH(_("IsPipe")))
759 test = IS_FIFO;
760 else if (MATCH(_("IsSocket")))
761 test = IS_SOCK;
762 else if (MATCH(_("IsDoor")))
763 test = IS_DOOR;
764 else if (MATCH(_("IsSUID")))
765 test = IS_SUID;
766 else if (MATCH(_("IsSGID")))
767 test = IS_SGID;
768 else if (MATCH(_("IsSticky")))
769 test = IS_STICKY;
770 else if (MATCH(_("IsReadable")))
771 test = IS_READABLE;
772 else if (MATCH(_("IsWriteable")))
773 test = IS_WRITEABLE;
774 else if (MATCH(_("IsExecutable")))
775 test = IS_EXEC;
776 else if (MATCH(_("IsEmpty")))
777 test = IS_EMPTY;
778 else if (MATCH(_("IsMine")))
779 test = IS_MINE;
780 else
781 return NULL;
783 cond = g_new(FindCondition, 1);
784 cond->test = &test_is;
785 cond->free = (FindFree) &g_free;
786 cond->data1 = NULL;
787 cond->data2 = NULL;
788 cond->value = test;
790 return cond;
793 /* Call this just after reading a ' */
794 static FindCondition *parse_match(const gchar **expression)
796 FindCondition *cond = NULL;
797 GString *str;
798 FindTest test = &test_leaf;
799 str = g_string_new(NULL);
801 while (NEXT != '\'')
803 gchar c = NEXT;
805 if (c == '\0')
806 goto out;
807 EAT;
809 if (c == '\\' && NEXT == '\'')
811 c = NEXT;
812 EAT;
815 if (c == '/')
816 test = &test_path;
818 g_string_append_c(str, c);
820 EAT;
822 cond = g_new(FindCondition, 1);
823 cond->test = test;
824 cond->free = &free_simple;
825 cond->data1 = str->str;
826 cond->data2 = NULL;
828 out:
829 g_string_free(str, cond ? FALSE : TRUE);
831 return cond;
834 /* NUMERIC EXPRESSIONS */
836 /* CALCULATIONS */
838 static double get_constant(Eval *eval, FindInfo *info)
840 double value = *((double *) (eval->data1));
841 gint flags = GPOINTER_TO_INT(eval->data2);
843 if (flags & FLAG_AGO)
844 value = info->now - value;
845 else if (flags & FLAG_HENCE)
846 value = info->now + value;
848 return value;
851 static double get_var(Eval *eval, FindInfo *info)
853 switch ((VarType) eval->data1)
855 case V_ATIME:
856 return info->stats.st_atime;
857 case V_CTIME:
858 return info->stats.st_ctime;
859 case V_MTIME:
860 return info->stats.st_mtime;
861 case V_SIZE:
862 return info->stats.st_size;
863 case V_INODE:
864 return info->stats.st_ino;
865 case V_NLINKS:
866 return info->stats.st_nlink;
867 case V_UID:
868 return info->stats.st_uid;
869 case V_GID:
870 return info->stats.st_gid;
871 case V_BLOCKS:
872 return info->stats.st_blocks;
875 return 0;
878 /* FREEING */
880 static void free_constant(Eval *eval)
882 g_free(eval->data1);
883 g_free(eval);
886 /* PARSING */
888 /* Parse something that evaluates to a number.
889 * This function tries to get a constant - if it fails then it tries
890 * interpreting the next token as a variable.
892 static Eval *parse_eval(const gchar **expression)
894 const char *start;
895 char *end;
896 double value;
897 Eval *eval;
898 gint flags = 0;
900 SKIP;
901 start = *expression;
902 value = strtol(start, &end, 0);
904 if (end == start)
906 if (MATCH(_("Now")))
908 value = 0;
909 flags |= FLAG_HENCE;
911 else
912 return parse_variable(expression);
914 else
915 *expression = end;
917 SKIP;
919 if (MATCH(_("Byte")) || MATCH(_("Bytes")))
921 else if (MATCH("Kb") || MATCH("K"))
922 value *= 1<<10;
923 else if (MATCH("Mb") || MATCH("M"))
924 value *= 1<<20;
925 else if (MATCH("Gb") || MATCH("G"))
926 value *= 1<<30;
927 else if (MATCH(_("Sec")) || MATCH(_("Secs")))
929 else if (MATCH(_("Min")) || MATCH(_("Mins")))
930 value *= 60;
931 else if (MATCH(_("Hour")) || MATCH(_("Hours")))
932 value *= 60 * 60;
933 else if (MATCH(_("Day")) || MATCH(_("Days")))
934 value *= 60 * 60 * 24;
935 else if (MATCH(_("Week")) || MATCH(_("Weeks")))
936 value *= 60 * 60 * 24 * 7;
937 else if (MATCH(_("Year")) || MATCH(_("Years")))
938 value *= 60 * 60 * 24 * 7 * 365.25;
940 eval = g_new(Eval, 1);
941 eval->calc = &get_constant;
942 eval->free = &free_constant;
943 eval->data1 = g_memdup(&value, sizeof(value));
945 SKIP;
946 if (MATCH(_("Ago")))
947 flags |= FLAG_AGO;
948 else if (MATCH(_("Hence")))
949 flags |= FLAG_HENCE;
951 eval->data2 = GINT_TO_POINTER(flags);
953 return eval;
956 static Eval *parse_variable(const gchar **expression)
958 Eval *eval;
959 VarType var;
961 SKIP;
963 if (MATCH(_("atime")))
964 var = V_ATIME;
965 else if (MATCH(_("ctime")))
966 var = V_CTIME;
967 else if (MATCH(_("mtime")))
968 var = V_MTIME;
969 else if (MATCH(_("size")))
970 var = V_SIZE;
971 else if (MATCH(_("inode")))
972 var = V_INODE;
973 else if (MATCH(_("nlinks")))
974 var = V_NLINKS;
975 else if (MATCH(_("uid")))
976 var = V_UID;
977 else if (MATCH(_("gid")))
978 var = V_GID;
979 else if (MATCH(_("blocks")))
980 var = V_BLOCKS;
981 else
982 return NULL;
984 eval = g_new(Eval, 1);
985 eval->calc = &get_var;
986 eval->free = (EvalFree) &g_free;
987 eval->data1 = (gpointer) var;
988 eval->data2 = NULL;
990 return eval;
993 static gboolean match(const gchar **expression, const gchar *word)
995 int len;
997 len = strlen(word);
998 if (g_strncasecmp(*expression, word, len))
999 return FALSE;
1001 if (isalpha(*(*expression + len)))
1002 return FALSE;
1004 (*expression) += len;
1006 return TRUE;