1 #include "a.h"
2 
3 enum
4 {
5 	Qroot = 0,        //  /smug/
6 	Qctl,             //  /smug/ctl
7 	Qrpclog,          //  /smug/rpclog
8 	Quploads,		// /smug/uploads
9 	Qnick,            //  /smug/nick/
10 	Qnickctl,         //  /smug/nick/ctl
11 	Qalbums,          //  /smug/nick/albums/
12 	Qalbumsctl,       //  /smug/nick/albums/ctl
13 	Qcategory,        //  /smug/nick/Category/
14 	Qcategoryctl,     //  /smug/nick/Category/ctl
15 	Qalbum,           //  /smug/nick/Category/Album/
16 	Qalbumctl,        //  /smug/nick/Category/Album/ctl
17 	Qalbumsettings,   //  /smug/nick/Category/Album/settings
18 	Quploadfile,      //  /smug/nick/Category/Album/upload/file.jpg
19 	Qimage,           //  /smug/nick/Category/Album/Image/
20 	Qimagectl,        //  /smug/nick/Category/Album/Image/ctl
21 	Qimageexif,       //  /smug/nick/Category/Album/Image/exif
22 	Qimagesettings,   //  /smug/nick/Category/Album/Image/settings
23 	Qimageurl,        //  /smug/nick/Category/Album/Image/url
24 	Qimagefile,       //  /smug/nick/Category/Album/Image/file.jpg
25 };
26 
27 void
mylock(Lock * lk)28 mylock(Lock *lk)
29 {
30 	lock(lk);
31 	fprint(2, "locked from %p\n", getcallerpc(&lk));
32 }
33 
34 void
myunlock(Lock * lk)35 myunlock(Lock *lk)
36 {
37 	unlock(lk);
38 	fprint(2, "unlocked from %p\n", getcallerpc(&lk));
39 }
40 
41 //#define lock mylock
42 //#define unlock myunlock
43 
44 typedef struct Upload Upload;
45 
46 typedef struct SmugFid SmugFid;
47 struct SmugFid
48 {
49 	int type;
50 	int nickid;
51 	vlong category;  // -1 for "albums"
52 	vlong album;
53 	char *albumkey;
54 	vlong image;
55 	char *imagekey;
56 	Upload *upload;
57 	int upwriter;
58 };
59 
60 #define QTYPE(p) ((p)&0xFF)
61 #define QARG(p) ((p)>>8)
62 #define QPATH(p, q) ((p)|((q)<<8))
63 
64 char **nick;
65 int nnick;
66 
67 struct Upload
68 {
69 	Lock lk;
70 	int fd;
71 	char *name;
72 	char *file;
73 	vlong album;
74 	vlong length;
75 	char *albumkey;
76 	int size;
77 	int ready;
78 	int nwriters;
79 	int uploaded;
80 	int ref;
81 	int uploading;
82 };
83 
84 Upload **up;
85 int nup;
86 QLock uploadlock;
87 Rendez uploadrendez;
88 
89 void uploader(void*);
90 
91 Upload*
newupload(SmugFid * sf,char * name)92 newupload(SmugFid *sf, char *name)
93 {
94 	Upload *u;
95 	int fd, i;
96 	char tmp[] = "/var/tmp/smugfs.XXXXXX";
97 
98 	if((fd = opentemp(tmp, ORDWR)) < 0)
99 		return nil;
100 	qlock(&uploadlock);
101 	for(i=0; i<nup; i++){
102 		u = up[i];
103 		lock(&u->lk);
104 		if(u->ref == 0){
105 			u->ref = 1;
106 			goto Reuse;
107 		}
108 		unlock(&u->lk);
109 	}
110 	if(nup == 0){
111 		uploadrendez.l = &uploadlock;
112 		proccreate(uploader, nil, STACKSIZE);
113 	}
114 	u = emalloc(sizeof *u);
115 	lock(&u->lk);
116 	u->ref = 1;
117 	up = erealloc(up, (nup+1)*sizeof up[0]);
118 	up[nup++] = u;
119 Reuse:
120 	qunlock(&uploadlock);
121 	u->fd = fd;
122 	u->name = estrdup(name);
123 	u->file = estrdup(tmp);
124 	u->album = sf->album;
125 	u->albumkey = estrdup(sf->albumkey);
126 	u->nwriters = 1;
127 	unlock(&u->lk);
128 	return u;
129 }
130 
131 void
closeupload(Upload * u)132 closeupload(Upload *u)
133 {
134 	lock(&u->lk);
135 	if(--u->ref > 0){
136 		unlock(&u->lk);
137 		return;
138 	}
139 	if(u->ref < 0)
140 		abort();
141 	if(u->fd >= 0){
142 		close(u->fd);
143 		u->fd = -1;
144 	}
145 	if(u->name){
146 		free(u->name);
147 		u->name = nil;
148 	}
149 	if(u->file){
150 		remove(u->file);
151 		free(u->file);
152 		u->file = nil;
153 	}
154 	u->album = 0;
155 	if(u->albumkey){
156 		free(u->albumkey);
157 		u->albumkey = nil;
158 	}
159 	u->size = 0;
160 	u->ready = 0;
161 	u->nwriters = 0;
162 	u->uploaded = 0;
163 	u->uploading = 0;
164 	u->length = 0;
165 	unlock(&u->lk);
166 }
167 
168 Upload*
getuploadindex(SmugFid * sf,int * index)169 getuploadindex(SmugFid *sf, int *index)
170 {
171 	int i;
172 	Upload *u;
173 
174 	qlock(&uploadlock);
175 	for(i=0; i<nup; i++){
176 		u = up[i];
177 		lock(&u->lk);
178 		if(u->ref > 0 && !u->uploaded && u->album == sf->album && (*index)-- == 0){
179 			qunlock(&uploadlock);
180 			u->ref++;
181 			unlock(&u->lk);
182 			return u;
183 		}
184 		unlock(&u->lk);
185 	}
186 	qunlock(&uploadlock);
187 	return nil;
188 }
189 
190 Upload*
getuploadname(SmugFid * sf,char * name)191 getuploadname(SmugFid *sf, char *name)
192 {
193 	int i;
194 	Upload *u;
195 
196 	qlock(&uploadlock);
197 	for(i=0; i<nup; i++){
198 		u = up[i];
199 		lock(&u->lk);
200 		if(u->ref > 0 && !u->uploaded && u->album == sf->album && strcmp(name, u->name) == 0){
201 			qunlock(&uploadlock);
202 			u->ref++;
203 			unlock(&u->lk);
204 			return u;
205 		}
206 		unlock(&u->lk);
207 	}
208 	qunlock(&uploadlock);
209 	return nil;
210 }
211 
212 void doupload(Upload*);
213 
214 void
uploader(void * v)215 uploader(void *v)
216 {
217 	int i, did;
218 	Upload *u;
219 
220 	qlock(&uploadlock);
221 	for(;;){
222 		did = 0;
223 		for(i=0; i<nup; i++){
224 			u = up[i];
225 			lock(&u->lk);
226 			if(u->ref > 0 && u->ready && !u->uploading && !u->uploaded){
227 				u->uploading = 1;
228 				unlock(&u->lk);
229 				qunlock(&uploadlock);
230 				doupload(u);
231 				closeupload(u);
232 				did = 1;
233 				qlock(&uploadlock);
234 			}else
235 				unlock(&u->lk);
236 		}
237 		if(!did)
238 			rsleep(&uploadrendez);
239 	}
240 }
241 
242 void
kickupload(Upload * u)243 kickupload(Upload *u)
244 {
245 	Dir *d;
246 
247 	lock(&u->lk);
248 	if((d = dirfstat(u->fd)) != nil)
249 		u->length = d->length;
250 	close(u->fd);
251 	u->fd = -1;
252 	u->ref++;
253 	u->ready = 1;
254 	unlock(&u->lk);
255 	qlock(&uploadlock);
256 	rwakeup(&uploadrendez);
257 	qunlock(&uploadlock);
258 }
259 
260 void
doupload(Upload * u)261 doupload(Upload *u)
262 {
263 	Dir *d;
264 	vlong datalen;
265 	Fmt fmt;
266 	char *req;
267 	char buf[8192];
268 	int n, total;
269 	uchar digest[MD5dlen];
270 	DigestState ds;
271 	Json *jv;
272 
273 	if((u->fd = open(u->file, OREAD)) < 0){
274 		fprint(2, "cannot reopen temporary file %s: %r\n", u->file);
275 		return;
276 	}
277 	if((d = dirfstat(u->fd)) == nil){
278 		fprint(2, "fstat: %r\n");
279 		return;
280 	}
281 	datalen = d->length;
282 	free(d);
283 
284 	memset(&ds, 0, sizeof ds);
285 	seek(u->fd, 0, 0);
286 	total = 0;
287 	while((n = read(u->fd, buf, sizeof buf)) > 0){
288 		md5((uchar*)buf, n, nil, &ds);
289 		total += n;
290 	}
291 	if(total != datalen){
292 		fprint(2, "bad total: %lld %lld\n", total, datalen);
293 		return;
294 	}
295 	md5(nil, 0, digest, &ds);
296 
297 	fmtstrinit(&fmt);
298 	fmtprint(&fmt, "PUT /%s HTTP/1.0\r\n", u->name);
299 	fmtprint(&fmt, "Content-Length: %lld\r\n", datalen);
300 	fmtprint(&fmt, "Content-MD5: %.16lH\r\n", digest);
301 	fmtprint(&fmt, "X-Smug-SessionID: %s\r\n", sessid);
302 	fmtprint(&fmt, "X-Smug-Version: %s\r\n", API_VERSION);
303 	fmtprint(&fmt, "X-Smug-ResponseType: JSON\r\n");
304 	// Can send X-Smug-ImageID instead to replace existing files.
305 	fmtprint(&fmt, "X-Smug-AlbumID: %lld\r\n", u->album);
306 	fmtprint(&fmt, "X-Smug-FileName: %s\r\n", u->name);
307 	fmtprint(&fmt, "\r\n");
308 	req = fmtstrflush(&fmt);
309 
310 	seek(u->fd, 0, 0);
311 	jv = jsonupload(&http, UPLOAD_HOST, req, u->fd, datalen);
312 	free(req);
313 	if(jv == nil){
314 		fprint(2, "upload: %r\n");
315 		return;
316 	}
317 
318 	close(u->fd);
319 	remove(u->file);
320 	free(u->file);
321 	u->file = nil;
322 	u->fd = -1;
323 	u->uploaded = 1;
324 	rpclog("uploaded: %J", jv);
325 	jclose(jv);
326 }
327 
328 int
nickindex(char * name)329 nickindex(char *name)
330 {
331 	int i;
332 	Json *v;
333 
334 	for(i=0; i<nnick; i++)
335 		if(strcmp(nick[i], name) == 0)
336 			return i;
337 	v = smug("smugmug.users.getTree", "NickName", name, nil);
338 	if(v == nil)
339 		return -1;
340 	nick = erealloc(nick, (nnick+1)*sizeof nick[0]);
341 	nick[nnick] = estrdup(name);
342 	return nnick++;
343 }
344 
345 char*
nickname(int i)346 nickname(int i)
347 {
348 	if(i < 0 || i >= nnick)
349 		return nil;
350 	return nick[i];
351 }
352 
353 void
responderrstr(Req * r)354 responderrstr(Req *r)
355 {
356 	char err[ERRMAX];
357 
358 	rerrstr(err, sizeof err);
359 	respond(r, err);
360 }
361 
362 static char*
xclone(Fid * oldfid,Fid * newfid)363 xclone(Fid *oldfid, Fid *newfid)
364 {
365 	SmugFid *sf;
366 
367 	if(oldfid->aux == nil)
368 		return nil;
369 
370 	sf = emalloc(sizeof *sf);
371 	*sf = *(SmugFid*)oldfid->aux;
372 	sf->upload = nil;
373 	sf->upwriter = 0;
374 	if(sf->albumkey)
375 		sf->albumkey = estrdup(sf->albumkey);
376 	if(sf->imagekey)
377 		sf->imagekey = estrdup(sf->imagekey);
378 	newfid->aux = sf;
379 	return nil;
380 }
381 
382 static void
xdestroyfid(Fid * fid)383 xdestroyfid(Fid *fid)
384 {
385 	SmugFid *sf;
386 
387 	sf = fid->aux;
388 	free(sf->albumkey);
389 	free(sf->imagekey);
390 	if(sf->upload){
391 		if(sf->upwriter && --sf->upload->nwriters == 0){
392 			fprint(2, "should upload %s\n", sf->upload->name);
393 			kickupload(sf->upload);
394 		}
395 		closeupload(sf->upload);
396 		sf->upload = nil;
397 	}
398 	free(sf);
399 }
400 
401 static Json*
getcategories(SmugFid * sf)402 getcategories(SmugFid *sf)
403 {
404 	Json *v, *w;
405 
406 	v = smug("smugmug.categories.get", "NickName", nickname(sf->nickid), nil);
407 	w = jincref(jwalk(v, "Categories"));
408 	jclose(v);
409 	return w;
410 }
411 
412 static Json*
getcategorytree(SmugFid * sf)413 getcategorytree(SmugFid *sf)
414 {
415 	Json *v, *w;
416 
417 	v = smug("smugmug.users.getTree", "NickName", nickname(sf->nickid), nil);
418 	w = jincref(jwalk(v, "Categories"));
419 	jclose(v);
420 	return w;
421 }
422 
423 static Json*
getcategory(SmugFid * sf,vlong id)424 getcategory(SmugFid *sf, vlong id)
425 {
426 	int i;
427 	Json *v, *w;
428 
429 	v = getcategorytree(sf);
430 	if(v == nil)
431 		return nil;
432 	for(i=0; i<v->len; i++){
433 		if(jint(jwalk(v->value[i], "id")) == id){
434 			w = jincref(v->value[i]);
435 			jclose(v);
436 			return w;
437 		}
438 	}
439 	jclose(v);
440 	return nil;
441 }
442 
443 static vlong
getcategoryid(SmugFid * sf,char * name)444 getcategoryid(SmugFid *sf, char *name)
445 {
446 	int i;
447 	vlong id;
448 	Json *v;
449 
450 	v = getcategories(sf);
451 	if(v == nil)
452 		return -1;
453 	for(i=0; i<v->len; i++){
454 		if(jstrcmp(jwalk(v->value[i], "Name"), name) == 0){
455 			id = jint(jwalk(v->value[i], "id"));
456 			if(id < 0){
457 				jclose(v);
458 				return -1;
459 			}
460 			jclose(v);
461 			return id;
462 		}
463 	}
464 	jclose(v);
465 	return -1;
466 }
467 
468 static vlong
getcategoryindex(SmugFid * sf,int i)469 getcategoryindex(SmugFid *sf, int i)
470 {
471 	Json *v;
472 	vlong id;
473 
474 	v = getcategories(sf);
475 	if(v == nil)
476 		return -1;
477 	if(i < 0 || i >= v->len){
478 		jclose(v);
479 		return -1;
480 	}
481 	id = jint(jwalk(v->value[i], "id"));
482 	jclose(v);
483 	return id;
484 }
485 
486 static Json*
getalbum(SmugFid * sf,vlong albumid,char * albumkey)487 getalbum(SmugFid *sf, vlong albumid, char *albumkey)
488 {
489 	char id[50];
490 	Json *v, *w;
491 
492 	snprint(id, sizeof id, "%lld", albumid);
493 	v = smug("smugmug.albums.getInfo",
494 		"AlbumID", id, "AlbumKey", albumkey,
495 		"NickName", nickname(sf->nickid), nil);
496 	w = jincref(jwalk(v, "Album"));
497 	jclose(v);
498 	return w;
499 }
500 
501 static Json*
getalbums(SmugFid * sf)502 getalbums(SmugFid *sf)
503 {
504 	Json *v, *w;
505 
506 	if(sf->category >= 0)
507 		v = getcategory(sf, sf->category);
508 	else
509 		v = smug("smugmug.albums.get",
510 			"NickName", nickname(sf->nickid), nil);
511 	w = jincref(jwalk(v, "Albums"));
512 	jclose(v);
513 	return w;
514 }
515 
516 static vlong
getalbumid(SmugFid * sf,char * name,char ** keyp)517 getalbumid(SmugFid *sf, char *name, char **keyp)
518 {
519 	int i;
520 	vlong id;
521 	Json *v;
522 	char *key;
523 
524 	v = getalbums(sf);
525 	if(v == nil)
526 		return -1;
527 	for(i=0; i<v->len; i++){
528 		if(jstrcmp(jwalk(v->value[i], "Title"), name) == 0){
529 			id = jint(jwalk(v->value[i], "id"));
530 			key = jstring(jwalk(v->value[i], "Key"));
531 			if(id < 0 || key == nil){
532 				jclose(v);
533 				return -1;
534 			}
535 			if(keyp)
536 				*keyp = estrdup(key);
537 			jclose(v);
538 			return id;
539 		}
540 	}
541 	jclose(v);
542 	return -1;
543 }
544 
545 static vlong
getalbumindex(SmugFid * sf,int i,char ** keyp)546 getalbumindex(SmugFid *sf, int i, char **keyp)
547 {
548 	vlong id;
549 	Json *v;
550 	char *key;
551 
552 	v = getalbums(sf);
553 	if(v == nil)
554 		return -1;
555 	if(i < 0 || i >= v->len){
556 		jclose(v);
557 		return -1;
558 	}
559 	id = jint(jwalk(v->value[i], "id"));
560 	key = jstring(jwalk(v->value[i], "Key"));
561 	if(id < 0 || key == nil){
562 		jclose(v);
563 		return -1;
564 	}
565 	if(keyp)
566 		*keyp = estrdup(key);
567 	jclose(v);
568 	return id;
569 }
570 
571 static Json*
getimages(SmugFid * sf,vlong albumid,char * albumkey)572 getimages(SmugFid *sf, vlong albumid, char *albumkey)
573 {
574 	char id[50];
575 	Json *v, *w;
576 
577 	snprint(id, sizeof id, "%lld", albumid);
578 	v = smug("smugmug.images.get",
579 		"AlbumID", id, "AlbumKey", albumkey,
580 		"NickName", nickname(sf->nickid), nil);
581 	w = jincref(jwalk(v, "Images"));
582 	jclose(v);
583 	return w;
584 }
585 
586 static vlong
getimageid(SmugFid * sf,char * name,char ** keyp)587 getimageid(SmugFid *sf, char *name, char **keyp)
588 {
589 	int i;
590 	vlong id;
591 	Json *v;
592 	char *p;
593 	char *key;
594 
595 	id = strtol(name, &p, 10);
596 	if(*p != 0 || *name == 0)
597 		return -1;
598 
599 	v = getimages(sf, sf->album, sf->albumkey);
600 	if(v == nil)
601 		return -1;
602 	for(i=0; i<v->len; i++){
603 		if(jint(jwalk(v->value[i], "id")) == id){
604 			key = jstring(jwalk(v->value[i], "Key"));
605 			if(key == nil){
606 				jclose(v);
607 				return -1;
608 			}
609 			if(keyp)
610 				*keyp = estrdup(key);
611 			jclose(v);
612 			return id;
613 		}
614 	}
615 	jclose(v);
616 	return -1;
617 }
618 
619 static Json*
getimageinfo(SmugFid * sf,vlong imageid,char * imagekey)620 getimageinfo(SmugFid *sf, vlong imageid, char *imagekey)
621 {
622 	char id[50];
623 	Json *v, *w;
624 
625 	snprint(id, sizeof id, "%lld", imageid);
626 	v = smug("smugmug.images.getInfo",
627 		"ImageID", id, "ImageKey", imagekey,
628 		"NickName", nickname(sf->nickid), nil);
629 	w = jincref(jwalk(v, "Image"));
630 	jclose(v);
631 	return w;
632 }
633 
634 static Json*
getimageexif(SmugFid * sf,vlong imageid,char * imagekey)635 getimageexif(SmugFid *sf, vlong imageid, char *imagekey)
636 {
637 	char id[50];
638 	Json *v, *w;
639 
640 	snprint(id, sizeof id, "%lld", imageid);
641 	v = smug("smugmug.images.getEXIF",
642 		"ImageID", id, "ImageKey", imagekey,
643 		"NickName", nickname(sf->nickid), nil);
644 	w = jincref(jwalk(v, "Image"));
645 	jclose(v);
646 	return w;
647 }
648 
649 static vlong
getimageindex(SmugFid * sf,int i,char ** keyp)650 getimageindex(SmugFid *sf, int i, char **keyp)
651 {
652 	vlong id;
653 	Json *v;
654 	char *key;
655 
656 	v = getimages(sf, sf->album, sf->albumkey);
657 	if(v == nil)
658 		return -1;
659 	if(i < 0 || i >= v->len){
660 		jclose(v);
661 		return -1;
662 	}
663 	id = jint(jwalk(v->value[i], "id"));
664 	key = jstring(jwalk(v->value[i], "Key"));
665 	if(id < 0 || key == nil){
666 		jclose(v);
667 		return -1;
668 	}
669 	if(keyp)
670 		*keyp = estrdup(key);
671 	jclose(v);
672 	return id;
673 }
674 
675 static char*
categoryname(SmugFid * sf)676 categoryname(SmugFid *sf)
677 {
678 	Json *v;
679 	char *s;
680 
681 	v = getcategory(sf, sf->category);
682 	s = jstring(jwalk(v, "Name"));
683 	if(s)
684 		s = estrdup(s);
685 	jclose(v);
686 	return s;
687 }
688 
689 static char*
albumname(SmugFid * sf)690 albumname(SmugFid *sf)
691 {
692 	Json *v;
693 	char *s;
694 
695 	v = getalbum(sf, sf->album, sf->albumkey);
696 	s = jstring(jwalk(v, "Title"));
697 	if(s)
698 		s = estrdup(s);
699 	jclose(v);
700 	return s;
701 }
702 
703 static char*
imagename(SmugFid * sf)704 imagename(SmugFid *sf)
705 {
706 	char *s;
707 	Json *v;
708 
709 	v = getimageinfo(sf, sf->image, sf->imagekey);
710 	s = jstring(jwalk(v, "FileName"));
711 	if(s && s[0])
712 		s = estrdup(s);
713 	else
714 		s = smprint("%lld.jpg", sf->image);	// TODO: use Format
715 	jclose(v);
716 	return s;
717 }
718 
719 static vlong
imagelength(SmugFid * sf)720 imagelength(SmugFid *sf)
721 {
722 	vlong length;
723 	Json *v;
724 
725 	v = getimageinfo(sf, sf->image, sf->imagekey);
726 	length = jint(jwalk(v, "Size"));
727 	jclose(v);
728 	return length;
729 }
730 
731 static struct {
732 	char *key;
733 	char *name;
734 } urls[] = {
735 	"AlbumURL", "album",
736 	"TinyURL", "tiny",
737 	"ThumbURL", "thumb",
738 	"SmallURL", "small",
739 	"MediumURL", "medium",
740 	"LargeURL", "large",
741 	"XLargeURL", "xlarge",
742 	"X2LargeURL", "xxlarge",
743 	"X3LargeURL", "xxxlarge",
744 	"OriginalURL", "original",
745 };
746 
747 static char*
imageurl(SmugFid * sf)748 imageurl(SmugFid *sf)
749 {
750 	Json *v;
751 	char *s;
752 	int i;
753 
754 	v = getimageinfo(sf, sf->image, sf->imagekey);
755 	for(i=nelem(urls)-1; i>=0; i--){
756 		if((s = jstring(jwalk(v, urls[i].key))) != nil){
757 			s = estrdup(s);
758 			jclose(v);
759 			return s;
760 		}
761 	}
762 	jclose(v);
763 	return nil;
764 }
765 
766 static char* imagestrings[] =
767 {
768 	"Caption",
769 	"LastUpdated",
770 	"FileName",
771 	"MD5Sum",
772 	"Watermark",
773 	"Format",
774 	"Keywords",
775 	"Date",
776 	"AlbumURL",
777 	"TinyURL",
778 	"ThumbURL",
779 	"SmallURL",
780 	"MediumURL",
781 	"LargeURL",
782 	"XLargeURL",
783 	"X2LargeURL",
784 	"X3LargeURL",
785 	"OriginalURL",
786 	"Album",
787 };
788 
789 static char* albumbools[] =
790 {
791 	"Public",
792 	"Printable",
793 	"Filenames",
794 	"Comments",
795 	"External",
796 	"Originals",
797 	"EXIF",
798 	"Share",
799 	"SortDirection",
800 	"FamilyEdit",
801 	"FriendEdit",
802 	"HideOwner",
803 	"CanRank",
804 	"Clean",
805 	"Geography",
806 	"SmugSearchable",
807 	"WorldSearchable",
808 	"SquareThumbs",
809 	"X2Larges",
810 	"X3Larges",
811 };
812 
813 static char* albumstrings[] =
814 {
815 	"Description"
816 	"Keywords",
817 	"Password",
818 	"PasswordHint",
819 	"SortMethod",
820 	"LastUpdated",
821 };
822 
823 static char*
readctl(SmugFid * sf)824 readctl(SmugFid *sf)
825 {
826 	int i;
827 	Upload *u;
828 	char *s;
829 	Json *v, *vv;
830 	Fmt fmt;
831 
832 	v = nil;
833 	switch(sf->type){
834 	case Qctl:
835 		return smprint("%#J\n", userinfo);
836 
837 	case Quploads:
838 		fmtstrinit(&fmt);
839 		qlock(&uploadlock);
840 		for(i=0; i<nup; i++){
841 			u = up[i];
842 			lock(&u->lk);
843 			if(u->ready && !u->uploaded && u->ref > 0)
844 				fmtprint(&fmt, "%s %s%s\n", u->name, u->file, u->uploading ? " [uploading]" : "");
845 			unlock(&u->lk);
846 		}
847 		qunlock(&uploadlock);
848 		return fmtstrflush(&fmt);
849 
850 	case Qnickctl:
851 		v = getcategories(sf);
852 		break;
853 
854 	case Qcategoryctl:
855 		v = getcategory(sf, sf->category);
856 		break;
857 
858 	case Qalbumctl:
859 		v = getimages(sf, sf->album, sf->albumkey);
860 		break;
861 
862 	case Qalbumsctl:
863 		v = getalbums(sf);
864 		break;
865 
866 	case Qimagectl:
867 		v = getimageinfo(sf, sf->image, sf->imagekey);
868 		break;
869 
870 	case Qimageurl:
871 		v = getimageinfo(sf, sf->image, sf->imagekey);
872 		fmtstrinit(&fmt);
873 		for(i=0; i<nelem(urls); i++)
874 			if((s = jstring(jwalk(v, urls[i].key))) != nil)
875 				fmtprint(&fmt, "%s %s\n", urls[i].name, s);
876 		jclose(v);
877 		return fmtstrflush(&fmt);
878 
879 	case Qimageexif:
880 		v = getimageexif(sf, sf->image, sf->imagekey);
881 		break;
882 
883 	case Qalbumsettings:
884 		v = getalbum(sf, sf->album, sf->albumkey);
885 		fmtstrinit(&fmt);
886 		fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id")));
887 		// TODO: Category/id
888 		// TODO: SubCategory/id
889 		// TODO: Community/id
890 		// TODO: Template/id
891 		fmtprint(&fmt, "Highlight\t%lld\n", jint(jwalk(v, "Highlight/id")));
892 		fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position")));
893 		fmtprint(&fmt, "ImageCount\t%lld\n", jint(jwalk(v, "ImageCount")));
894 		for(i=0; i<nelem(albumbools); i++){
895 			vv = jwalk(v, albumbools[i]);
896 			if(vv)
897 				fmtprint(&fmt, "%s\t%J\n", albumbools[i], vv);
898 		}
899 		for(i=0; i<nelem(albumstrings); i++){
900 			s = jstring(jwalk(v, albumstrings[i]));
901 			if(s)
902 				fmtprint(&fmt, "%s\t%s\n", albumstrings[i], s);
903 		}
904 		s = fmtstrflush(&fmt);
905 		jclose(v);
906 		return s;
907 
908 	case Qimagesettings:
909 		v = getimageinfo(sf, sf->image, sf->imagekey);
910 		fmtstrinit(&fmt);
911 		fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id")));
912 		fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position")));
913 		fmtprint(&fmt, "Serial\t%lld\n", jint(jwalk(v, "Serial")));
914 		fmtprint(&fmt, "Size\t%lld\t%lldx%lld\n",
915 			jint(jwalk(v, "Size")),
916 			jint(jwalk(v, "Width")),
917 			jint(jwalk(v, "Height")));
918 		vv = jwalk(v, "Hidden");
919 		fmtprint(&fmt, "Hidden\t%J\n", vv);
920 		// TODO: Album/id
921 		for(i=0; i<nelem(imagestrings); i++){
922 			s = jstring(jwalk(v, imagestrings[i]));
923 			if(s)
924 				fmtprint(&fmt, "%s\t%s\n", imagestrings[i], s);
925 		}
926 		s = fmtstrflush(&fmt);
927 		jclose(v);
928 		return s;
929 	}
930 
931 	if(v == nil)
932 		return estrdup("");
933 	s = smprint("%#J\n", v);
934 	jclose(v);
935 	return s;
936 }
937 
938 
939 static void
dostat(SmugFid * sf,Qid * qid,Dir * dir)940 dostat(SmugFid *sf, Qid *qid, Dir *dir)
941 {
942 	Qid q;
943 	char *name;
944 	int freename;
945 	ulong mode;
946 	char *uid;
947 	char *s;
948 	vlong length;
949 
950 	memset(&q, 0, sizeof q);
951 	name = nil;
952 	freename = 0;
953 	uid = "smugfs";
954 	q.type = 0;
955 	q.vers = 0;
956 	q.path = QPATH(sf->type, sf->nickid);
957 	length = 0;
958 	mode = 0444;
959 
960 	switch(sf->type){
961 	case Qroot:
962 		name = "/";
963 		q.type = QTDIR;
964 		break;
965 	case Qctl:
966 		name = "ctl";
967 		mode |= 0222;
968 		break;
969 	case Quploads:
970 		name = "uploads";
971 		s = readctl(sf);
972 		if(s){
973 			length = strlen(s);
974 			free(s);
975 		}
976 		break;
977 	case Qrpclog:
978 		name = "rpclog";
979 		break;
980 	case Qnick:
981 		name = nickname(sf->nickid);
982 		q.type = QTDIR;
983 		break;
984 	case Qnickctl:
985 		name = "ctl";
986 		mode |= 0222;
987 		break;
988 	case Qalbums:
989 		name = "albums";
990 		q.type = QTDIR;
991 		break;
992 	case Qalbumsctl:
993 		name = "ctl";
994 		mode |= 0222;
995 		break;
996 	case Qcategory:
997 		name = categoryname(sf);
998 		freename = 1;
999 		q.path |= QPATH(0, sf->category << 8);
1000 		q.type = QTDIR;
1001 		break;
1002 	case Qcategoryctl:
1003 		name = "ctl";
1004 		mode |= 0222;
1005 		q.path |= QPATH(0, sf->category << 8);
1006 		break;
1007 	case Qalbum:
1008 		name = albumname(sf);
1009 		freename = 1;
1010 		q.path |= QPATH(0, sf->album << 8);
1011 		q.type = QTDIR;
1012 		break;
1013 	case Qalbumctl:
1014 		name = "ctl";
1015 		mode |= 0222;
1016 		q.path |= QPATH(0, sf->album << 8);
1017 		break;
1018 	case Qalbumsettings:
1019 		name = "settings";
1020 		mode |= 0222;
1021 		q.path |= QPATH(0, sf->album << 8);
1022 		break;
1023 	case Quploadfile:
1024 		q.path |= QPATH(0, (uintptr)sf->upload << 8);
1025 		if(sf->upload){
1026 			Dir *dd;
1027 			name = sf->upload->name;
1028 			if(sf->upload->fd >= 0){
1029 				dd = dirfstat(sf->upload->fd);
1030 				if(dd){
1031 					length = dd->length;
1032 					free(dd);
1033 				}
1034 			}else
1035 				length = sf->upload->length;
1036 			if(!sf->upload->ready)
1037 				mode |= 0222;
1038 		}
1039 		break;
1040 	case Qimage:
1041 		name = smprint("%lld", sf->image);
1042 		freename = 1;
1043 		q.path |= QPATH(0, sf->image << 8);
1044 		q.type = QTDIR;
1045 		break;
1046 	case Qimagectl:
1047 		name = "ctl";
1048 		mode |= 0222;
1049 		q.path |= QPATH(0, sf->image << 8);
1050 		break;
1051 	case Qimagesettings:
1052 		name = "settings";
1053 		mode |= 0222;
1054 		q.path |= QPATH(0, sf->image << 8);
1055 		break;
1056 	case Qimageexif:
1057 		name = "exif";
1058 		q.path |= QPATH(0, sf->image << 8);
1059 		break;
1060 	case Qimageurl:
1061 		name = "url";
1062 		q.path |= QPATH(0, sf->image << 8);
1063 		break;
1064 	case Qimagefile:
1065 		name = imagename(sf);
1066 		freename = 1;
1067 		q.path |= QPATH(0, sf->image << 8);
1068 		length = imagelength(sf);
1069 		break;
1070 	default:
1071 		name = "?egreg";
1072 		q.path = 0;
1073 		break;
1074 	}
1075 
1076 	if(name == nil){
1077 		name = "???";
1078 		freename = 0;
1079 	}
1080 
1081 	if(qid)
1082 		*qid = q;
1083 	if(dir){
1084 		memset(dir, 0, sizeof *dir);
1085 		dir->name = estrdup9p(name);
1086 		dir->muid = estrdup9p("muid");
1087 		mode |= q.type<<24;
1088 		if(mode & DMDIR)
1089 			mode |= 0755;
1090 		dir->mode = mode;
1091 		dir->uid = estrdup9p(uid);
1092 		dir->gid = estrdup9p("smugfs");
1093 		dir->qid = q;
1094 		dir->length = length;
1095 	}
1096 	if(freename)
1097 		free(name);
1098 }
1099 
1100 static char*
xwalk1(Fid * fid,char * name,Qid * qid)1101 xwalk1(Fid *fid, char *name, Qid *qid)
1102 {
1103 	int dotdot, i;
1104 	vlong id;
1105 	char *key;
1106 	SmugFid *sf;
1107 	char *x;
1108 	Upload *u;
1109 
1110 	dotdot = strcmp(name, "..") == 0;
1111 	sf = fid->aux;
1112 	switch(sf->type){
1113 	default:
1114 	NotFound:
1115 		return "file not found";
1116 
1117 	case Qroot:
1118 		if(dotdot)
1119 			break;
1120 		if(strcmp(name, "ctl") == 0){
1121 			sf->type = Qctl;
1122 			break;
1123 		}
1124 		if(strcmp(name, "uploads") == 0){
1125 			sf->type = Quploads;
1126 			break;
1127 		}
1128 		if(strcmp(name, "rpclog") == 0){
1129 			sf->type = Qrpclog;
1130 			break;
1131 		}
1132 		if((i = nickindex(name)) >= 0){
1133 			sf->nickid = i;
1134 			sf->type = Qnick;
1135 			break;
1136 		}
1137 		goto NotFound;
1138 
1139 	case Qnick:
1140 		if(dotdot){
1141 			sf->type = Qroot;
1142 			sf->nickid = 0;
1143 			break;
1144 		}
1145 		if(strcmp(name, "ctl") == 0){
1146 			sf->type = Qnickctl;
1147 			break;
1148 		}
1149 		if(strcmp(name, "albums") == 0){
1150 			sf->category = -1;
1151 			sf->type = Qalbums;
1152 			break;
1153 		}
1154 		if((id = getcategoryid(sf, name)) >= 0){
1155 			sf->category = id;
1156 			sf->type = Qcategory;
1157 			break;
1158 		}
1159 		goto NotFound;
1160 
1161 	case Qalbums:
1162 	case Qcategory:
1163 		if(dotdot){
1164 			sf->category = 0;
1165 			sf->type = Qnick;
1166 			break;
1167 		}
1168 		if(strcmp(name, "ctl") == 0){
1169 			sf->type++;
1170 			break;
1171 		}
1172 		if((id = getalbumid(sf, name, &key)) >= 0){
1173 			sf->album = id;
1174 			sf->albumkey = key;
1175 			sf->type = Qalbum;
1176 			break;
1177 		}
1178 		goto NotFound;
1179 
1180 	case Qalbum:
1181 		if(dotdot){
1182 			free(sf->albumkey);
1183 			sf->albumkey = nil;
1184 			sf->album = 0;
1185 			if(sf->category == -1)
1186 				sf->type = Qalbums;
1187 			else
1188 				sf->type = Qcategory;
1189 			break;
1190 		}
1191 		if(strcmp(name, "ctl") == 0){
1192 			sf->type = Qalbumctl;
1193 			break;
1194 		}
1195 		if(strcmp(name, "settings") == 0){
1196 			sf->type = Qalbumsettings;
1197 			break;
1198 		}
1199 		if((id = getimageid(sf, name, &key)) >= 0){
1200 			sf->image = id;
1201 			sf->imagekey = key;
1202 			sf->type = Qimage;
1203 			break;
1204 		}
1205 		if((u = getuploadname(sf, name)) != nil){
1206 			sf->upload = u;
1207 			sf->type = Quploadfile;
1208 			break;
1209 		}
1210 		goto NotFound;
1211 
1212 	case Qimage:
1213 		if(dotdot){
1214 			free(sf->imagekey);
1215 			sf->imagekey = nil;
1216 			sf->image = 0;
1217 			sf->type = Qalbum;
1218 			break;
1219 		}
1220 		if(strcmp(name, "ctl") == 0){
1221 			sf->type = Qimagectl;
1222 			break;
1223 		}
1224 		if(strcmp(name, "url") == 0){
1225 			sf->type = Qimageurl;
1226 			break;
1227 		}
1228 		if(strcmp(name, "settings") == 0){
1229 			sf->type = Qimagesettings;
1230 			break;
1231 		}
1232 		if(strcmp(name, "exif") == 0){
1233 			sf->type = Qimageexif;
1234 			break;
1235 		}
1236 		x = imagename(sf);
1237 		if(x && strcmp(name, x) == 0){
1238 			free(x);
1239 			sf->type = Qimagefile;
1240 			break;
1241 		}
1242 		free(x);
1243 		goto NotFound;
1244 	}
1245 	dostat(sf, qid, nil);
1246 	fid->qid = *qid;
1247 	return nil;
1248 }
1249 
1250 static int
dodirgen(int i,Dir * d,void * v)1251 dodirgen(int i, Dir *d, void *v)
1252 {
1253 	SmugFid *sf, xsf;
1254 	char *key;
1255 	vlong id;
1256 	Upload *u;
1257 
1258 	sf = v;
1259 	xsf = *sf;
1260 	if(i-- == 0){
1261 		xsf.type++;	// ctl in every directory
1262 		dostat(&xsf, nil, d);
1263 		return 0;
1264 	}
1265 
1266 	switch(sf->type){
1267 	default:
1268 		return -1;
1269 
1270 	case Qroot:
1271 		if(i-- == 0){
1272 			xsf.type = Qrpclog;
1273 			dostat(&xsf, nil, d);
1274 			return 0;
1275 		}
1276 		if(i < 0 || i >= nnick)
1277 			return -1;
1278 		xsf.type = Qnick;
1279 		xsf.nickid = i;
1280 		dostat(&xsf, nil, d);
1281 		return 0;
1282 
1283 	case Qnick:
1284 		if(i-- == 0){
1285 			xsf.type = Qalbums;
1286 			dostat(&xsf, nil, d);
1287 			return 0;
1288 		}
1289 		if((id = getcategoryindex(sf, i)) < 0)
1290 			return -1;
1291 		xsf.type = Qcategory;
1292 		xsf.category = id;
1293 		dostat(&xsf, nil, d);
1294 		return 0;
1295 
1296 	case Qalbums:
1297 	case Qcategory:
1298 		if((id = getalbumindex(sf, i, &key)) < 0)
1299 			return -1;
1300 		xsf.type = Qalbum;
1301 		xsf.album = id;
1302 		xsf.albumkey = key;
1303 		dostat(&xsf, nil, d);
1304 		free(key);
1305 		return 0;
1306 
1307 	case Qalbum:
1308 		if(i-- == 0){
1309 			xsf.type = Qalbumsettings;
1310 			dostat(&xsf, nil, d);
1311 			return 0;
1312 		}
1313 		if((u = getuploadindex(sf, &i)) != nil){
1314 			xsf.upload = u;
1315 			xsf.type = Quploadfile;
1316 			dostat(&xsf, nil, d);
1317 			closeupload(u);
1318 			return 0;
1319 		}
1320 		if((id = getimageindex(sf, i, &key)) < 0)
1321 			return -1;
1322 		xsf.type = Qimage;
1323 		xsf.image = id;
1324 		xsf.imagekey = key;
1325 		dostat(&xsf, nil, d);
1326 		free(key);
1327 		return 0;
1328 
1329 	case Qimage:
1330 		if(i-- == 0){
1331 			xsf.type = Qimagefile;
1332 			dostat(&xsf, nil, d);
1333 			return 0;
1334 		}
1335 		if(i-- == 0){
1336 			xsf.type = Qimageexif;
1337 			dostat(&xsf, nil, d);
1338 			return 0;
1339 		}
1340 		if(i-- == 0){
1341 			xsf.type = Qimagesettings;
1342 			dostat(&xsf, nil, d);
1343 			return 0;
1344 		}
1345 		if(i-- == 0){
1346 			xsf.type = Qimageurl;
1347 			dostat(&xsf, nil, d);
1348 			return 0;
1349 		}
1350 		return -1;
1351 	}
1352 }
1353 
1354 static void
xstat(Req * r)1355 xstat(Req *r)
1356 {
1357 	dostat(r->fid->aux, nil, &r->d);
1358 	respond(r, nil);
1359 }
1360 
1361 static void
xwstat(Req * r)1362 xwstat(Req *r)
1363 {
1364 	SmugFid *sf;
1365 	Json *v;
1366 	char *s;
1367 	char strid[50];
1368 
1369 	sf = r->fid->aux;
1370 	if(r->d.uid[0] || r->d.gid[0] || r->d.muid[0] || ~r->d.mode != 0
1371 	|| ~r->d.atime != 0 || ~r->d.mtime != 0 || ~r->d.length != 0){
1372 		respond(r, "invalid wstat");
1373 		return;
1374 	}
1375 	if(r->d.name[0]){
1376 		switch(sf->type){
1377 		default:
1378 			respond(r, "invalid wstat");
1379 			return;
1380 		// TODO: rename category
1381 		case Qalbum:
1382 			snprint(strid, sizeof strid, "%lld", sf->album);
1383 			v = ncsmug("smugmug.albums.changeSettings",
1384 				"AlbumID", strid, "Title", r->d.name, nil);
1385 			if(v == nil)
1386 				responderrstr(r);
1387 			else
1388 				respond(r, nil);
1389 			s = smprint("&AlbumID=%lld&", sf->album);
1390 			jcacheflush(s);
1391 			free(s);
1392 			jcacheflush("smugmug.albums.get&");
1393 			return;
1394 		}
1395 	}
1396 	respond(r, "invalid wstat");
1397 }
1398 
1399 static void
xattach(Req * r)1400 xattach(Req *r)
1401 {
1402 	SmugFid *sf;
1403 
1404 	sf = emalloc(sizeof *sf);
1405 	r->fid->aux = sf;
1406 	sf->type = Qroot;
1407 	dostat(sf, &r->ofcall.qid, nil);
1408 	r->fid->qid = r->ofcall.qid;
1409 	respond(r, nil);
1410 }
1411 
1412 void
xopen(Req * r)1413 xopen(Req *r)
1414 {
1415 	SmugFid *sf;
1416 
1417 	if((r->ifcall.mode&~OTRUNC) > 2){
1418 		respond(r, "permission denied");
1419 		return;
1420 	}
1421 
1422 	sf = r->fid->aux;
1423 	switch(sf->type){
1424 	case Qctl:
1425 	case Qnickctl:
1426 	case Qalbumsctl:
1427 	case Qcategoryctl:
1428 	case Qalbumctl:
1429 	case Qimagectl:
1430 	case Qalbumsettings:
1431 	case Qimagesettings:
1432 		break;
1433 
1434 	case Quploadfile:
1435 		if(r->ifcall.mode != OREAD){
1436 			lock(&sf->upload->lk);
1437 			if(sf->upload->ready){
1438 				unlock(&sf->upload->lk);
1439 				respond(r, "permission denied");
1440 				return;
1441 			}
1442 			sf->upwriter = 1;
1443 			sf->upload->nwriters++;
1444 			unlock(&sf->upload->lk);
1445 		}
1446 		break;
1447 
1448 	default:
1449 		if(r->ifcall.mode != OREAD){
1450 			respond(r, "permission denied");
1451 			return;
1452 		}
1453 		break;
1454 	}
1455 
1456 	r->ofcall.qid = r->fid->qid;
1457 	respond(r, nil);
1458 }
1459 
1460 void
xcreate(Req * r)1461 xcreate(Req *r)
1462 {
1463 	SmugFid *sf;
1464 	Json *v;
1465 	vlong id;
1466 	char strid[50], *key;
1467 	Upload *u;
1468 
1469 	sf = r->fid->aux;
1470 	switch(sf->type){
1471 	case Qnick:
1472 		// Create new category.
1473 		if(!(r->ifcall.perm&DMDIR))
1474 			break;
1475 		v = ncsmug("smugmug.categories.create",
1476 			"Name", r->ifcall.name, nil);
1477 		if(v == nil){
1478 			responderrstr(r);
1479 			return;
1480 		}
1481 		id = jint(jwalk(v, "Category/id"));
1482 		if(id < 0){
1483 			fprint(2, "Create category: %J\n", v);
1484 			jclose(v);
1485 			responderrstr(r);
1486 			return;
1487 		}
1488 		sf->type = Qcategory;
1489 		sf->category = id;
1490 		jcacheflush("method=smugmug.users.getTree&");
1491 		jcacheflush("method=smugmug.categories.get&");
1492 		dostat(sf, &r->ofcall.qid, nil);
1493 		respond(r, nil);
1494 		return;
1495 
1496 	case Qcategory:
1497 		// Create new album.
1498 		if(!(r->ifcall.perm&DMDIR))
1499 			break;
1500 		snprint(strid, sizeof strid, "%lld", sf->category);
1501 		// Start with most restrictive settings.
1502 		v = ncsmug("smugmug.albums.create",
1503 			"Title", r->ifcall.name,
1504 			"CategoryID", strid,
1505 			"Public", "0",
1506 			"WorldSearchable", "0",
1507 			"SmugSearchable", "0",
1508 			nil);
1509 		if(v == nil){
1510 			responderrstr(r);
1511 			return;
1512 		}
1513 		id = jint(jwalk(v, "Album/id"));
1514 		key = jstring(jwalk(v, "Album/Key"));
1515 		if(id < 0 || key == nil){
1516 			fprint(2, "Create album: %J\n", v);
1517 			jclose(v);
1518 			responderrstr(r);
1519 			return;
1520 		}
1521 		sf->type = Qalbum;
1522 		sf->album = id;
1523 		sf->albumkey = estrdup(key);
1524 		jclose(v);
1525 		jcacheflush("method=smugmug.users.getTree&");
1526 		dostat(sf, &r->ofcall.qid, nil);
1527 		respond(r, nil);
1528 		return;
1529 
1530 	case Qalbum:
1531 		// Upload image to album.
1532 		if(r->ifcall.perm&DMDIR)
1533 			break;
1534 		u = newupload(sf, r->ifcall.name);
1535 		if(u == nil){
1536 			responderrstr(r);
1537 			return;
1538 		}
1539 		sf->upload = u;
1540 		sf->upwriter = 1;
1541 		sf->type = Quploadfile;
1542 		dostat(sf, &r->ofcall.qid, nil);
1543 		respond(r, nil);
1544 		return;
1545 	}
1546 	respond(r, "permission denied");
1547 }
1548 
1549 static int
writetofd(Req * r,int fd)1550 writetofd(Req *r, int fd)
1551 {
1552 	int total, n;
1553 
1554 	total = 0;
1555 	while(total < r->ifcall.count){
1556 		n = pwrite(fd, (char*)r->ifcall.data+total, r->ifcall.count-total, r->ifcall.offset+total);
1557 		if(n <= 0)
1558 			return -1;
1559 		total += n;
1560 	}
1561 	r->ofcall.count = r->ifcall.count;
1562 	return 0;
1563 }
1564 
1565 static void
readfromfd(Req * r,int fd)1566 readfromfd(Req *r, int fd)
1567 {
1568 	int n;
1569 	n = pread(fd, r->ofcall.data, r->ifcall.count, r->ifcall.offset);
1570 	if(n < 0)
1571 		n = 0;
1572 	r->ofcall.count = n;
1573 }
1574 
1575 void
xread(Req * r)1576 xread(Req *r)
1577 {
1578 	SmugFid *sf;
1579 	char *data;
1580 	int fd;
1581 	HTTPHeader hdr;
1582 	char *url;
1583 
1584 	sf = r->fid->aux;
1585 	r->ofcall.count = 0;
1586 	switch(sf->type){
1587 	default:
1588 		respond(r, "not implemented");
1589 		return;
1590 	case Qroot:
1591 	case Qnick:
1592 	case Qalbums:
1593 	case Qcategory:
1594 	case Qalbum:
1595 	case Qimage:
1596 		dirread9p(r, dodirgen, sf);
1597 		break;
1598 	case Qrpclog:
1599 		rpclogread(r);
1600 		return;
1601 	case Qctl:
1602 	case Qnickctl:
1603 	case Qalbumsctl:
1604 	case Qcategoryctl:
1605 	case Qalbumctl:
1606 	case Qimagectl:
1607 	case Qimageurl:
1608 	case Qimageexif:
1609 	case Quploads:
1610 	case Qimagesettings:
1611 	case Qalbumsettings:
1612 		data = readctl(sf);
1613 		readstr(r, data);
1614 		free(data);
1615 		break;
1616 	case Qimagefile:
1617 		url = imageurl(sf);
1618 		if(url == nil || (fd = download(url, &hdr)) < 0){
1619 			free(url);
1620 			responderrstr(r);
1621 			return;
1622 		}
1623 		readfromfd(r, fd);
1624 		free(url);
1625 		close(fd);
1626 		break;
1627 	case Quploadfile:
1628 		if(sf->upload)
1629 			readfromfd(r, sf->upload->fd);
1630 		break;
1631 	}
1632 	respond(r, nil);
1633 }
1634 
1635 void
xwrite(Req * r)1636 xwrite(Req *r)
1637 {
1638 	int sync;
1639 	char *s, *t, *p;
1640 	Json *v;
1641 	char strid[50];
1642 	SmugFid *sf;
1643 
1644 	sf = r->fid->aux;
1645 	r->ofcall.count = r->ifcall.count;
1646 	sync = (r->ifcall.count==4 && memcmp(r->ifcall.data, "sync", 4) == 0);
1647 	switch(sf->type){
1648 	case Qctl:
1649 		if(sync){
1650 			jcacheflush(nil);
1651 			respond(r, nil);
1652 			return;
1653 		}
1654 		break;
1655 	case Qnickctl:
1656 		if(sync){
1657 			s = smprint("&NickName=%s&", nickname(sf->nickid));
1658 			jcacheflush(s);
1659 			free(s);
1660 			respond(r, nil);
1661 			return;
1662 		}
1663 		break;
1664 	case Qalbumsctl:
1665 	case Qcategoryctl:
1666 		jcacheflush("smugmug.categories.get");
1667 		break;
1668 	case Qalbumctl:
1669 		if(sync){
1670 			s = smprint("&AlbumID=%lld&", sf->album);
1671 			jcacheflush(s);
1672 			free(s);
1673 			respond(r, nil);
1674 			return;
1675 		}
1676 		break;
1677 	case Qimagectl:
1678 		if(sync){
1679 			s = smprint("&ImageID=%lld&", sf->image);
1680 			jcacheflush(s);
1681 			free(s);
1682 			respond(r, nil);
1683 			return;
1684 		}
1685 		break;
1686 	case Quploadfile:
1687 		if(sf->upload){
1688 			if(writetofd(r, sf->upload->fd) < 0){
1689 				responderrstr(r);
1690 				return;
1691 			}
1692 			respond(r, nil);
1693 			return;
1694 		}
1695 		break;
1696 	case Qimagesettings:
1697 	case Qalbumsettings:
1698 		s = (char*)r->ifcall.data;	// lib9p nul-terminated it
1699 		t = strpbrk(s, " \r\t\n");
1700 		if(t == nil)
1701 			t = "";
1702 		else{
1703 			*t++ = 0;
1704 			while(*t == ' ' || *t == '\r' || *t == '\t' || *t == '\n')
1705 				t++;
1706 		}
1707 		p = strchr(t, '\n');
1708 		if(p && p[1] == 0)
1709 			*p = 0;
1710 		else if(p){
1711 			respond(r, "newline in argument");
1712 			return;
1713 		}
1714 		if(sf->type == Qalbumsettings)
1715 			goto Albumsettings;
1716 		snprint(strid, sizeof strid, "%lld", sf->image);
1717 		v = ncsmug("smugmug.images.changeSettings",
1718 			"ImageID", strid,
1719 			s, t, nil);
1720 		if(v == nil)
1721 			responderrstr(r);
1722 		else
1723 			respond(r, nil);
1724 		s = smprint("&ImageID=%lld&", sf->image);
1725 		jcacheflush(s);
1726 		free(s);
1727 		return;
1728 	Albumsettings:
1729 		snprint(strid, sizeof strid, "%lld", sf->album);
1730 		v = ncsmug("smugmug.albums.changeSettings",
1731 			"AlbumID", strid, s, t, nil);
1732 		if(v == nil)
1733 			responderrstr(r);
1734 		else
1735 			respond(r, nil);
1736 		s = smprint("&AlbumID=%lld&", sf->album);
1737 		jcacheflush(s);
1738 		free(s);
1739 		return;
1740 	}
1741 	respond(r, "invalid control message");
1742 	return;
1743 }
1744 
1745 void
xremove(Req * r)1746 xremove(Req *r)
1747 {
1748 	char id[100];
1749 	SmugFid *sf;
1750 	Json *v;
1751 
1752 	sf = r->fid->aux;
1753 	switch(sf->type){
1754 	default:
1755 		respond(r, "permission denied");
1756 		return;
1757 	case Qcategoryctl:
1758 	case Qalbumctl:
1759 	case Qalbumsettings:
1760 	case Qimagectl:
1761 	case Qimagesettings:
1762 	case Qimageexif:
1763 	case Qimageurl:
1764 	case Qimagefile:
1765 		/* ignore remove request, but no error, so rm -r works */
1766 		/* you can pretend they get removed and immediately grow back! */
1767 		respond(r, nil);
1768 		return;
1769 	case Qcategory:
1770 		v = getalbums(sf);
1771 		if(v && v->len > 0){
1772 			respond(r, "directory not empty");
1773 			return;
1774 		}
1775 		snprint(id, sizeof id, "%lld", sf->category);
1776 		v = ncsmug("smugmug.categories.delete",
1777 			"CategoryID", id, nil);
1778 		if(v == nil)
1779 			responderrstr(r);
1780 		else{
1781 			jclose(v);
1782 			jcacheflush("smugmug.users.getTree");
1783 			jcacheflush("smugmug.categories.get");
1784 			respond(r, nil);
1785 		}
1786 		return;
1787 	case Qalbum:
1788 		v = getimages(sf, sf->album, sf->albumkey);
1789 		if(v && v->len > 0){
1790 			respond(r, "directory not empty");
1791 			return;
1792 		}
1793 		snprint(id, sizeof id, "%lld", sf->album);
1794 		v = ncsmug("smugmug.albums.delete",
1795 			"AlbumID", id, nil);
1796 		if(v == nil)
1797 			responderrstr(r);
1798 		else{
1799 			jclose(v);
1800 			jcacheflush("smugmug.users.getTree");
1801 			jcacheflush("smugmug.categories.get");
1802 			jcacheflush("smugmug.albums.get");
1803 			respond(r, nil);
1804 		}
1805 		return;
1806 
1807 	case Qimage:
1808 		snprint(id, sizeof id, "%lld", sf->image);
1809 		v = ncsmug("smugmug.images.delete",
1810 			"ImageID", id, nil);
1811 		if(v == nil)
1812 			responderrstr(r);
1813 		else{
1814 			jclose(v);
1815 			snprint(id, sizeof id, "ImageID=%lld&", sf->image);
1816 			jcacheflush(id);
1817 			jcacheflush("smugmug.images.get&");
1818 			respond(r, nil);
1819 		}
1820 		return;
1821 	}
1822 }
1823 
1824 void
xflush(Req * r)1825 xflush(Req *r)
1826 {
1827 	rpclogflush(r->oldreq);
1828 	respond(r, nil);
1829 }
1830 
1831 Srv xsrv;
1832 
1833 void
xinit(void)1834 xinit(void)
1835 {
1836 	xsrv.attach = xattach;
1837 	xsrv.open = xopen;
1838 	xsrv.create = xcreate;
1839 	xsrv.read = xread;
1840 	xsrv.stat = xstat;
1841 	xsrv.walk1 = xwalk1;
1842 	xsrv.clone = xclone;
1843 	xsrv.destroyfid = xdestroyfid;
1844 	xsrv.remove = xremove;
1845 	xsrv.write = xwrite;
1846 	xsrv.flush = xflush;
1847 	xsrv.wstat = xwstat;
1848 }
1849