smugfs(4): new program

This commit is contained in:
Russ Cox 2008-08-03 07:42:27 -07:00
parent 3d36f44373
commit 18824b5868
17 changed files with 4299 additions and 0 deletions

278
man/man4/smugfs.4 Normal file
View file

@ -0,0 +1,278 @@
.TH SMUGFS 4
.SH NAME
smugfs \- file system access to SmugMug photo sharing
.SH SYNOPSIS
.B smugfs
[
.B -DFH
]
[
.B -k
.I keypattern
]
[
.B -m
.I mtpt
]
[
.B -s
.I srvname
]
.SH DESCRIPTION
.I Smugfs
is a user-level file system that provides access to images
stored on the SmugMug photo sharing service.
It logs in after
obtaining a password from
.IR factotum (4)
using
.B server=smugmug.com
and
.I keypattern
(if any)
as key criteria
(see
.IR auth (3)).
Then
.I smugfs
serves a virtual directory tree mounted at
.I mtpt
(default
.BR /n/smug )
and posted at
.I srvname ,
if the
.B -s
option is given.
.PP
The directory tree is arranged in five levels:
root, user, category, album, and image.
For example,
.B /n/smug/cmac/
is a user directory,
.B /n/smug/cmac/People/
is a category directory,
.B /n/smug/cmac/People/Friends/
is an album directory,
and
.B /n/smug/cmac/albums/Friends/2631/
is an image directory.
.PP
SmugMug allows fine-grained classification
via subcategories, but subcategories are not yet implemented.
.ig
Subcategories are inserted as
an additional directory level between category
and album.
[Subcategories are not yet implemented.]
..
.PP
All directories contain a special control file named
.BR ctl ;
text commands written to
.B ctl
change
.IR smugfs 's
behavior or implement functionality
that does not fit nicely into the file system
interface.
.PP
.I Smugfs
caches information about users, categories, albums,
and images. If changes are made outside of
.I smugfs
(for example, using a web browser),
the cache may need to be discarded.
Writing the string
.B sync
to a directory's
.B ctl
file causes
.I smugfs
to discard all cached information used to
present that directory and its children.
Thus, writing
.B sync
to the root
.B ctl
file discards all of
.I smugfs 's
cached information.
.SS "Root directory"
The root directory contains directories
named after users.
By default, it contains only a directory for
the logged-in user, but other directories will
be created as needed to satisfy directory lookups.
.PP
In addition to user directories, the root directory
contains three special files:
.BR ctl ,
.BR rpclog ,
and
.BR uploads .
Reading
.B rpclog
returns a list of recent RPCs issued to the SmugMug API server.
Reads at the end of the file block until a new RPC is issued.
The
.B uploads
file lists the file upload queue (q.v.).
.SS "User directories"
User directories contain category directories
named after the categories.
SmugMug pre-defines a variety of categories,
so it is common to have many categories that
do not contain albums.
.PP
In a user directory, creating a new directory
creates a new category on SmugMug.
Similarly, renaming or removing a category
directory renames or removes the category on SmugMug.
Categories cannot be removed if they contain albums.
.PP
User directories also contain a directory
named
.B albums
that itself contains all of that user's albums.
.SS "Category directories"
Each category directory contains album directories
named using the album's title.
.PP
In a category directory, creating a new directory
creates a new album on SmugMug.
Similarly, renaming or removing an album directory
renames or removes the album on SmugMug.
Albums cannot be removed if they contain images.
.ig
.PP
Category directories might also contain subcategory directories.
Like albums, subcategories can be renamed and removed (when empty).
Unlike albums, subcategories cannot be created via ordinary
file system operations.
Instead, write the command
.B subcategory
.I name
to the category's
.B ctl
file.
.PP
Subcategories are identical to categories
except that they cannot themselves contain subcategories.
..
.SS "Album directories"
Each album directory contains image directories
named using the image's decimal SmugMug ID.
Image directories cannot be created or renamed,
but they can be removed. Removing an image directory
removes the image from the album on SmugMug.
.PP
Album directories also contain three special files,
.BR ctl ,
.BR settings ,
and
.BR url .
.PP
The
.B settings
file contains the album settings in textual form,
one setting per line.
Each line represents a single setting and is formatted
as an alphabetic setting name followed by a single tab
followed by the value.
Many settings can be changed by writing new setting lines,
in the same format, to the
.B settings
file.
.PP
Copying a file into the album directory queues it for
uploading to SmugMug to be added to the album.
Files disappear from the album directory once they
have finished uploading, replaced by new image directories.
The
.B uploads
file in the root directory lists all pending uploads,
which are stored temporarily
in
.BR /var/tmp .
.SS "Image directories"
Each image directory contains an image file, named
with its original name, if available.
If the image belongs to another user, SmugMug does not
expose the original name, so the file is named
.RB \fInnnn\fP .jpg ,
where
.I nnnn
is the SmugMug image ID number.
The file content is the original image
or else the largest image available.
.PP
The directory contains a
.B settings
file holding per-image settings, similar to the
file in the album directory;
and a
.B url
file, containing URLs to the various sized images
on the SmugMug server.
.SH EXAMPLES
.LP
Mount
.I smugfs
on
.BR /n/smug ;
the current user must have write access to
.B /n/smug
and
.BR /dev/fuse .
.IP
.EX
% smugfs
.EE
Watch API calls as they execute:
.IP
.EX
% cat /n/smug/rpclog &
.EE
Create a new album in the Vacation category
and fill it with photos:
.IP
.EX
% mkdir /n/smug/you/Vacation/Summer
% cp *.jpg /n/smug/you/Vacation/Summer
.EE
.LP
The photos are now uploading in the background.
Wait for the uploads to finish:
.IP
.EX
% while(test -s /n/smug/uploads) sleep 60
.EE
.LP
Make the album publicly viewable and share it.
.IP
.EX
% echo public 1 >/n/smug/you/Vacation/Summer/settings
% cat /n/smug/you/Vacation/Summer/url | mail friends
.EE
.SH SOURCE
.B \*9/src/cmd/smugfs
.SH SEE ALSO
SmugMug,
.HR http://smugmug.com/
.SH BUGS
.PP
If multiple categories or albums have the same name,
only one will be accessible via the file system interface.
Renaming the accessible one via
.IR mv (1)
will resolve the problem.
.PP
Boolean values appear as
.B true
and
.B false
in settings files but must be changed using
.B 1
and
.BR 0 .

17
src/cmd/smugfs/COPYRIGHT Normal file
View file

@ -0,0 +1,17 @@
The files in this directory are subject to the following license.
The author of this software is Russ Cox.
Copyright (c) 2008 Russ Cox
Permission to use, copy, modify, and distribute this software for any
purpose without fee is hereby granted, provided that this entire notice
is included in all copies of any software which is or includes a copy
or modification of this software and in all copies of the supporting
documentation for such software.
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
FITNESS FOR ANY PARTICULAR PURPOSE.

