summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--lr.15
-rw-r--r--lr.c71
3 files changed, 81 insertions, 0 deletions
diff --git a/README.md b/README.md
index 50c3291..5a1f77e 100644
--- a/README.md
+++ b/README.md
@@ -183,9 +183,14 @@ Default: `n`.
 	                    | &          -- check if all bits of <octal> set
 	                    | |          -- check if any bit of <octal> set
 	                    ) <octal>
+	             | mode = "<chmod>"  -- check if symbolic mode is satisfied
 	
 	<octal> ::= [0-7]+
 
+	<chmod> ::= <clause> (, <clause>)+
+
+	<clause> ::= [guoa]* [+-=] [rwxXstugo]*  -- see chmod(1)
+
 ## EWONTFIX
 
 The following features won't be implemented:
diff --git a/lr.1 b/lr.1
index 6aa9817..9f88a98 100644
--- a/lr.1
+++ b/lr.1
@@ -285,8 +285,13 @@ tests are given by the following EBNF:
                     | &          -- check if all bits of <octal> set
                     | |          -- check if any bit of <octal> set
                     ) <octal>
+             | mode = "<chmod>"  -- check if symbolic mode is satisfied
 
 <octal> ::= [0-7]+
+
+<chmod> ::= <clause> (, <clause>)+
+
+<clause> ::= [guoa]* [+-=] [rwxXstugo]*  -- see chmod(1)
 .Ed
 .Sh EXIT STATUS
 .Ex -std
diff --git a/lr.c b/lr.c
index 05eeae9..6cdb380 100644
--- a/lr.c
+++ b/lr.c
@@ -118,6 +118,7 @@ static int maxdepth;
 static int uwid, gwid, fwid;
 
 static time_t now;
+static mode_t default_mask;
 
 struct fileinfo {
 	char *fpath;
@@ -150,6 +151,7 @@ enum op {
 	EXPR_TYPE,
 	EXPR_ALLSET,
 	EXPR_ANYSET,
+	EXPR_CHMOD,
 };
 
 enum prop {
@@ -211,6 +213,67 @@ parse_error(const char *msg, ...)
 	exit(2);
 }
 
+int
+test_chmod(char *c, mode_t oldmode)
+{
+	mode_t newmode = oldmode;
+	mode_t whom, what;
+	char op;
+
+	do {
+		whom = 0;
+		what = 0;
+
+		while (1) {
+			switch(*c) {
+			case 'u': whom |= 04700; break;
+			case 'g': whom |= 02070; break;
+			case 'o': whom |= 01007; break;
+			case 'a': whom |= 07777; break;
+			default: goto op;
+			}
+			c++;
+		}
+op:
+		if (whom == 0)
+			whom = default_mask;
+		op = *c++;
+		if (!(op == '-' || op == '+' || op == '='))
+			parse_error("invalid mode operator");
+
+		switch(*c) {
+		case 'u': what = 00111 * ((newmode >> 6) & 0007); break;
+		case 'g': what = 00111 * ((newmode >> 3) & 0007); break;
+		case 'o': what = 00111 * ((newmode     ) & 0007); break;
+		default:
+			while (1) {
+				switch(*c) {
+				case 'r': what |= 00444; break;
+				case 'w': what |= 00222; break;
+				case 'x': what |= 00111; break;
+				case 'X': if (newmode & 00111) what |= 00111; break;
+				case 's': what |= 06000; break;
+				case 't': what |= 01000; break;
+				case ',': case 0: goto doit;
+				default: parse_error("invalid permission");
+				}
+				c++;
+			}
+		}
+doit:
+		switch (op) {
+		case '-': newmode &= ~(whom & what); break;
+		case '+': newmode |= (whom & what); break;
+		case '=': newmode &= ~whom; newmode |= (whom & what); break;
+		}
+	} while (*c == ',' && c++);
+
+	if (*c)
+		parse_error("trailing garbage in mode string '%s'", c);
+
+	return newmode == oldmode;
+}
+
 static struct expr *
 mkexpr(enum op op)
 {
@@ -582,6 +645,7 @@ parse_mode()
 {
 	struct expr *e = mkexpr(0);
 	long n;
+	char *s;
 
 	e->a.prop = PROP_MODE;
 
@@ -597,6 +661,11 @@ parse_mode()
 
 	if (parse_octal(&n)) {
 		e->b.num = n;
+	} if (e->op == EXPR_EQ && parse_string(&s)) {
+		e->op = EXPR_CHMOD;
+		e->b.string = s;
+		default_mask = umask(umask(0));  /* cache for future usage */
+		test_chmod(s, 0);  /* run once to check for syntax */
 	} else {
 		parse_error("invalid mode at '%.15s'", pos);
 	}
@@ -1091,6 +1160,8 @@ eval(struct expr *e, struct fileinfo *fi)
 		case EXPR_ANYSET: return (v & e->b.num) > 0;
 		}
 	}
+	case EXPR_CHMOD:
+		return test_chmod(e->b.string, fi->sb.st_mode & 07777);
 	case EXPR_TYPE:	{
 		switch (e->a.filetype) {
 		case TYPE_BLOCK: return S_ISBLK(fi->sb.st_mode);