mirror of
https://github.com/9fans/plan9port.git
synced 2025-01-12 11:10:07 +00:00
smugfs(4): new program
This commit is contained in:
parent
3d36f44373
commit
18824b5868
17 changed files with 4299 additions and 0 deletions
278
man/man4/smugfs.4
Normal file
278
man/man4/smugfs.4
Normal 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
17
src/cmd/smugfs/COPYRIGHT
Normal 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
22
src/cmd/smugfs/NOTES
Normal 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
190
src/cmd/smugfs/a.h
Normal 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
149
src/cmd/smugfs/cache.c
Normal 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
105
src/cmd/smugfs/download.c
Normal 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
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
237
src/cmd/smugfs/http.c
Normal 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
171
src/cmd/smugfs/icache.c
Normal 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
555
src/cmd/smugfs/json.c
Normal 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
244
src/cmd/smugfs/jsonrpc.c
Normal 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
120
src/cmd/smugfs/log.c
Normal 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
108
src/cmd/smugfs/main.c
Normal 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
21
src/cmd/smugfs/mkfile
Normal 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
98
src/cmd/smugfs/openssl.c
Normal 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
50
src/cmd/smugfs/tcp.c
Normal 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
81
src/cmd/smugfs/util.c
Normal 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;
|
||||
}
|
||||
|
Loading…
Reference in a new issue