22
src/cmd/smugfs/NOTES Normal file
View file

@ -0,0 +1,22 @@
* Threading:
Uploads run in parallel with main fs operation.
Otherwise, main fs operation is single-threaded.
Could multi-thread the rest but would have to lock the
cache properly first.
Right now, only one upload at a time.
Could have more by kicking off multiple
uploader procs.
* Implement subcategories.
* Implement renames of categories.
* Implement renames of albums.
* Implement album settings file.
* Implement image settings file.

190
src/cmd/smugfs/a.h Normal file
View file

@ -0,0 +1,190 @@
#include <u.h>
#include <errno.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <auth.h>
#include <9p.h>
#include <libsec.h>
#define APIKEY "G9ANE2zvCozKEoLQ5qaR1AUtcE5YpuDj"
#define HOST "api.smugmug.com"
#define UPLOAD_HOST "upload.smugmug.com"
#define API_VERSION "1.2.1"
#define PATH "/services/api/json/" API_VERSION "/"
#define USER_AGENT "smugfs (part of Plan 9 from User Space)"
void* emalloc(int);
void* erealloc(void*, int);
char* estrdup(char*);
int urlencodefmt(Fmt*);
int timefmt(Fmt*);
int writen(int, void*, int);
// Generic cache
typedef struct Cache Cache;
typedef struct CEntry CEntry;
struct CEntry
{
char *name;
struct {
CEntry *next;
CEntry *prev;
} list;
struct {
CEntry *next;
} hash;
};
Cache *newcache(int sizeofentry, int maxentry, void (*cefree)(CEntry*));
CEntry *cachelookup(Cache*, char*, int);
void cacheflush(Cache*, char*);
// JSON parser
typedef struct Json Json;
enum
{
Jstring,
Jnumber,
Jobject,
Jarray,
Jtrue,
Jfalse,
Jnull
};
struct Json
{
int ref;
int type;
char *string;
double number;
char **name;
Json **value;
int len;
};
void jclose(Json*);
Json* jincref(Json*);
vlong jint(Json*);
Json* jlookup(Json*, char*);
double jnumber(Json*);
int jsonfmt(Fmt*);
int jstrcmp(Json*, char*);
char* jstring(Json*);
Json* jwalk(Json*, char*);
Json* parsejson(char*);
// Wrapper to hide whether we're using OpenSSL for HTTPS.
typedef struct Protocol Protocol;
typedef struct Pfd Pfd;
struct Protocol
{
Pfd *(*connect)(char *host);
int (*read)(Pfd*, void*, int);
int (*write)(Pfd*, void*, int);
void (*close)(Pfd*);
};
Protocol http;
Protocol https;
// HTTP library
typedef struct HTTPHeader HTTPHeader;
struct HTTPHeader
{
int code;
char proto[100];
char codedesc[100];
vlong contentlength;
char contenttype[100];
};
char *httpreq(Protocol *proto, char *host, char *request, HTTPHeader *hdr, int rfd, vlong rlength);
int httptofile(Protocol *proto, char *host, char *req, HTTPHeader *hdr, int wfd);
// URL downloader - caches in files on disk
int download(char *url, HTTPHeader *hdr);
void downloadflush(char*);
// JSON RPC
enum
{
MaxResponse = 1<<29,
};
Json* jsonrpc(Protocol *proto, char *host, char *path, char *method, char *name1, va_list arg, int usecache);
Json* jsonupload(Protocol *proto, char *host, char *req, int rfd, vlong rlength);
void jcacheflush(char*);
extern int chattyhttp;
// SmugMug RPC
#ifdef __GNUC__
#define check_nil __attribute__((sentinel))
#else
#define check_nil
#endif
Json* smug(char *method, char *name1, ...) check_nil; // cached, http
Json* ncsmug(char *method, char *name1, ...) check_nil; // not cached, https
// Session information
extern Json *userinfo;
extern char *sessid;
// File system
extern Srv xsrv;
void xinit(void);
extern int nickindex(char*);
// Logging
typedef struct Logbuf Logbuf;
struct Logbuf
{
Req *wait;
Req **waitlast;
int rp;
int wp;
char *msg[128];
};
extern void lbkick(Logbuf*);
extern void lbappend(Logbuf*, char*, ...);
extern void lbvappend(Logbuf*, char*, va_list);
/* #pragma varargck argpos lbappend 2 */
extern void lbread(Logbuf*, Req*);
extern void lbflush(Logbuf*, Req*);
/* #pragma varargck argpos flog 1 */
extern void rpclog(char*, ...);
extern void rpclogflush(Req*);
extern void rpclogread(Req*);
extern void rpclogwrite(Req*);
enum
{
STACKSIZE = 32768
};
extern int printerrors;

149
src/cmd/smugfs/cache.c Normal file
View file

@ -0,0 +1,149 @@
#include "a.h"
struct Cache
{
CEntry **hash;
int nhash;
CEntry *head;
CEntry *tail;
int nentry;
int maxentry;
int sizeofentry;
void (*cefree)(CEntry*);
};
static void
nop(CEntry *ce)
{
}
static uint
hash(const char *s)
{
uint h;
uchar *p;
h = 0;
for(p=(uchar*)s; *p; p++)
h = h*37 + *p;
return h;
}
Cache*
newcache(int sizeofentry, int maxentry, void (*cefree)(CEntry*))
{
Cache *c;
int i;
assert(sizeofentry >= sizeof(CEntry));
c = emalloc(sizeof *c);
c->sizeofentry = sizeofentry;
c->maxentry = maxentry;
c->nentry = 0;
for(i=1; i<maxentry; i<<=1)
;
c->nhash = i;
c->hash = emalloc(c->nhash * sizeof c->hash[0]);
if(cefree == nil)
cefree = nop;
c->cefree = cefree;
return c;
}
static void
popout(Cache *c, CEntry *e)
{
if(e->list.prev)
e->list.prev->list.next = e->list.next;
else
c->head = e->list.next;
if(e->list.next)
e->list.next->list.prev = e->list.prev;
else
c->tail = e->list.prev;
}
static void
insertfront(Cache *c, CEntry *e)
{
e->list.next = c->head;
c->head = e;
if(e->list.next)
e->list.next->list.prev = e;
else
c->tail = e;
}
static void
movetofront(Cache *c, CEntry *e)
{
popout(c, e);
insertfront(c, e);
}
static CEntry*
evict(Cache *c)
{
CEntry *e;
e = c->tail;
popout(c, e);
c->cefree(e);
free(e->name);
e->name = nil;
memset(e, 0, c->sizeofentry);
insertfront(c, e);
return e;
}
CEntry*
cachelookup(Cache *c, char *name, int create)
{
int h;
CEntry *e;
h = hash(name) % c->nhash;
for(e=c->hash[h]; e; e=e->hash.next){
if(strcmp(name, e->name) == 0){
movetofront(c, e);
return e;
}
}
if(!create)
return nil;
if(c->nentry >= c->maxentry)
e = evict(c);
else{
e = emalloc(c->sizeofentry);
insertfront(c, e);
c->nentry++;
}
e->name = estrdup(name);
h = hash(name) % c->nhash;
e->hash.next = c->hash[h];
c->hash[h] = e;
return e;
}
void
cacheflush(Cache *c, char *substr)
{
CEntry **l, *e;
int i;
for(i=0; i<c->nhash; i++){
for(l=&c->hash[i]; (e=*l); ){
if(substr == nil || strstr(e->name, substr)){
*l = e->hash.next;
c->nentry--;
popout(c, e);
c->cefree(e);
free(e->name);
free(e);
}else
l = &e->hash.next;
}
}
}

