diff options
Diffstat (limited to 'src/cmd/smugfs/fs.c')
-rw-r--r-- | src/cmd/smugfs/fs.c | 1853 |
1 files changed, 1853 insertions, 0 deletions
diff --git a/src/cmd/smugfs/fs.c b/src/cmd/smugfs/fs.c new file mode 100644 index 00000000..52b82051 --- /dev/null +++ b/src/cmd/smugfs/fs.c @@ -0,0 +1,1853 @@ +#include "a.h" + +enum +{ + Qroot = 0, // /smug/ + Qctl, // /smug/ctl + Qrpclog, // /smug/rpclog + Quploads, // /smug/uploads + Qnick, // /smug/nick/ + Qnickctl, // /smug/nick/ctl + Qalbums, // /smug/nick/albums/ + Qalbumsctl, // /smug/nick/albums/ctl + Qcategory, // /smug/nick/Category/ + Qcategoryctl, // /smug/nick/Category/ctl + Qalbum, // /smug/nick/Category/Album/ + Qalbumctl, // /smug/nick/Category/Album/ctl + Qalbumsettings, // /smug/nick/Category/Album/settings + Quploadfile, // /smug/nick/Category/Album/upload/file.jpg + Qimage, // /smug/nick/Category/Album/Image/ + Qimagectl, // /smug/nick/Category/Album/Image/ctl + Qimageexif, // /smug/nick/Category/Album/Image/exif + Qimagesettings, // /smug/nick/Category/Album/Image/settings + Qimageurl, // /smug/nick/Category/Album/Image/url + Qimagefile, // /smug/nick/Category/Album/Image/file.jpg +}; + +void +mylock(Lock *lk) +{ + lock(lk); + fprint(2, "locked from %p\n", getcallerpc(&lk)); +} + +void +myunlock(Lock *lk) +{ + unlock(lk); + fprint(2, "unlocked from %p\n", getcallerpc(&lk)); +} + +//#define lock mylock +//#define unlock myunlock + +typedef struct Upload Upload; + +typedef struct SmugFid SmugFid; +struct SmugFid +{ + int type; + int nickid; + vlong category; // -1 for "albums" + vlong album; + char *albumkey; + vlong image; + char *imagekey; + Upload *upload; + int upwriter; +}; + +#define QTYPE(p) ((p)&0xFF) +#define QARG(p) ((p)>>8) +#define QPATH(p, q) ((p)|((q)<<8)) + +char **nick; +int nnick; + +struct Upload +{ + Lock lk; + int fd; + char *name; + char *file; + vlong album; + vlong length; + char *albumkey; + int size; + int ready; + int nwriters; + int uploaded; + int ref; + int uploading; +}; + +Upload **up; +int nup; +QLock uploadlock; +Rendez uploadrendez; + +void uploader(void*); + +Upload* +newupload(SmugFid *sf, char *name) +{ + Upload *u; + int fd, i; + char tmp[] = "/var/tmp/smugfs.XXXXXX"; + + if((fd = opentemp(tmp, ORDWR)) < 0) + return nil; + qlock(&uploadlock); + for(i=0; i<nup; i++){ + u = up[i]; + lock(&u->lk); + if(u->ref == 0){ + u->ref = 1; + goto Reuse; + } + unlock(&u->lk); + } + if(nup == 0){ + uploadrendez.l = &uploadlock; + proccreate(uploader, nil, STACKSIZE); + } + u = emalloc(sizeof *u); + lock(&u->lk); + u->ref = 1; + up = erealloc(up, (nup+1)*sizeof up[0]); + up[nup++] = u; +Reuse: + qunlock(&uploadlock); + u->fd = fd; + u->name = estrdup(name); + u->file = estrdup(tmp); + u->album = sf->album; + u->albumkey = estrdup(sf->albumkey); + u->nwriters = 1; + unlock(&u->lk); + return u; +} + +void +closeupload(Upload *u) +{ + lock(&u->lk); +fprint(2, "close %p from %p: %d\n", u, getcallerpc(&u), u->ref); + if(--u->ref > 0){ + unlock(&u->lk); + return; + } + if(u->ref < 0) + abort(); + if(u->fd >= 0){ + close(u->fd); + u->fd = -1; + } + if(u->name){ + free(u->name); + u->name = nil; + } + if(u->file){ + remove(u->file); + free(u->file); + u->file = nil; + } + u->album = 0; + if(u->albumkey){ + free(u->albumkey); + u->albumkey = nil; + } + u->size = 0; + u->ready = 0; + u->nwriters = 0; + u->uploaded = 0; + u->uploading = 0; + u->length = 0; + unlock(&u->lk); +} + +Upload* +getuploadindex(SmugFid *sf, int *index) +{ + int i; + Upload *u; + + qlock(&uploadlock); + for(i=0; i<nup; i++){ + u = up[i]; + lock(&u->lk); + if(u->ref > 0 && !u->uploaded && u->album == sf->album && (*index)-- == 0){ + qunlock(&uploadlock); + u->ref++; +fprint(2, "bump %p from %p: %d\n", u, getcallerpc(&sf), u->ref); + unlock(&u->lk); + return u; + } + unlock(&u->lk); + } + qunlock(&uploadlock); + return nil; +} + +Upload* +getuploadname(SmugFid *sf, char *name) +{ + int i; + Upload *u; + + qlock(&uploadlock); + for(i=0; i<nup; i++){ + u = up[i]; + lock(&u->lk); + if(u->ref > 0 && !u->uploaded && u->album == sf->album && strcmp(name, u->name) == 0){ + qunlock(&uploadlock); + u->ref++; +fprint(2, "bump %p from %p: %d\n", u, getcallerpc(&sf), u->ref); + unlock(&u->lk); + return u; + } + unlock(&u->lk); + } + qunlock(&uploadlock); + return nil; +} + +void doupload(Upload*); + +void +uploader(void *v) +{ + int i, did; + Upload *u; + + qlock(&uploadlock); + for(;;){ + did = 0; + for(i=0; i<nup; i++){ + u = up[i]; + lock(&u->lk); + if(u->ref > 0 && u->ready && !u->uploading && !u->uploaded){ + u->uploading = 1; + unlock(&u->lk); + qunlock(&uploadlock); + doupload(u); + closeupload(u); +fprint(2, "done %d\n", u->ref); + did = 1; + qlock(&uploadlock); + }else + unlock(&u->lk); + } + if(!did) + rsleep(&uploadrendez); + } +} + +void +kickupload(Upload *u) +{ + Dir *d; + + lock(&u->lk); + if((d = dirfstat(u->fd)) != nil) + u->length = d->length; + close(u->fd); + u->fd = -1; + u->ref++; +fprint(2, "kick %p from %p: %d\n", u, getcallerpc(&u), u->ref); + u->ready = 1; + unlock(&u->lk); + qlock(&uploadlock); + rwakeup(&uploadrendez); + qunlock(&uploadlock); +} + +void +doupload(Upload *u) +{ + Dir *d; + vlong datalen; + Fmt fmt; + char *req; + char buf[8192]; + int n, total; + uchar digest[MD5dlen]; + DigestState ds; + Json *jv; + + if((u->fd = open(u->file, OREAD)) < 0){ + fprint(2, "cannot reopen temporary file %s: %r\n", u->file); + return; + } + if((d = dirfstat(u->fd)) == nil){ + fprint(2, "fstat: %r\n"); + return; + } + datalen = d->length; + free(d); + + memset(&ds, 0, sizeof ds); + seek(u->fd, 0, 0); + total = 0; + while((n = read(u->fd, buf, sizeof buf)) > 0){ + md5((uchar*)buf, n, nil, &ds); + total += n; + } + if(total != datalen){ + fprint(2, "bad total: %lld %lld\n", total, datalen); + return; + } + md5(nil, 0, digest, &ds); + + fmtstrinit(&fmt); + fmtprint(&fmt, "PUT /%s HTTP/1.0\r\n", u->name); + fmtprint(&fmt, "Content-Length: %lld\r\n", datalen); + fmtprint(&fmt, "Content-MD5: %.16lH\r\n", digest); + fmtprint(&fmt, "X-Smug-SessionID: %s\r\n", sessid); + fmtprint(&fmt, "X-Smug-Version: %s\r\n", API_VERSION); + fmtprint(&fmt, "X-Smug-ResponseType: JSON\r\n"); + // Can send X-Smug-ImageID instead to replace existing files. + fmtprint(&fmt, "X-Smug-AlbumID: %lld\r\n", u->album); + fmtprint(&fmt, "X-Smug-FileName: %s\r\n", u->name); + fmtprint(&fmt, "\r\n"); + req = fmtstrflush(&fmt); + + seek(u->fd, 0, 0); + jv = jsonupload(&http, UPLOAD_HOST, req, u->fd, datalen); + free(req); + if(jv == nil){ + fprint(2, "upload: %r\n"); + return; + } + + close(u->fd); + remove(u->file); + free(u->file); + u->file = nil; + u->fd = -1; + u->uploaded = 1; + rpclog("uploaded: %J", jv); + jclose(jv); +} + +int +nickindex(char *name) +{ + int i; + Json *v; + + for(i=0; i<nnick; i++) + if(strcmp(nick[i], name) == 0) + return i; + v = smug("smugmug.users.getTree", "NickName", name, nil); + if(v == nil) + return -1; + nick = erealloc(nick, (nnick+1)*sizeof nick[0]); + nick[nnick] = estrdup(name); + return nnick++; +} + +char* +nickname(int i) +{ + if(i < 0 || i >= nnick) + return nil; + return nick[i]; +} + +void +responderrstr(Req *r) +{ + char err[ERRMAX]; + + rerrstr(err, sizeof err); + respond(r, err); +} + +static char* +xclone(Fid *oldfid, Fid *newfid) +{ + SmugFid *sf; + + if(oldfid->aux == nil) + return nil; + + sf = emalloc(sizeof *sf); + *sf = *(SmugFid*)oldfid->aux; + sf->upload = nil; + sf->upwriter = 0; + if(sf->albumkey) + sf->albumkey = estrdup(sf->albumkey); + if(sf->imagekey) + sf->imagekey = estrdup(sf->imagekey); + newfid->aux = sf; + return nil; +} + +static void +xdestroyfid(Fid *fid) +{ + SmugFid *sf; + + sf = fid->aux; + free(sf->albumkey); + free(sf->imagekey); + if(sf->upload){ + if(sf->upwriter && --sf->upload->nwriters == 0){ + fprint(2, "should upload %s\n", sf->upload->name); + kickupload(sf->upload); + } + closeupload(sf->upload); + sf->upload = nil; + } + free(sf); +} + +static Json* +getcategories(SmugFid *sf) +{ + Json *v, *w; + + v = smug("smugmug.categories.get", "NickName", nickname(sf->nickid), nil); + w = jincref(jwalk(v, "Categories")); + jclose(v); + return w; +} + +static Json* +getcategorytree(SmugFid *sf) +{ + Json *v, *w; + + v = smug("smugmug.users.getTree", "NickName", nickname(sf->nickid), nil); + w = jincref(jwalk(v, "Categories")); + jclose(v); + return w; +} + +static Json* +getcategory(SmugFid *sf, vlong id) +{ + int i; + Json *v, *w; + + v = getcategorytree(sf); + if(v == nil) + return nil; + for(i=0; i<v->len; i++){ + if(jint(jwalk(v->value[i], "id")) == id){ + w = jincref(v->value[i]); + jclose(v); + return w; + } + } + jclose(v); + return nil; +} + +static vlong +getcategoryid(SmugFid *sf, char *name) +{ + int i; + vlong id; + Json *v; + + v = getcategories(sf); + if(v == nil) + return -1; + for(i=0; i<v->len; i++){ + if(jstrcmp(jwalk(v->value[i], "Name"), name) == 0){ + id = jint(jwalk(v->value[i], "id")); + if(id < 0){ + jclose(v); + return -1; + } + jclose(v); + return id; + } + } + jclose(v); + return -1; +} + +static vlong +getcategoryindex(SmugFid *sf, int i) +{ + Json *v; + vlong id; + + v = getcategories(sf); + if(v == nil) + return -1; + if(i < 0 || i >= v->len){ + jclose(v); + return -1; + } + id = jint(jwalk(v->value[i], "id")); + jclose(v); + return id; +} + +static Json* +getalbum(SmugFid *sf, vlong albumid, char *albumkey) +{ + char id[50]; + Json *v, *w; + + snprint(id, sizeof id, "%lld", albumid); + v = smug("smugmug.albums.getInfo", + "AlbumID", id, "AlbumKey", albumkey, + "NickName", nickname(sf->nickid), nil); + w = jincref(jwalk(v, "Album")); + jclose(v); + return w; +} + +static Json* +getalbums(SmugFid *sf) +{ + Json *v, *w; + + if(sf->category >= 0) + v = getcategory(sf, sf->category); + else + v = smug("smugmug.albums.get", + "NickName", nickname(sf->nickid), nil); + w = jincref(jwalk(v, "Albums")); + jclose(v); + return w; +} + +static vlong +getalbumid(SmugFid *sf, char *name, char **keyp) +{ + int i; + vlong id; + Json *v; + char *key; + + v = getalbums(sf); + if(v == nil) + return -1; + for(i=0; i<v->len; i++){ + if(jstrcmp(jwalk(v->value[i], "Title"), name) == 0){ + id = jint(jwalk(v->value[i], "id")); + key = jstring(jwalk(v->value[i], "Key")); + if(id < 0 || key == nil){ + jclose(v); + return -1; + } + if(keyp) + *keyp = estrdup(key); + jclose(v); + return id; + } + } + jclose(v); + return -1; +} + +static vlong +getalbumindex(SmugFid *sf, int i, char **keyp) +{ + vlong id; + Json *v; + char *key; + + v = getalbums(sf); + if(v == nil) + return -1; + if(i < 0 || i >= v->len){ + jclose(v); + return -1; + } + id = jint(jwalk(v->value[i], "id")); + key = jstring(jwalk(v->value[i], "Key")); + if(id < 0 || key == nil){ + jclose(v); + return -1; + } + if(keyp) + *keyp = estrdup(key); + jclose(v); + return id; +} + +static Json* +getimages(SmugFid *sf, vlong albumid, char *albumkey) +{ + char id[50]; + Json *v, *w; + + snprint(id, sizeof id, "%lld", albumid); + v = smug("smugmug.images.get", + "AlbumID", id, "AlbumKey", albumkey, + "NickName", nickname(sf->nickid), nil); + w = jincref(jwalk(v, "Images")); + jclose(v); + return w; +} + +static vlong +getimageid(SmugFid *sf, char *name, char **keyp) +{ + int i; + vlong id; + Json *v; + char *p; + char *key; + + id = strtol(name, &p, 10); + if(*p != 0 || *name == 0) + return -1; + + v = getimages(sf, sf->album, sf->albumkey); + if(v == nil) + return -1; + for(i=0; i<v->len; i++){ + if(jint(jwalk(v->value[i], "id")) == id){ + key = jstring(jwalk(v->value[i], "Key")); + if(key == nil){ + jclose(v); + return -1; + } + if(keyp) + *keyp = estrdup(key); + jclose(v); + return id; + } + } + jclose(v); + return -1; +} + +static Json* +getimageinfo(SmugFid *sf, vlong imageid, char *imagekey) +{ + char id[50]; + Json *v, *w; + + snprint(id, sizeof id, "%lld", imageid); + v = smug("smugmug.images.getInfo", + "ImageID", id, "ImageKey", imagekey, + "NickName", nickname(sf->nickid), nil); + w = jincref(jwalk(v, "Image")); + jclose(v); + return w; +} + +static Json* +getimageexif(SmugFid *sf, vlong imageid, char *imagekey) +{ + char id[50]; + Json *v, *w; + + snprint(id, sizeof id, "%lld", imageid); + v = smug("smugmug.images.getEXIF", + "ImageID", id, "ImageKey", imagekey, + "NickName", nickname(sf->nickid), nil); + w = jincref(jwalk(v, "Image")); + jclose(v); + return w; +} + +static vlong +getimageindex(SmugFid *sf, int i, char **keyp) +{ + vlong id; + Json *v; + char *key; + + v = getimages(sf, sf->album, sf->albumkey); + if(v == nil) + return -1; + if(i < 0 || i >= v->len){ + jclose(v); + return -1; + } + id = jint(jwalk(v->value[i], "id")); + key = jstring(jwalk(v->value[i], "Key")); + if(id < 0 || key == nil){ + jclose(v); + return -1; + } + if(keyp) + *keyp = estrdup(key); + jclose(v); + return id; +} + +static char* +categoryname(SmugFid *sf) +{ + Json *v; + char *s; + + v = getcategory(sf, sf->category); + s = jstring(jwalk(v, "Name")); + if(s) + s = estrdup(s); + jclose(v); + return s; +} + +static char* +albumname(SmugFid *sf) +{ + Json *v; + char *s; + + v = getalbum(sf, sf->album, sf->albumkey); + s = jstring(jwalk(v, "Title")); + if(s) + s = estrdup(s); + jclose(v); + return s; +} + +static char* +imagename(SmugFid *sf) +{ + char *s; + Json *v; + + v = getimageinfo(sf, sf->image, sf->imagekey); + s = jstring(jwalk(v, "FileName")); + if(s && s[0]) + s = estrdup(s); + else + s = smprint("%lld.jpg", sf->image); // TODO: use Format + jclose(v); + return s; +} + +static vlong +imagelength(SmugFid *sf) +{ + vlong length; + Json *v; + + v = getimageinfo(sf, sf->image, sf->imagekey); + length = jint(jwalk(v, "Size")); + jclose(v); + return length; +} + +static struct { + char *key; + char *name; +} urls[] = { + "AlbumURL", "album", + "TinyURL", "tiny", + "ThumbURL", "thumb", + "SmallURL", "small", + "MediumURL", "medium", + "LargeURL", "large", + "XLargeURL", "xlarge", + "X2LargeURL", "xxlarge", + "X3LargeURL", "xxxlarge", + "OriginalURL", "original", +}; + +static char* +imageurl(SmugFid *sf) +{ + Json *v; + char *s; + int i; + + v = getimageinfo(sf, sf->image, sf->imagekey); + for(i=nelem(urls)-1; i>=0; i--){ + if((s = jstring(jwalk(v, urls[i].key))) != nil){ + s = estrdup(s); + jclose(v); + return s; + } + } + jclose(v); + return nil; +} + +static char* imagestrings[] = +{ + "Caption", + "LastUpdated", + "FileName", + "MD5Sum", + "Watermark", + "Format", + "Keywords", + "Date", + "AlbumURL", + "TinyURL", + "ThumbURL", + "SmallURL", + "MediumURL", + "LargeURL", + "XLargeURL", + "X2LargeURL", + "X3LargeURL", + "OriginalURL", + "Album", +}; + +static char* albumbools[] = +{ + "Public", + "Printable", + "Filenames", + "Comments", + "External", + "Originals", + "EXIF", + "Share", + "SortDirection", + "FamilyEdit", + "FriendEdit", + "HideOwner", + "CanRank", + "Clean", + "Geography", + "SmugSearchable", + "WorldSearchable", + "SquareThumbs", + "X2Larges", + "X3Larges", +}; + +static char* albumstrings[] = +{ + "Description" + "Keywords", + "Password", + "PasswordHint", + "SortMethod", + "LastUpdated", +}; + +static char* +readctl(SmugFid *sf) +{ + int i; + Upload *u; + char *s; + Json *v, *vv; + Fmt fmt; + + v = nil; + switch(sf->type){ + case Qctl: + return smprint("%#J\n", userinfo); + + case Quploads: + fmtstrinit(&fmt); + qlock(&uploadlock); + for(i=0; i<nup; i++){ + u = up[i]; + lock(&u->lk); + if(u->ready && !u->uploaded && u->ref > 0) + fmtprint(&fmt, "%s %s%s\n", u->name, u->file, u->uploading ? " [uploading]" : ""); + unlock(&u->lk); + } + qunlock(&uploadlock); + return fmtstrflush(&fmt); + + case Qnickctl: + v = getcategories(sf); + break; + + case Qcategoryctl: + v = getcategory(sf, sf->category); + break; + + case Qalbumctl: + v = getimages(sf, sf->album, sf->albumkey); + break; + + case Qalbumsctl: + v = getalbums(sf); + break; + + case Qimagectl: + v = getimageinfo(sf, sf->image, sf->imagekey); + break; + + case Qimageurl: + v = getimageinfo(sf, sf->image, sf->imagekey); + fmtstrinit(&fmt); + for(i=0; i<nelem(urls); i++) + if((s = jstring(jwalk(v, urls[i].key))) != nil) + fmtprint(&fmt, "%s %s\n", urls[i].name, s); + jclose(v); + return fmtstrflush(&fmt); + + case Qimageexif: + v = getimageexif(sf, sf->image, sf->imagekey); + break; + + case Qalbumsettings: + v = getalbum(sf, sf->album, sf->albumkey); + fmtstrinit(&fmt); + fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id"))); + // TODO: Category/id + // TODO: SubCategory/id + // TODO: Community/id + // TODO: Template/id + fmtprint(&fmt, "Highlight\t%lld\n", jint(jwalk(v, "Highlight/id"))); + fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position"))); + fmtprint(&fmt, "ImageCount\t%lld\n", jint(jwalk(v, "ImageCount"))); + for(i=0; i<nelem(albumbools); i++){ + vv = jwalk(v, albumbools[i]); + if(vv) + fmtprint(&fmt, "%s\t%J\n", albumbools[i], vv); + } + for(i=0; i<nelem(albumstrings); i++){ + s = jstring(jwalk(v, albumstrings[i])); + if(s) + fmtprint(&fmt, "%s\t%s\n", albumstrings[i], s); + } + s = fmtstrflush(&fmt); + jclose(v); + return s; + + case Qimagesettings: + v = getimageinfo(sf, sf->image, sf->imagekey); + fmtstrinit(&fmt); + fmtprint(&fmt, "id\t%lld\n", jint(jwalk(v, "id"))); + fmtprint(&fmt, "Position\t%lld\n", jint(jwalk(v, "Position"))); + fmtprint(&fmt, "Serial\t%lld\n", jint(jwalk(v, "Serial"))); + fmtprint(&fmt, "Size\t%lld\t%lldx%lld\n", + jint(jwalk(v, "Size")), + jint(jwalk(v, "Width")), + jint(jwalk(v, "Height"))); + vv = jwalk(v, "Hidden"); + fmtprint(&fmt, "Hidden\t%J\n", vv); + // TODO: Album/id + for(i=0; i<nelem(imagestrings); i++){ + s = jstring(jwalk(v, imagestrings[i])); + if(s) + fmtprint(&fmt, "%s\t%s\n", imagestrings[i], s); + } + s = fmtstrflush(&fmt); + jclose(v); + return s; + } + + if(v == nil) + return estrdup(""); + s = smprint("%#J\n", v); + jclose(v); + return s; +} + + +static void +dostat(SmugFid *sf, Qid *qid, Dir *dir) +{ + Qid q; + char *name; + int freename; + ulong mode; + char *uid; + char *s; + vlong length; + + memset(&q, 0, sizeof q); + name = nil; + freename = 0; + uid = "smugfs"; + q.type = 0; + q.vers = 0; + q.path = QPATH(sf->type, sf->nickid); + length = 0; + mode = 0444; + + switch(sf->type){ + case Qroot: + name = "/"; + q.type = QTDIR; + break; + case Qctl: + name = "ctl"; + mode |= 0222; + break; + case Quploads: + name = "uploads"; + s = readctl(sf); + if(s){ + length = strlen(s); + free(s); + } + break; + case Qrpclog: + name = "rpclog"; + break; + case Qnick: + name = nickname(sf->nickid); + q.type = QTDIR; + break; + case Qnickctl: + name = "ctl"; + mode |= 0222; + break; + case Qalbums: + name = "albums"; + q.type = QTDIR; + break; + case Qalbumsctl: + name = "ctl"; + mode |= 0222; + break; + case Qcategory: + name = categoryname(sf); + freename = 1; + q.path |= QPATH(0, sf->category << 8); + q.type = QTDIR; + break; + case Qcategoryctl: + name = "ctl"; + mode |= 0222; + q.path |= QPATH(0, sf->category << 8); + break; + case Qalbum: + name = albumname(sf); + freename = 1; + q.path |= QPATH(0, sf->album << 8); + q.type = QTDIR; + break; + case Qalbumctl: + name = "ctl"; + mode |= 0222; + q.path |= QPATH(0, sf->album << 8); + break; + case Qalbumsettings: + name = "settings"; + mode |= 0222; + q.path |= QPATH(0, sf->album << 8); + break; + case Quploadfile: + q.path |= QPATH(0, (uintptr)sf->upload << 8); + if(sf->upload){ + Dir *dd; + name = sf->upload->name; + if(sf->upload->fd >= 0){ + dd = dirfstat(sf->upload->fd); + if(dd){ + length = dd->length; + free(dd); + } + }else + length = sf->upload->length; + if(!sf->upload->ready) + mode |= 0222; + } + break; + case Qimage: + name = smprint("%lld", sf->image); + freename = 1; + q.path |= QPATH(0, sf->image << 8); + q.type = QTDIR; + break; + case Qimagectl: + name = "ctl"; + mode |= 0222; + q.path |= QPATH(0, sf->image << 8); + break; + case Qimagesettings: + name = "settings"; + mode |= 0222; + q.path |= QPATH(0, sf->image << 8); + break; + case Qimageexif: + name = "exif"; + q.path |= QPATH(0, sf->image << 8); + break; + case Qimageurl: + name = "url"; + q.path |= QPATH(0, sf->image << 8); + break; + case Qimagefile: + name = imagename(sf); + freename = 1; + q.path |= QPATH(0, sf->image << 8); + length = imagelength(sf); + break; + default: + name = "?egreg"; + q.path = 0; + break; + } + + if(name == nil){ + name = "???"; + freename = 0; + } + + if(qid) + *qid = q; + if(dir){ + memset(dir, 0, sizeof *dir); + dir->name = estrdup9p(name); + dir->muid = estrdup9p("muid"); + mode |= q.type<<24; + if(mode & DMDIR) + mode |= 0555; + dir->mode = mode; + dir->uid = estrdup9p(uid); + dir->gid = estrdup9p("smugfs"); + dir->qid = q; + dir->length = length; + } + if(freename) + free(name); +} + +static char* +xwalk1(Fid *fid, char *name, Qid *qid) +{ + int dotdot, i; + vlong id; + char *key; + SmugFid *sf; + char *x; + Upload *u; + + dotdot = strcmp(name, "..") == 0; + sf = fid->aux; + switch(sf->type){ + default: + NotFound: + return "file not found"; + + case Qroot: + if(dotdot) + break; + if(strcmp(name, "ctl") == 0){ + sf->type = Qctl; + break; + } + if(strcmp(name, "uploads") == 0){ + sf->type = Quploads; + break; + } + if(strcmp(name, "rpclog") == 0){ + sf->type = Qrpclog; + break; + } + if((i = nickindex(name)) >= 0){ + sf->nickid = i; + sf->type = Qnick; + break; + } + goto NotFound; + + case Qnick: + if(dotdot){ + sf->type = Qroot; + sf->nickid = 0; + break; + } + if(strcmp(name, "ctl") == 0){ + sf->type = Qnickctl; + break; + } + if(strcmp(name, "albums") == 0){ + sf->category = -1; + sf->type = Qalbums; + break; + } + if((id = getcategoryid(sf, name)) >= 0){ + sf->category = id; + sf->type = Qcategory; + break; + } + goto NotFound; + + case Qalbums: + case Qcategory: + if(dotdot){ + sf->category = 0; + sf->type = Qnick; + break; + } + if(strcmp(name, "ctl") == 0){ + sf->type++; + break; + } + if((id = getalbumid(sf, name, &key)) >= 0){ + sf->album = id; + sf->albumkey = key; + sf->type = Qalbum; + break; + } + goto NotFound; + + case Qalbum: + if(dotdot){ + free(sf->albumkey); + sf->albumkey = nil; + sf->album = 0; + if(sf->category == -1) + sf->type = Qalbums; + else + sf->type = Qcategory; + break; + } + if(strcmp(name, "ctl") == 0){ + sf->type = Qalbumctl; + break; + } + if(strcmp(name, "settings") == 0){ + sf->type = Qalbumsettings; + break; + } + if((id = getimageid(sf, name, &key)) >= 0){ + sf->image = id; + sf->imagekey = key; + sf->type = Qimage; + break; + } + if((u = getuploadname(sf, name)) != nil){ + sf->upload = u; + sf->type = Quploadfile; + break; + } + goto NotFound; + + case Qimage: + if(dotdot){ + free(sf->imagekey); + sf->imagekey = nil; + sf->image = 0; + sf->type = Qalbum; + break; + } + if(strcmp(name, "ctl") == 0){ + sf->type = Qimagectl; + break; + } + if(strcmp(name, "url") == 0){ + sf->type = Qimageurl; + break; + } + if(strcmp(name, "settings") == 0){ + sf->type = Qimagesettings; + break; + } + if(strcmp(name, "exif") == 0){ + sf->type = Qimageexif; + break; + } + x = imagename(sf); + if(x && strcmp(name, x) == 0){ + free(x); + sf->type = Qimagefile; + break; + } + free(x); + goto NotFound; + } + dostat(sf, qid, nil); + fid->qid = *qid; + return nil; +} + +static int +dodirgen(int i, Dir *d, void *v) +{ + SmugFid *sf, xsf; + char *key; + vlong id; + Upload *u; + + sf = v; + xsf = *sf; + if(i-- == 0){ + xsf.type++; // ctl in every directory + dostat(&xsf, nil, d); + return 0; + } + + switch(sf->type){ + default: + return -1; + + case Qroot: + if(i-- == 0){ + xsf.type = Qrpclog; + dostat(&xsf, nil, d); + return 0; + } + if(i < 0 || i >= nnick) + return -1; + xsf.type = Qnick; + xsf.nickid = i; + dostat(&xsf, nil, d); + return 0; + + case Qnick: + if(i-- == 0){ + xsf.type = Qalbums; + dostat(&xsf, nil, d); + return 0; + } + if((id = getcategoryindex(sf, i)) < 0) + return -1; + xsf.type = Qcategory; + xsf.category = id; + dostat(&xsf, nil, d); + return 0; + + case Qalbums: + case Qcategory: + if((id = getalbumindex(sf, i, &key)) < 0) + return -1; + xsf.type = Qalbum; + xsf.album = id; + xsf.albumkey = key; + dostat(&xsf, nil, d); + free(key); + return 0; + + case Qalbum: + if(i-- == 0){ + xsf.type = Qalbumsettings; + dostat(&xsf, nil, d); + return 0; + } + if((u = getuploadindex(sf, &i)) != nil){ + xsf.upload = u; + xsf.type = Quploadfile; + dostat(&xsf, nil, d); + closeupload(u); + return 0; + } + if((id = getimageindex(sf, i, &key)) < 0) + return -1; + xsf.type = Qimage; + xsf.image = id; + xsf.imagekey = key; + dostat(&xsf, nil, d); + free(key); + return 0; + + case Qimage: + if(i-- == 0){ + xsf.type = Qimagefile; + dostat(&xsf, nil, d); + return 0; + } + if(i-- == 0){ + xsf.type = Qimageexif; + dostat(&xsf, nil, d); + return 0; + } + if(i-- == 0){ + xsf.type = Qimagesettings; + dostat(&xsf, nil, d); + return 0; + } + if(i-- == 0){ + xsf.type = Qimageurl; + dostat(&xsf, nil, d); + return 0; + } + return -1; + } +} + +static void +xstat(Req *r) +{ + dostat(r->fid->aux, nil, &r->d); + respond(r, nil); +} + +static void +xwstat(Req *r) +{ + SmugFid *sf; + Json *v; + char *s; + char strid[50]; + + sf = r->fid->aux; + if(r->d.uid[0] || r->d.gid[0] || r->d.muid[0] || ~r->d.mode != 0 + || ~r->d.atime != 0 || ~r->d.mtime != 0 || ~r->d.length != 0){ + respond(r, "invalid wstat"); + return; + } + if(r->d.name[0]){ + switch(sf->type){ + default: + respond(r, "invalid wstat"); + return; + // TODO: rename category + case Qalbum: + snprint(strid, sizeof strid, "%lld", sf->album); + v = ncsmug("smugmug.albums.changeSettings", + "AlbumID", strid, "Title", r->d.name, nil); + if(v == nil) + responderrstr(r); + else + respond(r, nil); + s = smprint("&AlbumID=%lld&", sf->album); + jcacheflush(s); + free(s); + jcacheflush("smugmug.albums.get&"); + return; + } + } + respond(r, "invalid wstat"); +} + +static void +xattach(Req *r) +{ + SmugFid *sf; + + sf = emalloc(sizeof *sf); + r->fid->aux = sf; + sf->type = Qroot; + dostat(sf, &r->ofcall.qid, nil); + r->fid->qid = r->ofcall.qid; + respond(r, nil); +} + +void +xopen(Req *r) +{ + SmugFid *sf; + + if((r->ifcall.mode&~OTRUNC) > 2){ + respond(r, "permission denied"); + return; + } + + sf = r->fid->aux; + switch(sf->type){ + case Qctl: + case Qnickctl: + case Qalbumsctl: + case Qcategoryctl: + case Qalbumctl: + case Qimagectl: + case Qalbumsettings: + case Qimagesettings: + break; + + case Quploadfile: + if(r->ifcall.mode != OREAD){ + lock(&sf->upload->lk); + if(sf->upload->ready){ + unlock(&sf->upload->lk); + respond(r, "permission denied"); + return; + } + sf->upwriter = 1; + sf->upload->nwriters++; + unlock(&sf->upload->lk); + } + break; + + default: + if(r->ifcall.mode != OREAD){ + respond(r, "permission denied"); + return; + } + break; + } + + r->ofcall.qid = r->fid->qid; + respond(r, nil); +} + +void +xcreate(Req *r) +{ + SmugFid *sf; + Json *v; + vlong id; + char strid[50], *key; + Upload *u; + + sf = r->fid->aux; + switch(sf->type){ + case Qnick: + // Create new category. + if(!(r->ifcall.perm&DMDIR)) + break; + v = ncsmug("smugmug.categories.create", + "Name", r->ifcall.name, nil); + if(v == nil){ + responderrstr(r); + return; + } + id = jint(jwalk(v, "Category/id")); + if(id < 0){ + fprint(2, "Create category: %J\n", v); + jclose(v); + responderrstr(r); + return; + } + sf->type = Qcategory; + sf->category = id; + jcacheflush("method=smugmug.users.getTree&"); + jcacheflush("method=smugmug.categories.get&"); + dostat(sf, &r->ofcall.qid, nil); + respond(r, nil); + return; + + case Qcategory: + // Create new album. + if(!(r->ifcall.perm&DMDIR)) + break; + snprint(strid, sizeof strid, "%lld", sf->category); + // Start with most restrictive settings. + v = ncsmug("smugmug.albums.create", + "Title", r->ifcall.name, + "CategoryID", strid, + "Public", "0", + "WorldSearchable", "0", + "SmugSearchable", "0", + nil); + if(v == nil){ + responderrstr(r); + return; + } + id = jint(jwalk(v, "Album/id")); + key = jstring(jwalk(v, "Album/Key")); + if(id < 0 || key == nil){ + fprint(2, "Create album: %J\n", v); + jclose(v); + responderrstr(r); + return; + } + sf->type = Qalbum; + sf->album = id; + sf->albumkey = estrdup(key); + jclose(v); + jcacheflush("method=smugmug.users.getTree&"); + dostat(sf, &r->ofcall.qid, nil); + respond(r, nil); + return; + + case Qalbum: + // Upload image to album. + if(r->ifcall.perm&DMDIR) + break; + u = newupload(sf, r->ifcall.name); + if(u == nil){ + responderrstr(r); + return; + } + sf->upload = u; + sf->upwriter = 1; + sf->type = Quploadfile; + dostat(sf, &r->ofcall.qid, nil); + respond(r, nil); + return; + } + respond(r, "permission denied"); +} + +static int +writetofd(Req *r, int fd) +{ + int total, n; + + total = 0; + while(total < r->ifcall.count){ + n = pwrite(fd, (char*)r->ifcall.data+total, r->ifcall.count-total, r->ifcall.offset+total); + if(n <= 0) + return -1; + total += n; + } + r->ofcall.count = r->ifcall.count; + return 0; +} + +static void +readfromfd(Req *r, int fd) +{ + int n; + n = pread(fd, r->ofcall.data, r->ifcall.count, r->ifcall.offset); + if(n < 0) + n = 0; + r->ofcall.count = n; +} + +void +xread(Req *r) +{ + SmugFid *sf; + char *data; + int fd; + HTTPHeader hdr; + char *url; + + sf = r->fid->aux; + r->ofcall.count = 0; + switch(sf->type){ + default: + respond(r, "not implemented"); + return; + case Qroot: + case Qnick: + case Qalbums: + case Qcategory: + case Qalbum: + case Qimage: + dirread9p(r, dodirgen, sf); + break; + case Qrpclog: + rpclogread(r); + return; + case Qctl: + case Qnickctl: + case Qalbumsctl: + case Qcategoryctl: + case Qalbumctl: + case Qimagectl: + case Qimageurl: + case Qimageexif: + case Quploads: + case Qimagesettings: + case Qalbumsettings: + data = readctl(sf); + readstr(r, data); + free(data); + break; + case Qimagefile: + url = imageurl(sf); + if(url == nil || (fd = download(url, &hdr)) < 0){ + free(url); + responderrstr(r); + return; + } + readfromfd(r, fd); + free(url); + close(fd); + break; + case Quploadfile: + if(sf->upload) + readfromfd(r, sf->upload->fd); + break; + } + respond(r, nil); +} + +void +xwrite(Req *r) +{ + int sync; + char *s, *t, *p; + Json *v; + char strid[50]; + SmugFid *sf; + + sf = r->fid->aux; + r->ofcall.count = r->ifcall.count; + sync = (r->ifcall.count==4 && memcmp(r->ifcall.data, "sync", 4) == 0); + switch(sf->type){ + case Qctl: + if(sync){ + jcacheflush(nil); + respond(r, nil); + return; + } + break; + case Qnickctl: + if(sync){ + s = smprint("&NickName=%s&", nickname(sf->nickid)); + jcacheflush(s); + free(s); + respond(r, nil); + return; + } + break; + case Qalbumsctl: + case Qcategoryctl: + jcacheflush("smugmug.categories.get"); + break; + case Qalbumctl: + if(sync){ + s = smprint("&AlbumID=%lld&", sf->album); + jcacheflush(s); + free(s); + respond(r, nil); + return; + } + break; + case Qimagectl: + if(sync){ + s = smprint("&ImageID=%lld&", sf->image); + jcacheflush(s); + free(s); + respond(r, nil); + return; + } + break; + case Quploadfile: + if(sf->upload){ + if(writetofd(r, sf->upload->fd) < 0){ + responderrstr(r); + return; + } + respond(r, nil); + return; + } + break; + case Qimagesettings: + case Qalbumsettings: + s = (char*)r->ifcall.data; // lib9p nul-terminated it + t = strpbrk(s, " \r\t\n"); + if(t == nil) + t = ""; + else{ + *t++ = 0; + while(*t == ' ' || *t == '\r' || *t == '\t' || *t == '\n') + t++; + } + p = strchr(t, '\n'); + if(p && p[1] == 0) + *p = 0; + else if(p){ + respond(r, "newline in argument"); + return; + } + if(sf->type == Qalbumsettings) + goto Albumsettings; + snprint(strid, sizeof strid, "%lld", sf->image); + v = ncsmug("smugmug.images.changeSettings", + "ImageID", strid, + s, t, nil); + if(v == nil) + responderrstr(r); + else + respond(r, nil); + s = smprint("&ImageID=%lld&", sf->image); + jcacheflush(s); + free(s); + return; + Albumsettings: + snprint(strid, sizeof strid, "%lld", sf->album); + v = ncsmug("smugmug.albums.changeSettings", + "AlbumID", strid, s, t, nil); + if(v == nil) + responderrstr(r); + else + respond(r, nil); + s = smprint("&AlbumID=%lld&", sf->album); + jcacheflush(s); + free(s); + return; + } + respond(r, "invalid control message"); + return; +} + +void +xremove(Req *r) +{ + char id[100]; + SmugFid *sf; + Json *v; + + sf = r->fid->aux; + switch(sf->type){ + default: + respond(r, "permission denied"); + return; + case Qcategoryctl: + case Qalbumctl: + case Qalbumsettings: + case Qimagectl: + case Qimagesettings: + case Qimageexif: + case Qimageurl: + case Qimagefile: + /* ignore remove request, but no error, so rm -r works */ + /* you can pretend they get removed and immediately grow back! */ + respond(r, nil); + return; + case Qcategory: + v = getalbums(sf); + if(v && v->len > 0){ + respond(r, "directory not empty"); + return; + } + snprint(id, sizeof id, "%lld", sf->category); + v = ncsmug("smugmug.categories.delete", + "CategoryID", id, nil); + if(v == nil) + responderrstr(r); + else{ + jclose(v); + jcacheflush("smugmug.users.getTree"); + jcacheflush("smugmug.categories.get"); + respond(r, nil); + } + return; + case Qalbum: + v = getimages(sf, sf->album, sf->albumkey); + if(v && v->len > 0){ + respond(r, "directory not empty"); + return; + } + snprint(id, sizeof id, "%lld", sf->album); + v = ncsmug("smugmug.albums.delete", + "AlbumID", id, nil); + if(v == nil) + responderrstr(r); + else{ + jclose(v); + jcacheflush("smugmug.users.getTree"); + jcacheflush("smugmug.categories.get"); + jcacheflush("smugmug.albums.get"); + respond(r, nil); + } + return; + + case Qimage: + snprint(id, sizeof id, "%lld", sf->image); + v = ncsmug("smugmug.images.delete", + "ImageID", id, nil); + if(v == nil) + responderrstr(r); + else{ + jclose(v); + snprint(id, sizeof id, "ImageID=%lld&", sf->image); + jcacheflush(id); + jcacheflush("smugmug.images.get&"); + respond(r, nil); + } + return; + } +} + +void +xflush(Req *r) +{ + rpclogflush(r->oldreq); + respond(r, nil); +} + +Srv xsrv; + +void +xinit(void) +{ + xsrv.attach = xattach; + xsrv.open = xopen; + xsrv.create = xcreate; + xsrv.read = xread; + xsrv.stat = xstat; + xsrv.walk1 = xwalk1; + xsrv.clone = xclone; + xsrv.destroyfid = xdestroyfid; + xsrv.remove = xremove; + xsrv.write = xwrite; + xsrv.flush = xflush; + xsrv.wstat = xwstat; +} |