From 6bf5cd6384d045c00d66893c5a4d37319bf4e00c Mon Sep 17 00:00:00 2001 From: root Date: Sun, 1 Jun 2014 00:11:56 +0400 Subject: [PATCH 01/14] Remove 10k per directory limitation --- cloudfsapi.c | 29 +++++++++++++++++++++++++---- cloudfsapi.h | 1 + 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index 583964d..a6cbb05 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -23,6 +23,9 @@ #define REQUEST_RETRIES 4 +// defined by Rackspace +#define MAX_RESULTS_PER_REQUEST 10000 + static char storage_url[MAX_URL_SIZE]; static char storage_token[MAX_HEADER_SIZE]; static pthread_mutex_t pool_mut; @@ -284,17 +287,16 @@ int cloudfs_object_truncate(const char *path, off_t size) return (response >= 200 && response < 300); } -int cloudfs_list_directory(const char *path, dir_entry **dir_list) +int cloudfs_list_directory_internal(const char *path, dir_entry **dir_list) { char container[MAX_PATH_SIZE * 3] = ""; char object[MAX_PATH_SIZE] = ""; char last_subdir[MAX_PATH_SIZE] = ""; int prefix_length = 0; int response = 0; - int retval = 0; + int retval = -1; int entry_count = 0; - *dir_list = NULL; xmlNode *onode = NULL, *anode = NULL, *text_node = NULL; xmlParserCtxtPtr xmlctx = xmlCreatePushParserCtxt(NULL, NULL, "", 0, NULL); if (!strcmp(path, "") || !strcmp(path, "/")) @@ -325,10 +327,16 @@ int cloudfs_list_directory(const char *path, dir_entry **dir_list) curl_free(encoded_object); } + if (*dir_list != NULL) { + strcat(container, "&marker="); + strcat(container, (*dir_list)->marker); + } + printf("%s\n", container); response = send_request("GET", container, NULL, xmlctx, NULL); xmlParseChunk(xmlctx, "", 0, 1); if (xmlctx->wellFormed && response >= 200 && response < 300) { + retval = 0; xmlNode *root_element = xmlDocGetRootElement(xmlctx->myDoc); for (onode = root_element->children; onode; onode = onode->next) { @@ -363,6 +371,7 @@ int cloudfs_list_directory(const char *path, dir_entry **dir_list) if (slash && (0 == *(slash + 1))) *slash = 0; + de->marker = strdup(content); if (asprintf(&(de->full_name), "%s/%s", path, de->name) < 0) de->full_name = NULL; } @@ -396,13 +405,13 @@ int cloudfs_list_directory(const char *path, dir_entry **dir_list) } de->next = *dir_list; *dir_list = de; + retval++; } else { debugf("unknown element: %s", onode->name); } } - retval = 1; } debugf("entry count: %d", entry_count); @@ -412,6 +421,18 @@ int cloudfs_list_directory(const char *path, dir_entry **dir_list) return retval; } +int cloudfs_list_directory(const char *path, dir_entry **dir_list) +{ + int retval; + *dir_list = NULL; + + do { + retval = cloudfs_list_directory_internal(path, dir_list); + } while(retval == MAX_RESULTS_PER_REQUEST); + + return retval == -1 ? 0 : 1; +} + void cloudfs_free_dir_list(dir_entry *dir_list) { while (dir_list) diff --git a/cloudfsapi.h b/cloudfsapi.h index b816640..1ba2bef 100644 --- a/cloudfsapi.h +++ b/cloudfsapi.h @@ -21,6 +21,7 @@ typedef struct dir_entry time_t last_modified; int isdir; struct dir_entry *next; + char *marker; } dir_entry; void cloudfs_init(); From ba7b5ecd91fbb53f37172177282d3835f6944292 Mon Sep 17 00:00:00 2001 From: Nash Yeung Date: Sat, 28 Jun 2014 15:31:39 +0800 Subject: [PATCH 02/14] Dirty hack to allow listing >10k files --- cloudfsapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index a6cbb05..2113754 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -428,7 +428,7 @@ int cloudfs_list_directory(const char *path, dir_entry **dir_list) do { retval = cloudfs_list_directory_internal(path, dir_list); - } while(retval == MAX_RESULTS_PER_REQUEST); + } while(retval > 0); return retval == -1 ? 0 : 1; } From 0b52ec023f35a66813f63caba392c031658ea1fe Mon Sep 17 00:00:00 2001 From: Nash Yeung Date: Sat, 28 Jun 2014 16:43:13 +0800 Subject: [PATCH 03/14] Fix URL not encoded problem --- cloudfsapi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index 2113754..ae09afa 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -328,8 +328,10 @@ int cloudfs_list_directory_internal(const char *path, dir_entry **dir_list) } if (*dir_list != NULL) { + char *encoded_marker = curl_escape((*dir_list)->marker, 0); strcat(container, "&marker="); - strcat(container, (*dir_list)->marker); + strcat(container, encoded_marker); + curl_free(encoded_marker); } printf("%s\n", container); response = send_request("GET", container, NULL, xmlctx, NULL); @@ -428,6 +430,7 @@ int cloudfs_list_directory(const char *path, dir_entry **dir_list) do { retval = cloudfs_list_directory_internal(path, dir_list); + printf("retval: %d\n", retval); } while(retval > 0); return retval == -1 ? 0 : 1; From d854287d6cb493e7fefe6de3e905554b94ff90fa Mon Sep 17 00:00:00 2001 From: Am1GO Date: Wed, 16 Jul 2014 22:17:02 +0400 Subject: [PATCH 04/14] Hard-link calls now mapped to Swift Copy Object requests --- README | 7 +++++-- cloudfsapi.c | 3 --- cloudfuse.c | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/README b/README index f706f59..40ae36e 100644 --- a/README +++ b/README @@ -104,8 +104,9 @@ BUGS/SHORTCOMINGS: containers in cloudfiles. * Directory entries are created as empty files with the content-type "application/directory". - * Cloud Files limits container and object listings to 10,000 items. - cloudfuse won't list more than that many files in a single directory. + * Cloud Files container and object listings no more limited to 10,000 items. + * Hard-link calls are mapped to Swift Copy Object requests: + http://docs.openstack.org/api/openstack-object-storage/1.0/content/copy-object.html AWESOME CONTRIBUTORS: @@ -118,6 +119,8 @@ AWESOME CONTRIBUTORS: * David Brownlee https://github.com/abs0 * Mike Lundy https://github.com/novas0x2a * justinb https://github.com/justinsb + * Am1GO https://github.com/Am1GO + * nashyeung https://github.com/nashyeung Thanks, and I hope you find it useful. diff --git a/cloudfsapi.c b/cloudfsapi.c index ae09afa..0b4a5d0 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -23,9 +23,6 @@ #define REQUEST_RETRIES 4 -// defined by Rackspace -#define MAX_RESULTS_PER_REQUEST 10000 - static char storage_url[MAX_URL_SIZE]; static char storage_token[MAX_HEADER_SIZE]; static pthread_mutex_t pool_mut; diff --git a/cloudfuse.c b/cloudfuse.c index cd99528..8ff2871 100644 --- a/cloudfuse.c +++ b/cloudfuse.c @@ -407,6 +407,22 @@ static int cfs_rename(const char *src, const char *dst) return -EIO; } +static int cfs_link(const char *src, const char *dst) +{ + dir_entry *src_de = path_info(src); + if (!src_de) + return -ENOENT; + if (src_de->isdir) + return -EISDIR; + if (cloudfs_copy_object(src, dst)) + { + /* FIXME this isn't quite right as doesn't preserve last modified */ + update_dir_cache(dst, src_de->size, 0); + return 0; + } + return -EIO; +} + static void *cfs_init(struct fuse_conn_info *conn) { signal(SIGPIPE, SIG_IGN); @@ -536,6 +552,7 @@ int main(int argc, char **argv) .chmod = cfs_chmod, .chown = cfs_chown, .rename = cfs_rename, + .link = cfs_link, .init = cfs_init, }; From a7e8314657dd8136e607f8600e4a542988ab0cf3 Mon Sep 17 00:00:00 2001 From: Am1GO Date: Wed, 16 Jul 2014 22:23:52 +0400 Subject: [PATCH 05/14] Increased curl pool size and request retries count for better compatibility with hubic --- cloudfsapi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index 0b4a5d0..29f5f30 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -21,12 +21,12 @@ #define RHEL5_LIBCURL_VERSION 462597 #define RHEL5_CERTIFICATE_FILE "/etc/pki/tls/certs/ca-bundle.crt" -#define REQUEST_RETRIES 4 +#define REQUEST_RETRIES 24 static char storage_url[MAX_URL_SIZE]; static char storage_token[MAX_HEADER_SIZE]; static pthread_mutex_t pool_mut; -static CURL *curl_pool[1024]; +static CURL *curl_pool[4096]; static int curl_pool_count = 0; static int debug = 0; static int verify_ssl = 1; From 0c8f2df36c56dd8dd8ff32b0c33637478413ef66 Mon Sep 17 00:00:00 2001 From: Am1GO Date: Thu, 17 Jul 2014 09:51:53 +0400 Subject: [PATCH 06/14] Reverted ba7b5ecd91fbb53f37172177282d3835f6944292 as it completely broke listings (dupes, inability to remove directory) --- cloudfsapi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index 29f5f30..87eb55f 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -23,6 +23,9 @@ #define REQUEST_RETRIES 24 +// defined by Rackspace +#define MAX_RESULTS_PER_REQUEST 10000 + static char storage_url[MAX_URL_SIZE]; static char storage_token[MAX_HEADER_SIZE]; static pthread_mutex_t pool_mut; @@ -427,8 +430,8 @@ int cloudfs_list_directory(const char *path, dir_entry **dir_list) do { retval = cloudfs_list_directory_internal(path, dir_list); - printf("retval: %d\n", retval); - } while(retval > 0); + debugf("cloudfs_list_directory_internal retval: %d\n", retval); + } while(retval >= MAX_RESULTS_PER_REQUEST); return retval == -1 ? 0 : 1; } From 80fe1350282890877cf71728e1da323484500aef Mon Sep 17 00:00:00 2001 From: Am1GO Date: Thu, 17 Jul 2014 11:56:50 +0400 Subject: [PATCH 07/14] Fixed valgrind warning on tm last_modified: Conditional jump or move depends on uninitialised value(s) --- cloudfsapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index 87eb55f..dfb6de1 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -388,7 +388,7 @@ int cloudfs_list_directory_internal(const char *path, dir_entry **dir_list) } if (!strcasecmp((const char *)anode->name, "last_modified")) { - struct tm last_modified; + struct tm last_modified = {0}; strptime(content, "%FT%T", &last_modified); de->last_modified = mktime(&last_modified); } From a53016ee4895c5cf2b5876c4fb67fd3f93574abe Mon Sep 17 00:00:00 2001 From: Am1GO Date: Thu, 17 Jul 2014 14:28:54 +0400 Subject: [PATCH 08/14] Statfs call now handled properly stat data is taken from Swift response headers --- cloudfsapi.c | 66 ++++++++++++++++++++++++++++++++++++++-------------- cloudfuse.c | 15 ++++-------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index dfb6de1..cb262aa 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -17,6 +17,7 @@ #include #include "cloudfsapi.h" #include "config.h" +#include #define RHEL5_LIBCURL_VERSION 462597 #define RHEL5_CERTIFICATE_FILE "/etc/pki/tls/certs/ca-bundle.crt" @@ -34,6 +35,17 @@ static int curl_pool_count = 0; static int debug = 0; static int verify_ssl = 1; static int rhel5_mode = 0; +static struct statvfs statcache = { + .f_bsize = 1, + .f_frsize = 1, + .f_blocks = INT_MAX, + .f_bfree = INT_MAX, + .f_bavail = INT_MAX, + .f_files = INT_MAX, + .f_ffree = 0, + .f_favail = 0, + .f_namemax = INT_MAX +}; #ifdef HAVE_OPENSSL #include @@ -95,6 +107,30 @@ static void add_header(curl_slist **headers, const char *name, *headers = curl_slist_append(*headers, x_header); } +static size_t header_dispatch(void *ptr, size_t size, size_t nmemb, void *stream) +{ + debugf("Dispatching response headers"); + char *header = (char *)alloca(size * nmemb + 1); + char *head = (char *)alloca(size * nmemb + 1); + char *value = (char *)alloca(size * nmemb + 1); + memcpy(header, (char *)ptr, size * nmemb); + header[size * nmemb] = '\0'; + if (sscanf(header, "%[^:]: %[^\r\n]", head, value) == 2) + { + if (!strncasecmp(head, "x-auth-token", size * nmemb)) + strncpy(storage_token, value, sizeof(storage_token)); + if (!strncasecmp(head, "x-storage-url", size * nmemb)) + strncpy(storage_url, value, sizeof(storage_url)); + if (!strncasecmp(head, "x-account-meta-quota", size * nmemb)) + statcache.f_blocks = strtoul(value, NULL, 10); + if (!strncasecmp(head, "x-account-bytes-used", size * nmemb)) + statcache.f_bfree = statcache.f_bavail = statcache.f_blocks - strtoul(value, NULL, 10); + if (!strncasecmp(head, "x-account-object-count", size * nmemb)) + statcache.f_files = strtoul(value, NULL, 10); + } + return size * nmemb; +} + static int send_request(char *method, const char *path, FILE *fp, xmlParserCtxtPtr xmlctx, curl_slist *extra_headers) { @@ -167,7 +203,10 @@ static int send_request(char *method, const char *path, FILE *fp, } } else + { curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &header_dispatch); + } /* add the headers from extra_headers if any */ curl_slist *extra; for (extra = extra_headers; extra; extra = extra->next) @@ -192,23 +231,6 @@ static int send_request(char *method, const char *path, FILE *fp, return response; } -static size_t header_dispatch(void *ptr, size_t size, size_t nmemb, void *stream) -{ - char *header = (char *)alloca(size * nmemb + 1); - char *head = (char *)alloca(size * nmemb + 1); - char *value = (char *)alloca(size * nmemb + 1); - memcpy(header, (char *)ptr, size * nmemb); - header[size * nmemb] = '\0'; - if (sscanf(header, "%[^:]: %[^\r\n]", head, value) == 2) - { - if (!strncasecmp(head, "x-auth-token", size * nmemb)) - strncpy(storage_token, value, sizeof(storage_token)); - if (!strncasecmp(head, "x-storage-url", size * nmemb)) - strncpy(storage_url, value, sizeof(storage_url)); - } - return size * nmemb; -} - /* * Public interface */ @@ -469,6 +491,16 @@ int cloudfs_copy_object(const char *src, const char *dst) return (response >= 200 && response < 300); } +int cloudfs_statfs(const char *path, struct statvfs *stat) +{ + int response = send_request("HEAD", "/", NULL, NULL, NULL); + + debugf("Assigning statvfs values from cache."); + *stat = statcache; + + return (response >= 200 && response < 300); +} + int cloudfs_create_directory(const char *path) { char *encoded = curl_escape(path, 0); diff --git a/cloudfuse.c b/cloudfuse.c index 8ff2871..6d06491 100644 --- a/cloudfuse.c +++ b/cloudfuse.c @@ -369,16 +369,11 @@ static int cfs_truncate(const char *path, off_t size) static int cfs_statfs(const char *path, struct statvfs *stat) { - stat->f_bsize = 4096; - stat->f_frsize = 4096; - stat->f_blocks = INT_MAX; - stat->f_bfree = stat->f_blocks; - stat->f_bavail = stat->f_blocks; - stat->f_files = INT_MAX; - stat->f_ffree = INT_MAX; - stat->f_favail = INT_MAX; - stat->f_namemax = INT_MAX; - return 0; + if (cloudfs_statfs(path, stat)){ + return 0; + } + else + return -EIO; } static int cfs_chown(const char *path, uid_t uid, gid_t gid) From de800c535321aef7b5c06bb11ceedfda9daf4c4b Mon Sep 17 00:00:00 2001 From: Am1GO Date: Fri, 18 Jul 2014 11:28:59 +0400 Subject: [PATCH 09/14] Fixed truncate on open (O_WRONLY) and file append bugs: https://github.com/redbo/cloudfuse/issues/62 https://github.com/redbo/cloudfuse/issues/57 --- cloudfuse.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cloudfuse.c b/cloudfuse.c index 6d06491..b69eead 100644 --- a/cloudfuse.c +++ b/cloudfuse.c @@ -270,9 +270,9 @@ static int cfs_open(const char *path, struct fuse_file_info *info) { FILE *temp_file = tmpfile(); dir_entry *de = path_info(path); - if (!(info->flags & O_WRONLY)) + if (!(info->flags & O_TRUNC)) { - if (!cloudfs_object_write_fp(path, temp_file)) + if (!cloudfs_object_write_fp(path, temp_file) && !(info->flags & O_CREAT)) { fclose(temp_file); return -ENOENT; @@ -285,6 +285,7 @@ static int cfs_open(const char *path, struct fuse_file_info *info) of->flags = info->flags; info->fh = (uintptr_t)of; info->direct_io = 1; + info->nonseekable = 1; // requires fuse>=2.8 return 0; } From 1a595c1408df274fc12f9d99851ef7c7decaaed2 Mon Sep 17 00:00:00 2001 From: Am1GO Date: Thu, 31 Jul 2014 22:14:40 +0400 Subject: [PATCH 10/14] Fixed bug with seeking (nonseekable fuse option) Bug was introduced in de800c535321aef7b5c06bb11ceedfda9daf4c4b --- cloudfuse.c | 1 - 1 file changed, 1 deletion(-) diff --git a/cloudfuse.c b/cloudfuse.c index b69eead..0dfb3e6 100644 --- a/cloudfuse.c +++ b/cloudfuse.c @@ -285,7 +285,6 @@ static int cfs_open(const char *path, struct fuse_file_info *info) of->flags = info->flags; info->fh = (uintptr_t)of; info->direct_io = 1; - info->nonseekable = 1; // requires fuse>=2.8 return 0; } From 87bbe37cf16a7bbbedf4183fcc6b0783e20b0213 Mon Sep 17 00:00:00 2001 From: Am1GO Date: Fri, 1 Aug 2014 00:05:02 +0400 Subject: [PATCH 11/14] No more hanging on DELETE requests when swift returns 404 code --- cloudfsapi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index cb262aa..6e24276 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -222,6 +222,8 @@ static int send_request(char *method, const char *path, FILE *fp, return_connection(curl); if (response >= 200 && response < 400) return response; + if (response == 404 && !strcasecmp(method, "DELETE")) + return response; sleep(8 << tries); // backoff if (response == 401 && !cloudfs_connect()) // re-authenticate on 401s return response; @@ -476,7 +478,7 @@ int cloudfs_delete_object(const char *path) char *encoded = curl_escape(path, 0); int response = send_request("DELETE", encoded, NULL, NULL, NULL); curl_free(encoded); - return (response >= 200 && response < 300); + return ((response >= 200 && response < 300) || response == 404); } int cloudfs_copy_object(const char *src, const char *dst) From 7ba60e8cc7cb222313a1889edfaa21adebdab9da Mon Sep 17 00:00:00 2001 From: Am1GO Date: Thu, 25 Sep 2014 10:28:43 +0400 Subject: [PATCH 12/14] No more memleaks on de->marker --- cloudfsapi.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index 6e24276..56d63dd 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -466,10 +466,18 @@ void cloudfs_free_dir_list(dir_entry *dir_list) { dir_entry *de = dir_list; dir_list = dir_list->next; - free(de->name); - free(de->full_name); - free(de->content_type); - free(de); + if (NULL != de){ + free(de->name); + de->name = NULL; + free(de->marker); + de->marker = NULL; + free(de->full_name); + de->full_name = NULL; + free(de->content_type); + de->content_type = NULL; + free(de); + de = NULL; + } } } From 09fbd2b899d120126e1ecdc5309d9a28b442e393 Mon Sep 17 00:00:00 2001 From: Am1GO Date: Mon, 29 Sep 2014 10:07:02 +0400 Subject: [PATCH 13/14] No more segfaults on free(de->marker) --- cloudfsapi.c | 2 +- cloudfuse.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index 56d63dd..6c86ec9 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -397,7 +397,7 @@ int cloudfs_list_directory_internal(const char *path, dir_entry **dir_list) if (slash && (0 == *(slash + 1))) *slash = 0; - de->marker = strdup(content); + de->marker = strdup(de->name); if (asprintf(&(de->full_name), "%s/%s", path, de->name) < 0) de->full_name = NULL; } diff --git a/cloudfuse.c b/cloudfuse.c index 0dfb3e6..b325a15 100644 --- a/cloudfuse.c +++ b/cloudfuse.c @@ -113,6 +113,7 @@ static void update_dir_cache(const char *path, off_t size, int isdir) de->size = size; de->isdir = isdir; de->name = strdup(&path[strlen(cw->path)+1]); + de->marker = strdup(de->name); de->full_name = strdup(path); de->content_type = strdup(isdir ? "application/directory" : "application/octet-stream"); de->last_modified = time(NULL); From 6ac76fdd889cfaa52c1aade69e85af9da5db73fd Mon Sep 17 00:00:00 2001 From: Am1GO Date: Mon, 24 Aug 2015 12:51:12 +0300 Subject: [PATCH 14/14] A little bit more confident inodes indication --- cloudfsapi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index 6c86ec9..4b4fa23 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -42,8 +42,8 @@ static struct statvfs statcache = { .f_bfree = INT_MAX, .f_bavail = INT_MAX, .f_files = INT_MAX, - .f_ffree = 0, - .f_favail = 0, + .f_ffree = INT_MAX, + .f_favail = INT_MAX, .f_namemax = INT_MAX }; @@ -126,7 +126,7 @@ static size_t header_dispatch(void *ptr, size_t size, size_t nmemb, void *stream if (!strncasecmp(head, "x-account-bytes-used", size * nmemb)) statcache.f_bfree = statcache.f_bavail = statcache.f_blocks - strtoul(value, NULL, 10); if (!strncasecmp(head, "x-account-object-count", size * nmemb)) - statcache.f_files = strtoul(value, NULL, 10); + statcache.f_ffree = statcache.f_bavail = statcache.f_files - strtoul(value, NULL, 10); } return size * nmemb; }