105
src/cmd/smugfs/download.c Normal file
View file

@ -0,0 +1,105 @@
#include "a.h"
typedef struct DEntry DEntry;
struct DEntry
{
CEntry ce;
HTTPHeader hdr;
char *tmpfile;
int fd;
};
static void
dfree(CEntry *ce)
{
DEntry *d;
d = (DEntry*)ce;
if(d->tmpfile){
remove(d->tmpfile);
free(d->tmpfile);
close(d->fd);
}
}
static Cache *downloadcache;
static char*
parseurl(char *url, char **path)
{
char *host, *p;
int len;
if(memcmp(url, "http://", 7) != 0)
return nil;
p = strchr(url+7, '/');
if(p == nil)
p = url+strlen(url);
len = p - (url+7);
host = emalloc(len+1);
memmove(host, url+7, len);
host[len] = 0;
if(*p == 0)
p = "/";
*path = p;
return host;
}
int
download(char *url, HTTPHeader *hdr)
{
DEntry *d;
char *host, *path;
char buf[] = "/var/tmp/smugfs.XXXXXX";
char *req;
int fd;
Fmt fmt;
if(downloadcache == nil)
downloadcache = newcache(sizeof(DEntry), 128, dfree);
host = parseurl(url, &path);
if(host == nil)
return -1;
d = (DEntry*)cachelookup(downloadcache, url, 1);
if(d->tmpfile){
free(host);
*hdr = d->hdr;
return dup(d->fd, -1);
}
d->fd = -1; // paranoia
if((fd = opentemp(buf, ORDWR|ORCLOSE)) < 0){
free(host);
return -1;
}
fmtstrinit(&fmt);
fmtprint(&fmt, "GET %s HTTP/1.0\r\n", path);
fmtprint(&fmt, "Host: %s\r\n", host);
fmtprint(&fmt, "User-Agent: " USER_AGENT "\r\n");
fmtprint(&fmt, "\r\n");
req = fmtstrflush(&fmt);
fprint(2, "Get %s\n", url);
if(httptofile(&http, host, req, hdr, fd) < 0){
free(host);
free(req);
return -1;
}
free(host);
free(req);
d->tmpfile = estrdup(buf);
d->fd = dup(fd, -1);
d->hdr = *hdr;
return fd;
}
void
downloadflush(char *substr)
{
if(downloadcache)
cacheflush(downloadcache, substr);
}

1853
src/cmd/smugfs/fs.c Normal file

File diff suppressed because it is too large Load diff

237
src/cmd/smugfs/http.c Normal file
View file

@ -0,0 +1,237 @@
#include "a.h"
static char*
haveheader(char *buf, int n)
{
int i;
for(i=0; i<n; i++){
if(buf[i] == '\n'){
if(i+2 < n && buf[i+1] == '\r' && buf[i+2] == '\n')
return buf+i+3;
if(i+1 < n && buf[i+1] == '\n')
return buf+i+2;
}
}
return 0;
}
static int
parseheader(char *buf, int n, HTTPHeader *hdr)
{
int nline;
char *data, *ebuf, *p, *q, *next;
memset(hdr, 0, sizeof *hdr);
ebuf = buf+n;
data = haveheader(buf, n);
if(data == nil)
return -1;
data[-1] = 0;
if(data[-2] == '\r')
data[-2] = 0;
if(chattyhttp > 1){
fprint(2, "--HTTP Response Header:\n");
fprint(2, "%s\n", buf);
fprint(2, "--\n");
}
nline = 0;
for(p=buf; *p; p=next, nline++){
q = strchr(p, '\n');
if(q){
next = q+1;
*q = 0;
if(q > p && q[-1] == '\r')
q[-1] = 0;
}else
next = p+strlen(p);
if(nline == 0){
if(memcmp(p, "HTTP/", 5) != 0){
werrstr("invalid HTTP version: %.10s", p);
return -1;
}
q = strchr(p, ' ');
if(q == nil){
werrstr("invalid HTTP version");
return -1;
}
*q++ = 0;
strncpy(hdr->proto, p, sizeof hdr->proto);
hdr->proto[sizeof hdr->proto-1] = 0;
while(*q == ' ')
q++;
if(*q < '0' || '9' < *q){
werrstr("invalid HTTP response code");
return -1;
}
p = q;
q = strchr(p, ' ');
if(q == nil)
q = p+strlen(p);
else
*q++ = 0;
hdr->code = strtol(p, &p, 10);
if(*p != 0)
return -1;
while(*q == ' ')
q++;
strncpy(hdr->codedesc, q, sizeof hdr->codedesc);
hdr->codedesc[sizeof hdr->codedesc-1] = 0;
continue;
}
q = strchr(p, ':');
if(q == nil)
continue;
*q++ = 0;
while(*q != 0 && (*q == ' ' || *q == '\t'))
q++;
if(cistrcmp(p, "Content-Type") == 0){
strncpy(hdr->contenttype, q, sizeof hdr->contenttype);
hdr->contenttype[sizeof hdr->contenttype-1] = 0;
continue;
}
if(cistrcmp(p, "Content-Length") == 0 && '0' <= *q && *q <= '9'){
hdr->contentlength = strtoll(q, 0, 10);
continue;
}
}
if(nline < 1){
werrstr("no header");
return -1;
}
memmove(buf, data, ebuf - data);
return ebuf - data;
}
static char*
genhttp(Protocol *proto, char *host, char *req, HTTPHeader *hdr, int wfd, int rfd, vlong rtotal)
{
int n, m, total, want;
char buf[8192], *data;
Pfd *fd;
if(chattyhttp > 1){
fprint(2, "--HTTP Request:\n");
fprint(2, "%s", req);
fprint(2, "--\n");
}
fd = proto->connect(host);
if(fd == nil){
if(chattyhttp > 0)
fprint(2, "connect %s: %r\n", host);
return nil;
}
n = strlen(req);
if(proto->write(fd, req, n) != n){
if(chattyhttp > 0)
fprint(2, "write %s: %r\n", host);
proto->close(fd);
return nil;
}
if(rfd >= 0){
while(rtotal > 0){
m = sizeof buf;
if(m > rtotal)
m = rtotal;
if((n = read(rfd, buf, m)) <= 0){
fprint(2, "read: missing data\n");
proto->close(fd);
return nil;
}
if(proto->write(fd, buf, n) != n){
fprint(2, "write data: %r\n");
proto->close(fd);
return nil;
}
rtotal -= n;
}
}
total = 0;
while(!haveheader(buf, total)){
n = proto->read(fd, buf+total, sizeof buf-total);
if(n <= 0){
if(chattyhttp > 0)
fprint(2, "read missing header\n");
proto->close(fd);
return nil;
}
total += n;
}
n = parseheader(buf, total, hdr);
if(n < 0){
fprint(2, "failed response parse: %r\n");
proto->close(fd);
return nil;
}
if(hdr->contentlength >= MaxResponse){
werrstr("response too long");
proto->close(fd);
return nil;
}
if(hdr->contentlength >= 0 && n > hdr->contentlength)
n = hdr->contentlength;
want = sizeof buf;
data = nil;
total = 0;
goto didread;
while(want > 0 && (n = proto->read(fd, buf, want)) > 0){
didread:
if(wfd >= 0){
if(writen(wfd, buf, n) < 0){
proto->close(fd);
werrstr("write error");
return nil;
}
}else{
data = erealloc(data, total+n);
memmove(data+total, buf, n);
}
total += n;
if(total > MaxResponse){
proto->close(fd);
werrstr("response too long");
return nil;
}
if(hdr->contentlength >= 0 && total + want > hdr->contentlength)
want = hdr->contentlength - total;
}
proto->close(fd);
if(hdr->contentlength >= 0 && total != hdr->contentlength){
werrstr("got wrong content size %d %d", total, hdr->contentlength);
return nil;
}
hdr->contentlength = total;
if(wfd >= 0)
return (void*)1;
else{
data = erealloc(data, total+1);
data[total] = 0;
}
return data;
}
char*
httpreq(Protocol *proto, char *host, char *req, HTTPHeader *hdr, int rfd, vlong rlength)
{
return genhttp(proto, host, req, hdr, -1, rfd, rlength);
}
int
httptofile(Protocol *proto, char *host, char *req, HTTPHeader *hdr, int fd)
{
if(fd < 0){
werrstr("bad fd");
return -1;
}
if(genhttp(proto, host, req, hdr, fd, -1, 0) == nil)
return -1;
return 0;
}

