/* vim: tabstop=4 shiftwidth=4 noexpandtab
 * This file is part of ToaruOS and is released under the terms
 * of the NCSA / University of Illinois License - see LICENSE.md
 * Copyright (C) 2018 K. Lange
 *
 * chmod - change file permissions
 *
 * Note: This implementation is likely non-compliant, though it does
 *       attempt to look similar to the standard POSIX syntax,
 *       supporting both octal mode setings and +/-rwx flavors.
 */
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>

enum mode_set {
	MODE_SET,
	MODE_ADD,
	MODE_REMOVE,
};

static int calc(int mode, int users) {
	int out = 0;
	if (users & 1) {
		out |= (mode << 6);
	}
	if (users & 2) {
		out |= (mode << 3);
	}
	if (users & 4) {
		out |= (mode << 0);
	}
	return out;
}

int main(int argc, char * argv[]) {
	if (argc < 3) {
		fprintf(stderr, "usage: %s OCTAL-MODE FILE...\n", argv[0]);
		return 1;
	}

	/* Parse mode */
	int mode = 0;
	enum mode_set mode_set = MODE_SET;
	char * c = argv[1];
	int user_modes = 0;
	int all_users = 7;

	while (*c) {
		switch (*c) {
			case '0':
				c++; /* 0 */
				while (*c >= '0' && *c <= '7') {
					mode *= 8;
					mode += (*c - '0');
					c++;
				}
				break;
			case 'u':
				all_users = 0;
				user_modes |= 1;
				c++;
				break;
			case 'g':
				all_users = 0;
				user_modes |= 2;
				c++;
				break;
			case 'o':
				all_users = 0;
				user_modes |= 4;
				c++;
				break;
			case 'a':
				all_users = 7;
				user_modes = 7;
				c++;
				break;
			case '-':
				mode_set = MODE_REMOVE;
				c++;
				break;
			case '+':
				mode_set = MODE_ADD;
				c++;
				break;
			case '=':
				mode_set = MODE_SET;
				c++;
				break;
			case 'r':
				mode |= calc(S_IROTH, user_modes | all_users);
				c++;
				break;
			case 'w':
				mode |= calc(S_IWOTH, user_modes | all_users);
				c++;
				break;
			case 'x':
				mode |= calc(S_IXOTH, user_modes | all_users);
				c++;
				break;
		}
	}

	int i = 2;
	while (i < argc) {
		int actual_mode = 0;
		struct stat _stat;
		if (stat(argv[i], &_stat) < 0) {
			fprintf(stderr, "%s: %s: error with stat\n", argv[0], argv[i]);
		}

		switch (mode_set) {
			case MODE_SET:
				actual_mode = mode;
				break;
			case MODE_ADD:
				actual_mode = _stat.st_mode | mode;
				break;
			case MODE_REMOVE:
				actual_mode = _stat.st_mode &= ~(mode);
				break;
		}

		if (chmod(argv[i], actual_mode) < 0) {
			fprintf(stderr, "%s: %s: error with chmod\n", argv[0], argv[i]);
			return 1;
		}
		i++;
	}

	return 0;
}
