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)
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.
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
);
100 typedef long (*EvalCalc
)(Eval
*eval
, FindInfo
*info
);
101 typedef void (*EvalFree
)(Eval
*eval
);
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))
120 # define S_ISVTX 0x0001000
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
)
134 guchar
**expression
= &string
;
136 g_return_val_if_fail(string
!= NULL
, NULL
);
138 cond
= parse_expression(expression
);
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
)
181 str
= g_string_new(NULL
);
191 if (c
== '\'' || c
== '"')
192 quote
= c
; /* Start quoted section */
193 else if (c
== '\\' && (NEXT
== '(' || NEXT
== ')'))
203 guchar
*retval
= str
->str
;
205 g_string_free(str
, FALSE
);
213 quote
= '\0'; /* End quoted section */
214 else if (c
== '\\' && NEXT
== quote
)
216 g_string_append_c(str
, c
);
221 g_string_append_c(str
, c
);
224 g_string_free(str
, TRUE
);
232 static gboolean
test_prune(FindCondition
*condition
, FindInfo
*info
)
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
;
256 to_sys
= g_string_new(NULL
);
258 while ((perc
= strchr(command
, '%')))
260 if (perc
> start
&& perc
[-1] == '\\')
263 while (command
< perc
)
264 g_string_append_c(to_sys
, *(command
++));
267 g_string_append(to_sys
, info
->fullpath
);
270 g_string_append_c(to_sys
, '%');
277 g_string_append(to_sys
, command
);
279 retcode
= system(to_sys
->str
);
281 g_string_free(to_sys
, TRUE
);
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
)
316 return S_ISDIR(mode
);
318 return S_ISREG(mode
);
320 return S_ISLNK(mode
);
322 return S_ISFIFO(mode
);
324 return S_ISSOCK(mode
);
326 return S_ISCHR(mode
);
328 return S_ISBLK(mode
);
333 return (mode
& S_ISUID
) != 0;
335 return (mode
& S_ISGID
) != 0;
337 return (mode
& S_ISVTX
) != 0;
338 /* NOTE: access() uses uid, not euid. Shouldn't matter? */
340 return access(info
->fullpath
, R_OK
) == 0;
342 return access(info
->fullpath
, W_OK
) == 0;
344 return access(info
->fullpath
, X_OK
) == 0;
346 return info
->stats
.st_size
== 0;
348 return info
->stats
.st_uid
== euid
;
354 static gboolean
test_comp(FindCondition
*condition
, FindInfo
*info
)
356 Eval
*first
= (Eval
*) condition
->data1
;
357 Eval
*second
= (Eval
*) condition
->data2
;
360 a
= first
->calc(first
, info
);
361 b
= second
->calc(second
, info
);
363 switch ((CompType
) condition
->value
)
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
);
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
;
403 second
->free(second
);
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
;
416 second
->free(second
);
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
432 static FindCondition
*parse_expression(guchar
**expression
)
434 FindCondition
*first
, *second
, *cond
;
436 first
= parse_case(expression
);
445 second
= parse_expression(expression
);
452 cond
= g_new(FindCondition
, 1);
453 cond
->test
= &test_OR
;
454 cond
->free
= &free_branch
;
456 cond
->data2
= second
;
461 static FindCondition
*parse_case(guchar
**expression
)
463 FindCondition
*first
, *second
, *cond
;
465 first
= parse_condition(expression
);
470 if (NEXT
== '\0' || NEXT
== ',' || NEXT
== ')')
475 second
= parse_case(expression
);
482 cond
= g_new(FindCondition
, 1);
483 cond
->test
= &test_AND
;
484 cond
->free
= &free_branch
;
486 cond
->data2
= second
;
491 static FindCondition
*parse_condition(guchar
**expression
)
493 FindCondition
*cond
= NULL
;
497 if (NEXT
== '!' || MATCH("Not"))
499 FindCondition
*operand
;
503 operand
= parse_condition(expression
);
506 cond
= g_new(FindCondition
, 1);
507 cond
->test
= test_neg
;
508 cond
->free
= free_branch
;
509 cond
->data1
= operand
;
516 FindCondition
*subcond
;
520 subcond
= parse_expression(expression
);
526 subcond
->free(subcond
);
537 return parse_match(expression
);
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
;
558 else if (g_strncasecmp(*expression
, "Is", 2) == 0)
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
);
578 cond
= g_new(FindCondition
, 1);
579 cond
->test
= test_system
;
580 cond
->free
= &free_simple
;
581 cond
->data1
= command_string
;
587 static FindCondition
*parse_comparison(guchar
**expression
)
589 FindCondition
*cond
= NULL
;
596 first
= parse_eval(expression
);
606 else if (NEXT
== '>')
617 else if (NEXT
== '<')
628 else if (NEXT
== '!' && (*expression
)[1] == '=')
634 else if (MATCH("After"))
636 else if (MATCH("Before"))
642 second
= parse_eval(expression
);
649 cond
= g_new(FindCondition
, 1);
650 cond
->test
= &test_comp
;
651 cond
->free
= (FindFree
) &free_comp
;
653 cond
->data2
= second
;
659 static FindCondition
*parse_is(guchar
**expression
)
666 else if (MATCH("Link"))
668 else if (MATCH("Dir"))
670 else if (MATCH("Char"))
672 else if (MATCH("Block"))
674 else if (MATCH("Dev"))
676 else if (MATCH("Pipe"))
678 else if (MATCH("Socket"))
680 else if (MATCH("SUID"))
682 else if (MATCH("SGID"))
684 else if (MATCH("Sticky"))
686 else if (MATCH("Readable"))
688 else if (MATCH("Writeable"))
690 else if (MATCH("Executable"))
692 else if (MATCH("Empty"))
694 else if (MATCH("Mine"))
699 cond
= g_new(FindCondition
, 1);
700 cond
->test
= &test_is
;
701 cond
->free
= (FindFree
) &g_free
;
709 /* Call this just after reading a ' */
710 static FindCondition
*parse_match(guchar
**expression
)
712 FindCondition
*cond
= NULL
;
714 FindTest test
= &test_leaf
;
715 str
= g_string_new(NULL
);
725 if (c
== '\\' && NEXT
== '\'')
734 g_string_append_c(str
, c
);
738 cond
= g_new(FindCondition
, 1);
740 cond
->free
= &free_simple
;
741 cond
->data1
= str
->str
;
745 g_string_free(str
, cond
? FALSE
: TRUE
);
750 /* NUMERIC EXPRESSIONS */
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
;
767 static long get_var(Eval
*eval
, FindInfo
*info
)
769 switch ((VarType
) eval
->data1
)
772 return info
->stats
.st_atime
;
774 return info
->stats
.st_ctime
;
776 return info
->stats
.st_mtime
;
778 return info
->stats
.st_size
;
780 return info
->stats
.st_ino
;
782 return info
->stats
.st_nlink
;
784 return info
->stats
.st_uid
;
786 return info
->stats
.st_gid
;
788 return info
->stats
.st_blocks
;
796 static void free_constant(Eval
*eval
)
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
)
817 value
= strtol(start
, &end
, 0);
827 return parse_variable(expression
);
834 if (MATCH("Byte") || MATCH("Bytes"))
836 else if (MATCH("Kb"))
838 else if (MATCH("Mb"))
840 else if (MATCH("Gb"))
842 else if (MATCH("Sec") || MATCH("Secs"))
844 else if (MATCH("Min") || MATCH("Mins"))
846 else if (MATCH("Hour") || MATCH("Hours"))
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
));
863 else if (MATCH("Hence"))
866 eval
->data2
= (gpointer
) flags
;
871 static Eval
*parse_variable(guchar
**expression
)
880 else if (MATCH("ctime"))
882 else if (MATCH("mtime"))
884 else if (MATCH("size"))
886 else if (MATCH("inode"))
888 else if (MATCH("nlinks"))
890 else if (MATCH("uid"))
892 else if (MATCH("gid"))
894 else if (MATCH("blocks"))
899 eval
= g_new(Eval
, 1);
900 eval
->calc
= &get_var
;
901 eval
->free
= (EvalFree
) &g_free
;
902 eval
->data1
= (gpointer
) var
;