171
src/cmd/smugfs/icache.c Normal file
View file

@ -0,0 +1,171 @@
#include "a.h"
// This code is almost certainly wrong.
typedef struct Icache Icache;
struct Icache
{
char *url;
HTTPHeader hdr;
char *tmpfile;
int fd;
Icache *next;
Icache *prev;
Icache *hash;
};
enum {
NHASH = 128,
MAXCACHE = 128,
};
static struct {
Icache *hash[NHASH];
Icache *head;
Icache *tail;
int n;
} icache;
static Icache*
icachefind(char *url)
{
int h;
Icache *ic;
h = hash(url) % NHASH;
for(ic=icache.hash[h]; ic; ic=ic->hash){
if(strcmp(ic->url, url) == 0){
/* move to front */
if(ic->prev) {
ic->prev->next = ic->next;
if(ic->next)
ic->next->prev = ic->prev;
else
icache.tail = ic->prev;
ic->prev = nil;
ic->next = icache.head;
icache.head->prev = ic;
icache.head = ic;
}
return ic;
}
}
return nil;
}
static Icache*
icacheinsert(char *url, HTTPHeader *hdr, char *file, int fd)
{
int h;
Icache *ic, **l;
if(icache.n == MAXCACHE){
ic = icache.tail;
icache.tail = ic->prev;
if(ic->prev)
ic->prev->next = nil;
else
icache.head = ic->prev;
h = hash(ic->url) % NHASH;
for(l=&icache.hash[h]; *l; l=&(*l)->hash){
if(*l == ic){
*l = ic->hash;
goto removed;
}
}
sysfatal("cannot find ic in cache");
removed:
free(ic->url);
close(ic->fd);
remove(ic->file);
free(ic->file);
}else{
ic = emalloc(sizeof *ic);
icache.n++;
}
ic->url = estrdup(url);
ic->fd = dup(fd, -1);
ic->file = estrdup(file);
ic->hdr = *hdr;
h = hash(url) % NHASH;
ic->hash = icache.hash[h];
icache.hash[h] = ic;
ic->prev = nil;
ic->next = icache.head;
if(ic->next)
ic->next->prev = ic;
else
icache.tail = ic;
return ic;
}
void
icacheflush(char *substr)
{
Icache **l, *ic;
for(l=&icache.head; (ic=*l); ) {
if(substr == nil || strstr(ic->url, substr)) {
icache.n--;
*l = ic->next;
free(ic->url);
close(ic->fd);
remove(ic->file);
free(ic->file);
free(ic);
}else
l = &ic->next;
}
if(icache.head) {
icache.head->prev = nil;
for(ic=icache.head; ic; ic=ic->next){
if(ic->next)
ic->next->prev = ic;
else
icache.tail = ic;
}
}else
icache.tail = nil;
}
int
urlfetch(char *url, HTTPHeader hdr)
{
Icache *ic;
char buf[50], *host, *path, *p;
int fd, len;
ic = icachefind(url);
if(ic != nil){
*hdr = ic->hdr;
return dup(ic->fd, -1);
}
if(memcmp(url, "http://", 7) != 0){
werrstr("non-http url");
return -1;
}
p = strchr(url+7, '/');
if(p == nil)
p = url+strlen(url);
len = p - (url+7);
host = emalloc(len+1);
memmove(host, url+7, len);
host[len] = 0;
if(*p == 0)
p = "/";
strcpy(buf, "/var/tmp/smugfs.XXXXXX");
fd = opentemp(buf, ORDWR|ORCLOSE);
if(fd < 0)
return -1;
if(httptofile(http, host, req, &hdr, fd) < 0){
free(host);
return -1;
}
free(host);
icacheinsert(url, &hdr, buf, fd);
return fd;
}

555
src/cmd/smugfs/json.c Normal file
View file

