moved generic curl routines to their own file

git-svn-id: http://s3fs.googlecode.com/svn/trunk@332 df820570-a93a-0410-bd06-b72b767a4274
This commit is contained in:
ben.lemasurier@gmail.com 2011-03-01 19:35:55 +00:00
parent 60f96aa8fb
commit 6cd9e9e65d
5 changed files with 700 additions and 681 deletions

View File

@ -2,6 +2,6 @@ bin_PROGRAMS=s3fs
AM_CPPFLAGS = $(DEPS_CFLAGS)
s3fs_SOURCES = s3fs.cpp s3fs.h cache.cpp cache.h string_util.cpp string_util.h
s3fs_SOURCES = s3fs.cpp s3fs.h curl.cpp curl.h cache.cpp cache.h string_util.cpp string_util.h
s3fs_LDADD = $(DEPS_LIBS)

567
src/curl.cpp Normal file
View File

@ -0,0 +1,567 @@
/*
* s3fs - FUSE-based file system backed by Amazon S3
*
* Copyright 2007-2008 Randy Rizun <rrizun@gmail.com>
*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <syslog.h>
#include <pthread.h>
#include <curl/curl.h>
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include "curl.h"
using namespace std;
pthread_mutex_t curl_handles_lock;
std::map<CURL*, time_t> curl_times;
std::map<CURL*, progress_t> curl_progress;
std::string curl_ca_bundle;
CURL *create_curl_handle(void) {
time_t now;
CURL *curl_handle;
pthread_mutex_lock(&curl_handles_lock);
curl_handle = curl_easy_init();
curl_easy_reset(curl_handle);
curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, connect_timeout);
curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt(curl_handle, CURLOPT_PROGRESSFUNCTION, my_curl_progress);
curl_easy_setopt(curl_handle, CURLOPT_PROGRESSDATA, curl_handle);
curl_easy_setopt(curl_handle, CURLOPT_FORBID_REUSE, 1);
now = time(0);
curl_times[curl_handle] = now;
curl_progress[curl_handle] = progress_t(-1, -1);
pthread_mutex_unlock(&curl_handles_lock);
return curl_handle;
}
void destroy_curl_handle(CURL *curl_handle) {
if(curl_handle != NULL) {
pthread_mutex_lock(&curl_handles_lock);
curl_times.erase(curl_handle);
curl_progress.erase(curl_handle);
curl_easy_cleanup(curl_handle);
pthread_mutex_unlock(&curl_handles_lock);
}
return;
}
/**
* @return fuse return code
*/
int my_curl_easy_perform(CURL* curl, BodyStruct* body, FILE* f) {
char url[256];
time_t now;
char* ptr_url = url;
curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL , &ptr_url);
if(debug)
syslog(LOG_DEBUG, "connecting to URL %s", ptr_url);
// curl_easy_setopt(curl, CURLOPT_VERBOSE, true);
if(ssl_verify_hostname.substr(0,1) == "0")
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
if(curl_ca_bundle.size() != 0)
curl_easy_setopt(curl, CURLOPT_CAINFO, curl_ca_bundle.c_str());
long responseCode;
// 1 attempt + retries...
int t = retries + 1;
while (t-- > 0) {
if (f) {
rewind(f);
}
CURLcode curlCode = curl_easy_perform(curl);
switch (curlCode) {
case CURLE_OK:
// Need to look at the HTTP response code
if (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode) != 0) {
syslog(LOG_ERR, "curl_easy_getinfo failed while trying to retrieve HTTP response code");
return -EIO;
}
if(debug)
syslog(LOG_DEBUG, "HTTP response code %ld", responseCode);
if (responseCode < 400) {
return 0;
}
if (responseCode >= 500) {
syslog(LOG_ERR, "###HTTP response=%ld", responseCode);
sleep(4);
break;
}
// Service response codes which are >= 400 && < 500
switch(responseCode) {
case 400:
if(debug) syslog(LOG_ERR, "HTTP response code 400 was returned");
if(body) {
if(body->size && debug) {
syslog(LOG_ERR, "Body Text: %s", body->text);
}
}
if(debug) syslog(LOG_DEBUG, "Now returning EIO");
return -EIO;
case 403:
if(debug) syslog(LOG_ERR, "HTTP response code 403 was returned");
if(body) {
if(body->size && debug) {
syslog(LOG_ERR, "Body Text: %s", body->text);
}
}
if(debug) syslog(LOG_DEBUG, "Now returning EIO");
return -EIO;
case 404:
if(debug) syslog(LOG_DEBUG, "HTTP response code 404 was returned");
if(body) {
if(body->size && debug) {
syslog(LOG_DEBUG, "Body Text: %s", body->text);
}
}
if(debug) syslog(LOG_DEBUG, "Now returning ENOENT");
return -ENOENT;
default:
syslog(LOG_ERR, "###response=%ld", responseCode);
printf("responseCode %ld\n", responseCode);
if(body) {
if(body->size) {
printf("Body Text %s\n", body->text);
}
}
return -EIO;
}
break;
case CURLE_WRITE_ERROR:
syslog(LOG_ERR, "### CURLE_WRITE_ERROR");
sleep(2);
break;
case CURLE_OPERATION_TIMEDOUT:
syslog(LOG_ERR, "### CURLE_OPERATION_TIMEDOUT");
sleep(2);
break;
case CURLE_COULDNT_RESOLVE_HOST:
syslog(LOG_ERR, "### CURLE_COULDNT_RESOLVE_HOST");
sleep(2);
break;
case CURLE_COULDNT_CONNECT:
syslog(LOG_ERR, "### CURLE_COULDNT_CONNECT");
sleep(4);
break;
case CURLE_GOT_NOTHING:
syslog(LOG_ERR, "### CURLE_GOT_NOTHING");
sleep(4);
break;
case CURLE_ABORTED_BY_CALLBACK:
syslog(LOG_ERR, "### CURLE_ABORTED_BY_CALLBACK");
sleep(4);
now = time(0);
curl_times[curl] = now;
break;
case CURLE_PARTIAL_FILE:
syslog(LOG_ERR, "### CURLE_PARTIAL_FILE");
sleep(4);
break;
case CURLE_SSL_CACERT:
// try to locate cert, if successful, then set the
// option and continue
if (curl_ca_bundle.size() == 0) {
locate_bundle();
if (curl_ca_bundle.size() != 0) {
t++;
curl_easy_setopt(curl, CURLOPT_CAINFO, curl_ca_bundle.c_str());
continue;
}
}
syslog(LOG_ERR, "curlCode: %i msg: %s", curlCode,
curl_easy_strerror(curlCode));;
fprintf (stderr, "%s: curlCode: %i -- %s\n",
program_name.c_str(),
curlCode,
curl_easy_strerror(curlCode));
exit(EXIT_FAILURE);
break;
#ifdef CURLE_PEER_FAILED_VERIFICATION
case CURLE_PEER_FAILED_VERIFICATION:
first_pos = bucket.find_first_of(".");
if (first_pos != string::npos) {
fprintf (stderr, "%s: curl returned a CURL_PEER_FAILED_VERIFICATION error\n", program_name.c_str());
fprintf (stderr, "%s: security issue found: buckets with periods in their name are incompatible with https\n", program_name.c_str());
fprintf (stderr, "%s: This check can be over-ridden by using the -o ssl_verify_hostname=0\n", program_name.c_str());
fprintf (stderr, "%s: The certificate will still be checked but the hostname will not be verified.\n", program_name.c_str());
fprintf (stderr, "%s: A more secure method would be to use a bucket name without periods.\n", program_name.c_str());
} else {
fprintf (stderr, "%s: my_curl_easy_perform: curlCode: %i -- %s\n",
program_name.c_str(),
curlCode,
curl_easy_strerror(curlCode));
}
exit(EXIT_FAILURE);
break;
#endif
// This should be invalid since curl option HTTP FAILONERROR is now off
case CURLE_HTTP_RETURNED_ERROR:
syslog(LOG_ERR, "### CURLE_HTTP_RETURNED_ERROR");
if (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode) != 0) {
return -EIO;
}
syslog(LOG_ERR, "###response=%ld", responseCode);
// Let's try to retrieve the
if (responseCode == 404) {
return -ENOENT;
}
if (responseCode < 500) {
return -EIO;
}
break;
// Unknown CURL return code
default:
syslog(LOG_ERR, "###curlCode: %i msg: %s", curlCode,
curl_easy_strerror(curlCode));;
exit(EXIT_FAILURE);
break;
}
syslog(LOG_ERR, "###retrying...");
}
syslog(LOG_ERR, "###giving up");
return -EIO;
}
// libcurl callback
size_t WriteMemoryCallback(void *ptr, size_t blockSize, size_t numBlocks, void *data) {
size_t realsize = blockSize * numBlocks;
struct BodyStruct *mem = (struct BodyStruct *)data;
mem->text = (char *)realloc(mem->text, mem->size + realsize + 1);
if(mem->text == NULL) {
/* out of memory! */
fprintf(stderr, "not enough memory (realloc returned NULL)\n");
exit(EXIT_FAILURE);
}
memcpy(&(mem->text[mem->size]), ptr, realsize);
mem->size += realsize;
mem->text[mem->size] = 0;
return realsize;
}
// read_callback
// http://curl.haxx.se/libcurl/c/post-callback.html
size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp) {
struct WriteThis *pooh = (struct WriteThis *)userp;
if(size*nmemb < 1)
return 0;
if(pooh->sizeleft) {
*(char *)ptr = pooh->readptr[0]; /* copy one single byte */
pooh->readptr++; /* advance pointer */
pooh->sizeleft--; /* less data left */
return 1; /* we return 1 byte at a time! */
}
return 0; /* no more data left to deliver */
}
// homegrown timeout mechanism
int my_curl_progress(
void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
CURL* curl = static_cast<CURL*>(clientp);
time_t now = time(0);
progress_t p(dlnow, ulnow);
pthread_mutex_lock(&curl_handles_lock);
// any progress?
if(p != curl_progress[curl]) {
// yes!
curl_times[curl] = now;
curl_progress[curl] = p;
} else {
// timeout?
if (now - curl_times[curl] > readwrite_timeout) {
pthread_mutex_unlock( &curl_handles_lock );
syslog(LOG_ERR, "timeout now: %li curl_times[curl]: %lil readwrite_timeout: %li",
(long int)now, curl_times[curl], (long int)readwrite_timeout);
return CURLE_ABORTED_BY_CALLBACK;
}
}
pthread_mutex_unlock(&curl_handles_lock);
return 0;
}
void locate_bundle(void) {
// See if environment variable CURL_CA_BUNDLE is set
// if so, check it, if it is a good path, then set the
// curl_ca_bundle variable to it
char *CURL_CA_BUNDLE;
if(curl_ca_bundle.size() == 0) {
CURL_CA_BUNDLE = getenv("CURL_CA_BUNDLE");
if(CURL_CA_BUNDLE != NULL) {
// check for existance and readability of the file
ifstream BF(CURL_CA_BUNDLE);
if(BF.good()) {
BF.close();
curl_ca_bundle.assign(CURL_CA_BUNDLE);
} else {
fprintf(stderr, "%s: file specified by CURL_CA_BUNDLE environment variable is not readable\n",
program_name.c_str());
exit(EXIT_FAILURE);
}
return;
}
}
// not set via environment variable, look in likely locations
///////////////////////////////////////////
// from curl's (7.21.2) acinclude.m4 file
///////////////////////////////////////////
// dnl CURL_CHECK_CA_BUNDLE
// dnl -------------------------------------------------
// dnl Check if a default ca-bundle should be used
// dnl
// dnl regarding the paths this will scan:
// dnl /etc/ssl/certs/ca-certificates.crt Debian systems
// dnl /etc/pki/tls/certs/ca-bundle.crt Redhat and Mandriva
// dnl /usr/share/ssl/certs/ca-bundle.crt old(er) Redhat
// dnl /usr/local/share/certs/ca-root.crt FreeBSD
// dnl /etc/ssl/cert.pem OpenBSD
// dnl /etc/ssl/certs/ (ca path) SUSE
ifstream BF("/etc/pki/tls/certs/ca-bundle.crt");
if(BF.good()) {
BF.close();
curl_ca_bundle.assign("/etc/pki/tls/certs/ca-bundle.crt");
return;
}
return;
}
// Multi CURL stuff
CURLHLL *create_h_element(CURL *handle) {
CURLHLL *p;
p = (CURLHLL *) malloc(sizeof(CURLHLL));
if(p == NULL) {
printf("create_h_element: could not allocation memory\n");
exit(EXIT_FAILURE);
}
p->handle = handle;
p->next = NULL;
return p;
}
CURLMHLL *create_mh_element(CURLM *handle) {
CURLMHLL *p;
p = (CURLMHLL *) malloc(sizeof(CURLMHLL));
if(p == NULL) {
printf("create_mh_element: could not allocation memory\n");
exit(EXIT_FAILURE);
}
p->handle = handle;
p->curlhll_head = NULL;
p->next = NULL;
return p;
}
CURLMHLL *add_mh_element(CURLMHLL *head, CURLM *handle) {
CURLMHLL *p;
CURLMHLL *p_new;
p_new = create_mh_element(handle);
for (p = head; p->next != NULL; p = p->next);
;
p->next = p_new;
return p_new;
}
void add_h_element(CURLHLL *head, CURL *handle) {
CURLHLL *p;
CURLHLL *p_new;
p_new = create_h_element(handle);
for (p = head; p->next != NULL; p = p->next);
;
p->next = p_new;
return;
}
void add_h_to_mh(CURL *h, CURLMHLL *mh) {
CURLHLL *h_head;
h_head = mh->curlhll_head;
if(h_head == NULL) {
h_head = create_h_element(h);
mh->curlhll_head = h_head;
} else {
add_h_element(h_head, h);
}
return;
}
void cleanup_multi_stuff(CURLMHLL *mhhead) {
// move this to it's own cleanup function
CURLMHLL *my_mhhead;
CURLMHLL *pnext;
CURLHLL *cnext;
CURLHLL *chhead;
CURLMcode curlm_code;
CURL *curl_handle;
CURLM *curl_multi_handle;
if(mhhead == NULL)
return;
// Remove all of the easy handles from its multi handle
my_mhhead = mhhead;
pnext = NULL;
cnext = NULL;
chhead = NULL;
do {
chhead = my_mhhead->curlhll_head;
while(chhead != NULL) {
cnext = chhead->next;
curl_multi_handle = my_mhhead->handle;
curl_handle = chhead->handle;
curlm_code = curl_multi_remove_handle(curl_multi_handle, curl_handle);
if(curlm_code != CURLM_OK) {
syslog(LOG_ERR, "curl_multi_remove_handle code: %d msg: %s",
curlm_code, curl_multi_strerror(curlm_code));
}
chhead = cnext;
}
pnext = my_mhhead->next;
my_mhhead = pnext;
} while(my_mhhead != NULL);
// now clean up the easy handles
my_mhhead = mhhead;
pnext = NULL;
cnext = NULL;
chhead = NULL;
do {
chhead = my_mhhead->curlhll_head;
while(chhead != NULL) {
cnext = chhead->next;
destroy_curl_handle(chhead->handle);
chhead = cnext;
}
pnext = my_mhhead->next;
my_mhhead = pnext;
} while(my_mhhead != NULL);
// now cleanup the multi handles
my_mhhead = mhhead;
pnext = NULL;
cnext = NULL;
chhead = NULL;
do {
pnext = my_mhhead->next;
curlm_code = curl_multi_cleanup(my_mhhead->handle);
if(curlm_code != CURLM_OK) {
syslog(LOG_ERR, "curl_multi_cleanup code: %d msg: %s",
curlm_code, curl_multi_strerror(curlm_code));
}
my_mhhead = pnext;
} while(my_mhhead != NULL);
// Now free the memory structures
my_mhhead = mhhead;
pnext = NULL;
cnext = NULL;
chhead = NULL;
do {
chhead = my_mhhead->curlhll_head;
while(chhead != NULL) {
cnext = chhead->next;
free(chhead);
chhead = cnext;
}
pnext = my_mhhead->next;
free(my_mhhead);
my_mhhead = pnext;
} while(my_mhhead != NULL);
return;
}

