22232fa45aebd16dba5668440fcb974be9d08254 — Ben Brown 1 year, 2 days ago b29e60c
Adding handling of symbolic arguments to umask
1 files changed, 133 insertions(+), 11 deletions(-)

M builtin/umask.c
M builtin/umask.c => builtin/umask.c +133 -11
@@ 7,21 7,139 @@
 #include <sys/stat.h>
 #include "builtin.h"
 
-/*
- * TODO: Implement symbolic mode
- */
-
 static const char umask_usage[] = "usage: umask [-S] [mode]\n";
 
+enum umask_symbolic_state {
+	UMASK_WHO,
+	UMASK_PERM,
+};
+
+static mode_t umask_current_mask(void) {
+	const mode_t default_mode = 0022;
+
+	mode_t mask = umask(default_mode);
+	umask(mask);
+	return mask;
+}
+
+static bool umask_update_mode(mode_t *mode, char action, mode_t *perm_mask, mode_t *who_mask) {
+	switch (action) {
+	case '+':
+		*mode |= (*who_mask & *perm_mask);
+		break;
+	case '=':
+		*mode = (*mode & ~(*who_mask)) | (*perm_mask & *who_mask);
+		break;
+	case '-':
+		*mode &= (0777 & ~(*who_mask & *perm_mask));
+		break;
+	default:
+		fprintf(stderr, "unknown action -- '%c'\n", action);
+		return false;
+	}
+
+	*perm_mask = *who_mask = 0;
+	return true;
+}
+
+static bool umask_mode(mode_t *mode, char *symbolic) {
+	mode_t tmp_mode = 0777 & ~(umask_current_mask());
+	enum umask_symbolic_state state = UMASK_WHO;
+	mode_t who_mask = 0;
+	mode_t perm_mask = 0;
+	char action = '\0';
+
+	for (char *c = symbolic; *c != '\0'; c++) {
+		switch (state) {
+		case UMASK_WHO:
+			switch (*c) {
+			case 'u':
+				who_mask |= S_IRWXU;
+				break;
+			case 'g':
+				who_mask |= S_IRWXG;
+				break;
+			case 'o':
+				who_mask |= S_IRWXO;
+				break;
+			case 'a':
+				who_mask |= (S_IRWXU | S_IRWXG | S_IRWXO);
+				break;
+			case '+':
+			case '-':
+			case '=':
+				if (who_mask == 0) {
+					who_mask |= (S_IRWXU | S_IRWXG | S_IRWXO);
+				}
+				action = *c;
+				state = UMASK_PERM;
+				break;
+			default:
+				fprintf(stderr, "Unknown who -- '%c'\n", *c);
+				return false;
+			}
+
+			break;
+
+		case UMASK_PERM:
+			switch (*c) {
+			case 'u':
+				perm_mask = (tmp_mode & S_IRWXU) | ((tmp_mode & S_IRWXU) >> 3) | ((tmp_mode & S_IRWXU) >> 6);
+				break;
+			case 'g':
+				perm_mask = ((tmp_mode & S_IRWXG) << 3) | (tmp_mode & S_IRWXG) | ((tmp_mode & S_IRWXG) >> 3);
+				break;
+			case 'o':
+				perm_mask = ((tmp_mode & S_IRWXO) << 6) | ((tmp_mode & S_IRWXO) << 3) | (tmp_mode & S_IRWXO);
+				break;
+			case 'r':
+				perm_mask |= (S_IRUSR | S_IRGRP | S_IROTH);
+				break;
+			case 'w':
+				perm_mask |= (S_IWUSR | S_IWGRP | S_IWOTH);
+				break;
+			case 'x':
+				perm_mask |= (S_IXUSR | S_IXGRP | S_IXOTH);
+				break;
+			case ',':
+				state = UMASK_WHO;
+				if (!umask_update_mode(&tmp_mode, action, &perm_mask, &who_mask)) {
+					return false;
+				}
+				break;
+			default:
+				fprintf(stderr, "Invalid permission -- '%c'\n", *c);
+				return false;
+			}
+			break;
+		}
+	}
+
+	if (state == UMASK_PERM) {
+		if (!umask_update_mode(&tmp_mode, action, &perm_mask, &who_mask)) {
+			return false;
+		}
+	} else {
+		fprintf(stderr, "Missing permission from symbolic mode\n");
+		return false;
+	}
+
+	*mode = 0777 & ~tmp_mode;
+
+	return true;
+}
+
 static void umask_modestring(char string[static 4], mode_t mode) {
 	size_t i = 0;
 
 	if (S_IROTH & mode) {
 		string[i++] = 'r';
 	}
+
 	if (S_IWOTH & mode) {
 		string[i++] = 'w';
 	}
+
 	if (S_IXOTH & mode) {
 		string[i++] = 'x';
 	}


@@ 42,16 160,17 @@ static void umask_print_symbolic(mode_t mask) {
 
 int builtin_umask(struct mrsh_state *state, int argc, char *argv[]) {
 	mode_t mode;
-	mode_t default_mode = 0022;
 	bool umask_symbolic = false;
 
 	optind = 0;
 	int opt;
+
 	while ((opt = getopt(argc, argv, ":S")) != -1) {
-		switch(opt) {
+		switch (opt) {
 		case 'S':
 			umask_symbolic = true;
 			break;
+
 		default:
 			fprintf(stderr, "Unknown option -- '%c'\n", optopt);
 			fprintf(stderr, umask_usage);


@@ 60,22 179,25 @@ int builtin_umask(struct mrsh_state *state, int argc, char *argv[]) {
 	}
 
 	if (optind == argc) {
-		mode = umask(default_mode);
-		umask(mode);
+		mode = umask_current_mask();
+
 		if (umask_symbolic) {
 			umask_print_symbolic(mode);
 		} else {
 			printf("%04o\n", mode);
 		}
+
 		return EXIT_SUCCESS;
 	}
 
 	char *endptr;
 	mode = strtol(argv[optind], &endptr, 8);
+
 	if (*endptr != '\0') {
-		fprintf(stderr, "Invalid mode %s\n", argv[optind]);
-		fprintf(stderr, umask_usage);
-		return EXIT_FAILURE;
+		if (!umask_mode(&mode, argv[optind])) {
+			fprintf(stderr, umask_usage);
+			return EXIT_FAILURE;
+		}
 	}
 
 	umask(mode);