@ -0,0 +1,555 @@
#include "a.h"
static Json *parsevalue(char**);
static char*
wskip(char *p)
{
while(*p == ' ' || *p == '\t' || *p == '\n' || *p == '\v')
p++;
return p;
}
static int
ishex(int c)
{
return '0' <= c && c <= '9' ||
'a' <= c && c <= 'f' ||
'A' <= c && c <= 'F';
}
static Json*
newjval(int type)
{
Json *v;
v = emalloc(sizeof *v);
v->ref = 1;
v->type = type;
return v;
}
static Json*
badjval(char **pp, char *fmt, ...)
{
char buf[ERRMAX];
va_list arg;
if(fmt){
va_start(arg, fmt);
vsnprint(buf, sizeof buf, fmt, arg);
va_end(arg);
errstr(buf, sizeof buf);
}
*pp = nil;
return nil;
}
static char*
_parsestring(char **pp, int *len)
{
char *p, *q, *w, *s, *r;
char buf[5];
Rune rune;
p = wskip(*pp);
if(*p != '"'){
badjval(pp, "missing opening quote for string");
return nil;
}
for(q=p+1; *q && *q != '\"'; q++){
if(*q == '\\' && *(q+1) != 0)
q++;
if((*q & 0xFF) < 0x20){ // no control chars
badjval(pp, "control char in string");
return nil;
}
}
if(*q == 0){
badjval(pp, "no closing quote in string");
return nil;
}
s = emalloc(q - p);
w = s;
for(r=p+1; r<q; ){
if(*r != '\\'){
*w++ = *r++;
continue;
}
r++;
switch(*r){
default:
free(s);
badjval(pp, "bad escape \\%c in string", *r&0xFF);
return nil;
case '\\':
case '\"':
case '/':
*w++ = *r++;
break;
case 'b':
*w++ = '\b';
r++;
break;
case 'f':
*w++ = '\f';
r++;
break;
case 'n':
*w++ = '\n';
r++;
break;
case 'r':
*w++ = '\r';
r++;
break;
case 't':
*w++ = '\t';
r++;
break;
case 'u':
r++;
if(!ishex(r[0]) || !ishex(r[1]) || !ishex(r[2]) || !ishex(r[3])){
free(s);
badjval(pp, "bad hex \\u%.4s", r);
return nil;
}
memmove(buf, r, 4);
buf[4] = 0;
rune = strtol(buf, 0, 16);
if(rune == 0){
free(s);
badjval(pp, "\\u0000 in string");
return nil;
}
r += 4;
w += runetochar(w, &rune);
break;
}
}
*w = 0;
if(len)
*len = w - s;
*pp = q+1;
return s;
}
static Json*
parsenumber(char **pp)
{
char *p, *q;
char *t;
double d;
Json *v;
/* -?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([Ee][-+]?[0-9]+) */
p = wskip(*pp);
q = p;
if(*q == '-')
q++;
if(*q == '0')
q++;
else{
if(*q < '1' || *q > '9')
return badjval(pp, "invalid number");
while('0' <= *q && *q <= '9')
q++;
}
if(*q == '.'){
q++;
if(*q < '0' || *q > '9')
return badjval(pp, "invalid number");
while('0' <= *q && *q <= '9')
q++;
}
if(*q == 'e' || *q == 'E'){
q++;
if(*q == '-' || *q == '+')
q++;
if(*q < '0' || *q > '9')
return badjval(pp, "invalid number");
while('0' <= *q && *q <= '9')
q++;
}
t = emalloc(q-p+1);
memmove(t, p, q-p);
t[q-p] = 0;
errno = 0;
d = strtod(t, nil);
if(errno != 0){
free(t);
return badjval(pp, nil);
}
free(t);
v = newjval(Jnumber);
v->number = d;
*pp = q;
return v;
}
static Json*
parsestring(char **pp)
{
char *s;
Json *v;
int len;
s = _parsestring(pp, &len);
if(s == nil)
return nil;
v = newjval(Jstring);
v->string = s;
v->len = len;
return v;
}
static Json*
parsename(char **pp)
{
if(strncmp(*pp, "true", 4) == 0){
*pp += 4;
return newjval(Jtrue);
}
if(strncmp(*pp, "false", 5) == 0){
*pp += 5;
return newjval(Jfalse);
}
if(strncmp(*pp, "null", 4) == 0){
*pp += 4;
return newjval(Jtrue);
}
return badjval(pp, "invalid name");
}
static Json*
parsearray(char **pp)
{
char *p;
Json *v;
p = *pp;
if(*p++ != '[')
return badjval(pp, "missing bracket for array");
v = newjval(Jarray);
p = wskip(p);
if(*p != ']'){
for(;;){
if(v->len%32 == 0)
v->value = erealloc(v->value, (v->len+32)*sizeof v->value[0]);
if((v->value[v->len++] = parsevalue(&p)) == nil){
jclose(v);
return badjval(pp, nil);
}
p = wskip(p);
if(*p == ']')
break;
if(*p++ != ','){
jclose(v);
return badjval(pp, "missing comma in array");
}
}
}
p++;
*pp = p;
return v;
}
static Json*
parseobject(char **pp)
{
char *p;
Json *v;
p = *pp;
if(*p++ != '{')
return badjval(pp, "missing brace for object");
v = newjval(Jobject);
p = wskip(p);
if(*p != '}'){
for(;;){
if(v->len%32 == 0){
v->name = erealloc(v->name, (v->len+32)*sizeof v->name[0]);
v->value = erealloc(v->value, (v->len+32)*sizeof v->value[0]);
}
if((v->name[v->len++] = _parsestring(&p, nil)) == nil){
jclose(v);
return badjval(pp, nil);
}
p = wskip(p);
if(*p++ != ':'){
jclose(v);
return badjval(pp, "missing colon in object");
}
if((v->value[v->len-1] = parsevalue(&p)) == nil){
jclose(v);
return badjval(pp, nil);
}
p = wskip(p);
if(*p == '}')
break;
if(*p++ != ','){
jclose(v);
return badjval(pp, "missing comma in object");
}
}
}
p++;
*pp = p;
return v;
}
static Json*
parsevalue(char **pp)
{
*pp = wskip(*pp);
switch(**pp){
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
return parsenumber(pp);
case 't':
case 'f':
case 'n':
return parsename(pp);
case '\"':
return parsestring(pp);
case '[':
return parsearray(pp);
case '{':
return parseobject(pp);
default:
return badjval(pp, "unexpected char <%02x>", **pp & 0xFF);
}
}
Json*
parsejson(char *text)
{
Json *v;
v = parsevalue(&text);
if(v && text && *wskip(text) != 0){
jclose(v);
werrstr("extra data in json");
return nil;
}
return v;
}
void
_printjval(Fmt *fmt, Json *v, int n)
{
int i;
if(v == nil){
fmtprint(fmt, "nil");
return;
}
switch(v->type){
case Jstring:
fmtprint(fmt, "\"%s\"", v->string);
break;
case Jnumber:
if(floor(v->number) == v->number)
fmtprint(fmt, "%.0f", v->number);
else
fmtprint(fmt, "%g", v->number);
break;
case Jobject:
fmtprint(fmt, "{");
if(n >= 0)
n++;
for(i=0; i<v->len; i++){
if(n > 0)
fmtprint(fmt, "\n%*s", n*4, "");
fmtprint(fmt, "\"%s\" : ", v->name[i]);
_printjval(fmt, v->value[i], n);
fmtprint(fmt, ",");
}
if(n > 0){
n--;
if(v->len > 0)
fmtprint(fmt, "\n%*s", n*4);
}
fmtprint(fmt, "}");
break;
case Jarray:
fmtprint(fmt, "[");
if(n >= 0)
n++;
for(i=0; i<v->len; i++){
if(n > 0)
fmtprint(fmt, "\n%*s", n*4, "");
_printjval(fmt, v->value[i], n);
fmtprint(fmt, ",");
}
if(n > 0){
n--;
if(v->len > 0)
fmtprint(fmt, "\n%*s", n*4);
}
fmtprint(fmt, "]");
break;
case Jtrue:
fmtprint(fmt, "true");
break;
case Jfalse:
fmtprint(fmt, "false");
break;
case Jnull:
fmtprint(fmt, "null");
break;
}
}
/*
void
printjval(Json *v)
{
Fmt fmt;
char buf[256];
fmtfdinit(&fmt, 1, buf, sizeof buf);
_printjval(&fmt, v, 0);
fmtprint(&fmt, "\n");
fmtfdflush(&fmt);
}
*/
int
jsonfmt(Fmt *fmt)
{
Json *v;
v = va_arg(fmt->args, Json*);
if(fmt->flags&FmtSharp)
_printjval(fmt, v, 0);
else
_printjval(fmt, v, -1);
return 0;
}
Json*
jincref(Json *v)
{
if(v == nil)
return nil;
++v->ref;
return v;
}
void
jclose(Json *v)
{
int i;
if(v == nil)
return;
if(--v->ref > 0)
return;
if(v->ref < 0)
sysfatal("jclose: ref %d", v->ref);
switch(v->type){
case Jstring:
free(v->string);
break;
case Jarray:
for(i=0; i<v->len; i++)
jclose(v->value[i]);
free(v->value);
break;
case Jobject:
for(i=0; i<v->len; i++){
free(v->name[i]);
jclose(v->value[i]);
}
free(v->value);
free(v->name);
break;
}
free(v);
}
Json*
jlookup(Json *v, char *name)
{
int i;
if(v->type != Jobject)
return nil;
for(i=0; i<v->len; i++)
if(strcmp(v->name[i], name) == 0)
return v->value[i];
return nil;
}
Json*
jwalk(Json *v, char *path)
{
char elem[128], *p, *next;
int n;
for(p=path; *p && v; p=next){
next = strchr(p, '/');
if(next == nil)
next = p+strlen(p);
if(next-p >= sizeof elem)
sysfatal("jwalk path elem too long - %s", path);
memmove(elem, p, next-p);
elem[next-p] = 0;
if(*next == '/')
next++;
if(v->type == Jarray && *elem && (n=strtol(elem, &p, 10)) >= 0 && *p == 0){
if(n >= v->len)
return nil;
v = v->value[n];
}else
v = jlookup(v, elem);
}
return v;
}
char*
jstring(Json *jv)
{
if(jv == nil || jv->type != Jstring)
return nil;
return jv->string;
}
vlong
jint(Json *jv)
{
if(jv == nil || jv->type != Jnumber)
return -1;
return jv->number;
}
double
jnumber(Json *jv)
{
if(jv == nil || jv->type != Jnumber)
return 0;
return jv->number;
}
int
jstrcmp(Json *jv, char *s)
{
char *t;
t = jstring(jv);
if(t == nil)
return -2;
return strcmp(t, s);
}