52
src/curl.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef S3FS_CURL_H_
#define S3FS_CURL_H_
// memory structure for curl write memory callback
struct BodyStruct {
char *text;
size_t size;
};
// memory structure for POST
struct WriteThis {
const char *readptr;
int sizeleft;
};
typedef struct curlhll {
CURL *handle;
struct curlhll *next;
} CURLHLL;
typedef struct curlmhll {
CURLM *handle;
struct curlhll *curlhll_head;
struct curlmhll * next;
} CURLMHLL;
typedef std::pair<double, double> progress_t;
extern int retries;
extern long connect_timeout;
extern time_t readwrite_timeout;
extern bool debug;
extern std::string program_name;
extern std::string ssl_verify_hostname;
CURL *create_curl_handle(void);
void destroy_curl_handle(CURL *curl_handle);
int my_curl_easy_perform(CURL* curl, BodyStruct* body = NULL, FILE* f = 0);
size_t WriteMemoryCallback(void *ptr, size_t blockSize, size_t numBlocks, void *data);
size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp);
int my_curl_progress(
void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
void locate_bundle(void);
CURLHLL *create_h_element(CURL *handle);
CURLMHLL *create_mh_element(CURLM *handle);
CURLMHLL *add_mh_element(CURLMHLL *head, CURLM *handle);
void add_h_element(CURLHLL *head, CURL *handle);
void add_h_to_mh(CURL *h, CURLMHLL *mh);
void cleanup_multi_stuff(CURLMHLL *mhhead);
#endif // S3FS_CURL_H_

View File

@ -18,7 +18,6 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -43,6 +42,7 @@
#include <string>
#include "s3fs.h"
#include "curl.h"
#include "cache.h"
#include "string_util.h"
@ -63,30 +63,6 @@ class auto_curl_slist {
struct curl_slist* slist;
};
// Memory structure for the alternate
// write memory callback used with curl_easy_perform
struct BodyStruct {
char *text;
size_t size;
};
// Memory structure for POST
struct WriteThis {
const char *readptr;
int sizeleft;
};
typedef struct curlhll {
CURL *handle;
struct curlhll *next;
} CURLHLL;
typedef struct curlmhll {
CURLM *handle;
struct curlhll *curlhll_head;
struct curlmhll * next;
} CURLMHLL;
typedef struct mvnode {
char *old_path;
char *new_path;
@ -115,6 +91,16 @@ struct cleanup_stuff {
}
};
struct case_insensitive_compare_func {
bool operator ()(const string &a, const string &b) {
return strcasecmp(a.c_str(), b.c_str()) < 0;
}
};
typedef map<string, string, case_insensitive_compare_func> mimes_t;
static mimes_t mimeTypes;
class auto_stuff {
public:
auto_stuff() { }
@ -128,82 +114,6 @@ private:
stuffMap_t stuffMap;
};
// homegrown timeout mechanism
static int my_curl_progress(
void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
CURL* curl = static_cast<CURL*>(clientp);
time_t now = time(0);
progress_t p(dlnow, ulnow);
//###cout << "/dlnow=" << dlnow << "/ulnow=" << ulnow << endl;
pthread_mutex_lock( &curl_handles_lock );
// any progress?
if (p != curl_progress[curl]) {
// yes!
curl_times[curl] = now;
curl_progress[curl] = p;
} else {
// timeout?
if (now - curl_times[curl] > readwrite_timeout) {
pthread_mutex_unlock( &curl_handles_lock );
syslog(LOG_ERR, "timeout now: %li curl_times[curl]: %lil readwrite_timeout: %li",
(long int)now, curl_times[curl], (long int)readwrite_timeout);
return CURLE_ABORTED_BY_CALLBACK;
}
}
pthread_mutex_unlock( &curl_handles_lock );
return 0;
}
CURL *create_curl_handle(void) {
long signal;
time_t now;
CURL *curl_handle;
pthread_mutex_lock( &curl_handles_lock );
curl_handle = curl_easy_init();
///////////////////////////////////////////////////////////
// was part of alloc_curl_handle
///////////////////////////////////////////////////////////
curl_easy_reset(curl_handle);
signal = 1;
curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, signal);
curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, connect_timeout);
curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt(curl_handle, CURLOPT_PROGRESSFUNCTION, my_curl_progress);
curl_easy_setopt(curl_handle, CURLOPT_PROGRESSDATA, curl_handle);
curl_easy_setopt(curl_handle, CURLOPT_FORBID_REUSE, 1);
now = time(0);
curl_times[curl_handle] = now;
curl_progress[curl_handle] = progress_t(-1, -1);
///////////////////////////////////////////////////////////
pthread_mutex_unlock( &curl_handles_lock );
return curl_handle;
}
void destroy_curl_handle(CURL *curl_handle) {
if(curl_handle != NULL) {
pthread_mutex_lock( &curl_handles_lock );
curl_times.erase(curl_handle);
curl_progress.erase(curl_handle);
curl_easy_cleanup(curl_handle);
pthread_mutex_unlock( &curl_handles_lock );
}
return;
}
MVNODE *create_mvnode(char *old_path, char *new_path, bool is_dir) {
MVNODE *p;
char *p_old_path;
@ -281,180 +191,6 @@ void free_mvnodes(MVNODE *head) {
return;
}
/////////////////////////////////////////////////
// Multi CURL stuff
/////////////////////////////////////////////////
CURLHLL *create_h_element(CURL *handle) {
CURLHLL *p;
p = (CURLHLL *) malloc(sizeof(CURLHLL));
if (p == NULL) {
printf("create_h_element: could not allocation memory\n");
exit(EXIT_FAILURE);
}
p->handle = handle;
p->next = NULL;
return p;
}
void add_h_element(CURLHLL *head, CURL *handle) {
CURLHLL *p;
CURLHLL *p_new;
p_new = create_h_element(handle);
for (p = head; p->next != NULL; p = p->next);
;
p->next = p_new;
return;
}
CURLMHLL *create_mh_element(CURLM *handle) {
CURLMHLL *p;
p = (CURLMHLL *) malloc(sizeof(CURLMHLL));
if (p == NULL) {
printf("create_mh_element: could not allocation memory\n");
exit(EXIT_FAILURE);
}
p->handle = handle;
p->curlhll_head = NULL;
p->next = NULL;
return p;
}
CURLMHLL *add_mh_element(CURLMHLL *head, CURLM *handle) {
CURLMHLL *p;
CURLMHLL *p_new;
p_new = create_mh_element(handle);
for (p = head; p->next != NULL; p = p->next);
;
p->next = p_new;
return p_new;
}
void add_h_to_mh(CURL *h, CURLMHLL *mh) {
CURLHLL *h_head;
h_head = mh->curlhll_head;
if(h_head == NULL) {
h_head = create_h_element(h);
mh->curlhll_head = h_head;
} else {
add_h_element(h_head, h);
}
return;
}
void cleanup_multi_stuff(CURLMHLL *mhhead) {
// move this to it's own cleanup function
CURLMHLL *my_mhhead;
CURLMHLL *pnext;
CURLHLL *cnext;
CURLHLL *chhead;
CURLMcode curlm_code;
CURL *curl_handle;
CURLM *curl_multi_handle;
if(mhhead == NULL) {
return;
}
// Remove all of the easy handles from its multi handle
my_mhhead = mhhead;
pnext = NULL;
cnext = NULL;
chhead = NULL;
do {
chhead = my_mhhead->curlhll_head;
while(chhead != NULL) {
cnext = chhead->next;
curl_multi_handle = my_mhhead->handle;
curl_handle = chhead->handle;
curlm_code = curl_multi_remove_handle(curl_multi_handle, curl_handle);
if(curlm_code != CURLM_OK) {
syslog(LOG_ERR, "curl_multi_remove_handle code: %d msg: %s",
curlm_code, curl_multi_strerror(curlm_code));
}
chhead = cnext;
}
pnext = my_mhhead->next;
my_mhhead = pnext;
} while(my_mhhead != NULL);
// now clean up the easy handles
my_mhhead = mhhead;
pnext = NULL;
cnext = NULL;
chhead = NULL;
do {
chhead = my_mhhead->curlhll_head;
while(chhead != NULL) {
cnext = chhead->next;
destroy_curl_handle(chhead->handle);
chhead = cnext;
}
pnext = my_mhhead->next;
my_mhhead = pnext;
} while(my_mhhead != NULL);
// now cleanup the multi handles
my_mhhead = mhhead;
pnext = NULL;
cnext = NULL;
chhead = NULL;
do {
pnext = my_mhhead->next;
curlm_code = curl_multi_cleanup(my_mhhead->handle);
if(curlm_code != CURLM_OK) {
syslog(LOG_ERR, "curl_multi_cleanup code: %d msg: %s",
curlm_code, curl_multi_strerror(curlm_code));
}
my_mhhead = pnext;
} while(my_mhhead != NULL);
// Now free the memory structures
my_mhhead = mhhead;
pnext = NULL;
cnext = NULL;
chhead = NULL;
do {
chhead = my_mhhead->curlhll_head;
while(chhead != NULL) {
cnext = chhead->next;
free(chhead);
chhead = cnext;
}
pnext = my_mhhead->next;
free(my_mhhead);
my_mhhead = pnext;
} while(my_mhhead != NULL);
return;
}
static string prepare_url(const char* url) {
if(debug) syslog(LOG_DEBUG, "URL is %s", url);
@ -475,265 +211,7 @@ static string prepare_url(const char* url) {
return str(url_str);
}
////////////////////////////////////////////////////////////
// locate_bundle
////////////////////////////////////////////////////////////
static void locate_bundle(void) {
// See if environment variable CURL_CA_BUNDLE is set
// if so, check it, if it is a good path, then set the
// curl_ca_bundle variable to it
char * CURL_CA_BUNDLE;
if (curl_ca_bundle.size() == 0) {
CURL_CA_BUNDLE = getenv("CURL_CA_BUNDLE");
if (CURL_CA_BUNDLE != NULL) {
// check for existance and readability of the file
ifstream BF(CURL_CA_BUNDLE);
if (BF.good()) {
BF.close();
curl_ca_bundle.assign(CURL_CA_BUNDLE);
} else {
fprintf(stderr, "%s: file specified by CURL_CA_BUNDLE environment variable is not readable\n",
program_name.c_str());
exit(EXIT_FAILURE);
}
return;
}
}
// not set by Environment Variable
// look in likely locations
///////////////////////////////////////////
// from curl's (7.21.2) acinclude.m4 file
///////////////////////////////////////////
// dnl CURL_CHECK_CA_BUNDLE
// dnl -------------------------------------------------
// dnl Check if a default ca-bundle should be used
// dnl
// dnl regarding the paths this will scan:
// dnl /etc/ssl/certs/ca-certificates.crt Debian systems
// dnl /etc/pki/tls/certs/ca-bundle.crt Redhat and Mandriva
// dnl /usr/share/ssl/certs/ca-bundle.crt old(er) Redhat
// dnl /usr/local/share/certs/ca-root.crt FreeBSD
// dnl /etc/ssl/cert.pem OpenBSD
// dnl /etc/ssl/certs/ (ca path) SUSE
ifstream BF("/etc/pki/tls/certs/ca-bundle.crt");
if (BF.good()) {
BF.close();
curl_ca_bundle.assign("/etc/pki/tls/certs/ca-bundle.crt");
return;
}
return;
}
/**
* @return fuse return code
*/
static int my_curl_easy_perform(CURL* curl, BodyStruct* body = NULL, FILE* f = 0) {
char url[256];
time_t now;
char* ptr_url = url;
curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL , &ptr_url);
if(debug)
syslog(LOG_DEBUG, "connecting to URL %s", ptr_url);
// curl_easy_setopt(curl, CURLOPT_VERBOSE, true);
if(ssl_verify_hostname.substr(0,1) == "0")
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
if(curl_ca_bundle.size() != 0)
curl_easy_setopt(curl, CURLOPT_CAINFO, curl_ca_bundle.c_str());
long responseCode;
// 1 attempt + retries...
int t = retries + 1;
while (t-- > 0) {
if (f) {
rewind(f);
}
CURLcode curlCode = curl_easy_perform(curl);
switch (curlCode) {
case CURLE_OK:
// Need to look at the HTTP response code
if (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode) != 0) {
syslog(LOG_ERR, "curl_easy_getinfo failed while trying to retrieve HTTP response code");
return -EIO;
}
if(debug)
syslog(LOG_DEBUG, "HTTP response code %ld", responseCode);
if (responseCode < 400) {
return 0;
}
if (responseCode >= 500) {
syslog(LOG_ERR, "###HTTP response=%ld", responseCode);
sleep(4);
break;
}
// Service response codes which are >= 400 && < 500
switch(responseCode) {
case 400:
if(debug) syslog(LOG_ERR, "HTTP response code 400 was returned");
if(body) {
if(body->size && debug) {
syslog(LOG_ERR, "Body Text: %s", body->text);
}
}
if(debug) syslog(LOG_DEBUG, "Now returning EIO");
return -EIO;
case 403:
if(debug) syslog(LOG_ERR, "HTTP response code 403 was returned");
if(body) {
if(body->size && debug) {
syslog(LOG_ERR, "Body Text: %s", body->text);
}
}
if(debug) syslog(LOG_DEBUG, "Now returning EIO");
return -EIO;
case 404:
if(debug) syslog(LOG_DEBUG, "HTTP response code 404 was returned");
if(body) {
if(body->size && debug) {
syslog(LOG_DEBUG, "Body Text: %s", body->text);
}
}
if(debug) syslog(LOG_DEBUG, "Now returning ENOENT");
return -ENOENT;
default:
syslog(LOG_ERR, "###response=%ld", responseCode);
printf("responseCode %ld\n", responseCode);
if(body) {
if(body->size) {
printf("Body Text %s\n", body->text);
}
}
return -EIO;
}
break;
case CURLE_WRITE_ERROR:
syslog(LOG_ERR, "### CURLE_WRITE_ERROR");
sleep(2);
break;
case CURLE_OPERATION_TIMEDOUT:
syslog(LOG_ERR, "### CURLE_OPERATION_TIMEDOUT");
sleep(2);
break;
case CURLE_COULDNT_RESOLVE_HOST:
syslog(LOG_ERR, "### CURLE_COULDNT_RESOLVE_HOST");
sleep(2);
break;
case CURLE_COULDNT_CONNECT:
syslog(LOG_ERR, "### CURLE_COULDNT_CONNECT");
sleep(4);
break;
case CURLE_GOT_NOTHING:
syslog(LOG_ERR, "### CURLE_GOT_NOTHING");
sleep(4);
break;
case CURLE_ABORTED_BY_CALLBACK:
syslog(LOG_ERR, "### CURLE_ABORTED_BY_CALLBACK");
sleep(4);
now = time(0);
curl_times[curl] = now;
break;
case CURLE_PARTIAL_FILE:
syslog(LOG_ERR, "### CURLE_PARTIAL_FILE");
sleep(4);
break;
case CURLE_SSL_CACERT:
// try to locate cert, if successful, then set the
// option and continue
if (curl_ca_bundle.size() == 0) {
locate_bundle();
if (curl_ca_bundle.size() != 0) {
t++;
curl_easy_setopt(curl, CURLOPT_CAINFO, curl_ca_bundle.c_str());
continue;
}
}
syslog(LOG_ERR, "curlCode: %i msg: %s", curlCode,
curl_easy_strerror(curlCode));;
fprintf (stderr, "%s: curlCode: %i -- %s\n",
program_name.c_str(),
curlCode,
curl_easy_strerror(curlCode));
exit(EXIT_FAILURE);
break;
#ifdef CURLE_PEER_FAILED_VERIFICATION
case CURLE_PEER_FAILED_VERIFICATION:
first_pos = bucket.find_first_of(".");
if (first_pos != string::npos) {
fprintf (stderr, "%s: curl returned a CURL_PEER_FAILED_VERIFICATION error\n", program_name.c_str());
fprintf (stderr, "%s: security issue found: buckets with periods in their name are incompatible with https\n", program_name.c_str());
fprintf (stderr, "%s: This check can be over-ridden by using the -o ssl_verify_hostname=0\n", program_name.c_str());
fprintf (stderr, "%s: The certificate will still be checked but the hostname will not be verified.\n", program_name.c_str());
fprintf (stderr, "%s: A more secure method would be to use a bucket name without periods.\n", program_name.c_str());
} else {
fprintf (stderr, "%s: my_curl_easy_perform: curlCode: %i -- %s\n",
program_name.c_str(),
curlCode,
curl_easy_strerror(curlCode));
}
exit(EXIT_FAILURE);
break;
#endif
// This should be invalid since curl option HTTP FAILONERROR is now off
case CURLE_HTTP_RETURNED_ERROR:
syslog(LOG_ERR, "### CURLE_HTTP_RETURNED_ERROR");
if (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode) != 0) {
return -EIO;
}
syslog(LOG_ERR, "###response=%ld", responseCode);
// Let's try to retrieve the
if (responseCode == 404) {
return -ENOENT;
}
if (responseCode < 500) {
return -EIO;
}
break;
// Unknown CURL return code
default:
syslog(LOG_ERR, "###curlCode: %i msg: %s", curlCode,
curl_easy_strerror(curlCode));;
exit(EXIT_FAILURE);
break;
}
syslog(LOG_ERR, "###retrying...");
}
syslog(LOG_ERR, "###giving up");
return -EIO;
}
/**
* urlEncode a fuse path,
* taking into special consideration "/",
@ -887,45 +365,6 @@ string calc_signature(
return Signature;
}
// libcurl callback
// another write callback as shown by example
// http://curl.haxx.se/libcurl/c/getinmemory.html
static size_t WriteMemoryCallback(void *ptr, size_t blockSize, size_t numBlocks, void *data) {
size_t realsize = blockSize * numBlocks;
struct BodyStruct *mem = (struct BodyStruct *)data;
mem->text = (char *)realloc(mem->text, mem->size + realsize + 1);
if (mem->text == NULL) {
/* out of memory! */
fprintf(stderr, "not enough memory (realloc returned NULL)\n");
exit(EXIT_FAILURE);
}
memcpy(&(mem->text[mem->size]), ptr, realsize);
mem->size += realsize;
mem->text[mem->size] = 0;
return realsize;
}
// read_callback
// http://curl.haxx.se/libcurl/c/post-callback.html
static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp) {
struct WriteThis *pooh = (struct WriteThis *)userp;
if(size*nmemb < 1)
return 0;
if(pooh->sizeleft) {
*(char *)ptr = pooh->readptr[0]; /* copy one single byte */
pooh->readptr++; /* advance pointer */
pooh->sizeleft--; /* less data left */
return 1; /* we return 1 byte at a time! */
}
return 0; /* no more data left to deliver */
}
static size_t header_callback(void *data, size_t blockSize, size_t numBlocks, void *userPtr) {
headers_t* headers = reinterpret_cast<headers_t*>(userPtr);
string header(reinterpret_cast<char*>(data), blockSize * numBlocks);
@ -1304,17 +743,13 @@ static int put_local_fd_small_file(const char* path, headers_t meta, int fd) {
result = my_curl_easy_perform(curl, &body, f);
//////////////////////////////////
if(body.text) {
if(body.text)
free(body.text);
}
destroy_curl_handle(curl);
if(result != 0) {
if(result != 0)
return result;
}
return 0;
}
@ -1907,10 +1342,8 @@ static int s3fs_getattr(const char *path, struct stat *stbuf) {
return 0;
}
{
if(get_stat_cache_entry(path, stbuf) == 0)
return 0;
}
if(get_stat_cache_entry(path, stbuf) == 0)
return 0;
body.text = (char *)malloc(1);
body.size = 0;
@ -2024,20 +1457,12 @@ static int s3fs_readlink(const char *path, char *buf, size_t size) {
buf[size] = 0;
}
if(fd > 0) close(fd);
if(fd > 0)
close(fd);
return 0;
}
struct case_insensitive_compare_func {
bool operator ()(const string &a, const string &b) {
return strcasecmp(a.c_str(), b.c_str()) < 0;
}
};
typedef map<string, string, case_insensitive_compare_func> mimes_t;
static mimes_t mimeTypes;
/**
* @param s e.g., "index.html"
* @return e.g., "text/html"
@ -2049,14 +1474,12 @@ string lookupMimeType(string s) {
string prefix, ext, ext2;
// No dots in name, just return
if (last_pos == string::npos) {
return result;
}
if(last_pos == string::npos)
return result;
// extract the last extension
if (last_pos != string::npos) {
if(last_pos != string::npos)
ext = s.substr(1+last_pos, string::npos);
}
if (last_pos != string::npos) {
// one dot was found, now look for another
@ -2080,9 +1503,8 @@ string lookupMimeType(string s) {
}
// return with the default result if there isn't a second extension
if (first_pos == last_pos) {
if(first_pos == last_pos)
return result;
}
// Didn't find a mime-type for the first extension
// Look for second extension in mimeTypes, return if found
@ -2097,7 +1519,6 @@ string lookupMimeType(string s) {
return result;
}
////////////////////////////////////////////////////////
// common function for creation of a plain object
////////////////////////////////////////////////////////
@ -2137,19 +1558,14 @@ static int create_file_object(const char *path, mode_t mode) {
curl_easy_setopt(curl, CURLOPT_URL, my_url.c_str());
result = my_curl_easy_perform(curl);
destroy_curl_handle(curl);
if(result != 0) {
return result;
}
if(result != 0)
return result;
return 0;
}
////////////////////////////////////////////////////////
// s3fs_mknod
////////////////////////////////////////////////////////
static int s3fs_mknod(const char *path, mode_t mode, dev_t rdev) {
int result;
if(foreground)
@ -2167,9 +1583,6 @@ static int s3fs_mknod(const char *path, mode_t mode, dev_t rdev) {
return 0;
}
////////////////////////////////////////////////////////////
// s3fs_create
////////////////////////////////////////////////////////////
static int s3fs_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
int result;
headers_t meta;
@ -2377,15 +1790,14 @@ static int s3fs_rmdir(const char *path) {
result = my_curl_easy_perform(curl_handle);
if(body.text) {
if(body.text)
free(body.text);
}
destroy_curl_handle(curl);
destroy_curl_handle(curl_handle);
if(result != 0) {
if(result != 0)
return result;
}
// delete cache entry
delete_stat_cache_entry(path);
@ -2410,19 +1822,25 @@ static int s3fs_symlink(const char *from, const char *to) {
return -errno;
}
if (pwrite(fd, from, strlen(from), 0) == -1) {
if(pwrite(fd, from, strlen(from), 0) == -1) {
syslog(LOG_ERR, "%d###result=%d", __LINE__, -errno);
if(fd > 0) close(fd);
if(fd > 0)
close(fd);
return -errno;
}
result = put_local_fd(to, headers, fd);
if (result != 0) {
if(fd > 0) close(fd);
if(result != 0) {
if(fd > 0)
close(fd);
return result;
}
if(fd > 0) close(fd);
if(fd > 0)
close(fd);
return 0;
}
@ -2430,27 +1848,30 @@ static int rename_object( const char *from, const char *to) {
int result;
headers_t meta;
if(foreground) {
if(foreground)
cout << "rename_object[from=" << from << "][to=" << to << "]" << endl;
}
if(debug) syslog(LOG_DEBUG, "rename_object [from=%s] [to=%s]", from, to);
if(debug)
syslog(LOG_DEBUG, "rename_object [from=%s] [to=%s]", from, to);
if(debug)
syslog(LOG_DEBUG, " rename_object: calling get_headers....");
if(debug) syslog(LOG_DEBUG, " rename_object: calling get_headers....");
result = get_headers(from, meta);
if(debug) syslog(LOG_DEBUG, " rename_object: returning from get_headers....");
if(result != 0) {
return result;
}
if(debug)
syslog(LOG_DEBUG, " rename_object: returning from get_headers....");
if(result != 0)
return result;
meta["x-amz-copy-source"] = urlEncode("/" + bucket + from);
meta["Content-Type"] = lookupMimeType(to);
meta["x-amz-metadata-directive"] = "REPLACE";
result = put_headers(to, meta);
if (result != 0) {
if(result != 0)
return result;
}
result = s3fs_unlink(from);
@ -2467,23 +1888,20 @@ static int clone_directory_object( const char *from, const char *to) {
// create the new directory object
result = s3fs_mkdir(to, mode);
if ( result != 0) {
if(result != 0)
return result;
}
// and transfer its attributes
result = get_headers(from, meta);
if(result != 0) {
return result;
}
if(result != 0)
return result;
meta["x-amz-copy-source"] = urlEncode("/" + bucket + from);
meta["x-amz-metadata-directive"] = "REPLACE";
result = put_headers(to, meta);
if (result != 0) {
if(result != 0)
return result;
}
return 0;
}
@ -2504,7 +1922,8 @@ static int rename_directory( const char *from, const char *to) {
if(foreground)
cout << "rename_directory[from=" << from << "][to=" << to << "]" << endl;
if(debug) syslog(LOG_DEBUG, "rename_directory [from=%s] [to=%s]", from, to);
if(debug)
syslog(LOG_DEBUG, "rename_directory [from=%s] [to=%s]", from, to);
CURL *curl;
struct BodyStruct body;
@ -2528,9 +1947,7 @@ static int rename_directory( const char *from, const char *to) {
tail = head;
// printf("back from create_mvnode\n");
while (IsTruncated == "true") {
string resource = urlEncode(service_path + bucket);
// string query = "delimiter=/&prefix=";
string query = "prefix=";
@ -2666,8 +2083,6 @@ static int rename_directory( const char *from, const char *to) {
// push this one onto the stack
tail = add_mvnode(head, (char *)path.c_str(), (char *)new_path.c_str(), is_dir);
}
} // if (cur_node->children != NULL) {
@ -2681,7 +2096,6 @@ static int rename_directory( const char *from, const char *to) {
NextMarker = Key;
}
} // while (IsTruncated == "true") {
if(body.text) {
@ -2690,9 +2104,8 @@ static int rename_directory( const char *from, const char *to) {
// iterate over the list - clone directories first - top down
if(head == NULL) {
return 0;
}
if(head == NULL)
return 0;
MVNODE *my_head;
MVNODE *my_tail;
@ -2740,7 +2153,6 @@ static int rename_directory( const char *from, const char *to) {
my_head = next;
} while(my_head != NULL);
// Iterate over old the directories, bottoms up and remove
do {
// printf("old_path: %s\n", my_tail->old_path);
@ -2764,13 +2176,14 @@ static int rename_directory( const char *from, const char *to) {
}
static int s3fs_rename(const char *from, const char *to) {
struct stat buf;
int result;
if(foreground)
cout << "rename[from=" << from << "][to=" << to << "]" << endl;
if(debug) syslog(LOG_DEBUG, "rename [from=%s] [to=%s]", from, to);
struct stat buf;
int result;
if(debug)
syslog(LOG_DEBUG, "rename [from=%s] [to=%s]", from, to);
s3fs_getattr(from, &buf);
@ -2791,14 +2204,14 @@ static int s3fs_link(const char *from, const char *to) {
static int s3fs_chmod(const char *path, mode_t mode) {
int result;
if(foreground)
cout << "chmod[path=" << path << "][mode=" << mode << "]" << endl;
headers_t meta;
if(foreground)
cout << "chmod[path=" << path << "][mode=" << mode << "]" << endl;
result = get_headers(path, meta);
if(result != 0) {
return result;
}
if(result != 0)
return result;
meta["x-amz-meta-mode"] = str(mode);
meta["x-amz-copy-source"] = urlEncode("/" + bucket + path);
@ -2809,7 +2222,6 @@ static int s3fs_chmod(const char *path, mode_t mode) {
return put_headers(path, meta);
}
static int s3fs_chown(const char *path, uid_t uid, gid_t gid) {
int result;
if(foreground)
@ -2849,9 +2261,8 @@ static int s3fs_truncate(const char *path, off_t size) {
headers_t meta;
result = get_headers(path, meta);
if(result != 0) {
if(result != 0)
return result;
}
fd = fileno(tmpfile());
if(fd == -1) {
@ -2972,9 +2383,8 @@ static int s3fs_release(const char *path, struct fuse_file_info *fi) {
if(foreground)
cout << "s3fs_release[path=" << path << "][fd=" << fd << "]" << endl;
if (close(fd) == -1) {
if(close(fd) == -1)
YIKES(-errno);
}
if((fi->flags & O_RDWR) || (fi->flags & O_WRONLY))
delete_stat_cache_entry(path);
@ -3196,7 +2606,6 @@ static int s3fs_readdir(
return -EIO;
}
while (running_handles) {
fd_set read_fd_set;
fd_set write_fd_set;
@ -3459,7 +2868,6 @@ static int list_multipart_uploads(void) {
resource.append("?uploads");
// printf("resource: %s\n", resource.c_str());
url = host + resource;
// printf("url: %s\n", url.c_str());

View File

@ -24,29 +24,21 @@
return result; \
}
typedef std::pair<double, double> progress_t;
long connect_timeout = 10;
time_t readwrite_timeout = 30;
static long connect_timeout = 10;
static time_t readwrite_timeout = 30;
// static stack<CURL*> curl_handles;
static pthread_mutex_t curl_handles_lock;
static std::map<CURL*, time_t> curl_times;
static std::map<CURL*, progress_t> curl_progress;
static int retries = 2;
int retries = 2;
static std::string bucket;
static std::string mountpoint;
static std::string program_name;
std::string program_name;
static std::string AWSAccessKeyId;
static std::string AWSSecretAccessKey;
static std::string host = "http://s3.amazonaws.com";
static std::string curl_ca_bundle;
static mode_t root_mode = 0;
static std::string service_path = "/";
static std::string passwd_file = "";
static bool debug = 0;
bool debug = 0;
static bool utility_mode = 0;
bool foreground = 0;
unsigned long max_stat_cache_size = 10000;
@ -54,10 +46,12 @@ unsigned long max_stat_cache_size = 10000;
// if .size()==0 then local file cache is disabled
static std::string use_cache;
static std::string use_rrs;
static std::string ssl_verify_hostname = "1";
std::string ssl_verify_hostname = "1";
static std::string public_bucket;
extern pthread_mutex_t stat_cache_lock;
extern pthread_mutex_t curl_handles_lock;
extern std::string curl_ca_bundle;
// TODO(apetresc): make this an enum
// private, public-read, public-read-write, authenticated-read
@ -102,7 +96,6 @@ static int s3fs_unlink(const char *path);
static int s3fs_rmdir(const char *path);
static int s3fs_symlink(const char *from, const char *to);
static int s3fs_rename(const char *from, const char *to);
static int s3fs_link(const char *from, const char *to);
static int s3fs_chmod(const char *path, mode_t mode);
static int s3fs_chown(const char *path, uid_t uid, gid_t gid);
@ -122,5 +115,4 @@ static int s3fs_utimens(const char *path, const struct timespec ts[2]);
static void* s3fs_init(struct fuse_conn_info *conn);
static void s3fs_destroy(void*);
#endif // S3FS_S3_H_