about summary refs log tree commit diff
path: root/lr.c
diff options
context:
space:
mode:
authorChristian Neukirchen <chneukirchen@gmail.com>2016-02-27 21:20:01 +0100
committerChristian Neukirchen <chneukirchen@gmail.com>2016-02-27 21:20:01 +0100
commitd39c189b87ac60f8c16c1f71c74f1185f1672a6b (patch)
tree57cc75a9434c0f3a0f86131aac1a9346878ebf2f /lr.c
parent87e610e70e6252f45fb2b5c9f4f11f9c49eddf79 (diff)
downloadlr-d39c189b87ac60f8c16c1f71c74f1185f1672a6b.tar.gz
lr-d39c189b87ac60f8c16c1f71c74f1185f1672a6b.tar.xz
lr-d39c189b87ac60f8c16c1f71c74f1185f1672a6b.zip
implement matching against chmod-style symbolic modes
Diffstat (limited to 'lr.c')
-rw-r--r--lr.c71
1 files changed, 71 insertions, 0 deletions
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);