244
src/cmd/smugfs/jsonrpc.c Normal file
View file

@ -0,0 +1,244 @@
#include "a.h"
// JSON request/reply cache.
int chattyhttp;
typedef struct JEntry JEntry;
struct JEntry
{
CEntry ce;
Json *reply;
};
static Cache *jsoncache;
static void
jfree(CEntry *ce)
{
JEntry *j;
j = (JEntry*)ce;
jclose(j->reply);
}
static JEntry*
jcachelookup(char *request)
{
if(jsoncache == nil)
jsoncache = newcache(sizeof(JEntry), 1000, jfree);
return (JEntry*)cachelookup(jsoncache, request, 1);
}
void
jcacheflush(char *substr)
{
if(jsoncache == nil)
return;
cacheflush(jsoncache, substr);
}
// JSON RPC over HTTP
static char*
makehttprequest(char *host, char *path, char *postdata)
{
Fmt fmt;
fmtstrinit(&fmt);
fmtprint(&fmt, "POST %s HTTP/1.0\r\n", path);
fmtprint(&fmt, "Host: %s\r\n", host);
fmtprint(&fmt, "User-Agent: " USER_AGENT "\r\n");
fmtprint(&fmt, "Content-Type: application/x-www-form-urlencoded\r\n");
fmtprint(&fmt, "Content-Length: %d\r\n", strlen(postdata));
fmtprint(&fmt, "\r\n");
fmtprint(&fmt, "%s", postdata);
return fmtstrflush(&fmt);
}
static char*
makerequest(char *method, char *name1, va_list arg)
{
char *p, *key, *val;
Fmt fmt;
fmtstrinit(&fmt);
fmtprint(&fmt, "&");
p = name1;
while(p != nil){
key = p;
val = va_arg(arg, char*);
if(val == nil)
sysfatal("jsonrpc: nil value");
fmtprint(&fmt, "%U=%U&", key, val);
p = va_arg(arg, char*);
}
// TODO: These are SmugMug-specific, probably.
fmtprint(&fmt, "method=%s&", method);
if(sessid)
fmtprint(&fmt, "SessionID=%s&", sessid);
fmtprint(&fmt, "APIKey=%s", APIKEY);
return fmtstrflush(&fmt);
}
static char*
dojsonhttp(Protocol *proto, char *host, char *request, int rfd, vlong rlength)
{
char *data;
HTTPHeader hdr;
data = httpreq(proto, host, request, &hdr, rfd, rlength);
if(data == nil){
fprint(2, "httpreq: %r\n");
return nil;
}
if(strcmp(hdr.contenttype, "application/json") != 0 &&
(strcmp(hdr.contenttype, "text/html; charset=utf-8") != 0 || data[0] != '{')){ // upload.smugmug.com, sigh
werrstr("bad content type: %s", hdr.contenttype);
fprint(2, "Content-Type: %s\n", hdr.contenttype);
write(2, data, hdr.contentlength);
return nil;
}
if(hdr.contentlength == 0){
werrstr("no content");
return nil;
}
return data;
}
Json*
jsonrpc(Protocol *proto, char *host, char *path, char *method, char *name1, va_list arg, int usecache)
{
char *httpreq, *request, *reply;
JEntry *je;
Json *jv, *jstat, *jmsg;
request = makerequest(method, name1, arg);
je = nil;
if(usecache){
je = jcachelookup(request);
if(je->reply){
free(request);
return jincref(je->reply);
}
}
rpclog("%T %s", request);
httpreq = makehttprequest(host, path, request);
free(request);
if((reply = dojsonhttp(proto, host, httpreq, -1, 0)) == nil){
free(httpreq);
return nil;
}
free(httpreq);
jv = parsejson(reply);
free(reply);
if(jv == nil){
rpclog("%s: error parsing JSON reply: %r", method);
return nil;
}
if(jstrcmp((jstat = jlookup(jv, "stat")), "ok") == 0){
if(je)
je->reply = jincref(jv);
return jv;
}
if(jstrcmp(jstat, "fail") == 0){
jmsg = jlookup(jv, "message");
if(jmsg){
// If there are no images, that's not an error!
// (But SmugMug says it is.)
if(strcmp(method, "smugmug.images.get") == 0 &&
jstrcmp(jmsg, "empty set - no images found") == 0){
jclose(jv);
jv = parsejson("{\"stat\":\"ok\", \"Images\":[]}");
if(jv == nil)
sysfatal("parsejson: %r");
je->reply = jincref(jv);
return jv;
}
if(printerrors)
fprint(2, "%s: %J\n", method, jv);
rpclog("%s: %J", method, jmsg);
werrstr("%J", jmsg);
jclose(jv);
return nil;
}
rpclog("%s: json status: %J", method, jstat);
jclose(jv);
return nil;
}
rpclog("%s: json stat=%J", method, jstat);
jclose(jv);
return nil;
}
Json*
ncsmug(char *method, char *name1, ...)
{
Json *jv;
va_list arg;
va_start(arg, name1);
// TODO: Could use https only for login.
jv = jsonrpc(&https, HOST, PATH, method, name1, arg, 0);
va_end(arg);
rpclog("reply: %J", jv);
return jv;
}
Json*
smug(char *method, char *name1, ...)
{
Json *jv;
va_list arg;
va_start(arg, name1);
jv = jsonrpc(&http, HOST, PATH, method, name1, arg, 1);
va_end(arg);
return jv;
}
Json*
jsonupload(Protocol *proto, char *host, char *req, int rfd, vlong rlength)
{
Json *jv, *jstat, *jmsg;
char *reply;
if((reply = dojsonhttp(proto, host, req, rfd, rlength)) == nil)
return nil;
jv = parsejson(reply);
free(reply);
if(jv == nil){
fprint(2, "upload: error parsing JSON reply\n");
return nil;
}
if(jstrcmp((jstat = jlookup(jv, "stat")), "ok") == 0)
return jv;
if(jstrcmp(jstat, "fail") == 0){
jmsg = jlookup(jv, "message");
if(jmsg){
fprint(2, "upload: %J\n", jmsg);
werrstr("%J", jmsg);
jclose(jv);
return nil;
}
fprint(2, "upload: json status: %J\n", jstat);
jclose(jv);
return nil;
}
fprint(2, "upload: %J\n", jv);
jclose(jv);
return nil;
}

