#include <9pm/u.h>
#include <9pm/libc.h>

#define U(x) (x<<6)
#define G(x) (x<<3)
#define O(x) (x)
#define A(x) (U(x)|G(x)|O(x))

#define DMRWE (DMREAD|DMWRITE|DMEXEC)

int parsemode(char *, ulong *, ulong *);

void
main(int argc, char *argv[])
{
	int i;
	Dir *dir, ndir;
	ulong mode, mask;
	char *p;

	if(argc < 3){
		fprint(2, "usage: chmod 0777 file ... or chmod [who]op[rwxal] file ...\n");
		exits("usage");
	}
	mode = strtol(argv[1], &p, 8);
	if(*p == 0)
		mask = A(DMRWE);
	else if(!parsemode(argv[1], &mask, &mode)){
		fprint(2, "chmod: bad mode: %s\n", argv[1]);
		exits("mode");
	}
	for(i=2; i<argc; i++){
		dir = dirstat(argv[i]);
		if(dir == nil){
			fprint(2, "chmod: can't stat %s: %r\n", argv[i]);
			continue;
		}
		nulldir(&ndir);
		ndir.mode = (dir->mode & ~mask) | (mode & mask);
		free(dir);
		if(dirwstat(argv[i], &ndir)==-1){
			fprint(2, "chmod: can't wstat %s: %r\n", argv[i]);
			continue;
		}
	}
	exits(0);
}

int
parsemode(char *spec, ulong *pmask, ulong *pmode)
{
	ulong mode, mask;
	int done, op;
	char *s;

	s = spec;
	mask = DMAPPEND | DMEXCL;
	for(done=0; !done; ){
		switch(*s){
		case 'u':
			mask |= U(DMRWE); break;
		case 'g':
			mask |= G(DMRWE); break;
		case 'o':
			mask |= O(DMRWE); break;
		case 'a':
			mask |= A(DMRWE); break;
		case 0:
			return 0;
		default:
			done = 1;
		}
		if(!done)
			s++;
	}
	if(s == spec)
		mask |= A(DMRWE);
	op = *s++;
	if(op != '+' && op != '-' && op != '=')
		return 0;
	mode = 0;
	for(; *s ; s++){
		switch(*s){
		case 'r':
			mode |= A(DMREAD); break;
		case 'w':
			mode |= A(DMWRITE); break;
		case 'x':
			mode |= A(DMEXEC); break;
		case 'a':
			mode |= DMAPPEND; break;
		case 'l':
			mode |= DMEXCL; break;
		default:
			return 0;
		}
	}
	if(*s != 0)
		return 0;
	if(op == '+' || op == '-')
		mask &= mode;
	if(op == '-')
		mode = ~mode;
	*pmask = mask;
	*pmode = mode;
	return 1;
}
