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)
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(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
);
106 typedef double (*EvalCalc
)(Eval
*eval
, FindInfo
*info
);
107 typedef void (*EvalFree
)(Eval
*eval
);
109 struct _FindCondition
113 /* These next three depend on the first two... */
127 #define EAT ((*expression)++)
128 #define NEXT (**expression)
129 #define SKIP while (NEXT == ' ' || NEXT == '\t') EAT
130 #define MATCH(word) (match(expression, word))
133 # define S_ISVTX 0x0001000
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
)
147 const gchar
**expression
= &string
;
149 g_return_val_if_fail(string
!= NULL
, NULL
);
151 cond
= parse_expression(expression
);
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
)
194 str
= g_string_new(NULL
);
204 if (c
== '\'' || c
== '"')
205 quote
= c
; /* Start quoted section */
206 else if (c
== '\\' && (NEXT
== '(' || NEXT
== ')'))
216 gchar
*retval
= str
->str
;
218 g_string_free(str
, FALSE
);
226 quote
= '\0'; /* End quoted section */
227 else if (c
== '\\' && NEXT
== quote
)
229 g_string_append_c(str
, c
);
234 g_string_append_c(str
, c
);
237 g_string_free(str
, TRUE
);
245 static gboolean
test_prune(FindCondition
*condition
, FindInfo
*info
)
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
;
269 to_sys
= g_string_new(NULL
);
271 while ((perc
= strchr(command
, '%')))
273 if (perc
> start
&& perc
[-1] == '\\')
276 while (command
< perc
)
277 g_string_append_c(to_sys
, *(command
++));
280 g_string_append(to_sys
, info
->fullpath
);
283 g_string_append_c(to_sys
, '%');
290 g_string_append(to_sys
, command
);
292 retcode
= system(to_sys
->str
);
294 g_string_free(to_sys
, TRUE
);
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
)
329 return S_ISDIR(mode
);
331 return S_ISREG(mode
);
333 return S_ISLNK(mode
);
335 return S_ISFIFO(mode
);
337 return S_ISSOCK(mode
);
339 return S_ISCHR(mode
);
341 return S_ISBLK(mode
);
346 return S_ISDOOR(mode
);
348 return (mode
& S_ISUID
) != 0;
350 return (mode
& S_ISGID
) != 0;
352 return (mode
& S_ISVTX
) != 0;
353 /* NOTE: access() uses uid, not euid. Shouldn't matter? */
355 return access(info
->fullpath
, R_OK
) == 0;
357 return access(info
->fullpath
, W_OK
) == 0;
359 return access(info
->fullpath
, X_OK
) == 0;
361 return info
->stats
.st_size
== 0;
363 return info
->stats
.st_uid
== euid
;
369 static gboolean
test_comp(FindCondition
*condition
, FindInfo
*info
)
371 Eval
*first
= (Eval
*) condition
->data1
;
372 Eval
*second
= (Eval
*) condition
->data2
;
375 a
= first
->calc(first
, info
);
376 b
= second
->calc(second
, info
);
378 switch ((CompType
) condition
->value
)
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
);
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
;
418 second
->free(second
);
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
;
431 second
->free(second
);
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
447 static FindCondition
*parse_expression(const gchar
**expression
)
449 FindCondition
*first
, *second
, *cond
;
451 first
= parse_case(expression
);
460 second
= parse_expression(expression
);
467 cond
= g_new(FindCondition
, 1);
468 cond
->test
= &test_OR
;
469 cond
->free
= &free_branch
;
471 cond
->data2
= second
;
476 static FindCondition
*parse_case(const gchar
**expression
)
478 FindCondition
*first
, *second
, *cond
;
480 first
= parse_condition(expression
);
485 if (NEXT
== '\0' || NEXT
== ',' || NEXT
== ')')
488 (void) MATCH(_("And"));
490 second
= parse_case(expression
);
497 cond
= g_new(FindCondition
, 1);
498 cond
->test
= &test_AND
;
499 cond
->free
= &free_branch
;
501 cond
->data2
= second
;
506 static FindCondition
*parse_condition(const gchar
**expression
)
508 FindCondition
*cond
= NULL
;
512 if (NEXT
== '!' || MATCH(_("Not")))
514 FindCondition
*operand
;
518 operand
= parse_condition(expression
);
521 cond
= g_new(FindCondition
, 1);
522 cond
->test
= test_neg
;
523 cond
->free
= free_branch
;
524 cond
->data1
= operand
;
531 FindCondition
*subcond
;
535 subcond
= parse_expression(expression
);
541 subcond
->free(subcond
);
552 return parse_match(expression
);
555 if (MATCH(_("system")))
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
;
574 cond
= parse_dash(expression
);
578 cond
= parse_is(expression
);
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
);
595 cond
= g_new(FindCondition
, 1);
596 cond
->test
= test_system
;
597 cond
->free
= &free_simple
;
598 cond
->data1
= command_string
;
604 static FindCondition
*parse_comparison(const gchar
**expression
)
606 FindCondition
*cond
= NULL
;
613 first
= parse_eval(expression
);
623 else if (NEXT
== '>')
634 else if (NEXT
== '<')
645 else if (NEXT
== '!' && (*expression
)[1] == '=')
651 else if (MATCH(_("After")))
653 else if (MATCH(_("Before")))
659 second
= parse_eval(expression
);
666 cond
= g_new(FindCondition
, 1);
667 cond
->test
= &test_comp
;
668 cond
->free
= (FindFree
) &free_comp
;
670 cond
->data2
= second
;
676 static FindCondition
*parse_dash(const gchar
**expression
)
678 const gchar
*exp
= *expression
;
679 FindCondition
*cond
, *retval
= NULL
;
686 while (exp
[i
] && !isspace(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;
709 find_condition_free(retval
);
714 cond
= g_new(FindCondition
, 1);
715 cond
->test
= &test_is
;
716 cond
->free
= (FindFree
) &g_free
;
725 new = g_new(FindCondition
, 1);
726 new->test
= &test_AND
;
727 new->free
= &free_branch
;
742 /* Returns NULL if expression is not an is-expression */
743 static FindCondition
*parse_is(const gchar
**expression
)
748 if (MATCH(_("IsReg")))
750 else if (MATCH(_("IsLink")))
752 else if (MATCH(_("IsDir")))
754 else if (MATCH(_("IsChar")))
756 else if (MATCH(_("IsBlock")))
758 else if (MATCH(_("IsDev")))
760 else if (MATCH(_("IsPipe")))
762 else if (MATCH(_("IsSocket")))
764 else if (MATCH(_("IsDoor")))
766 else if (MATCH(_("IsSUID")))
768 else if (MATCH(_("IsSGID")))
770 else if (MATCH(_("IsSticky")))
772 else if (MATCH(_("IsReadable")))
774 else if (MATCH(_("IsWriteable")))
776 else if (MATCH(_("IsExecutable")))
778 else if (MATCH(_("IsEmpty")))
780 else if (MATCH(_("IsMine")))
785 cond
= g_new(FindCondition
, 1);
786 cond
->test
= &test_is
;
787 cond
->free
= (FindFree
) &g_free
;
795 /* Call this just after reading a ' */
796 static FindCondition
*parse_match(const gchar
**expression
)
798 FindCondition
*cond
= NULL
;
800 FindTest test
= &test_leaf
;
801 str
= g_string_new(NULL
);
811 if (c
== '\\' && NEXT
== '\'')
820 g_string_append_c(str
, c
);
824 cond
= g_new(FindCondition
, 1);
826 cond
->free
= &free_simple
;
827 cond
->data1
= str
->str
;
831 g_string_free(str
, cond
? FALSE
: TRUE
);
836 /* NUMERIC EXPRESSIONS */
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
;
853 static double get_var(Eval
*eval
, FindInfo
*info
)
855 switch ((VarType
) eval
->data1
)
858 return info
->stats
.st_atime
;
860 return info
->stats
.st_ctime
;
862 return info
->stats
.st_mtime
;
864 return info
->stats
.st_size
;
866 return info
->stats
.st_ino
;
868 return info
->stats
.st_nlink
;
870 return info
->stats
.st_uid
;
872 return info
->stats
.st_gid
;
874 return info
->stats
.st_blocks
;
882 static void free_constant(Eval
*eval
)
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
)
904 value
= strtol(start
, &end
, 0);
914 return parse_variable(expression
);
921 if (MATCH(_("Byte")) || MATCH(_("Bytes")))
923 else if (MATCH("Kb") || MATCH("K"))
925 else if (MATCH("Mb") || MATCH("M"))
927 else if (MATCH("Gb") || MATCH("G"))
929 else if (MATCH(_("Sec")) || MATCH(_("Secs")))
931 else if (MATCH(_("Min")) || MATCH(_("Mins")))
933 else if (MATCH(_("Hour")) || MATCH(_("Hours")))
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
));
950 else if (MATCH(_("Hence")))
953 eval
->data2
= GINT_TO_POINTER(flags
);
958 static Eval
*parse_variable(const gchar
**expression
)
965 if (MATCH(_("atime")))
967 else if (MATCH(_("ctime")))
969 else if (MATCH(_("mtime")))
971 else if (MATCH(_("size")))
973 else if (MATCH(_("inode")))
975 else if (MATCH(_("nlinks")))
977 else if (MATCH(_("uid")))
979 else if (MATCH(_("gid")))
981 else if (MATCH(_("blocks")))
986 eval
= g_new(Eval
, 1);
987 eval
->calc
= &get_var
;
988 eval
->free
= (EvalFree
) &g_free
;
989 eval
->data1
= (gpointer
) var
;
995 static gboolean
match(const gchar
**expression
, const gchar
*word
)
1000 if (g_strncasecmp(*expression
, word
, len
))
1003 if (isalpha(*(*expression
+ len
)))
1006 (*expression
) += len
;