120
src/cmd/smugfs/log.c Normal file
View file

@ -0,0 +1,120 @@
#include "a.h"
void
lbkick(Logbuf *lb)
{
char *s;
int n;
Req *r;
while(lb->wait && lb->rp != lb->wp){
r = lb->wait;
lb->wait = r->aux;
if(lb->wait == nil)
lb->waitlast = &lb->wait;
r->aux = nil;
if(r->ifcall.count < 5){
respond(r, "log read request count too short");
continue;
}
s = lb->msg[lb->rp];
lb->msg[lb->rp] = nil;
if(++lb->rp == nelem(lb->msg))
lb->rp = 0;
n = r->ifcall.count;
if(n < strlen(s)+1+1){
memmove(r->ofcall.data, s, n-5);
n -= 5;
r->ofcall.data[n] = '\0';
/* look for first byte of UTF-8 sequence by skipping continuation bytes */
while(n>0 && (r->ofcall.data[--n]&0xC0)==0x80)
;
strcpy(r->ofcall.data+n, "...\n");
}else{
strcpy(r->ofcall.data, s);
strcat(r->ofcall.data, "\n");
}
r->ofcall.count = strlen(r->ofcall.data);
free(s);
respond(r, nil);
}
}
void
lbread(Logbuf *lb, Req *r)
{
if(lb->waitlast == nil)
lb->waitlast = &lb->wait;
*lb->waitlast = r;
lb->waitlast = (Req**)(void*)&r->aux;
r->aux = nil;
lbkick(lb);
}
void
lbflush(Logbuf *lb, Req *r)
{
Req **l;
for(l=&lb->wait; *l; l=(Req**)(void*)&(*l)->aux){
if(*l == r){
*l = r->aux;
r->aux = nil;
if(*l == nil)
lb->waitlast = l;
respond(r, "interrupted");
break;
}
}
}
void
lbappend(Logbuf *lb, char *fmt, ...)
{
va_list arg;
va_start(arg, fmt);
lbvappend(lb, fmt, arg);
va_end(arg);
}
void
lbvappend(Logbuf *lb, char *fmt, va_list arg)
{
char *s;
s = vsmprint(fmt, arg);
if(s == nil)
sysfatal("out of memory");
if(lb->msg[lb->wp])
free(lb->msg[lb->wp]);
lb->msg[lb->wp] = s;
if(++lb->wp == nelem(lb->msg))
lb->wp = 0;
lbkick(lb);
}
Logbuf rpclogbuf;
void
rpclogread(Req *r)
{
lbread(&rpclogbuf, r);
}
void
rpclogflush(Req *r)
{
lbflush(&rpclogbuf, r);
}
void
rpclog(char *fmt, ...)
{
va_list arg;
va_start(arg, fmt);
lbvappend(&rpclogbuf, fmt, arg);
va_end(arg);
}

108
src/cmd/smugfs/main.c Normal file
View file

