xref: /plan9-contrib/sys/src/cmd/fossil/9user.c (revision a6a9e07217f318acf170f99684a55fba5200524f)
1 #include "stdinc.h"
2 
3 #include "9.h"
4 
5 enum {
6 	NUserHash	= 1009,
7 };
8 
9 typedef struct Ubox Ubox;
10 typedef struct User User;
11 
12 typedef struct User {
13 	char*	uid;
14 	char*	uname;
15 	char*	leader;
16 	char**	group;
17 	int	ngroup;
18 
19 	User*	next;			/* */
20 	User*	ihash;			/* lookup by .uid */
21 	User*	nhash;			/* lookup by .uname */
22 } User;
23 
24 #pragma varargck type "U"   User*
25 
26 typedef struct Ubox {
27 	User*	head;
28 	User*	tail;
29 	char*	name;
30 	int	nuser;
31 	int	len;
32 
33 	User*	ihash[NUserHash];	/* lookup by .uid */
34 	User*	nhash[NUserHash];	/* lookup by .uname */
35 } Ubox;
36 
37 static struct {
38 	VtLock*	lock;
39 
40 	Ubox*	box;
41 } ubox;
42 
43 static char usersDefault[] = {
44 	"adm:adm:adm:sys\n"
45 	"none:none::\n"
46 	"noworld:noworld::\n"
47 	"sys:sys::\n"
48 };
49 
50 static char* usersMandatory[] = {
51 	"adm",
52 	"none",
53 	"noworld",
54 	"sys",
55 	nil,
56 };
57 
58 char* uidadm = "adm";
59 char* unamenone = "none";
60 char* uidnoworld = "noworld";
61 
62 static u32int
63 userHash(char* s)
64 {
65 	uchar *p;
66 	u32int hash;
67 
68 	hash = 0;
69 	for(p = (uchar*)s; *p != '\0'; p++)
70 		hash = hash*7 + *p;
71 
72 	return hash % NUserHash;
73 }
74 
75 static User*
76 _userByUid(Ubox* box, char* uid)
77 {
78 	User *u;
79 
80 	if(box != nil){
81 		for(u = box->ihash[userHash(uid)]; u != nil; u = u->ihash){
82 			if(strcmp(u->uid, uid) == 0)
83 				return u;
84 		}
85 	}
86 	vtSetError("uname: uid '%s' not found", uid);
87 	return nil;
88 }
89 
90 char*
91 unameByUid(char* uid)
92 {
93 	User *u;
94 	char *uname;
95 
96 	vtRLock(ubox.lock);
97 	if((u = _userByUid(ubox.box, uid)) == nil){
98 		vtRUnlock(ubox.lock);
99 		return nil;
100 	}
101 	uname = vtStrDup(u->uname);
102 	vtRUnlock(ubox.lock);
103 
104 	return uname;
105 }
106 
107 static User*
108 _userByUname(Ubox* box, char* uname)
109 {
110 	User *u;
111 
112 	if(box != nil){
113 		for(u = box->nhash[userHash(uname)]; u != nil; u = u->nhash){
114 			if(strcmp(u->uname, uname) == 0)
115 				return u;
116 		}
117 	}
118 	vtSetError("uname: uname '%s' not found", uname);
119 	return nil;
120 }
121 
122 char*
123 uidByUname(char* uname)
124 {
125 	User *u;
126 	char *uid;
127 
128 	vtRLock(ubox.lock);
129 	if((u = _userByUname(ubox.box, uname)) == nil){
130 		vtRUnlock(ubox.lock);
131 		return nil;
132 	}
133 	uid = vtStrDup(u->uid);
134 	vtRUnlock(ubox.lock);
135 
136 	return uid;
137 }
138 
139 static int
140 _groupMember(Ubox* box, char* group, char* member, int whenNoGroup)
141 {
142 	int i;
143 	User *g, *m;
144 
145 	/*
146 	 * Is 'member' a member of 'group'?
147 	 * Note that 'group' is a 'uid' and not a 'uname'.
148 	 * A 'member' is automatically in their own group.
149 	 */
150 	if((g = _userByUid(box, group)) == nil)
151 		return whenNoGroup;
152 	if((m = _userByUname(box, member)) == nil)
153 		return 0;
154 	if(m == g)
155 		return 1;
156 	for(i = 0; i < g->ngroup; i++){
157 		if(strcmp(g->group[i], member) == 0)
158 			return 1;
159 	}
160 	return 0;
161 }
162 
163 int
164 groupWriteMember(char* uname)
165 {
166 	int ret;
167 
168 	/*
169 	 * If there is a ``write'' group, then only its members can write
170 	 * to the file system, no matter what the permission bits say.
171 	 *
172 	 * To users not in the ``write'' group, the file system appears
173 	 * read only.  This is used to serve sources.cs.bell-labs.com
174 	 * to the world.
175 	 *
176 	 * Note that if there is no ``write'' group, then this routine
177 	 * makes it look like everyone is a member -- the opposite
178 	 * of what groupMember does.
179 	 *
180 	 * We use this for sources.cs.bell-labs.com.
181 	 * If this slows things down too much on systems that don't
182 	 * use this functionality, we could cache the write group lookup.
183 	 */
184 
185 	vtRLock(ubox.lock);
186 	ret = _groupMember(ubox.box, "write", uname, 1);
187 	vtRUnlock(ubox.lock);
188 	return ret;
189 }
190 
191 static int
192 _groupRemMember(Ubox* box, User* g, char* member)
193 {
194 	int i;
195 
196 	if(_userByUname(box, member) == nil)
197 		return 0;
198 
199 	for(i = 0; i < g->ngroup; i++){
200 		if(strcmp(g->group[i], member) == 0)
201 			break;
202 	}
203 	if(i >= g->ngroup){
204 		if(strcmp(g->uname, member) == 0)
205 			vtSetError("uname: '%s' always in own group", member);
206 		else
207 			vtSetError("uname: '%s' not in group '%s'",
208 				member, g->uname);
209 		return 0;
210 	}
211 
212 	vtMemFree(g->group[i]);
213 
214 	box->len -= strlen(member);
215 	if(g->ngroup > 1)
216 		box->len--;
217 	g->ngroup--;
218 	switch(g->ngroup){
219 	case 0:
220 		vtMemFree(g->group);
221 		g->group = nil;
222 		break;
223 	default:
224 		while(i < g->ngroup){
225 			g->group[i] = g->group[i+1];
226 			i++;
227 		}
228 		/*FALLTHROUGH*/
229 	case 1:
230 		g->group = vtMemRealloc(g->group, (g->ngroup)*sizeof(char*));
231 		break;
232 	}
233 
234 	return 1;
235 }
236 
237 static int
238 _groupAddMember(Ubox* box, User* g, char* member)
239 {
240 	User *u;
241 
242 	if((u = _userByUname(box, member)) == nil)
243 		return 0;
244 	if(_groupMember(box, g->uid, u->uname, 0)){
245 		if(strcmp(g->uname, member) == 0)
246 			vtSetError("uname: '%s' always in own group", member);
247 		else
248 			vtSetError("uname: '%s' already in group '%s'",
249 				member, g->uname);
250 		return 0;
251 	}
252 
253 	g->group = vtMemRealloc(g->group, (g->ngroup+1)*sizeof(char*));
254 	g->group[g->ngroup] = vtStrDup(member);
255 	box->len += strlen(member);
256 	g->ngroup++;
257 	if(g->ngroup > 1)
258 		box->len++;
259 
260 	return 1;
261 }
262 
263 int
264 groupMember(char* group, char* member)
265 {
266 	int r;
267 
268 	if(group == nil)
269 		return 0;
270 
271 	vtRLock(ubox.lock);
272 	r = _groupMember(ubox.box, group, member, 0);
273 	vtRUnlock(ubox.lock);
274 
275 	return r;
276 }
277 
278 int
279 groupLeader(char* group, char* member)
280 {
281 	int r;
282 	User *g;
283 
284 	/*
285 	 * Is 'member' the leader of 'group'?
286 	 * Note that 'group' is a 'uid' and not a 'uname'.
287 	 * Uname 'none' cannot be a group leader.
288 	 */
289 	if(strcmp(member, unamenone) == 0 || group == nil)
290 		return 0;
291 
292 	vtRLock(ubox.lock);
293 	if((g = _userByUid(ubox.box, group)) == nil){
294 		vtRUnlock(ubox.lock);
295 		return 0;
296 	}
297 	if(g->leader != nil){
298 		if(strcmp(g->leader, member) == 0){
299 			vtRUnlock(ubox.lock);
300 			return 1;
301 		}
302 		r = 0;
303 	}
304 	else
305 		r = _groupMember(ubox.box, group, member, 0);
306 	vtRUnlock(ubox.lock);
307 
308 	return r;
309 }
310 
311 static void
312 userFree(User* u)
313 {
314 	int i;
315 
316 	vtMemFree(u->uid);
317 	vtMemFree(u->uname);
318 	if(u->leader != nil)
319 		vtMemFree(u->leader);
320 	if(u->ngroup){
321 		for(i = 0; i < u->ngroup; i++)
322 			vtMemFree(u->group[i]);
323 		vtMemFree(u->group);
324 	}
325 	vtMemFree(u);
326 }
327 
328 static User*
329 userAlloc(char* uid, char* uname)
330 {
331 	User *u;
332 
333 	u = vtMemAllocZ(sizeof(User));
334 	u->uid = vtStrDup(uid);
335 	u->uname = vtStrDup(uname);
336 
337 	return u;
338 }
339 
340 int
341 validUserName(char* name)
342 {
343 	Rune *r;
344 	static Rune invalid[] = L"#:,()";
345 
346 	for(r = invalid; *r != '\0'; r++){
347 		if(utfrune(name, *r))
348 			return 0;
349 	}
350 	return 1;
351 }
352 
353 static int
354 userFmt(Fmt* fmt)
355 {
356 	User *u;
357 	int i, r;
358 
359 	u = va_arg(fmt->args, User*);
360 
361 	r = fmtprint(fmt, "%s:%s:", u->uid, u->uname);
362 	if(u->leader != nil)
363 		r += fmtprint(fmt, u->leader);
364 	r += fmtprint(fmt, ":");
365 	if(u->ngroup){
366 		r += fmtprint(fmt, u->group[0]);
367 		for(i = 1; i < u->ngroup; i++)
368 			r += fmtprint(fmt, ",%s", u->group[i]);
369 	}
370 
371 	return r;
372 }
373 
374 static int
375 usersFileWrite(Ubox* box)
376 {
377 	Fs *fs;
378 	User *u;
379 	int i, r;
380 	Fsys *fsys;
381 	char *p, *q, *s;
382 	File *dir, *file;
383 
384 	if((fsys = fsysGet("main")) == nil)
385 		return 0;
386 	fsysFsRlock(fsys);
387 	fs = fsysGetFs(fsys);
388 
389 	/*
390 	 * BUG:
391 	 * 	the owner/group/permissions need to be thought out.
392 	 */
393 	r = 0;
394 	if((dir = fileOpen(fs, "/active")) == nil)
395 		goto tidy0;
396 	if((file = fileWalk(dir, "adm")) == nil)
397 		file = fileCreate(dir, "adm", ModeDir|0775, uidadm);
398 	fileDecRef(dir);
399 	if(file == nil)
400 		goto tidy;
401 	dir = file;
402 	if((file = fileWalk(dir, "users")) == nil)
403 		file = fileCreate(dir, "users", 0664, uidadm);
404 	fileDecRef(dir);
405 	if(file == nil)
406 		goto tidy;
407 	if(!fileTruncate(file, uidadm))
408 		goto tidy;
409 
410 	p = s = vtMemAlloc(box->len+1);
411 	q = p + box->len+1;
412 	for(u = box->head; u != nil; u = u->next){
413 		p += snprint(p, q-p, "%s:%s:", u->uid, u->uname);
414 		if(u->leader != nil)
415 			p+= snprint(p, q-p, u->leader);
416 		p += snprint(p, q-p, ":");
417 		if(u->ngroup){
418 			p += snprint(p, q-p, u->group[0]);
419 			for(i = 1; i < u->ngroup; i++)
420 				p += snprint(p, q-p, ",%s", u->group[i]);
421 		}
422 		p += snprint(p, q-p, "\n");
423 	}
424 	r = fileWrite(file, s, box->len, 0, uidadm);
425 	vtMemFree(s);
426 
427 tidy:
428 	if(file != nil)
429 		fileDecRef(file);
430 tidy0:
431 	fsysFsRUnlock(fsys);
432 	fsysPut(fsys);
433 
434 	return r;
435 }
436 
437 static void
438 uboxRemUser(Ubox* box, User *u)
439 {
440 	User **h, *up;
441 
442 	h = &box->ihash[userHash(u->uid)];
443 	for(up = *h; up != nil && up != u; up = up->ihash)
444 		h = &up->ihash;
445 	assert(up == u);
446 	*h = up->ihash;
447 	box->len -= strlen(u->uid);
448 
449 	h = &box->nhash[userHash(u->uname)];
450 	for(up = *h; up != nil && up != u; up = up->nhash)
451 		h = &up->nhash;
452 	assert(up == u);
453 	*h = up->nhash;
454 	box->len -= strlen(u->uname);
455 
456 	h = &box->head;
457 	for(up = *h; up != nil && strcmp(up->uid, u->uid) != 0; up = up->next)
458 		h = &up->next;
459 	assert(up == u);
460 	*h = u->next;
461 	u->next = nil;
462 
463 	box->len -= 4;
464 	box->nuser--;
465 }
466 
467 static void
468 uboxAddUser(Ubox* box, User* u)
469 {
470 	User **h, *up;
471 
472 	h = &box->ihash[userHash(u->uid)];
473 	u->ihash = *h;
474 	*h = u;
475 	box->len += strlen(u->uid);
476 
477 	h = &box->nhash[userHash(u->uname)];
478 	u->nhash = *h;
479 	*h = u;
480 	box->len += strlen(u->uname);
481 
482 	h = &box->head;
483 	for(up = *h; up != nil && strcmp(up->uid, u->uid) < 0; up = up->next)
484 		h = &up->next;
485 	u->next = *h;
486 	*h = u;
487 
488 	box->len += 4;
489 	box->nuser++;
490 }
491 
492 static void
493 uboxDump(Ubox* box)
494 {
495 	User* u;
496 
497 	consPrint("nuser %d len = %d\n", box->nuser, box->len);
498 
499 	for(u = box->head; u != nil; u = u->next)
500 		consPrint("%U\n", u);
501 }
502 
503 static void
504 uboxFree(Ubox* box)
505 {
506 	User *next, *u;
507 
508 	for(u = box->head; u != nil; u = next){
509 		next = u->next;
510 		userFree(u);
511 	}
512 	if(box->name != nil)
513 		vtMemFree(box->name);
514 	vtMemFree(box);
515 }
516 
517 static int
518 uboxInit(char* name, char* users, int len)
519 {
520 	User *g, *u;
521 	Ubox *box, *obox;
522 	int blank, comment, i, nuser;
523 	char *buf, *f[5], **line, *p, *q, *s;
524 
525 	/*
526 	 * Strip out whitespace and comments.
527 	 * Note that comments are pointless, they disappear
528 	 * when the server writes the database back out.
529 	 */
530 	blank = 1;
531 	comment = nuser = 0;
532 
533 	s = p = buf = vtMemAlloc(len+1);
534 	for(q = users; *q != '\0'; q++){
535 		if(*q == '\r' || *q == '\t' || *q == ' ')
536 			continue;
537 		if(*q == '\n'){
538 			if(!blank){
539 				if(p != s){
540 					*p++ = '\n';
541 					nuser++;
542 					s = p;
543 				}
544 				blank = 1;
545 			}
546 			comment = 0;
547 			continue;
548 		}
549 		if(*q == '#')
550 			comment = 1;
551 		blank = 0;
552 		if(!comment)
553 			*p++ = *q;
554 	}
555 	*p = '\0';
556 
557 	line = vtMemAllocZ((nuser+2)*sizeof(char*));
558 	if((i = gettokens(buf, line, nuser+2, "\n")) != nuser){
559 		fprint(2, "nuser %d (%d) botch\n", nuser, i);
560 		vtMemFree(line);
561 		vtMemFree(buf);
562 		return 0;
563 	}
564 
565 	fprint(2, "nuser %d\n", nuser);
566 
567 	/*
568 	 * Everything us updated in a local Ubox until verified.
569 	 */
570 	box = vtMemAllocZ(sizeof(Ubox));
571 	if(name != nil)
572 		box->name = vtStrDup(name);
573 
574 	/*
575 	 * First pass - check format, check for duplicates
576 	 * and enter in hash buckets.
577 	 */
578 	for(i = 0; i < nuser; i++){
579 		s = vtStrDup(line[i]);
580 		if(getfields(s, f, nelem(f), 0, ":") != 4){
581 			fprint(2, "bad line '%s'\n", line[i]);
582 			vtMemFree(s);
583 			continue;
584 		}
585 		if(*f[0] == '\0' || *f[1] == '\0'){
586 			fprint(2, "bad line '%s'\n", line[i]);
587 			vtMemFree(s);
588 			continue;
589 		}
590 		if(!validUserName(f[0])){
591 			fprint(2, "invalid uid '%s'\n", f[0]);
592 			vtMemFree(s);
593 			continue;
594 		}
595 		if(_userByUid(box, f[0]) != nil){
596 			fprint(2, "duplicate uid '%s'\n", f[0]);
597 			vtMemFree(s);
598 			continue;
599 		}
600 		if(!validUserName(f[1])){
601 			fprint(2, "invalid uname '%s'\n", f[0]);
602 			vtMemFree(s);
603 			continue;
604 		}
605 		if(_userByUname(box, f[1]) != nil){
606 			fprint(2, "duplicate uname '%s'\n", f[1]);
607 			vtMemFree(s);
608 			continue;
609 		}
610 
611 		u = userAlloc(f[0], f[1]);
612 		uboxAddUser(box, u);
613 
614 		vtMemFree(s);
615 	}
616 	assert(box->nuser == nuser);
617 
618 	/*
619 	 * Second pass - fill in leader and group information.
620 	 */
621 	for(i = 0; i < nuser; i++){
622 		s = vtStrDup(line[i]);
623 		getfields(s, f, nelem(f), 0, ":");
624 
625 		assert(g = _userByUname(box, f[1]));
626 		if(*f[2] != '\0'){
627 			if((u = _userByUname(box, f[2])) == nil)
628 				g->leader = vtStrDup(g->uname);
629 			else
630 				g->leader = vtStrDup(u->uname);
631 			box->len += strlen(g->leader);
632 		}
633 		for(p = f[3]; p != nil; p = q){
634 			if((q = utfrune(p, L',')) != nil)
635 				*q++ = '\0';
636 			if(!_groupAddMember(box, g, p)){
637 				// print/log error here
638 			}
639 		}
640 
641 		vtMemFree(s);
642 	}
643 
644 	vtMemFree(line);
645 	vtMemFree(buf);
646 
647 	for(i = 0; usersMandatory[i] != nil; i++){
648 		if((u = _userByUid(box, usersMandatory[i])) == nil){
649 			vtSetError("user '%s' is mandatory", usersMandatory[i]);
650 			uboxFree(box);
651 			return 0;
652 		}
653 		if(strcmp(u->uid, u->uname) != 0){
654 			vtSetError("uid/uname for user '%s' must match",
655 				usersMandatory[i]);
656 			uboxFree(box);
657 			return 0;
658 		}
659 	}
660 
661 	vtLock(ubox.lock);
662 	if(name != nil && usersFileWrite(box) == 0){
663 		/*
664 		 * What to do here? How much whining?
665 		 */
666 	}
667 	obox = ubox.box;
668 	ubox.box = box;
669 	vtUnlock(ubox.lock);
670 
671 	if(obox != nil)
672 		uboxFree(obox);
673 
674 	return 1;
675 }
676 
677 static int
678 usersFileRead(char* path)
679 {
680 	char *p;
681 	File *file;
682 	Fsys *fsys;
683 	int len, r;
684 	uvlong size;
685 
686 	if((fsys = fsysGet("main")) == nil)
687 		return 0;
688 	fsysFsRlock(fsys);
689 
690 	r = 0;
691 	if((file = fileOpen(fsysGetFs(fsys), path)) != nil){
692 		if(fileGetSize(file, &size)){
693 			len = size;
694 			p = vtMemAlloc(size+1);
695 			if(fileRead(file, p, len, 0) == len){
696 				p[len] = '\0';
697 				r = uboxInit(path, p, len);
698 			}
699 		}
700 		fileDecRef(file);
701 	}
702 
703 	fsysFsRUnlock(fsys);
704 	fsysPut(fsys);
705 
706 	return r;
707 }
708 
709 static int
710 cmdUname(int argc, char* argv[])
711 {
712 	User *u, *up;
713 	int d, dflag, i, r;
714 	char *p, *uid, *uname;
715 	char *createfmt = "fsys main create /active/usr/%s %s %s d775";
716 	char *usage = "usage: uname [-d] uname [uid|:uid|%%newname|=leader|+member|-member]";
717 
718 	dflag = 0;
719 
720 	ARGBEGIN{
721 	default:
722 		return cliError(usage);
723 	case 'd':
724 		dflag = 1;
725 		break;
726 	}ARGEND
727 
728 	if(argc < 1){
729 		if(!dflag)
730 			return cliError(usage);
731 		vtRLock(ubox.lock);
732 		uboxDump(ubox.box);
733 		vtRUnlock(ubox.lock);
734 		return 1;
735 	}
736 
737 	uname = argv[0];
738 	argc--; argv++;
739 
740 	if(argc == 0){
741 		vtRLock(ubox.lock);
742 		if((u = _userByUname(ubox.box, uname)) == nil){
743 			vtRUnlock(ubox.lock);
744 			return 0;
745 		}
746 		consPrint("\t%U\n", u);
747 		vtRUnlock(ubox.lock);
748 		return 1;
749 	}
750 
751 	vtLock(ubox.lock);
752 	u = _userByUname(ubox.box, uname);
753 	while(argc--){
754 		if(argv[0][0] == '%'){
755 			if(u == nil){
756 				vtUnlock(ubox.lock);
757 				return 0;
758 			}
759 			p = &argv[0][1];
760 			if((up = _userByUname(ubox.box, p)) != nil){
761 				vtSetError("uname: uname '%s' already exists",
762 					up->uname);
763 				vtUnlock(ubox.lock);
764 				return 0;
765 			}
766 			for(i = 0; usersMandatory[i] != nil; i++){
767 				if(strcmp(usersMandatory[i], uname) != 0)
768 					continue;
769 				vtSetError("uname: uname '%s' is mandatory",
770 					uname);
771 				vtUnlock(ubox.lock);
772 				return 0;
773 			}
774 
775 			d = strlen(p) - strlen(u->uname);
776 			for(up = ubox.box->head; up != nil; up = up->next){
777 				if(up->leader != nil){
778 					if(strcmp(up->leader, u->uname) == 0){
779 						vtMemFree(up->leader);
780 						up->leader = vtStrDup(p);
781 						ubox.box->len += d;
782 					}
783 				}
784 				for(i = 0; i < up->ngroup; i++){
785 					if(strcmp(up->group[i], u->uname) != 0)
786 						continue;
787 					vtMemFree(up->group[i]);
788 					up->group[i] = vtStrDup(p);
789 					ubox.box->len += d;
790 					break;
791 				}
792 			}
793 
794 			uboxRemUser(ubox.box, u);
795 			vtMemFree(u->uname);
796 			u->uname = vtStrDup(p);
797 			uboxAddUser(ubox.box, u);
798 		}
799 		else if(argv[0][0] == '='){
800 			if(u == nil){
801 				vtUnlock(ubox.lock);
802 				return 0;
803 			}
804 			if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
805 				if(argv[0][1] != '\0'){
806 					vtUnlock(ubox.lock);
807 					return 0;
808 				}
809 			}
810 			if(u->leader != nil){
811 				ubox.box->len -= strlen(u->leader);
812 				vtMemFree(u->leader);
813 				u->leader = nil;
814 			}
815 			if(up != nil){
816 				u->leader = vtStrDup(up->uname);
817 				ubox.box->len += strlen(u->leader);
818 			}
819 		}
820 		else if(argv[0][0] == '+'){
821 			if(u == nil){
822 				vtUnlock(ubox.lock);
823 				return 0;
824 			}
825 			if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
826 				vtUnlock(ubox.lock);
827 				return 0;
828 			}
829 			if(!_groupAddMember(ubox.box, u, up->uname)){
830 				vtUnlock(ubox.lock);
831 				return 0;
832 			}
833 		}
834 		else if(argv[0][0] == '-'){
835 			if(u == nil){
836 				vtUnlock(ubox.lock);
837 				return 0;
838 			}
839 			if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
840 				vtUnlock(ubox.lock);
841 				return 0;
842 			}
843 			if(!_groupRemMember(ubox.box, u, up->uname)){
844 				vtUnlock(ubox.lock);
845 				return 0;
846 			}
847 		}
848 		else{
849 			if(u != nil){
850 				vtSetError("uname: uname '%s' already exists",
851 					u->uname);
852 				vtUnlock(ubox.lock);
853 				return 0;
854 			}
855 
856 			uid = argv[0];
857 			if(*uid == ':')
858 				uid++;
859 			if((u = _userByUid(ubox.box, uid)) != nil){
860 				vtSetError("uname: uid '%s' already exists",
861 					u->uid);
862 				vtUnlock(ubox.lock);
863 				return 0;
864 			}
865 
866 			u = userAlloc(uid, uname);
867 			uboxAddUser(ubox.box, u);
868 			if(argv[0][0] != ':'){
869 				// should have an option for the mode and gid
870 				p = smprint(createfmt, uname, uname, uname);
871 				r = cliExec(p);
872 				vtMemFree(p);
873 				if(r != 0){
874 					vtUnlock(ubox.lock);
875 					return 0;
876 				}
877 			}
878 		}
879 		argv++;
880 	}
881 
882 	if(usersFileWrite(ubox.box) == 0){
883 		vtUnlock(ubox.lock);
884 		return 0;
885 	}
886 	if(dflag)
887 		uboxDump(ubox.box);
888 	vtUnlock(ubox.lock);
889 
890 	return 1;
891 }
892 
893 static int
894 cmdUsers(int argc, char* argv[])
895 {
896 	Ubox *box;
897 	int dflag, r, wflag;
898 	char *usage = "usage: users [-dw] [file]";
899 
900 	dflag = wflag = 0;
901 
902 	ARGBEGIN{
903 	default:
904 		return cliError(usage);
905 	case 'd':
906 		dflag = 1;
907 		break;
908 	case 'w':
909 		wflag = 1;
910 		break;
911 	}ARGEND
912 
913 	switch(argc){
914 	default:
915 		return cliError(usage);
916 	case 0:
917 		if(dflag)
918 			uboxInit(nil, usersDefault, sizeof(usersDefault));
919 		vtRLock(ubox.lock);
920 		box = ubox.box;
921 		if(box->name != nil)
922 			consPrint("\tfile %s\n", box->name);
923 		else
924 			consPrint("\tno file\n");
925 		consPrint("\tnuser %d len %d\n", box->nuser, box->len);
926 		vtRUnlock(ubox.lock);
927 		break;
928 	case 1:
929 		if(dflag)
930 			return cliError(usage);
931 		if(usersFileRead(argv[0]) == 0)
932 			return 0;
933 		break;
934 	}
935 
936 	if(wflag){
937 		vtRLock(ubox.lock);
938 		r = usersFileWrite(ubox.box);
939 		vtRUnlock(ubox.lock);
940 		return r;
941 	}
942 
943 	return 1;
944 }
945 
946 int
947 usersInit(void)
948 {
949 	fmtinstall('U', userFmt);
950 
951 	ubox.lock = vtLockAlloc();
952 	uboxInit(nil, usersDefault, sizeof(usersDefault));
953 
954 	cliAddCmd("users", cmdUsers);
955 	cliAddCmd("uname", cmdUname);
956 
957 	return 1;
958 }
959