void stats_srv_add_op_history (StatSrv *stat_srv, const gchar *str) { // stats server is disabled if (!conf_get_boolean (application_get_conf (stat_srv->app), "statistics.enabled")) return; // maintain queue size while (g_queue_get_length (stat_srv->q_op_history) + 1 >= conf_get_uint (application_get_conf (stat_srv->app), "statistics.history_size")) { gchar *tmp = g_queue_pop_tail (stat_srv->q_op_history); g_free (tmp); } g_queue_push_head (stat_srv->q_op_history, g_strdup (str)); }
static void fileio_release_update_headers (FileIO *fop) { // update MD5 headers only if versioning is disabled if (conf_get_boolean (application_get_conf (fop->app), "s3.versioning")) { LOG_debug (FIO_LOG, INO_H"File uploaded !", INO_T (fop->ino)); fileio_destroy (fop); } else { if (!client_pool_get_client (application_get_write_client_pool (fop->app), fileio_release_on_update_headers_con_cb, fop)) { LOG_err (FIO_LOG, INO_H"Failed to get HTTP client !", INO_T (fop->ino)); fileio_destroy (fop); return; } } }
StatSrv *stat_srv_create (Application *app) { StatSrv *stat_srv; stat_srv = g_new0 (StatSrv, 1); stat_srv->app = app; stat_srv->q_op_history = g_queue_new (); stat_srv->boot_time = time (NULL); // stats server is disabled if (!conf_get_boolean (application_get_conf (stat_srv->app), "statistics.enabled")) { return stat_srv; } stat_srv->http = evhttp_new (application_get_evbase (app)); if (!stat_srv->http) { LOG_err (STAT_LOG, "Failed to create statistics server !"); return NULL; } // bind if (evhttp_bind_socket (stat_srv->http, conf_get_string (application_get_conf (stat_srv->app), "statistics.host"), conf_get_int (application_get_conf (stat_srv->app), "statistics.port")) == -1) { LOG_err (STAT_LOG, "Failed to bind statistics server to %s:%d", conf_get_string (application_get_conf (stat_srv->app), "statistics.host"), conf_get_int (application_get_conf (stat_srv->app), "statistics.port") ); return NULL; } // install handlers evhttp_set_cb (stat_srv->http, conf_get_string (application_get_conf (stat_srv->app), "statistics.stats_path"), stat_srv_on_stats_cb, stat_srv); evhttp_set_gencb (stat_srv->http, stat_srv_on_gen_cb, stat_srv); return stat_srv; }
/*{{{ main */ int main (int argc, char *argv[]) { Application *app; gboolean verbose = FALSE; gboolean version = FALSE; GError *error = NULL; GOptionContext *context; gchar **s_params = NULL; gchar **s_config = NULL; gboolean foreground = FALSE; gchar conf_str[1023]; struct stat st; gchar **cache_dir = NULL; gchar **s_fuse_opts = NULL; gchar **s_log_file = NULL; guint32 part_size = 0; gboolean disable_syslog = FALSE; gboolean disable_stats = FALSE; gboolean force_head_requests = FALSE; gint uid = -1; gint gid = -1; gint fmode = -1; gint dmode = -1; struct event_config *ev_config; srand (time (NULL)); app = g_new0 (Application, 1); app->conf_path = g_build_filename (SYSCONFDIR, "riofs.conf.xml", NULL); g_snprintf (conf_str, sizeof (conf_str), "Path to configuration file. Default: %s", app->conf_path); GOptionEntry entries[] = { { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &s_params, NULL, NULL }, { "config", 'c', 0, G_OPTION_ARG_FILENAME_ARRAY, &s_config, conf_str, NULL}, { "uid", 0, 0, G_OPTION_ARG_INT, &uid, "Set UID of filesystem owner.", NULL }, { "gid", 0, 0, G_OPTION_ARG_INT, &gid, "Set GID of filesystem owner.", NULL }, { "fmode", 0, 0, G_OPTION_ARG_INT, &fmode, "Set mode for all files.", NULL }, { "dmode", 0, 0, G_OPTION_ARG_INT, &dmode, "Set mode for all directories.", NULL }, { "foreground", 'f', 0, G_OPTION_ARG_NONE, &foreground, "Flag. Do not daemonize process.", NULL }, { "cache-dir", 0, 0, G_OPTION_ARG_STRING_ARRAY, &cache_dir, "Set cache directory.", NULL }, { "fuse-options", 'o', 0, G_OPTION_ARG_STRING_ARRAY, &s_fuse_opts, "Fuse options.", "\"opt[,opt...]\"" }, { "disable-syslog", 0, 0, G_OPTION_ARG_NONE, &disable_syslog, "Flag. Disable logging to syslog.", NULL }, { "disable-stats", 0, 0, G_OPTION_ARG_NONE, &disable_stats, "Flag. Disable Statistics HTTP interface.", NULL }, { "part-size", 0, 0, G_OPTION_ARG_INT, &part_size, "Set file part size (in bytes).", NULL }, { "log-file", 'l', 0, G_OPTION_ARG_STRING_ARRAY, &s_log_file, "File to write output.", NULL }, { "force-head-requests", 0, 0, G_OPTION_ARG_NONE, &force_head_requests, "Flag. Send HEAD request for each file.", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Verbose output.", NULL }, { "version", 'V', 0, G_OPTION_ARG_NONE, &version, "Show application version and exit.", NULL }, { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } }; // init libraries CRYPTO_set_mem_functions (g_malloc0, g_realloc, g_free); ENGINE_load_builtin_engines (); ENGINE_register_all_complete (); ERR_load_crypto_strings (); OpenSSL_add_all_algorithms (); #ifdef SSL_ENABLED SSL_load_error_strings (); SSL_library_init (); #endif g_random_set_seed (time (NULL)); // init main app structure ev_config = event_config_new (); #if defined(__APPLE__) // method select is the preferred method on OS X. kqueue and poll are not supported. event_config_avoid_method (ev_config, "kqueue"); event_config_avoid_method (ev_config, "poll"); #endif app->evbase = event_base_new_with_config (ev_config); event_config_free (ev_config); if (!app->evbase) { LOG_err (APP_LOG, "Failed to create event base !"); application_destroy (app); return -1; } app->dns_base = evdns_base_new (app->evbase, 1); if (!app->dns_base) { LOG_err (APP_LOG, "Failed to create DNS base !"); application_destroy (app); return -1; } app->f_log = NULL; app->log_file_name = NULL; /*{{{ cmd line args */ // parse command line options context = g_option_context_new ("[bucketname] [mountpoint]"); g_option_context_add_main_entries (context, entries, NULL); g_option_context_set_description (context, "Please set both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables!"); if (!g_option_context_parse (context, &argc, &argv, &error)) { g_fprintf (stderr, "Failed to parse command line options: %s\n", error->message); application_destroy (app); g_option_context_free (context); return -1; } g_option_context_free (context); // check if --version is specified if (version) { g_fprintf (stdout, "RioFS File System v%s\n", VERSION); g_fprintf (stdout, "Copyright (C) 2012-2014 Paul Ionkin <*****@*****.**>\n"); g_fprintf (stdout, "Copyright (C) 2012-2014 Skoobe GmbH. All rights reserved.\n"); g_fprintf (stdout, "Libraries:\n"); g_fprintf (stdout, " GLib: %d.%d.%d libevent: %s fuse: %d.%d", GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION, LIBEVENT_VERSION, FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION ); #if defined(__APPLE__) || defined(__FreeBSD__) || !defined(__GLIBC__) g_fprintf (stdout, "\n"); #else g_fprintf (stdout, " glibc: %s\n", gnu_get_libc_version ()); #endif g_fprintf (stdout, "Features:\n"); g_fprintf (stdout, " libevent backend method: %s\n", event_base_get_method(app->evbase)); #ifdef SSL_ENABLED g_fprintf (stdout, " SSL enabled\n"); #endif /* { int i; const char **methods = event_get_supported_methods (); g_fprintf (stdout, " Available libevent backend methods:\n"); for (i = 0; methods[i] != NULL; ++i) { g_fprintf (stdout, " %s\n", methods[i]); } } */ return 0; } if (!s_params || g_strv_length (s_params) != 2) { LOG_err (APP_LOG, "Wrong number of provided arguments!\nTry `%s --help' for more information.", argv[0]); application_destroy (app); return -1; } if (verbose) log_level = LOG_debug; else log_level = LOG_msg; /*}}}*/ /*{{{ parse config file */ // user provided alternative config path if (s_config && g_strv_length (s_config) > 0) { g_free (app->conf_path); app->conf_path = g_strdup (s_config[0]); g_strfreev (s_config); } app->conf = conf_create (); if (access (app->conf_path, R_OK) == 0) { LOG_debug (APP_LOG, "Using config file: %s", app->conf_path); if (!conf_parse_file (app->conf, app->conf_path)) { LOG_err (APP_LOG, "Failed to parse configuration file: %s", app->conf_path); application_destroy (app); return -1; } } else { LOG_err (APP_LOG, "Configuration file is not found !"); application_destroy (app); return -1; } if (!conf_check_keys (app->conf, conf_keys_str, conf_keys_len)) { LOG_err (APP_LOG, "Configuration file is missing keys, please re-check your configuration file: %s", app->conf_path); application_destroy (app); return -1; } if (disable_syslog) { conf_set_boolean (app->conf, "log.use_syslog", FALSE); } // update logging settings logger_set_syslog (conf_get_boolean (app->conf, "log.use_syslog")); logger_set_color (conf_get_boolean (app->conf, "log.use_color")); if (cache_dir && g_strv_length (cache_dir) > 0) { conf_set_string (app->conf, "filesystem.cache_dir", cache_dir[0]); g_strfreev (cache_dir); } if (!verbose) log_level = conf_get_int (app->conf, "log.level"); if (uid >= 0) conf_set_int (app->conf, "filesystem.uid", uid); if (gid >= 0) conf_set_int (app->conf, "filesystem.gid", gid); if (fmode >= 0) conf_set_int (app->conf, "filesystem.file_mode", fmode); if (dmode >= 0) conf_set_int (app->conf, "filesystem.dir_mode", dmode); /*}}}*/ // try to get access parameters from the environment if (getenv ("AWS_ACCESS_KEY_ID")) { conf_set_string (app->conf, "s3.access_key_id", getenv ("AWS_ACCESS_KEY_ID")); // else check if it's set it the config file } else { if (!conf_node_exists (app->conf, "s3.access_key_id")) { LOG_err (APP_LOG, "Environment variables are not set!\nTry `%s --help' for more information.", argv[0]); application_destroy (app); return -1; } } if (getenv ("AWS_SECRET_ACCESS_KEY")) { conf_set_string (app->conf, "s3.secret_access_key", getenv ("AWS_SECRET_ACCESS_KEY")); } else { if (!conf_node_exists (app->conf, "s3.secret_access_key")) { LOG_err (APP_LOG, "Environment variables are not set!\nTry `%s --help' for more information.", argv[0]); application_destroy (app); return -1; } } // check if both strings are set if (!conf_get_string (app->conf, "s3.access_key_id") || !conf_get_string (app->conf, "s3.secret_access_key")) { LOG_err (APP_LOG, "Environment variables are not set!\nTry `%s --help' for more information.", argv[0]); application_destroy (app); return -1; } // foreground is set if (foreground) conf_set_boolean (app->conf, "app.foreground", foreground); if (part_size) conf_set_uint (app->conf, "s3.part_size", part_size); if (disable_stats) conf_set_boolean (app->conf, "statistics.enabled", FALSE); if (force_head_requests) conf_set_boolean (app->conf, "s3.force_head_requests_on_lookup", TRUE); else conf_set_boolean (app->conf, "s3.force_head_requests_on_lookup", FALSE); conf_set_string (app->conf, "s3.bucket_name", s_params[0]); if (!application_set_url (app, conf_get_string (app->conf, "s3.endpoint"))) { application_destroy (app); return -1; } if (s_fuse_opts && g_strv_length (s_fuse_opts) > 0) { app->fuse_opts = g_strdup (s_fuse_opts[0]); g_strfreev (s_fuse_opts); } if (s_log_file && g_strv_length (s_log_file) > 0) { app->log_file_name = g_strdup (s_log_file[0]); app->f_log = fopen (s_log_file[0], "a+"); if (!app->f_log) { LOG_err (APP_LOG, "Failed to open log file: %s Error: %s", s_log_file[0], strerror (errno)); application_destroy (app); return -1; } LOG_debug (APP_LOG, "Using %s for storing application logs.", s_log_file[0]); logger_set_file (app->f_log); g_strfreev (s_log_file); } conf_set_string (app->conf, "app.mountpoint", s_params[1]); // check if directory exists if (stat (conf_get_string (app->conf, "app.mountpoint"), &st) == -1) { LOG_err (APP_LOG, "Mountpoint %s does not exist! Please check directory permissions!", conf_get_string (app->conf, "app.mountpoint")); application_destroy (app); return -1; } // check if it's a directory if (!S_ISDIR (st.st_mode)) { LOG_err (APP_LOG, "Mountpoint %s is not a directory!", conf_get_string (app->conf, "app.mountpoint")); application_destroy (app); return -1; } g_strfreev (s_params); #ifdef SSL_ENABLED app->ssl_ctx = SSL_CTX_new (SSLv23_client_method ()); if (!app->ssl_ctx) { LOG_err (APP_LOG, "Failed to initialize SSL engine !"); application_exit (app); return -1; } SSL_CTX_set_options (app->ssl_ctx, SSL_OP_ALL); #endif #ifdef MAGIC_ENABLED app->magic_ctx = magic_open(MAGIC_MIME_TYPE); if (!app->magic_ctx) { LOG_err(APP_LOG, "Failed to initialize magic library\n"); return -1; } if (magic_load(app->magic_ctx, NULL)) { LOG_err(APP_LOG, "Failed to load magic database: %s\n", magic_error(app->magic_ctx)); magic_close(app->magic_ctx); return -1; } #endif app->stat_srv = stat_srv_create (app); if (!app->stat_srv) { application_exit (app); return -1; } // perform the initial request to get bucket ACL (handles redirect as well) app->service_con = http_connection_create (app); if (!app->service_con) { application_destroy (app); return -1; } bucket_client_get (app->service_con, "/?acl", application_on_bucket_acl_cb, app); // start the loop event_base_dispatch (app->evbase); application_destroy (app); return 0; }
/*{{{ application_finish_initialization_and_run */ static gint application_finish_initialization_and_run (Application *app) { struct sigaction sigact; /*{{{ create Pools */ // create ClientPool for reading operations app->read_client_pool = client_pool_create (app, conf_get_int (app->conf, "pool.readers"), http_connection_create, http_connection_destroy, http_connection_set_on_released_cb, http_connection_check_rediness, http_connection_get_stats_info_caption, http_connection_get_stats_info_data ); if (!app->read_client_pool) { LOG_err (APP_LOG, "Failed to create ClientPool !"); application_exit (app); return -1; } // create ClientPool for writing operations app->write_client_pool = client_pool_create (app, conf_get_int (app->conf, "pool.writers"), http_connection_create, http_connection_destroy, http_connection_set_on_released_cb, http_connection_check_rediness, http_connection_get_stats_info_caption, http_connection_get_stats_info_data ); if (!app->write_client_pool) { LOG_err (APP_LOG, "Failed to create ClientPool !"); application_exit (app); return -1; } // create ClientPool for various operations app->ops_client_pool = client_pool_create (app, conf_get_int (app->conf, "pool.operations"), http_connection_create, http_connection_destroy, http_connection_set_on_released_cb, http_connection_check_rediness, http_connection_get_stats_info_caption, http_connection_get_stats_info_data ); if (!app->ops_client_pool) { LOG_err (APP_LOG, "Failed to create ClientPool !"); application_exit (app); return -1; } /*}}}*/ /*{{{ CacheMng */ app->cmng = cache_mng_create (app); if (!app->cmng) { LOG_err (APP_LOG, "Failed to create CacheMng !"); application_exit (app); return -1; } /*}}}*/ /*{{{ DirTree*/ app->dir_tree = dir_tree_create (app); if (!app->dir_tree) { LOG_err (APP_LOG, "Failed to create DirTree !"); application_exit (app); return -1; } /*}}}*/ /*{{{ FUSE*/ app->rfuse = rfuse_new (app, conf_get_string (app->conf, "app.mountpoint"), app->fuse_opts); if (!app->rfuse) { LOG_err (APP_LOG, "Failed to create FUSE fs ! Mount point: %s", conf_get_string (app->conf, "app.mountpoint")); application_exit (app); return -1; } /*}}}*/ // set global App variable _app = app; /*{{{ signal handlers*/ // SIGINT app->sigint_ev = evsignal_new (app->evbase, SIGINT, sigint_cb, app); event_add (app->sigint_ev, NULL); // SIGSEGV sigact.sa_sigaction = sigsegv_cb; sigact.sa_flags = (int)SA_RESETHAND | SA_SIGINFO; sigemptyset (&sigact.sa_mask); if (sigaction (SIGSEGV, &sigact, (struct sigaction *) NULL) != 0) { LOG_err (APP_LOG, "error setting signal handler for %d (%s)\n", SIGSEGV, strsignal(SIGSEGV)); application_exit (app); return 1; } // SIGTERM app->sigterm_ev = evsignal_new (app->evbase, SIGTERM, sigterm_cb, app); event_add (app->sigterm_ev, NULL); // SIGABRT sigact.sa_sigaction = sigsegv_cb; sigact.sa_flags = (int)SA_RESETHAND | SA_SIGINFO; sigemptyset (&sigact.sa_mask); if (sigaction (SIGABRT, &sigact, (struct sigaction *) NULL) != 0) { LOG_err (APP_LOG, "error setting signal handler for %d (%s)\n", SIGABRT, strsignal(SIGABRT)); application_exit (app); return 1; } // SIGPIPE app->sigpipe_ev = evsignal_new (app->evbase, SIGPIPE, sigpipe_cb, app); event_add (app->sigpipe_ev, NULL); // SIGUSR1 app->sigusr1_ev = evsignal_new (app->evbase, SIGUSR1, sigusr1_cb, app); event_add (app->sigusr1_ev, NULL); // SIGUSR2 app->sigusr2_ev = evsignal_new (app->evbase, SIGUSR2, sigusr2_cb, app); event_add (app->sigusr2_ev, NULL); /*}}}*/ if (!conf_get_boolean (app->conf, "app.foreground")) fuse_daemonize (0); return 0; }
static void fileio_read_on_head_cb (HttpConnection *con, void *ctx, gboolean success, G_GNUC_UNUSED const gchar *buf, G_GNUC_UNUSED size_t buf_len, struct evkeyvalq *headers) { FileReadData *rdata = (FileReadData *) ctx; const char *content_len_header; DirTree *dtree; // release HttpConnection http_connection_release (con); if (!success) { LOG_err (FIO_LOG, INO_CON_H"Failed to get HEAD from server !", INO_T (rdata->ino), con); rdata->on_buffer_read_cb (rdata->ctx, FALSE, NULL, 0); g_free (rdata); return; } rdata->fop->head_req_sent = TRUE; // update DirTree dtree = application_get_dir_tree (rdata->fop->app); dir_tree_set_entry_exist (dtree, rdata->ino); // consistency checking: // 1. check local and remote file sizes content_len_header = http_find_header (headers, "Content-Length"); if (content_len_header) { guint64 local_size = 0; gint64 size = 0; size = strtoll ((char *)content_len_header, NULL, 10); if (size < 0) { LOG_err (FIO_LOG, INO_CON_H"Header contains incorrect file size!", INO_T (rdata->ino), con); size = 0; } rdata->fop->file_size = size; LOG_debug (FIO_LOG, INO_H"Remote file size: %"G_GUINT64_FORMAT, INO_T (rdata->ino), rdata->fop->file_size); local_size = cache_mng_get_file_length (application_get_cache_mng (rdata->fop->app), rdata->ino); if (local_size != rdata->fop->file_size) { LOG_debug (FIO_LOG, INO_H"Local and remote file sizes do not match, invalidating local cached file!", INO_T (rdata->ino)); cache_mng_remove_file (application_get_cache_mng (rdata->fop->app), rdata->ino); } } // 2. use one of the following ways to check that local and remote files are identical // if versioning is enabled: compare version IDs // if bucket has versioning disabled: compare MD5 sums if (conf_get_boolean (application_get_conf (rdata->fop->app), "s3.versioning")) { const char *versioning_header = http_find_header (headers, "x-amz-version-id"); if (versioning_header) { const gchar *local_version_id = cache_mng_get_version_id (application_get_cache_mng (rdata->fop->app), rdata->ino); if (local_version_id && !strcmp (local_version_id, versioning_header)) { LOG_debug (FIO_LOG, INO_H"Both version IDs match, using local cached file!", INO_T (rdata->ino)); } else { LOG_debug (FIO_LOG, INO_H"Version IDs do not match, invalidating local cached file!: %s %s", INO_T (rdata->ino), local_version_id, versioning_header); cache_mng_remove_file (application_get_cache_mng (rdata->fop->app), rdata->ino); } // header was not found } else { LOG_debug (FIO_LOG, INO_H"Versioning header was not found, invalidating local cached file!", INO_T (rdata->ino)); cache_mng_remove_file (application_get_cache_mng (rdata->fop->app), rdata->ino); } //check for MD5 } else { const char *md5_header = http_find_header (headers, "x-amz-meta-md5"); if (md5_header) { gchar *md5str = NULL; // at this point we have both remote and local MD5 sums if (cache_mng_get_md5 (application_get_cache_mng (rdata->fop->app), rdata->ino, &md5str)) { if (!strncmp (md5_header, md5str, 32)) { LOG_debug (FIO_LOG, INO_H"MD5 sums match, using local cached file!", INO_T (rdata->ino)); } else { LOG_debug (FIO_LOG, INO_H"MD5 sums do not match, invalidating local cached file!", INO_T (rdata->ino)); cache_mng_remove_file (application_get_cache_mng (rdata->fop->app), rdata->ino); } } else { LOG_debug (FIO_LOG, INO_H"Failed to get local MD5 sum, invalidating local cached file!", INO_T (rdata->ino)); cache_mng_remove_file (application_get_cache_mng (rdata->fop->app), rdata->ino); } if (md5str) g_free (md5str); // header was not found } else { LOG_debug (FIO_LOG, INO_H"MD5 sum header was not found, invalidating local cached file!", INO_T (rdata->ino)); cache_mng_remove_file (application_get_cache_mng (rdata->fop->app), rdata->ino); } } // resume downloading file fileio_read_get_buf (rdata); }