@ -0,0 +1,108 @@
#include "a.h"
char *keypattern = "";
char *sessid;
Json *userinfo;
int printerrors;
void
usage(void)
{
fprint(2, "usage: smugfs [-k keypattern] [-m mtpt] [-s srv]\n");
threadexitsall("usage");
}
void
smuglogin(void)
{
Json *v;
char *s;
UserPasswd *up;
printerrors = 1;
up = auth_getuserpasswd(auth_getkey,
"proto=pass role=client server=smugmug.com "
"user? !password? %s", keypattern);
if(up == nil)
sysfatal("cannot get username/password: %r");
v = ncsmug("smugmug.login.withPassword",
"EmailAddress", up->user,
"Password", up->passwd,
nil);
if(v == nil)
sysfatal("login failed: %r");
memset(up->user, 'X', strlen(up->user));
memset(up->passwd, 'X', strlen(up->passwd));
free(up);
sessid = jstring(jwalk(v, "Login/Session/id"));
if(sessid == nil)
sysfatal("no session id");
sessid = estrdup(sessid);
s = jstring(jwalk(v, "Login/User/NickName"));
if(s == nil)
sysfatal("no nick name");
if(nickindex(s) != 0)
sysfatal("bad nick name");
userinfo = jincref(jwalk(v, "Login"));
jclose(v);
printerrors = 0;
}
void
threadmain(int argc, char **argv)
{
char *mtpt, *name;
mtpt = nil;
name = nil;
ARGBEGIN{
case 'D':
chatty9p++;
break;
case 'F':
chattyfuse++;
break;
case 'H':
chattyhttp++;
break;
case 'm':
mtpt = EARGF(usage());
break;
case 's':
name = EARGF(usage());
break;
case 'k':
keypattern = EARGF(usage());
break;
default:
usage();
}ARGEND
if(argc != 0)
usage();
if(name == nil && mtpt == nil)
mtpt = "/n/smug";
/*
* Check twice -- if there is an exited smugfs instance
* mounted there, the first access will fail but unmount it.
*/
if(mtpt && access(mtpt, AEXIST) < 0 && access(mtpt, AEXIST) < 0)
sysfatal("mountpoint %s does not exist", mtpt);
fmtinstall('H', encodefmt);
fmtinstall('[', encodefmt); // base-64
fmtinstall('J', jsonfmt);
fmtinstall('M', dirmodefmt);
fmtinstall('T', timefmt);
fmtinstall('U', urlencodefmt);
xinit();
smuglogin();
threadpostmountsrv(&xsrv, name, mtpt, 0);
threadexits(nil);
}

21
src/cmd/smugfs/mkfile Normal file
View file

@ -0,0 +1,21 @@
<$PLAN9/src/mkhdr
TARG=smugfs
HFILES=a.h
OFILES=\
cache.$O\
download.$O\
fs.$O\
http.$O\
json.$O\
jsonrpc.$O\
log.$O\
main.$O\
openssl.$O\
tcp.$O\
util.$O\
<$PLAN9/src/mkone

98
src/cmd/smugfs/openssl.c Normal file
View file

@ -0,0 +1,98 @@
#include <u.h>
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "a.h"
AUTOLIB(ssl)
static void
httpsinit(void)
{
ERR_load_crypto_strings();
ERR_load_SSL_strings();
SSL_load_error_strings();
SSL_library_init();
}
struct Pfd
{
BIO *sbio;
};
static Pfd*
opensslconnect(char *host)
{
Pfd *pfd;
BIO *sbio;
SSL_CTX *ctx;
SSL *ssl;
static int didinit;
char buf[1024];
if(!didinit){
httpsinit();
didinit = 1;
}
ctx = SSL_CTX_new(SSLv23_client_method());
sbio = BIO_new_ssl_connect(ctx);
BIO_get_ssl(sbio, &ssl);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
snprint(buf, sizeof buf, "%s:https", host);
BIO_set_conn_hostname(sbio, buf);
if(BIO_do_connect(sbio) <= 0 || BIO_do_handshake(sbio) <= 0){
ERR_error_string_n(ERR_get_error(), buf, sizeof buf);
BIO_free_all(sbio);
werrstr("openssl: %s", buf);
return nil;
}
pfd = emalloc(sizeof *pfd);
pfd->sbio = sbio;
return pfd;
}
static void
opensslclose(Pfd *pfd)
{
if(pfd == nil)
return;
BIO_free_all(pfd->sbio);
free(pfd);
}
static int
opensslwrite(Pfd *pfd, void *v, int n)
{
int m, total;
char *p;
p = v;
total = 0;
while(total < n){
if((m = BIO_write(pfd->sbio, p+total, n-total)) <= 0){
if(total == 0)
return m;
return total;
}
total += m;
}
return total;
}
static int
opensslread(Pfd *pfd, void *v, int n)
{
return BIO_read(pfd->sbio, v, n);
}
Protocol https =
{
opensslconnect,
opensslread,
opensslwrite,
opensslclose
};

50
src/cmd/smugfs/tcp.c Normal file
View file

@ -0,0 +1,50 @@
#include "a.h"
struct Pfd
{
int fd;
};
static Pfd*
httpconnect(char *host)
{
char buf[1024];
Pfd *pfd;
int fd;
snprint(buf, sizeof buf, "tcp!%s!http", host);
if((fd = dial(buf, nil, nil, nil)) < 0)
return nil;
pfd = emalloc(sizeof *pfd);
pfd->fd = fd;
return pfd;
}
static void
httpclose(Pfd *pfd)
{
if(pfd == nil)
return;
close(pfd->fd);
free(pfd);
}
static int
httpwrite(Pfd *pfd, void *v, int n)
{
return writen(pfd->fd, v, n);
}
static int
httpread(Pfd *pfd, void *v, int n)
{
return read(pfd->fd, v, n);
}
Protocol http = {
httpconnect,
httpread,
httpwrite,
httpclose,
};

81
src/cmd/smugfs/util.c Normal file
View file

@ -0,0 +1,81 @@
#include "a.h"
void*
emalloc(int n)
{
void *v;
v = mallocz(n, 1);
if(v == nil)
sysfatal("out of memory");
return v;
}
void*
erealloc(void *v, int n)
{
v = realloc(v, n);
if(v == nil)
sysfatal("out of memory");
return v;
}
char*
estrdup(char *s)
{
s = strdup(s);
if(s == nil)
sysfatal("out of memory");
return s;
}
int
timefmt(Fmt *f)
{
Tm tm;
vlong ms;
ms = nsec()/1000000;
tm = *localtime(ms/1000);
fmtprint(f, "%02d:%02d:%02d.%03d",
tm.hour, tm.min, tm.sec,
(int)(ms%1000));
return 0;
}
int
writen(int fd, void *buf, int n)
{
long m, tot;
for(tot=0; tot<n; tot+=m){
m = n - tot;
if(m > 8192)
m = 8192;
if(write(fd, (uchar*)buf+tot, m) != m)
break;
}
return tot;
}
int
urlencodefmt(Fmt *fmt)
{
int x;
char *s;
s = va_arg(fmt->args, char*);
for(; *s; s++){
x = (uchar)*s;
if(x == ' ')
fmtrune(fmt, '+');
else if(('a' <= x && x <= 'z') || ('A' <= x && x <= 'Z') || ('0' <= x && x <= '9')
|| strchr("$-_.+!*'()", x)){
fmtrune(fmt, x);
}else
fmtprint(fmt, "%%%02ux", x);
}
return 0;
}