in turbonfs/src/main.cpp [616:942]
int main(int argc, char *argv[])
{
// Initialize logger first thing.
init_log();
AZLogInfo("aznfsclient version {}.{}.{}",
AZNFSCLIENT_VERSION_MAJOR,
AZNFSCLIENT_VERSION_MINOR,
AZNFSCLIENT_VERSION_PATCH);
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
struct fuse_session *se = NULL;
struct fuse_cmdline_opts opts;
struct fuse_loop_config *loop_config = fuse_loop_cfg_create();
int ret = -1;
int wait_iter;
std::string mount_source;
std::string extra_options;
/*
* There can only be 1 reader of this pipe. Hence, we should ensure we
* don't send messages multiple times to avoid wait loops.
*/
bool status_pipe_closed = false;
// Check if the status mount pipe is set.
const char *pipe_name = std::getenv("MOUNT_STATUS_PIPE");
if (!pipe_name) {
status_pipe_closed = true;
AZLogWarn("MOUNT_STATUS_PIPE environment variable is not set.");
}
/* Don't mask creation mode, kernel already did that */
umask(0);
/*
* Parse general cmdline options first for properly honoring help
* and debug level arguments.
*/
if (fuse_parse_cmdline(&args, &opts) != 0) {
goto err_out0;
}
if (opts.mountpoint == nullptr) {
AZLogError("Mountpoint must be provided!");
goto err_out0;
}
if (opts.show_help) {
aznfsc_help(argv[0]);
fuse_cmdline_help();
fuse_lowlevel_help();
ret = 0;
goto err_out1;
} else if (opts.show_version) {
printf("FUSE library version %s\n", fuse_pkgversion());
fuse_lowlevel_version();
ret = 0;
goto err_out1;
}
/*
* If -d or "-o debug" cmdline option was passed, reset log level to
* debug.
*/
if (opts.debug) {
enable_debug_logs = true;
spdlog::set_level(spdlog::level::debug);
}
// Parse fuse_conn_info_opts options like -o writeback_cache.
fuse_conn_info_opts_ptr = fuse_parse_conn_info_opts(&args);
// Parse aznfsclient specific options.
if (fuse_opt_parse(&args, &aznfsc_cfg, aznfsc_opts, NULL) == -1) {
goto err_out1;
}
/*
* TODO: Add validity checks for aznfsc_cfg cmdline options, similar to
* parse_config_yaml().
*/
// Parse config yaml if --config-yaml option provided.
if (!aznfsc_cfg.parse_config_yaml()) {
goto err_out1;
}
/*
* If config yaml had debug config set to true, reset log level to debug.
*/
if (aznfsc_cfg.debug) {
opts.debug = true;
}
if (opts.debug) {
enable_debug_logs = true;
spdlog::set_level(spdlog::level::debug);
}
/*
* account and container are mandatory parameters which do not have a
* default value, so ensure they are set before proceeding further.
*/
if (aznfsc_cfg.account == nullptr) {
AZLogError("Account name must be set either from cmdline or config yaml!");
goto err_out1;
}
if (aznfsc_cfg.container == nullptr) {
AZLogError("Container name must be set either from cmdline or config yaml!");
goto err_out1;
}
aznfsc_cfg.mountpoint = opts.mountpoint;
// Set default values for config variables not set using the above.
if (!aznfsc_cfg.set_defaults_and_sanitize()) {
AZLogError("Error setting one or more default config!");
goto err_out1;
}
/*
* Honour "-o max_threads=" cmdline option, else use the fuse_max_threads
* value from the config, if set.
*/
if (opts.max_threads == 10 /* FUSE_LOOP_MT_DEF_MAX_THREADS */) {
if (aznfsc_cfg.fuse_max_threads != -1) {
opts.max_threads = aznfsc_cfg.fuse_max_threads;
}
}
/*
* Honour "-o max_idle_threads=" cmdline option, else use the
* fuse_max_idle_threads value from the config, if set.
*/
if (opts.max_idle_threads == (UINT_MAX) -1 /* FUSE_LOOP_MT_DEF_IDLE_THREADS */) {
if (aznfsc_cfg.fuse_max_idle_threads != -1) {
opts.max_idle_threads = aznfsc_cfg.fuse_max_idle_threads;
}
}
/*
* Hide fuse'ism and behave like a normal POSIX fs.
* Note that we ask fuse to do the permission checks instead of the NFS
* server. This way we get 16+ groups handling for free.
* TODO: Make this configurable?
*
* Also set fsname to the correct mount source for clearer mount output.
* Also PID of the fuse process is useful to associate a mount with the
* fuse process, which helps in debugging.
*/
mount_source = aznfsc_cfg.server + ":" + aznfsc_cfg.export_path +
"[PID=" + std::to_string(::getpid()) + "][vers=" +
std::to_string(AZNFSCLIENT_VERSION_MAJOR) + "." +
std::to_string(AZNFSCLIENT_VERSION_MINOR) + "." +
std::to_string(AZNFSCLIENT_VERSION_PATCH) + "]";
extra_options = std::string("-oallow_other,default_permissions,fsname=") + mount_source;
if (fuse_opt_add_arg(&args, extra_options.c_str()) == -1) {
goto err_out1;
}
se = fuse_session_new(&args, &aznfsc_ll_ops, sizeof(aznfsc_ll_ops),
&nfs_client::get_instance());
if (se == NULL) {
AZLogError("fuse_session_new failed");
goto err_out1;
}
if (fuse_set_signal_handlers(se) != 0) {
AZLogError("fuse_set_signal_handlers failed");
goto err_out2;
}
#ifdef ENABLE_RELEASE_BUILD
block_termination_signals();
#endif
/*
* Setup SIGUSR1 handler for dumping RPC stats.
*/
if (set_signal_handler(SIGUSR1, handle_usr1) != 0) {
AZLogError("set_signal_handler(SIGUSR1) failed: {}", ::strerror(errno));
goto err_out3;
}
if (fuse_session_mount(se, opts.mountpoint) != 0) {
AZLogError("fuse_session_mount failed");
goto err_out3;
}
if (fuse_daemonize(opts.foreground) != 0) {
AZLogError("fuse_daemonize failed");
goto err_out4;
}
if (aznfsc_cfg.auth) {
// Set the auth token callback for this connection if auth is enabled.
set_auth_token_callback(get_auth_token_and_setargs_cb);
}
/*
* Initialize nfs_client singleton.
* This creates the libnfs polling thread(s) and hence it MUST be called
* after fuse_daemonize(), else those threads will get killed.
*/
if (!nfs_client::get_instance().init()) {
AZLogError("Failed to init the NFS client");
goto err_out4;
}
client_started = true;
AZLogInfo("==> Aznfsclient fuse driver ready to serve requests!");
// Open the pipe for writing.
if (!status_pipe_closed) {
std::ofstream pipe(pipe_name);
if (!pipe.is_open()) {
AZLogError("Aznfsclient unable to send mount status on pipe.");
} else {
pipe << 0 << endl;
status_pipe_closed = true;
}
}
if (opts.singlethread) {
ret = fuse_session_loop(se);
} else {
fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads);
fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
ret = fuse_session_loop_mt(se, loop_config);
}
/*
* We come here when user unmounts the fuse filesystem.
*/
AZLogInfo("Shutting down!");
/*
* Clear the stats signal, else it may cause a crash if received while
* we start cleaning up things.
*/
if (set_signal_handler(SIGUSR1, SIG_DFL) != 0) {
AZLogWarn("set_signal_handler(SIG_DFL) failed: {}", ::strerror(errno));
/* Continue and hope we don't get the signal */
}
/*
* After we exit the fuse session loop above, libfuse won't read any more
* messages from kernel, but we may have some fuse messages that we have
* received but still not responded. We must wait for those fuse messages
* to be responded before proceeding with the tear down.
*/
wait_iter = 0;
while (rpc_stats_az::fuse_responses_awaited) {
if (wait_iter++ == 100) {
AZLogWarn("Giving up on {} pending fuse requests",
rpc_stats_az::fuse_responses_awaited.load());
break;
}
AZLogWarn("Waiting for {} pending fuse requests to complete",
rpc_stats_az::fuse_responses_awaited.load());
/*
* 100ms wait should be large enough to let those requests complete
* and small enough to not make unmount wait unnecessarily long.
*/
::usleep(100 * 1000);
}
err_out4:
fuse_loop_cfg_destroy(loop_config);
/*
* Note: fuse_session_unmount() calls kernel umount which causes a statfs()
* call. This causes statfs_callback() to be called in libnfs thread
* context. TSAN shows a data race with this thread which is winding
* down fuse data structures.
*/
fuse_session_unmount(se);
err_out3:
fuse_remove_signal_handlers(se);
err_out2:
fuse_session_destroy(se);
err_out1:
free(opts.mountpoint);
fuse_opt_free_args(&args);
err_out0:
if (!status_pipe_closed && ret != 0) {
// Open the pipe for writing.
std::ofstream pipe(pipe_name);
if (!pipe.is_open()) {
AZLogError("Aznfsclient unable to send mount status on pipe.");
} else {
// If is_azlogin_required is true, share the error code = -2 over the pipe.
if (is_azlogin_required) {
ret = -2;
AZLogError("Not logged in using 'az login' when auth is enabled");
pipe << ret << endl;
} else if (!status_pipe_error_string.empty()) {
ret = -3;
AZLogError("Returing error string '-3 {}' on the pipe", status_pipe_error_string);
pipe << "-3 " << status_pipe_error_string << endl;
} else {
// TODO: Extend this with meaningful error codes.
pipe << ret << endl;
}
status_pipe_closed = true;
}
return 1;
}
/*
* Shutdown the client after fuse cleanup is performed so that we don't
* get any more requests from fuse.
*/
if (client_started) {
nfs_client::get_instance().shutdown();
}
return ret ? 1 : 0;
}