httpfs2-0.1.4/.hg_archival.txt0000644000000000000000000000013611346770234016345 0ustar00usergroup00000000000000repo: 0a3adf16a128bc06926900bfd0a8c87c5977f12e node: 15e08b18de750cb01eca32d70b2bde0574771bac httpfs2-0.1.4/Makefile0000644000000000000000000000412411346770234014720 0ustar00usergroup00000000000000CC=gcc -g CFLAGS := -Os -Wall $(shell pkg-config fuse --cflags) CPPFLAGS := -Wall -DUSE_AUTH -D_XOPEN_SOURCE=500 -D_ISOC99_SOURCE THR_CPPFLAGS := -DUSE_THREAD THR_LDFLAGS := -lpthread SSL_CPPFLAGS := -DUSE_SSL $(shell pkg-config openssl --cflags) SSL_LDFLAGS := $(shell pkg-config openssl --libs) LDFLAGS := $(shell pkg-config fuse --libs | sed -e s/-lrt// -e s/-ldl//) intermediates = binaries = httpfs2 #httpfs2_ssl manpages = $(addsuffix .1,$(binaries)) intermediates += $(addsuffix .xml,$(manpages)) targets = $(binaries) $(manpages) all: $(targets) httpfs2: httpfs2.c $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) httpfs2.c -o httpfs2 httpfs2_ssl: httpfs2.c $(CC) $(CPPFLAGS) $(THR_CPPFLAGS) $(SSL_CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(THR_LDFLAGS) $(SSL_LDFLAGS) httpfs2.c -o httpfs2_ssl httpfs2_ssl.1: httpfs2.1 ln -sf httpfs2.1 httpfs2_ssl.1 clean: rm -f $(targets) $(intermediates) %.1: %.1.txt a2x -f manpage $< # Rules to automatically make a Debian package package = $(shell parsechangelog | grep ^Source: | sed -e s,'^Source: ',,) version = $(shell parsechangelog | grep ^Version: | sed -e s,'^Version: ',, -e 's,-.*,,') revision = $(shell parsechangelog | grep ^Version: | sed -e -e 's,.*-,,') architecture = $(shell dpkg --print-architecture) tar_dir = $(package)-$(version) tar_gz = $(tar_dir).tar.gz pkg_deb_dir = pkgdeb unpack_dir = $(pkg_deb_dir)/$(tar_dir) orig_tar_gz = $(pkg_deb_dir)/$(package)_$(version).orig.tar.gz pkg_deb_src = $(pkg_deb_dir)/$(package)_$(version)-$(revision)_source.changes pkg_deb_bin = $(pkg_deb_dir)/$(package)_$(version)-$(revision)_$(architecture).changes deb_pkg_key = CB8C5858 debclean: rm -rf $(pkg_deb_dir) deb: debsrc debbin debbin: $(unpack_dir) cd $(unpack_dir) && dpkg-buildpackage -b -k$(deb_pkg_key) debsrc: $(unpack_dir) cd $(unpack_dir) && dpkg-buildpackage -S -k$(deb_pkg_key) $(unpack_dir): $(orig_tar_gz) tar -zxf $(orig_tar_gz) -C $(pkg_deb_dir) $(pkg_deb_dir): mkdir $(pkg_deb_dir) $(pkg_deb_dir)/$(tar_gz): $(pkg_deb_dir) hg archive -t tgz $(pkg_deb_dir)/$(tar_gz) $(orig_tar_gz): $(pkg_deb_dir)/$(tar_gz) ln -s $(tar_gz) $(orig_tar_gz) httpfs2-0.1.4/debian/changelog0000644000000000000000000000076211346770234016360 0ustar00usergroup00000000000000httpfs2 (0.1.4-1) unstable; urgency=low * New upstream release: - Fixes -f option (Closes: #566362). - Fixes error handling in exchange() loop (Closes: #567128). * Bump standards version to 3.8.4. * Change dependency wrapping to make diffing easier. -- Michal Suchanek Fri, 12 Mar 2010 17:34:20 +0100 httpfs2 (0.1.3-1) unstable; urgency=low * Initial release (Closes: #380461). -- Michal Suchanek Mon, 11 Jan 2010 15:40:13 +0100 httpfs2-0.1.4/debian/compat0000644000000000000000000000000211346770234015677 0ustar00usergroup000000000000007 httpfs2-0.1.4/debian/control0000644000000000000000000000122611346770234016105 0ustar00usergroup00000000000000Source: httpfs2 Section: web Priority: extra Maintainer: Michal Suchanek Build-Depends: debhelper (>= 7.0.50~), asciidoc, xmlto, libfuse-dev (>= 2.6), pkg-config Standards-Version: 3.8.4 Homepage: http://sourceforge.net/projects/httpfs/ Package: httpfs2 Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends} Recommends: fuse-utils Description: FUSE filesystem for mounting files from http servers httpfs2 is a FUSE based filesystem for mounting http or https URLS as files in the filesystem. There is no notion of listable directories in http so only a single URL can be mounted. The server must be able to send byte ranges. httpfs2-0.1.4/debian/copyright0000644000000000000000000000220311346770234016431 0ustar00usergroup00000000000000Authors: Michal Suchanek hmb Download: http://sourceforge.net/projects/httpfs/ Files: * Copyright: (C) 2008-2010 Michal Suchanek (C) 2006 hmb (C) 2001-2007 Miklos Szeredi License: GPL-2+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. . On Debian systems, the complete text of the GNU General Public License can be found in /usr/share/common-licenses/GPL-2 file. httpfs2-0.1.4/debian/httpfs2.install0000644000000000000000000000002111346770234017454 0ustar00usergroup00000000000000httpfs2 /usr/bin httpfs2-0.1.4/debian/httpfs2.manpages0000644000000000000000000000001211346770234017601 0ustar00usergroup00000000000000httpfs2.1 httpfs2-0.1.4/debian/rules0000755000000000000000000000004011346770234015553 0ustar00usergroup00000000000000#!/usr/bin/make -f %: dh ${@} httpfs2-0.1.4/httpfs2.1.txt0000644000000000000000000000355511346770234015561 0ustar00usergroup00000000000000HTTPFS2(1) =========== Michal Suchanek NAME ---- httpfs2 - mount a file from a http server into the filesystem SYNOPSIS -------- *httpfs2* ['OPTIONS'] 'URL' 'FUSE-OPTIONS' *httpfs2_ssl* ['OPTIONS'] 'URL' 'FUSE-OPTIONS' DESCRIPTION ----------- httpfs2 is a *FUSE* based filesystem for mounting http or https URLS as files in the filesystem. There is no notion of listable directories in http so only a single URL can be mounted. The server must be able to send byte ranges. OPTIONS ------- *-c 'console'*:: Attempt to use the file ior device 'console' for output after fork. The default is '/dev/console'. *-f*:: Do not fork, stay in foreground. *-t 'timeout'*:: Use different timeout for connections. Default '30's. *'URL'*:: The url should specify the protocol as http or https, and it may specify basic authentication username and password. Currently special characters like whitespace are not handled so the URL cannot contain them. See a sample URL below: http://user:password@server.com/dir/file *'FUSE-OPTIONS'*:: These options are passed to the *FUSE* library. At the very least the mount point should be specified. EXIT STATUS ----------- *0*:: Successfully connected to the server *other*:: Failure (url parsing error, server error, FUSE setup error). Some FUSE errors may happen only after the process forks so they will not be returned in exit value. BUGS ---- The process can be stopped by typing ^Z on the terminal which may not be desirable under some circumstances. AUTHORS ------- Miklos Szeredi hmb marionraven at users.sourceforge.net Michal Suchanek COPYING ------- Free use of this software is granted under the terms of the GNU General Public License (GPL). httpfs2-0.1.4/httpfs2.c0000644000000000000000000010243611346770234015023 0ustar00usergroup00000000000000/* * HTTPFS: import a file from a web server to local file system * the main use is, to mount an iso on a web server with loop device * * depends on: * FUSE: Filesystem in Userspace * Copyright (C) 2001-2007 Miklos Szeredi * * This program can be distributed under the terms of the GNU GPL. * */ /* * (c) 2006 hmb marionraven at users.sourceforge.net * This 'beam me up, Scotty'-branch of httpfs tries to achieve, * that the mount-point-folder behaves as before. * But how can you access the original folder after the mount? * Answer comes from FuseCompress: * Open the folder before the mount, * keep it open all the time, * make a chdir to it * and always use a relative path. * It suffices not to chdir in main() and it's unnecessary to * do it in every function. httpfs_init is the right place. * */ /* * Modified to work with fuse 2.7. * Added keepalive * The passthru functionality removed to simplify the code. * (c) 2008 Michal Suchanek * */ #define FUSE_USE_VERSION 26 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_THREAD #include static pthread_key_t url_key; #define FUSE_LOOP fuse_session_loop_mt #else #define FUSE_LOOP fuse_session_loop #endif #ifdef USE_SSL #include #include #endif #define TIMEOUT 30 #define CONSOLE "/dev/console" #define HEADER_SIZE 1024 #define VERSION "0.1.4 \"Monolith\"" static char* argv0; #define MAX_REQUEST (32*1024) #define SOCK_CLOSED 0 #define SOCK_OPEN 1 #define SOCK_KEEPALIVE 2 typedef struct url { int proto; int timeout; char * host; /*hostname*/ int port; char * path; /*get path*/ char * name; /*file name*/ #ifdef USE_AUTH char * auth; /*encoded auth data*/ #endif int sockfd; int sock_type; #ifdef USE_SSL SSL_CTX* ssl_ctx; SSL* ssl; #endif char * req_buf; size_t req_buf_size; size_t file_size; time_t last_modified; } struct_url; static struct_url main_url; static ssize_t get_stat(struct_url*, struct stat * stbuf); static ssize_t get_data(struct_url*, off_t start, size_t size); static int open_client_socket(struct_url *url); static int close_client_socket(struct_url *url); static int close_client_force(struct_url *url); static struct_url * thread_setup(void); static void destroy_url_copy(void *); /* Protocol symbols. */ #define PROTO_HTTP 0 #define PROTO_HTTPS 1 #ifdef USE_SSL #endif #ifdef USE_AUTH static char b64_encode_table[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', /* 0-7 */ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', /* 8-15 */ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', /* 16-23 */ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', /* 24-31 */ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', /* 32-39 */ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', /* 40-47 */ 'w', 'x', 'y', 'z', '0', '1', '2', '3', /* 48-55 */ '4', '5', '6', '7', '8', '9', '+', '/' /* 56-63 */ }; /* Do base-64 encoding on a hunk of bytes. Return pointer to the ** bytes generated. Base-64 encoding takes up 4/3 the space of the original, ** plus a bit for end-padding. 3/2+5 gives a safe margin. */ static char * b64_encode(unsigned const char* ptr, int len) { char * space; int ptr_idx; unsigned char c = 0; unsigned char d = 0; int space_idx = 0; int phase = 0; /*FIXME calculate the occupied space properly*/ int size = (len * 3) /2 + 5; space = malloc(size+1); space[size] = 0; for (ptr_idx = 0; ptr_idx < len; ++ptr_idx) { switch (phase++) { case 0: c = ptr[ptr_idx] >> 2; d = (ptr[ptr_idx] & 0x3) << 4; break; case 1: c = d | (ptr[ptr_idx] >> 4); d = (ptr[ptr_idx] & 0xf) << 2; break; case 2: c = d | (ptr[ptr_idx] >> 6); if (space_idx < size) space[space_idx++] = b64_encode_table[c]; c = ptr[ptr_idx] & 0x3f; break; } space[space_idx++] = b64_encode_table[c]; if (space_idx == size) return space; phase %= 3; } if (phase != 0) { space[space_idx++] = b64_encode_table[d]; if (space_idx == size) return space; /* Pad with ='s. */ while (phase++ > 0) { space[space_idx++] = '='; if (space_idx == size) return space; phase %= 3; } } return space; } #endif /* USE_AUTH */ /* * The FUSE operations originally ripped from the hello_ll sample. */ static int httpfs_stat(fuse_ino_t ino, struct stat *stbuf) { stbuf->st_ino = ino; switch (ino) { case 1: stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; break; case 2: { struct_url * url = thread_setup(); stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; return get_stat(url, stbuf); }; break; default: errno = ENOENT; return -1; } return 0; } static void httpfs_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { struct stat stbuf; (void) fi; memset(&stbuf, 0, sizeof(stbuf)); if (httpfs_stat(ino, &stbuf) < 0) assert(errno),fuse_reply_err(req, errno); else fuse_reply_attr(req, &stbuf, 1.0); } static void httpfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) { struct fuse_entry_param e; memset(&e, 0, sizeof(e)); e.attr_timeout = 1.0; e.entry_timeout = 1.0; if (parent != 1 || strcmp(name, main_url.name) != 0){ e.ino = 0; } else { e.ino = 2; if(httpfs_stat(e.ino, &e.attr) < 0){ assert(errno); fuse_reply_err(req, errno); return; } } fuse_reply_entry(req, &e); } struct dirbuf { char *p; size_t size; }; static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, fuse_ino_t ino) { struct stat stbuf; size_t oldsize = b->size; b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); b->p = (char *) realloc(b->p, b->size); memset(&stbuf, 0, sizeof(stbuf)); stbuf.st_ino = ino; fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, b->size); } #define min(x, y) ((x) < (y) ? (x) : (y)) static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, off_t off, size_t maxsize) { if (off < bufsize) return fuse_reply_buf(req, buf + off, min(bufsize - off, maxsize)); else return fuse_reply_buf(req, NULL, 0); } static void httpfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { (void) fi; if (ino != 1) fuse_reply_err(req, ENOTDIR); else { struct dirbuf b; memset(&b, 0, sizeof(b)); dirbuf_add(req, &b, ".", 1); dirbuf_add(req, &b, "..", 1); dirbuf_add(req, &b, main_url.name, 2); reply_buf_limited(req, b.p, b.size, off, size); free(b.p); } } static void httpfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { if (ino != 2) fuse_reply_err(req, EISDIR); else if ((fi->flags & 3) != O_RDONLY) fuse_reply_err(req, EACCES); else{ /* direct_io is supposed to allow partial reads. However, setting * the flag causes read length max at 4096 bytes which leads to * *many* requests, poor performance, and errors. Some resources * like TCP ports are recycled too fast for Linux to cope. */ //fi->direct_io = 1; fuse_reply_open(req, fi); } } static void httpfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { (void) fi; struct_url * url = thread_setup(); int res; assert(ino == 2); assert(url->file_size >= off); size=min(size, url->file_size - off); if(url->file_size == off) { /* Handling of EOF is not well documented, returning EOF as error * does not work but this does. */ fuse_reply_buf(req, NULL, 0); return; } /* since we have to return all stuff requested the buffer cannot be * allocated in advance */ if(url->req_buf && ( (url->req_buf_size < size ) || ( (url->req_buf_size > size ) && (url->req_buf_size > MAX_REQUEST) ) ) ){ free(url->req_buf); url->req_buf = 0; } if(! url->req_buf){ url->req_buf_size = size; url->req_buf = malloc(size); } if((res = get_data(url, off, size)) < 0){ assert(errno); fuse_reply_err(req, errno); }else{ fuse_reply_buf(req, url->req_buf, res); } } static struct fuse_lowlevel_ops httpfs_oper = { .lookup = httpfs_lookup, .getattr = httpfs_getattr, .readdir = httpfs_readdir, .open = httpfs_open, .read = httpfs_read, }; /* * A few utility functions */ static char * strndup(const char * str, int n){ if(n > strlen(str)) n = strlen(str); char * res = malloc(n + 1); memcpy(res, str, n); res[n] = 0; return res; } static int mempref(const char * mem, const char * pref, size_t size) { /* return true if found */ if (size < strlen(pref)) return 0; return ! memcmp(mem, pref, strlen(pref)); } static void errno_report(const char * where) { int e = errno; fprintf(stderr, "%s: %s: %d %s.\n", argv0, where, errno, strerror(errno)); errno = e; } static char * url_encode(char * path) { return strdup(path); /*FIXME encode*/ } /* * functions for handling struct_url */ static int init_url(struct_url* url) { memset(url, 0, sizeof(url)); url->sock_type = SOCK_CLOSED; url->timeout = TIMEOUT; return 0; } static int free_url(struct_url* url) { if(url->host) free(url->host); url->host = 0; if(url->path) free(url->path); url->path = 0; if(url->name) free(url->name); url->name = 0; #ifdef USE_AUTH if(url->auth) free(url->auth); url->auth = 0; #endif if(url->sock_type != SOCK_CLOSED) close_client_force(url); url->port = 0; url->proto = 0; /* only after socket closed */ url->file_size=0; url->last_modified=0; return 0; } static void print_url(FILE *f, const struct_url * url) { char * protocol = "?!?"; switch(url->proto){ case PROTO_HTTP: protocol = "http"; break;; case PROTO_HTTPS: protocol = "https"; break;; } fprintf(f, "file name: \t%s\n", url->name); fprintf(f, "host name: \t%s\n", url->host); fprintf(f, "port number: \t%d\n", url->port); fprintf(f, "protocol: \t%s\n", protocol); fprintf(f, "request path: \t%s\n", url->path); #ifdef USE_AUTH fprintf(f, "auth data: \t%s\n", url->auth ? "(present)" : "(null)"); #endif } static int parse_url(const char * url, struct_url* res) { const char * url_orig = url; char* http = "http://"; #ifdef USE_SSL char* https = "https://"; #endif /* USE_SSL */ int path_start = '/'; assert(url); if (strncmp(http, url, strlen(http)) == 0) { url += strlen(http); res->proto = PROTO_HTTP; res->port = 80; #ifdef USE_SSL } else if (strncmp(https, url, strlen(https)) == 0) { url += strlen(https); res->proto = PROTO_HTTPS; res->port = 443; #endif /* USE_SSL */ } else { fprintf(stderr, "Invalid protocol in url: %s\n", url_orig); return -1; } /* determine if path was given */ if(strchr(url, path_start)) res->path = url_encode(strchr(url, path_start)); else{ path_start = 0; res->path = strdup("/"); } #ifdef USE_AUTH /* Get user and password */ if(strchr(url, '@') && (strchr(url, '@') < strchr(url, path_start))){ res->auth = b64_encode((unsigned char *)url, strchr(url, '@') - url); url = strchr(url, '@') + 1; }else{ res->auth = 0; } #endif /* USE_AUTH */ /* Get port number. */ int host_end = path_start; if(strchr(url, ':') && (strchr(url, ':') < strchr(url, path_start))){ /* FIXME check that port is a valid numeric value */ res->port = atoi(strchr(url, ':') + 1); if (! res->port) { fprintf(stderr, "Invalid port in url: %s\n", url_orig); return -1; } host_end = ':'; } /* Get the host name. */ if (url == strchr(url, host_end)){ /*no hastname in the url */ fprintf(stderr, "No hostname in url: %s\n", url_orig); return -1; } res->host = strndup(url, strchr(url, host_end) -url); /* Get the file name. */ url = strchr(url, path_start); const char * end = url + strlen(url); end--; //handle broken urls with multiple slashes while((end > url) && (*end == '/')) end--; end++; if((path_start == 0) || (end == url) || (strncmp(url, "/", end - url) == 0)){ res->name = strdup(res->host); }else{ while(strchr(url, '/') && (strchr(url, '/') < end)) url = strchr(url, '/') + 1; res->name = strndup(url, end - url); } return res->proto; } static void usage(void) { fprintf(stderr, "%s >>> Version: %s <<<\n", __FILE__, VERSION); fprintf(stderr, "usage: %s [-c [console]] [-f] [-t timeout] url mount-parameters\n\n", argv0); fprintf(stderr, "\t -c \tuse console for standard input/output/error (default: %s)\n", CONSOLE); fprintf(stderr, "\t -f \tstay in foreground - do not fork\n"); fprintf(stderr, "\t -t \tset socket timeout in seconds (default: %i)\n", TIMEOUT); fprintf(stderr, "\tmount-parameters should include the mount point\n"); } #define shift { if(!argv[1]) { usage(); return 4; };\ argc--; argv[1] = argv[0]; argv = argv + 1;} int main(int argc, char *argv[]) { char * fork_terminal = CONSOLE; int do_fork = 1; putenv("TZ=");/*UTC*/ argv0 = argv[0]; init_url(&main_url); while( argv[1] && (*(argv[1]) == '-') ) { char * arg = argv[1]; shift; while (*++arg){ switch (*arg){ case 'c': if( *(argv[1]) != '-' ) { fork_terminal = argv[1]; shift; }else{ fork_terminal = 0; } break; case 't': { char * end = " "; if( isdigit(*(argv[1]))) { main_url.timeout = strtol(argv[1], &end, 0); /* now end should point to '\0' */ } if(*end){ usage(); fprintf(stderr, "'%s' is not a number.\n", argv[0]); return 4; } shift; } break; case 'f': do_fork = 0; break; default: usage(); fprintf(stderr, "Unknown option '%c'.\n", *arg); return 4; } } } if (argc < 3) { usage(); return 1; } if(parse_url(argv[1], &main_url) == -1){ fprintf(stderr, "invalid url: %s\n", argv[1]); return 2; } print_url(stderr, &main_url); int sockfd = open_client_socket(&main_url); if(sockfd < 0) { fprintf(stderr, "Connection failed.\n"); return 3; } close_client_socket(&main_url); struct stat st; long long size = get_stat(&main_url, &st); if(size >= 0) { fprintf(stderr, "file size: \t%lld\n", size); }else{ return 3; } shift; if(fork_terminal && access(fork_terminal, O_RDWR)){ errno_report(fork_terminal); fork_terminal=0; } #ifdef USE_THREAD close_client_force(&main_url); /* each thread should open its own socket */ pthread_key_create(&url_key, &destroy_url_copy); #endif struct fuse_args args = FUSE_ARGS_INIT(argc, argv); struct fuse_chan *ch; char *mountpoint; int err = -1; int fork_res = 0; if (fuse_parse_cmdline(&args, &mountpoint, NULL, NULL) != -1 && (ch = fuse_mount(mountpoint, &args)) != NULL) { /* try to fork at some point where the setup is mostly done */ /* FIXME try to close std* and the like ? */ if(do_fork) fork_res = fork(); switch (fork_res) { case 0: { if(fork_terminal){ /* if we can access the console use it */ int fd = open(fork_terminal, O_RDONLY); dup2(fd, 0); close (fd); fd = open(fork_terminal, O_WRONLY); dup2(fd, 1); close (fd); fd = open(fork_terminal, O_WRONLY|O_SYNC); dup2(fd, 2); close (fd); } struct fuse_session *se; se = fuse_lowlevel_new(&args, &httpfs_oper, sizeof(httpfs_oper), NULL); if (se != NULL) { if (fuse_set_signal_handlers(se) != -1) { fuse_session_add_chan(se, ch); err = FUSE_LOOP(se); fuse_remove_signal_handlers(se); fuse_session_remove_chan(ch); } fuse_session_destroy(se); } fuse_unmount(mountpoint, ch); } break;; case -1: errno_report("fork"); break;; default: err = 0; break;; } } fuse_opt_free_args(&args); return err ? err : 0; } /* * Socket operations that abstract ssl and keepalive as much as possible. * Keepalive is set when parsing the headers. * */ static int close_client_socket(struct_url *url) { if (url->sock_type == SOCK_KEEPALIVE) return SOCK_KEEPALIVE; return close_client_force(url); } static int close_client_force(struct_url *url) { if(url->sock_type != SOCK_CLOSED){ #ifdef USE_SSL if (url->proto == PROTO_HTTPS) { SSL_free(url->ssl); SSL_CTX_free(url->ssl_ctx); /* FIXME ssl errors * The openssl documentation says they can be collected here but * they are probably seen as read/write errors anyway.. */ } #endif close(url->sockfd); } return url->sock_type = SOCK_CLOSED; } #ifdef USE_THREAD static void destroy_url_copy(void * urlptr) { if(urlptr){ fprintf(stderr, "%s: Thread %08lX ended.\n", argv0, pthread_self()); close_client_force(urlptr); free(urlptr); } } static void * create_url_copy(const struct_url * url) { void * res = malloc(sizeof(struct_url)); memcpy(res, url, sizeof(struct_url)); return res; } static struct_url * thread_setup(void) { struct_url * res = pthread_getspecific(url_key); if(!res) { fprintf(stderr, "%s: Thread %08lX started.\n", argv0, pthread_self()); res = create_url_copy(&main_url); pthread_setspecific(url_key, res); } return res; } #else /*USE_THREAD*/ static struct_url * thread_setup(void) { return &main_url; } #endif static int read_client_socket(struct_url *url, void * buf, size_t len) { int res; struct timeval timeout; timeout.tv_sec = url->timeout; timeout.tv_usec = 0; setsockopt(url->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); #ifdef USE_SSL if (url->proto == PROTO_HTTPS) res = SSL_read(url->ssl, buf, len); else #endif res = read(url->sockfd, buf, len); if(res <= 0) errno_report("read"); return res; } static ssize_t write_client_socket(struct_url *url, const void * buf, size_t len) { do { int fd = open_client_socket(url); ssize_t res; if(fd < 0) return -1; /*error hopefully reported by open*/ #ifdef USE_SSL if (url->proto == PROTO_HTTPS) res = SSL_write(url->ssl, buf, len); else #endif res = write(url->sockfd, buf, len); if(res <= 0) errno_report("write"); if ( !(res <= 0) || (url->sock_type != SOCK_KEEPALIVE )) return res; /* retry a failed keepalive socket */ close_client_force(url); } while (url->sock_type == SOCK_KEEPALIVE); return -1; /*should not reach*/ } /* * Function yields either a positive int after connecting to * host 'hostname' on port 'port' or < 0 in case of error * * It handles keepalive by not touching keepalive sockets. * The SSL context is created so that read/write can use it. * * hostname is something like 'www.tmtd.de' or 192.168.0.86 * port is expected in machine order (not net order) * * ((Flonix defines USE_IPV6)) * */ #if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED) #define USE_IPV6 #endif static int open_client_socket(struct_url *url) { #ifdef USE_IPV6 struct addrinfo hints; char portstr[10]; int gaierr; struct addrinfo* ai; struct addrinfo* aiv4; struct addrinfo* aiv6 = 0; struct sockaddr_in6 sa; #else /* USE_IPV6 */ struct hostent *he; struct sockaddr_in sa; #endif /* USE_IPV6 */ int sa_len, sock_family, sock_type, sock_protocol; if(url->sock_type == SOCK_KEEPALIVE) return url->sock_type; if(url->sock_type != SOCK_CLOSED) close_client_socket(url); (void) memset((void*) &sa, 0, sizeof(sa)); #ifdef USE_IPV6 (void) memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; (void) snprintf(portstr, sizeof(portstr), "%d", (int) url->port); if ((gaierr = getaddrinfo(url->host, portstr, &hints, &ai)) != 0) { (void) fprintf(stderr, "%s: getaddrinfo %s - %s\n", argv0, url->host, gai_strerror(gaierr)); return -1; } /* Find the first IPv4 and IPv6 entries. */ for (aiv4 = ai; aiv4 != NULL; aiv4 = aiv4->ai_next) { if (aiv4->ai_family == AF_INET) break; if ((aiv4->ai_family == AF_INET6) && (aiv6 == NULL)) aiv6 = aiv4; } /* If there's an IPv4 address, use that, otherwise try IPv6. */ if (aiv4 == NULL) aiv4 = aiv6; if (aiv4 == NULL) { (void) fprintf(stderr, "%s: no valid address found for host %s\n", argv0, url->host); errno = EIO; return -1; } if (sizeof(sa) < aiv4->ai_addrlen) { (void) fprintf(stderr, "%s - sockaddr too small (%lu < %lu)\n", url->host, (unsigned long) sizeof(sa), (unsigned long) aiv4->ai_addrlen); errno = EIO; return -1; } sock_family = aiv4->ai_family; sock_type = aiv4->ai_socktype; sock_protocol = aiv4->ai_protocol; sa_len = aiv4->ai_addrlen; (void) memmove(&sa, aiv4->ai_addr, sa_len); freeaddrinfo(ai); #else /* USE_IPV6 */ he = gethostbyname(url->host); if (he == NULL) { (void) fprintf(stderr, "%s: unknown host - %s\n", argv0, url->host); errno = EIO; return -1; } sock_family = sa.sin_family = he->h_addrtype; sock_type = SOCK_STREAM; sock_protocol = 0; sa_len = sizeof(sa); (void) memmove(&sa.sin_addr, he->h_addr, he->h_length); sa.sin_port = htons(url->port); #endif /* USE_IPV6 */ url->sockfd = socket(sock_family, sock_type, sock_protocol); if (url->sockfd < 0) { errno_report("couldn't get socket"); return -1; } if (connect(url->sockfd, (struct sockaddr*) &sa, sa_len) < 0) { errno_report("couldn't connect socket"); return -1; } #ifdef USE_SSL if ((url->proto) == PROTO_HTTPS) { /* Make SSL connection. */ int r; SSL_load_error_strings(); SSLeay_add_ssl_algorithms(); url->ssl_ctx = SSL_CTX_new(SSLv23_client_method()); url->ssl = SSL_new(url->ssl_ctx); SSL_set_fd(url->ssl, url->sockfd); r = SSL_connect(url->ssl); if (r <= 0) { close(url->sockfd); (void) fprintf(stderr, "%s: %s:%d - SSL connection failed - %d\n", argv0, url->host, url->port, r); ERR_print_errors_fp(stderr); errno = EIO; return -1; } } #endif return url->sock_type = SOCK_OPEN; } static void plain_report(const char * reason, const char * method, const char * buf, size_t len) { fprintf(stderr, "%s: %s: %s\n", argv0, method, reason); fwrite(buf, len, 1, stderr); if(len && ( *(buf+len-1) != '\n')) fputc('\n', stderr); } /* * Scan the received header for interesting fields. Since C does not have * tools for working with potentially unterminated strings this is quite * long and ugly. * * Return the length of the header in case part of the data was * read with the header. * Content-Length means different thing whith GET and HEAD. */ static ssize_t parse_header(struct_url *url, const char * buf, ssize_t bytes, const char * method, size_t * content_length, int expect) { /* FIXME check the header parser */ int status; const char * ptr = buf; const char * end; int seen_accept = 0, seen_length = 0, seen_close = 0; if (bytes <= 0) { return -1; } end = memchr(ptr, '\n', bytes); if(!end) { plain_report ( "reply does not contain newline!", method, buf, 0); errno = EIO; return -1; } end = ptr; while(1){ end = memchr(end + 1, '\n', bytes - (end - ptr)); if(!end || ((end + 1) >= (ptr + bytes)) ) { plain_report ("reply does not contain end of header!", method, buf, bytes); errno = EIO; return -1; } if(mempref(end, "\n\r\n", bytes - (end - ptr))) break; } size_t header_len = (end + 3) - ptr; end = memchr(ptr, '\n', bytes); char * http = "HTTP/1.1 "; if(!mempref(ptr, http, end - ptr) || !isdigit( *(ptr + strlen(http))) ) { plain_report ("reply does not contain status!", method, buf, header_len); errno = EIO; return -1; } status = strtol( ptr + strlen(http), (char **)&ptr, 10); if (status != expect) { fprintf(stderr, "%s: %s: failed with status: %d%.*s.\n", argv0, method, status, (end - ptr) - 1, ptr); if (!strcmp("HEAD", method)) fwrite(buf, bytes, 1, stderr); /*DEBUG*/ errno = EIO; if (status == 404) errno = ENOENT; return -1; } char * content_length_str = "Content-Length: "; char * accept = "Accept-Ranges: bytes"; char * date = "Last-Modified: "; char * close = "Connection: close"; struct tm tm; while(1) { ptr = end+1; if( !(ptr < buf + (header_len - 4))){ if(! seen_accept){ plain_report("server must Accept-Range: bytes", method, buf, 0); errno = EIO; return -1; } if(! seen_length){ plain_report("reply didn't contain Content-Length!", method, buf, 0); errno = EIO; return -1; } if(url->sock_type == SOCK_OPEN && !seen_close) url->sock_type = SOCK_KEEPALIVE; if(url->sock_type == SOCK_KEEPALIVE && seen_close) url->sock_type = SOCK_OPEN; return header_len; } end = memchr(ptr, '\n', bytes - (ptr - buf)); if( mempref(ptr, content_length_str, end - ptr) && isdigit( *(ptr + strlen(content_length_str))) ){ *content_length = atoll(ptr + strlen(content_length_str)); seen_length = 1; continue; } if( mempref(ptr, accept, end - ptr) ){ seen_accept = 1; continue; } if( mempref(ptr, date, end - ptr) ){ memset(&tm, 0, sizeof(tm)); if(!strptime(ptr + strlen(date), "%n%a, %d %b %Y %T %Z", &tm)){ plain_report("invalid time", method, ptr + strlen(date), (end - ptr) - strlen(date)) ; continue; } url->last_modified = mktime(&tm); continue; } if( mempref(ptr, close, end - ptr) ){ seen_close = 1; } } } /* * Send the header, and get a reply. * This relies on 1k reads and writes being generally atomic - * - they fit into a single frame. The header should fit into that * and we do not need partial read handling so the exchange is simple. * However, broken sockets have to be handled here. */ static ssize_t exchange(struct_url *url, char * buf, const char * method, size_t * content_length, off_t start, off_t end, size_t * header_length) { ssize_t res; ssize_t bytes; int range = (end > 0); /* Build request buffer, starting with the request method. */ bytes = snprintf(buf, HEADER_SIZE, "%s %s HTTP/1.1\r\nHost: %s\r\n", method, url->path, url->host); bytes += snprintf(buf + bytes, HEADER_SIZE - bytes, "User-Agent: %s %s\r\n", __FILE__, VERSION); if (range) bytes += snprintf(buf + bytes, HEADER_SIZE - bytes, "Range: bytes=%llu-%llu\r\n", (unsigned long long) start, (unsigned long long) end); #ifdef USE_AUTH if ( url->auth ) bytes += snprintf(buf + bytes, HEADER_SIZE - bytes, "Authorization: Basic %s\r\n", url->auth); #endif bytes += snprintf(buf + bytes, HEADER_SIZE - bytes, "\r\n"); /* Now actually send it. */ while(1){ /* * It looks like the sockets abandoned by the server do not go away. * Instead of returning EPIPE they allow zero writes and zero reads. So * this is the place where a stale socket would be detected. * * Socket that return EAGAIN cause long delays. Reopen. * * Reset errno because reads/writes of 0 bytes are a success and are not * required to touch it but are handled as error below. * */ errno = 0; res = write_client_socket(url, buf, bytes); if ( ((res <= 0) && ! errno) || (errno == EAGAIN) || (errno == EPIPE)) { errno_report("exchange: failed to send request, retrying"); /* DEBUG */ close_client_force(url); continue; } if (res <= 0){ errno_report("exchange: failed to send request"); /* DEBUG */ return res; } res = read_client_socket(url, buf, HEADER_SIZE); if ( ((res <= 0) && ! errno) || (errno == EAGAIN) || (errno == EPIPE)) { errno_report("exchange: did not receive a reply, retrying"); /* DEBUG */ close_client_force(url); continue; } else if (res <= 0) { errno_report("exchange: failed receving reply from server"); /* DEBUG */ return res; } else break; /* Not reached */ } bytes = res; res = parse_header(url, buf, bytes, method, content_length, range ? 206 : 200); if (res <= 0){ plain_report("exchange: server error", method, buf, bytes); return res; } if (header_length) *header_length = res; return bytes; } /* * Function uses HEAD-HTTP-Request * to determine the file size */ static ssize_t get_stat(struct_url *url, struct stat * stbuf) { char buf[HEADER_SIZE]; if( exchange(url, buf, "HEAD", &(url->file_size), 0, 0, 0) < 0 ) return -1; close_client_socket(url); stbuf->st_mtime = url->last_modified; return stbuf->st_size = url->file_size; } /* * get_data does all the magic * a GET-Request with Range-Header * allows to read arbitrary bytes */ static ssize_t get_data(struct_url *url, off_t start, size_t size) { char buf[HEADER_SIZE]; const char * b; ssize_t bytes; off_t end = start + size - 1; char * destination = url->req_buf; size_t content_length, header_length; bytes = exchange(url, buf, "GET", &content_length, start, end, &header_length); if(bytes <= 0) return -1; if (content_length != size) { plain_report("didn't yield the whole piece.", "GET", 0, 0); size = min(content_length, size); } b = buf + header_length; bytes -= (b - buf); memcpy(destination, b, bytes); size -= bytes; destination +=bytes; for (; size > 0; size -= bytes, destination += bytes) { bytes = read_client_socket(url, destination, size); if (bytes < 0) { errno_report("GET (read)"); return -1; } if (bytes == 0) { break; } } close_client_socket(url); return end - start + 1 - size; }