r1892: Use K, M and G consistantly (Marcin Juszkiewicz).
[rox-filer.git] / ROX-Filer / src / find.c
blob0c69fa86b5555e644ce7d6ee2c295dd76ba6cbd6
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(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 g_return_if_fail(condition != NULL);
177 condition->free(condition);
180 /****************************************************************
181 * INTERNAL FUNCTIONS *
182 ****************************************************************/
184 /* Call this when you've just eaten '('. Returns the string upto the
185 * matching ')' (and eats that bracket), or NULL on failure.
186 * Brackets within the string may be quoted or escaped.
188 static gchar *get_bracketed_string(const gchar **expression)
190 GString *str;
191 int opens = 1;
192 gchar quote = '\0';
194 str = g_string_new(NULL);
196 while (NEXT != '\0')
198 gchar c = NEXT;
200 EAT;
202 if (quote == '\0')
204 if (c == '\'' || c == '"')
205 quote = c; /* Start quoted section */
206 else if (c == '\\' && (NEXT == '(' || NEXT == ')'))
208 c = NEXT;
209 EAT;
211 else if (c == ')')
213 opens--;
214 if (opens < 1)
216 gchar *retval = str->str;
218 g_string_free(str, FALSE);
219 return retval;
222 else if (c == '(')
223 opens++;
225 else if (c == quote)
226 quote = '\0'; /* End quoted section */
227 else if (c == '\\' && NEXT == quote)
229 g_string_append_c(str, c);
230 c = NEXT;
231 EAT;
234 g_string_append_c(str, c);
237 g_string_free(str, TRUE);
239 return NULL;
243 /* TESTING CODE */
245 static gboolean test_prune(FindCondition *condition, FindInfo *info)
247 info->prune = TRUE;
248 return FALSE;
251 static gboolean test_leaf(FindCondition *condition, FindInfo *info)
253 return fnmatch(condition->data1, info->leaf, 0) == 0;
256 static gboolean test_path(FindCondition *condition, FindInfo *info)
258 return fnmatch(condition->data1, info->fullpath, FNM_PATHNAME) == 0;
261 static gboolean test_system(FindCondition *condition, FindInfo *info)
263 gchar *command = (gchar *) condition->data1;
264 gchar *start = command;
265 GString *to_sys = NULL;
266 gchar *perc;
267 int retcode;
269 to_sys = g_string_new(NULL);
271 while ((perc = strchr(command, '%')))
273 if (perc > start && perc[-1] == '\\')
274 perc--;
276 while (command < perc)
277 g_string_append_c(to_sys, *(command++));
279 if (*perc == '%')
280 g_string_append(to_sys, info->fullpath);
281 else
283 g_string_append_c(to_sys, '%');
284 perc++;
287 command = perc + 1;
290 g_string_append(to_sys, command);
292 retcode = system(to_sys->str);
294 g_string_free(to_sys, TRUE);
296 return retcode == 0;
299 static gboolean test_OR(FindCondition *condition, FindInfo *info)
301 FindCondition *first = (FindCondition *) condition->data1;
302 FindCondition *second = (FindCondition *) condition->data2;
304 return first->test(first, info) || second->test(second, info);
307 static gboolean test_AND(FindCondition *condition, FindInfo *info)
309 FindCondition *first = (FindCondition *) condition->data1;
310 FindCondition *second = (FindCondition *) condition->data2;
312 return first->test(first, info) && second->test(second, info);
315 static gboolean test_neg(FindCondition *condition, FindInfo *info)
317 FindCondition *first = (FindCondition *) condition->data1;
319 return !first->test(first, info);
322 static gboolean test_is(FindCondition *condition, FindInfo *info)
324 mode_t mode = info->stats.st_mode;
326 switch ((IsTest) condition->value)
328 case IS_DIR:
329 return S_ISDIR(mode);
330 case IS_REG:
331 return S_ISREG(mode);
332 case IS_LNK:
333 return S_ISLNK(mode);
334 case IS_FIFO:
335 return S_ISFIFO(mode);
336 case IS_SOCK:
337 return S_ISSOCK(mode);
338 case IS_CHR:
339 return S_ISCHR(mode);
340 case IS_BLK:
341 return S_ISBLK(mode);
342 case IS_DEV:
343 return S_ISCHR(mode)
344 || S_ISBLK(mode);
345 case IS_DOOR:
346 return S_ISDOOR(mode);
347 case IS_SUID:
348 return (mode & S_ISUID) != 0;
349 case IS_SGID:
350 return (mode & S_ISGID) != 0;
351 case IS_STICKY:
352 return (mode & S_ISVTX) != 0;
353 /* NOTE: access() uses uid, not euid. Shouldn't matter? */
354 case IS_READABLE:
355 return access(info->fullpath, R_OK) == 0;
356 case IS_WRITEABLE:
357 return access(info->fullpath, W_OK) == 0;
358 case IS_EXEC:
359 return access(info->fullpath, X_OK) == 0;
360 case IS_EMPTY:
361 return info->stats.st_size == 0;
362 case IS_MINE:
363 return info->stats.st_uid == euid;
366 return FALSE;
369 static gboolean test_comp(FindCondition *condition, FindInfo *info)
371 Eval *first = (Eval *) condition->data1;
372 Eval *second = (Eval *) condition->data2;
373 double a, b;
375 a = first->calc(first, info);
376 b = second->calc(second, info);
378 switch ((CompType) condition->value)
380 case COMP_LT:
381 return a < b;
382 case COMP_LE:
383 return a <= b;
384 case COMP_EQ:
385 return a == b;
386 case COMP_NE:
387 return a != b;
388 case COMP_GE:
389 return a >= b;
390 case COMP_GT:
391 return a > b;
394 return FALSE;
397 /* FREEING CODE */
399 /* Frees the structure and g_free()s both data items (NULL is OK) */
400 static void free_simple(FindCondition *condition)
402 g_return_if_fail(condition != NULL);
404 g_free(condition->data1);
405 g_free(condition->data2);
406 g_free(condition);
409 /* Treats data1 and data2 as conditions (or NULL) and frees recursively */
410 static void free_branch(FindCondition *condition)
412 FindCondition *first = (FindCondition *) condition->data1;
413 FindCondition *second = (FindCondition *) condition->data2;
415 if (first)
416 first->free(first);
417 if (second)
418 second->free(second);
419 g_free(condition);
422 /* Treats data1 and data2 as evals (or NULL) and frees recursively */
423 static void free_comp(FindCondition *condition)
425 Eval *first = (Eval *) condition->data1;
426 Eval *second = (Eval *) condition->data2;
428 if (first)
429 first->free(first);
430 if (second)
431 second->free(second);
432 g_free(condition);
435 /* PARSING CODE */
437 /* These all work in the same way - you give them an expression and the
438 * parse as much as they can, returning a condition for everything that
439 * was parsed and updating the expression pointer to the first unknown
440 * token. NULL indicates an error.
444 /* An expression is a series of comma-separated cases, any of which
445 * may match.
447 static FindCondition *parse_expression(const gchar **expression)
449 FindCondition *first, *second, *cond;
451 first = parse_case(expression);
452 if (!first)
453 return NULL;
455 SKIP;
456 if (NEXT != ',')
457 return first;
458 EAT;
460 second = parse_expression(expression);
461 if (!second)
463 first->free(first);
464 return NULL;
467 cond = g_new(FindCondition, 1);
468 cond->test = &test_OR;
469 cond->free = &free_branch;
470 cond->data1 = first;
471 cond->data2 = second;
473 return cond;
476 static FindCondition *parse_case(const gchar **expression)
478 FindCondition *first, *second, *cond;
480 first = parse_condition(expression);
481 if (!first)
482 return NULL;
484 SKIP;
485 if (NEXT == '\0' || NEXT == ',' || NEXT == ')')
486 return first;
488 (void) MATCH(_("And"));
490 second = parse_case(expression);
491 if (!second)
493 first->free(first);
494 return NULL;
497 cond = g_new(FindCondition, 1);
498 cond->test = &test_AND;
499 cond->free = &free_branch;
500 cond->data1 = first;
501 cond->data2 = second;
503 return cond;
506 static FindCondition *parse_condition(const gchar **expression)
508 FindCondition *cond = NULL;
510 SKIP;
512 if (NEXT == '!' || MATCH(_("Not")))
514 FindCondition *operand;
516 EAT;
518 operand = parse_condition(expression);
519 if (!operand)
520 return NULL;
521 cond = g_new(FindCondition, 1);
522 cond->test = test_neg;
523 cond->free = free_branch;
524 cond->data1 = operand;
525 cond->data2 = NULL;
526 return cond;
529 if (NEXT == '(')
531 FindCondition *subcond;
533 EAT;
535 subcond = parse_expression(expression);
536 if (!subcond)
537 return NULL;
538 SKIP;
539 if (NEXT != ')')
541 subcond->free(subcond);
542 return NULL;
545 EAT;
546 return subcond;
549 if (NEXT == '\'')
551 EAT;
552 return parse_match(expression);
555 if (MATCH(_("system")))
557 SKIP;
558 if (NEXT != '(')
559 return NULL;
560 EAT;
561 return parse_system(expression);
563 else if (MATCH(_("prune")))
565 cond = g_new(FindCondition, 1);
566 cond->test = test_prune;
567 cond->free = (FindFree) g_free;
568 cond->data1 = NULL;
569 cond->data2 = NULL;
571 return cond;
574 cond = parse_dash(expression);
575 if (cond)
576 return cond;
578 cond = parse_is(expression);
579 if (cond)
580 return cond;
582 return parse_comparison(expression);
585 /* Call this when you've just eaten 'system(' */
586 static FindCondition *parse_system(const gchar **expression)
588 FindCondition *cond = NULL;
589 gchar *command_string;
591 command_string = get_bracketed_string(expression);
592 if (!command_string)
593 return NULL;
595 cond = g_new(FindCondition, 1);
596 cond->test = test_system;
597 cond->free = &free_simple;
598 cond->data1 = command_string;
599 cond->data2 = NULL;
601 return cond;
604 static FindCondition *parse_comparison(const gchar **expression)
606 FindCondition *cond = NULL;
607 Eval *first;
608 Eval *second;
609 CompType comp;
611 SKIP;
613 first = parse_eval(expression);
614 if (!first)
615 return NULL;
617 SKIP;
618 if (NEXT == '=')
620 comp = COMP_EQ;
621 EAT;
623 else if (NEXT == '>')
625 EAT;
626 if (NEXT == '=')
628 EAT;
629 comp = COMP_GE;
631 else
632 comp = COMP_GT;
634 else if (NEXT == '<')
636 EAT;
637 if (NEXT == '=')
639 EAT;
640 comp = COMP_LE;
642 else
643 comp = COMP_LT;
645 else if (NEXT == '!' && (*expression)[1] == '=')
647 EAT;
648 EAT;
649 comp = COMP_NE;
651 else if (MATCH(_("After")))
652 comp = COMP_GT;
653 else if (MATCH(_("Before")))
654 comp = COMP_LT;
655 else
656 return NULL;
658 SKIP;
659 second = parse_eval(expression);
660 if (!second)
662 first->free(first);
663 return NULL;
666 cond = g_new(FindCondition, 1);
667 cond->test = &test_comp;
668 cond->free = (FindFree) &free_comp;
669 cond->data1 = first;
670 cond->data2 = second;
671 cond->value = comp;
673 return cond;
676 static FindCondition *parse_dash(const gchar **expression)
678 const gchar *exp = *expression;
679 FindCondition *cond, *retval = NULL;
680 IsTest test;
681 int i = 1;
683 if (NEXT != '-')
684 return NULL;
686 while (exp[i] && !isspace(exp[i]))
688 switch (exp[i])
690 case 'f': test = IS_REG; break;
691 case 'l': test = IS_LNK; break;
692 case 'd': test = IS_DIR; break;
693 case 'b': test = IS_BLK; break;
694 case 'c': test = IS_CHR; break;
695 case 'D': test = IS_DEV; break;
696 case 'p': test = IS_FIFO; break;
697 case 'S': test = IS_SOCK; break;
698 case 'O': test = IS_DOOR; break;
699 case 'u': test = IS_SUID; break;
700 case 'g': test = IS_SGID; break;
701 case 'k': test = IS_STICKY; break;
702 case 'r': test = IS_READABLE; break;
703 case 'w': test = IS_WRITEABLE; break;
704 case 'x': test = IS_EXEC; break;
705 case 'o': test = IS_MINE; break;
706 case 'z': test = IS_EMPTY; break;
707 default:
708 if (retval)
709 find_condition_free(retval);
710 return NULL;
712 i++;
714 cond = g_new(FindCondition, 1);
715 cond->test = &test_is;
716 cond->free = (FindFree) &g_free;
717 cond->data1 = NULL;
718 cond->data2 = NULL;
719 cond->value = test;
721 if (retval)
723 FindCondition *new;
725 new = g_new(FindCondition, 1);
726 new->test = &test_AND;
727 new->free = &free_branch;
728 new->data1 = retval;
729 new->data2 = cond;
731 retval = new;
733 else
734 retval = cond;
737 (*expression) += i;
739 return retval;
742 /* Returns NULL if expression is not an is-expression */
743 static FindCondition *parse_is(const gchar **expression)
745 FindCondition *cond;
746 IsTest test;
748 if (MATCH(_("IsReg")))
749 test = IS_REG;
750 else if (MATCH(_("IsLink")))
751 test = IS_LNK;
752 else if (MATCH(_("IsDir")))
753 test = IS_DIR;
754 else if (MATCH(_("IsChar")))
755 test = IS_CHR;
756 else if (MATCH(_("IsBlock")))
757 test = IS_BLK;
758 else if (MATCH(_("IsDev")))
759 test = IS_DEV;
760 else if (MATCH(_("IsPipe")))
761 test = IS_FIFO;
762 else if (MATCH(_("IsSocket")))
763 test = IS_SOCK;
764 else if (MATCH(_("IsDoor")))
765 test = IS_DOOR;
766 else if (MATCH(_("IsSUID")))
767 test = IS_SUID;
768 else if (MATCH(_("IsSGID")))
769 test = IS_SGID;
770 else if (MATCH(_("IsSticky")))
771 test = IS_STICKY;
772 else if (MATCH(_("IsReadable")))
773 test = IS_READABLE;
774 else if (MATCH(_("IsWriteable")))
775 test = IS_WRITEABLE;
776 else if (MATCH(_("IsExecutable")))
777 test = IS_EXEC;
778 else if (MATCH(_("IsEmpty")))
779 test = IS_EMPTY;
780 else if (MATCH(_("IsMine")))
781 test = IS_MINE;
782 else
783 return NULL;
785 cond = g_new(FindCondition, 1);
786 cond->test = &test_is;
787 cond->free = (FindFree) &g_free;
788 cond->data1 = NULL;
789 cond->data2 = NULL;
790 cond->value = test;
792 return cond;
795 /* Call this just after reading a ' */
796 static FindCondition *parse_match(const gchar **expression)
798 FindCondition *cond = NULL;
799 GString *str;
800 FindTest test = &test_leaf;
801 str = g_string_new(NULL);
803 while (NEXT != '\'')
805 gchar c = NEXT;
807 if (c == '\0')
808 goto out;
809 EAT;
811 if (c == '\\' && NEXT == '\'')
813 c = NEXT;
814 EAT;
817 if (c == '/')
818 test = &test_path;
820 g_string_append_c(str, c);
822 EAT;
824 cond = g_new(FindCondition, 1);
825 cond->test = test;
826 cond->free = &free_simple;
827 cond->data1 = str->str;
828 cond->data2 = NULL;
830 out:
831 g_string_free(str, cond ? FALSE : TRUE);
833 return cond;
836 /* NUMERIC EXPRESSIONS */
838 /* CALCULATIONS */
840 static double get_constant(Eval *eval, FindInfo *info)
842 double value = *((double *) (eval->data1));
843 gint flags = GPOINTER_TO_INT(eval->data2);
845 if (flags & FLAG_AGO)
846 value = info->now - value;
847 else if (flags & FLAG_HENCE)
848 value = info->now + value;
850 return value;
853 static double get_var(Eval *eval, FindInfo *info)
855 switch ((VarType) eval->data1)
857 case V_ATIME:
858 return info->stats.st_atime;
859 case V_CTIME:
860 return info->stats.st_ctime;
861 case V_MTIME:
862 return info->stats.st_mtime;
863 case V_SIZE:
864 return info->stats.st_size;
865 case V_INODE:
866 return info->stats.st_ino;
867 case V_NLINKS:
868 return info->stats.st_nlink;
869 case V_UID:
870 return info->stats.st_uid;
871 case V_GID:
872 return info->stats.st_gid;
873 case V_BLOCKS:
874 return info->stats.st_blocks;
877 return 0;
880 /* FREEING */
882 static void free_constant(Eval *eval)
884 g_free(eval->data1);
885 g_free(eval);
888 /* PARSING */
890 /* Parse something that evaluates to a number.
891 * This function tries to get a constant - if it fails then it tries
892 * interpreting the next token as a variable.
894 static Eval *parse_eval(const gchar **expression)
896 const char *start;
897 char *end;
898 double value;
899 Eval *eval;
900 gint flags = 0;
902 SKIP;
903 start = *expression;
904 value = strtol(start, &end, 0);
906 if (end == start)
908 if (MATCH(_("Now")))
910 value = 0;
911 flags |= FLAG_HENCE;
913 else
914 return parse_variable(expression);
916 else
917 *expression = end;
919 SKIP;
921 if (MATCH(_("Byte")) || MATCH(_("Bytes")))
923 else if (MATCH("Kb") || MATCH("K"))
924 value *= 1<<10;
925 else if (MATCH("Mb") || MATCH("M"))
926 value *= 1<<20;
927 else if (MATCH("Gb") || MATCH("G"))
928 value *= 1<<30;
929 else if (MATCH(_("Sec")) || MATCH(_("Secs")))
931 else if (MATCH(_("Min")) || MATCH(_("Mins")))
932 value *= 60;
933 else if (MATCH(_("Hour")) || MATCH(_("Hours")))
934 value *= 60 * 60;
935 else if (MATCH(_("Day")) || MATCH(_("Days")))
936 value *= 60 * 60 * 24;
937 else if (MATCH(_("Week")) || MATCH(_("Weeks")))
938 value *= 60 * 60 * 24 * 7;
939 else if (MATCH(_("Year")) || MATCH(_("Years")))
940 value *= 60 * 60 * 24 * 7 * 365.25;
942 eval = g_new(Eval, 1);
943 eval->calc = &get_constant;
944 eval->free = &free_constant;
945 eval->data1 = g_memdup(&value, sizeof(value));
947 SKIP;
948 if (MATCH(_("Ago")))
949 flags |= FLAG_AGO;
950 else if (MATCH(_("Hence")))
951 flags |= FLAG_HENCE;
953 eval->data2 = GINT_TO_POINTER(flags);
955 return eval;
958 static Eval *parse_variable(const gchar **expression)
960 Eval *eval;
961 VarType var;
963 SKIP;
965 if (MATCH(_("atime")))
966 var = V_ATIME;
967 else if (MATCH(_("ctime")))
968 var = V_CTIME;
969 else if (MATCH(_("mtime")))
970 var = V_MTIME;
971 else if (MATCH(_("size")))
972 var = V_SIZE;
973 else if (MATCH(_("inode")))
974 var = V_INODE;
975 else if (MATCH(_("nlinks")))
976 var = V_NLINKS;
977 else if (MATCH(_("uid")))
978 var = V_UID;
979 else if (MATCH(_("gid")))
980 var = V_GID;
981 else if (MATCH(_("blocks")))
982 var = V_BLOCKS;
983 else
984 return NULL;
986 eval = g_new(Eval, 1);
987 eval->calc = &get_var;
988 eval->free = (EvalFree) &g_free;
989 eval->data1 = (gpointer) var;
990 eval->data2 = NULL;
992 return eval;
995 static gboolean match(const gchar **expression, const gchar *word)
997 int len;
999 len = strlen(word);
1000 if (g_strncasecmp(*expression, word, len))
1001 return FALSE;
1003 if (isalpha(*(*expression + len)))
1004 return FALSE;
1006 (*expression) += len;
1008 return TRUE;