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)
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
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.
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
);
104 typedef long (*EvalCalc
)(Eval
*eval
, FindInfo
*info
);
105 typedef void (*EvalFree
)(Eval
*eval
);
107 struct _FindCondition
111 /* These next three depend on the first two... */
125 #define EAT ((*expression)++)
126 #define NEXT (**expression)
127 #define SKIP while (NEXT == ' ' || NEXT == '\t') EAT
128 #define MATCH(word) (match(expression, word))
131 # define S_ISVTX 0x0001000
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
)
145 guchar
**expression
= &string
;
147 g_return_val_if_fail(string
!= NULL
, NULL
);
149 cond
= parse_expression(expression
);
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
)
192 str
= g_string_new(NULL
);
202 if (c
== '\'' || c
== '"')
203 quote
= c
; /* Start quoted section */
204 else if (c
== '\\' && (NEXT
== '(' || NEXT
== ')'))
214 guchar
*retval
= str
->str
;
216 g_string_free(str
, FALSE
);
224 quote
= '\0'; /* End quoted section */
225 else if (c
== '\\' && NEXT
== quote
)
227 g_string_append_c(str
, c
);
232 g_string_append_c(str
, c
);
235 g_string_free(str
, TRUE
);
243 static gboolean
test_prune(FindCondition
*condition
, FindInfo
*info
)
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
;
267 to_sys
= g_string_new(NULL
);
269 while ((perc
= strchr(command
, '%')))
271 if (perc
> start
&& perc
[-1] == '\\')
274 while (command
< perc
)
275 g_string_append_c(to_sys
, *(command
++));
278 g_string_append(to_sys
, info
->fullpath
);
281 g_string_append_c(to_sys
, '%');
288 g_string_append(to_sys
, command
);
290 retcode
= system(to_sys
->str
);
292 g_string_free(to_sys
, TRUE
);
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
)
327 return S_ISDIR(mode
);
329 return S_ISREG(mode
);
331 return S_ISLNK(mode
);
333 return S_ISFIFO(mode
);
335 return S_ISSOCK(mode
);
337 return S_ISCHR(mode
);
339 return S_ISBLK(mode
);
344 return (mode
& S_ISUID
) != 0;
346 return (mode
& S_ISGID
) != 0;
348 return (mode
& S_ISVTX
) != 0;
349 /* NOTE: access() uses uid, not euid. Shouldn't matter? */
351 return access(info
->fullpath
, R_OK
) == 0;
353 return access(info
->fullpath
, W_OK
) == 0;
355 return access(info
->fullpath
, X_OK
) == 0;
357 return info
->stats
.st_size
== 0;
359 return info
->stats
.st_uid
== euid
;
365 static gboolean
test_comp(FindCondition
*condition
, FindInfo
*info
)
367 Eval
*first
= (Eval
*) condition
->data1
;
368 Eval
*second
= (Eval
*) condition
->data2
;
371 a
= first
->calc(first
, info
);
372 b
= second
->calc(second
, info
);
374 switch ((CompType
) condition
->value
)
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
);
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
;
414 second
->free(second
);
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
;
427 second
->free(second
);
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
443 static FindCondition
*parse_expression(guchar
**expression
)
445 FindCondition
*first
, *second
, *cond
;
447 first
= parse_case(expression
);
456 second
= parse_expression(expression
);
463 cond
= g_new(FindCondition
, 1);
464 cond
->test
= &test_OR
;
465 cond
->free
= &free_branch
;
467 cond
->data2
= second
;
472 static FindCondition
*parse_case(guchar
**expression
)
474 FindCondition
*first
, *second
, *cond
;
476 first
= parse_condition(expression
);
481 if (NEXT
== '\0' || NEXT
== ',' || NEXT
== ')')
484 (void) MATCH(_("And"));
486 second
= parse_case(expression
);
493 cond
= g_new(FindCondition
, 1);
494 cond
->test
= &test_AND
;
495 cond
->free
= &free_branch
;
497 cond
->data2
= second
;
502 static FindCondition
*parse_condition(guchar
**expression
)
504 FindCondition
*cond
= NULL
;
508 if (NEXT
== '!' || MATCH(_("Not")))
510 FindCondition
*operand
;
514 operand
= parse_condition(expression
);
517 cond
= g_new(FindCondition
, 1);
518 cond
->test
= test_neg
;
519 cond
->free
= free_branch
;
520 cond
->data1
= operand
;
527 FindCondition
*subcond
;
531 subcond
= parse_expression(expression
);
537 subcond
->free(subcond
);
548 return parse_match(expression
);
551 if (MATCH(_("system")))
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
;
570 cond
= parse_is(expression
);
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
);
587 cond
= g_new(FindCondition
, 1);
588 cond
->test
= test_system
;
589 cond
->free
= &free_simple
;
590 cond
->data1
= command_string
;
596 static FindCondition
*parse_comparison(guchar
**expression
)
598 FindCondition
*cond
= NULL
;
605 first
= parse_eval(expression
);
615 else if (NEXT
== '>')
626 else if (NEXT
== '<')
637 else if (NEXT
== '!' && (*expression
)[1] == '=')
643 else if (MATCH(_("After")))
645 else if (MATCH(_("Before")))
651 second
= parse_eval(expression
);
658 cond
= g_new(FindCondition
, 1);
659 cond
->test
= &test_comp
;
660 cond
->free
= (FindFree
) &free_comp
;
662 cond
->data2
= second
;
668 /* Returns NULL if expression is not an is-expression */
669 static FindCondition
*parse_is(guchar
**expression
)
674 if (MATCH(_("IsReg")))
676 else if (MATCH(_("IsLink")))
678 else if (MATCH(_("IsDir")))
680 else if (MATCH(_("IsChar")))
682 else if (MATCH(_("IsBlock")))
684 else if (MATCH(_("IsDev")))
686 else if (MATCH(_("IsPipe")))
688 else if (MATCH(_("IsSocket")))
690 else if (MATCH(_("IsSUID")))
692 else if (MATCH(_("IsSGID")))
694 else if (MATCH(_("IsSticky")))
696 else if (MATCH(_("IsReadable")))
698 else if (MATCH(_("IsWriteable")))
700 else if (MATCH(_("IsExecutable")))
702 else if (MATCH(_("IsEmpty")))
704 else if (MATCH(_("IsMine")))
709 cond
= g_new(FindCondition
, 1);
710 cond
->test
= &test_is
;
711 cond
->free
= (FindFree
) &g_free
;
719 /* Call this just after reading a ' */
720 static FindCondition
*parse_match(guchar
**expression
)
722 FindCondition
*cond
= NULL
;
724 FindTest test
= &test_leaf
;
725 str
= g_string_new(NULL
);
735 if (c
== '\\' && NEXT
== '\'')
744 g_string_append_c(str
, c
);
748 cond
= g_new(FindCondition
, 1);
750 cond
->free
= &free_simple
;
751 cond
->data1
= str
->str
;
755 g_string_free(str
, cond
? FALSE
: TRUE
);
760 /* NUMERIC EXPRESSIONS */
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
;
777 static long get_var(Eval
*eval
, FindInfo
*info
)
779 switch ((VarType
) eval
->data1
)
782 return info
->stats
.st_atime
;
784 return info
->stats
.st_ctime
;
786 return info
->stats
.st_mtime
;
788 return info
->stats
.st_size
;
790 return info
->stats
.st_ino
;
792 return info
->stats
.st_nlink
;
794 return info
->stats
.st_uid
;
796 return info
->stats
.st_gid
;
798 return info
->stats
.st_blocks
;
806 static void free_constant(Eval
*eval
)
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
)
827 value
= strtol(start
, &end
, 0);
837 return parse_variable(expression
);
844 if (MATCH(_("Byte")) || MATCH(_("Bytes")))
846 else if (MATCH("Kb"))
848 else if (MATCH("Mb"))
850 else if (MATCH("Gb"))
852 else if (MATCH(_("Sec")) || MATCH(_("Secs")))
854 else if (MATCH(_("Min")) || MATCH(_("Mins")))
856 else if (MATCH(_("Hour")) || MATCH(_("Hours")))
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
));
873 else if (MATCH(_("Hence")))
876 eval
->data2
= (gpointer
) flags
;
881 static Eval
*parse_variable(guchar
**expression
)
888 if (MATCH(_("atime")))
890 else if (MATCH(_("ctime")))
892 else if (MATCH(_("mtime")))
894 else if (MATCH(_("size")))
896 else if (MATCH(_("inode")))
898 else if (MATCH(_("nlinks")))
900 else if (MATCH(_("uid")))
902 else if (MATCH(_("gid")))
904 else if (MATCH(_("blocks")))
909 eval
= g_new(Eval
, 1);
910 eval
->calc
= &get_var
;
911 eval
->free
= (EvalFree
) &g_free
;
912 eval
->data1
= (gpointer
) var
;
918 static gboolean
match(guchar
**expression
, guchar
*word
)
923 if (g_strncasecmp(*expression
, word
, len
))
926 if (isalpha(*(*expression
+ len
)))
929 (*expression
) += len
;