/* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. * Copyright (C) 2004-2012 Red Hat, Inc. All rights reserved. * * This file is part of LVM2. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License v.2.1. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "lib.h" #include "toolcontext.h" #include "metadata.h" #include "defaults.h" #include "lvm-string.h" #include "activate.h" #include "filter.h" #include "filter-composite.h" #include "filter-md.h" #include "filter-mpath.h" #include "filter-persistent.h" #include "filter-regex.h" #include "filter-sysfs.h" #include "label.h" #include "lvm-file.h" #include "format-text.h" #include "display.h" #include "memlock.h" #include "str_list.h" #include "segtype.h" #include "lvmcache.h" #include "lvmetad.h" #include "dev-cache.h" #include "archiver.h" #ifdef HAVE_LIBDL #include "sharedlib.h" #endif #ifdef LVM1_INTERNAL #include "format1.h" #endif #ifdef POOL_INTERNAL #include "format_pool.h" #endif #include #include #include #include #include #ifdef linux # include #endif static const size_t linebuffer_size = 4096; static int _get_env_vars(struct cmd_context *cmd) { const char *e; /* Set to "" to avoid using any system directory */ if ((e = getenv("LVM_SYSTEM_DIR"))) { if (dm_snprintf(cmd->system_dir, sizeof(cmd->system_dir), "%s", e) < 0) { log_error("LVM_SYSTEM_DIR environment variable " "is too long."); return 0; } } return 1; } static void _get_sysfs_dir(struct cmd_context *cmd) { static char proc_mounts[PATH_MAX]; static char *split[4], buffer[PATH_MAX + 16]; FILE *fp; char *sys_mnt = NULL; cmd->sysfs_dir[0] = '\0'; if (!*cmd->proc_dir) { log_debug("No proc filesystem found: skipping sysfs detection"); return; } if (dm_snprintf(proc_mounts, sizeof(proc_mounts), "%s/mounts", cmd->proc_dir) < 0) { log_error("Failed to create /proc/mounts string for sysfs detection"); return; } if (!(fp = fopen(proc_mounts, "r"))) { log_sys_error("_get_sysfs_dir fopen", proc_mounts); return; } while (fgets(buffer, sizeof(buffer), fp)) { if (dm_split_words(buffer, 4, 0, split) == 4 && !strcmp(split[2], "sysfs")) { sys_mnt = split[1]; break; } } if (fclose(fp)) log_sys_error("fclose", proc_mounts); if (!sys_mnt) { log_error("Failed to find sysfs mount point"); return; } strncpy(cmd->sysfs_dir, sys_mnt, sizeof(cmd->sysfs_dir)); } static int _parse_debug_classes(struct cmd_context *cmd) { const struct dm_config_node *cn; const struct dm_config_value *cv; int debug_classes = 0; if (!(cn = find_config_tree_node(cmd, log_debug_classes_CFG))) return DEFAULT_LOGGED_DEBUG_CLASSES; for (cv = cn->v; cv; cv = cv->next) { if (cv->type != DM_CFG_STRING) { log_verbose("log/debug_classes contains a value " "which is not a string. Ignoring."); continue; } if (!strcasecmp(cv->v.str, "all")) return -1; if (!strcasecmp(cv->v.str, "memory")) debug_classes |= LOG_CLASS_MEM; else if (!strcasecmp(cv->v.str, "devices")) debug_classes |= LOG_CLASS_DEVS; else if (!strcasecmp(cv->v.str, "activation")) debug_classes |= LOG_CLASS_ACTIVATION; else if (!strcasecmp(cv->v.str, "allocation")) debug_classes |= LOG_CLASS_ALLOC; else if (!strcasecmp(cv->v.str, "lvmetad")) debug_classes |= LOG_CLASS_LVMETAD; else if (!strcasecmp(cv->v.str, "metadata")) debug_classes |= LOG_CLASS_METADATA; else if (!strcasecmp(cv->v.str, "cache")) debug_classes |= LOG_CLASS_CACHE; else if (!strcasecmp(cv->v.str, "locking")) debug_classes |= LOG_CLASS_LOCKING; else log_verbose("Unrecognised value for log/debug_classes: %s", cv->v.str); } return debug_classes; } static void _init_logging(struct cmd_context *cmd) { int append = 1; time_t t; const char *log_file; char timebuf[26]; /* Syslog */ cmd->default_settings.syslog = find_config_tree_bool(cmd, log_syslog_CFG); if (cmd->default_settings.syslog != 1) fin_syslog(); if (cmd->default_settings.syslog > 1) init_syslog(cmd->default_settings.syslog); /* Debug level for log file output */ cmd->default_settings.debug = find_config_tree_int(cmd, log_level_CFG); init_debug(cmd->default_settings.debug); /* * Suppress all non-essential stdout? * -qq can override the default of 0 to 1 later. * Once set to 1, there is no facility to change it back to 0. */ cmd->default_settings.silent = silent_mode() ? : find_config_tree_bool(cmd, log_silent_CFG); init_silent(cmd->default_settings.silent); /* Verbose level for tty output */ cmd->default_settings.verbose = find_config_tree_bool(cmd, log_verbose_CFG); init_verbose(cmd->default_settings.verbose + VERBOSE_BASE_LEVEL); /* Log message formatting */ init_indent(find_config_tree_bool(cmd, log_indent_CFG)); init_abort_on_internal_errors(find_config_tree_bool(cmd, global_abort_on_internal_errors_CFG)); cmd->default_settings.msg_prefix = find_config_tree_str_allow_empty(cmd, log_prefix_CFG); init_msg_prefix(cmd->default_settings.msg_prefix); cmd->default_settings.cmd_name = find_config_tree_bool(cmd, log_command_names_CFG); init_cmd_name(cmd->default_settings.cmd_name); /* Test mode */ cmd->default_settings.test = find_config_tree_bool(cmd, global_test_CFG); init_test(cmd->default_settings.test); /* Settings for logging to file */ if (find_config_tree_bool(cmd, log_overwrite_CFG)) append = 0; log_file = find_config_tree_str(cmd, log_file_CFG); if (log_file) { release_log_memory(); fin_log(); init_log_file(log_file, append); } log_file = find_config_tree_str(cmd, log_activate_file_CFG); if (log_file) init_log_direct(log_file, append); init_log_while_suspended(find_config_tree_bool(cmd, log_activation_CFG)); cmd->default_settings.debug_classes = _parse_debug_classes(cmd); log_debug("Setting log debug classes to %d", cmd->default_settings.debug_classes); init_debug_classes_logged(cmd->default_settings.debug_classes); t = time(NULL); ctime_r(&t, &timebuf[0]); timebuf[24] = '\0'; log_verbose("Logging initialised at %s", timebuf); /* Tell device-mapper about our logging */ #ifdef DEVMAPPER_SUPPORT dm_log_with_errno_init(print_log); #endif reset_log_duplicated(); reset_lvm_errno(1); } static int _check_disable_udev(const char *msg) { if (getenv("DM_DISABLE_UDEV")) { log_very_verbose("DM_DISABLE_UDEV environment variable set. " "Overriding configuration to use " "udev_rules=0, udev_sync=0, verify_udev_operations=1."); if (udev_is_running()) log_warn("Udev is running and DM_DISABLE_UDEV environment variable is set. " "Bypassing udev, LVM will %s.", msg); return 1; } return 0; } #ifdef UDEV_SYNC_SUPPORT /* * Until the DM_UEVENT_GENERATED_FLAG was introduced in kernel patch * 856a6f1dbd8940e72755af145ebcd806408ecedd * some operations could not be performed by udev, requiring our fallback code. */ static int _dm_driver_has_stable_udev_support(void) { char vsn[80]; unsigned maj, min, patchlevel; return driver_version(vsn, sizeof(vsn)) && (sscanf(vsn, "%u.%u.%u", &maj, &min, &patchlevel) == 3) && (maj == 4 ? min >= 18 : maj > 4); } #endif static int _process_config(struct cmd_context *cmd) { mode_t old_umask; const char *read_ahead; struct stat st; const struct dm_config_node *cn; const struct dm_config_value *cv; int64_t pv_min_kb; const char *lvmetad_socket; int udev_disabled = 0; if (!config_def_check(cmd, 0, 0, 0) && find_config_tree_bool(cmd, config_abort_on_errors_CFG)) { log_error("LVM configuration invalid."); return 0; } /* umask */ cmd->default_settings.umask = find_config_tree_int(cmd, global_umask_CFG); if ((old_umask = umask((mode_t) cmd->default_settings.umask)) != (mode_t) cmd->default_settings.umask) log_verbose("Set umask from %04o to %04o", old_umask, cmd->default_settings.umask); /* dev dir */ if (dm_snprintf(cmd->dev_dir, sizeof(cmd->dev_dir), "%s/", find_config_tree_str(cmd, devices_dir_CFG)) < 0) { log_error("Device directory given in config file too long"); return 0; } #ifdef DEVMAPPER_SUPPORT dm_set_dev_dir(cmd->dev_dir); if (!dm_set_uuid_prefix("LVM-")) return_0; #endif /* proc dir */ if (dm_snprintf(cmd->proc_dir, sizeof(cmd->proc_dir), "%s", find_config_tree_str(cmd, global_proc_CFG)) < 0) { log_error("Device directory given in config file too long"); return 0; } if (*cmd->proc_dir && !dir_exists(cmd->proc_dir)) { log_warn("WARNING: proc dir %s not found - some checks will be bypassed", cmd->proc_dir); cmd->proc_dir[0] = '\0'; } /* FIXME Use global value of sysfs_dir everywhere instead cmd->sysfs_dir. */ _get_sysfs_dir(cmd); set_sysfs_dir_path(cmd->sysfs_dir); dm_set_sysfs_dir(cmd->sysfs_dir); /* activation? */ cmd->default_settings.activation = find_config_tree_bool(cmd, global_activation_CFG); set_activation(cmd->default_settings.activation); cmd->default_settings.suffix = find_config_tree_bool(cmd, global_suffix_CFG); if (!(cmd->default_settings.unit_factor = units_to_bytes(find_config_tree_str(cmd, global_units_CFG), &cmd->default_settings.unit_type))) { log_error("Invalid units specification"); return 0; } read_ahead = find_config_tree_str(cmd, activation_readahead_CFG); if (!strcasecmp(read_ahead, "auto")) cmd->default_settings.read_ahead = DM_READ_AHEAD_AUTO; else if (!strcasecmp(read_ahead, "none")) cmd->default_settings.read_ahead = DM_READ_AHEAD_NONE; else { log_error("Invalid readahead specification"); return 0; } /* * If udev is disabled using DM_DISABLE_UDEV environment * variable, override existing config and hardcode these: * - udev_rules = 0 * - udev_sync = 0 * - udev_fallback = 1 */ udev_disabled = _check_disable_udev("manage logical volume symlinks in device directory"); cmd->default_settings.udev_rules = udev_disabled ? 0 : find_config_tree_bool(cmd, activation_udev_rules_CFG); cmd->default_settings.udev_sync = udev_disabled ? 0 : find_config_tree_bool(cmd, activation_udev_sync_CFG); init_retry_deactivation(find_config_tree_bool(cmd, activation_retry_deactivation_CFG)); init_activation_checks(find_config_tree_bool(cmd, activation_checks_CFG)); #ifdef UDEV_SYNC_SUPPORT /* * Use udev fallback automatically in case udev * is disabled via DM_DISABLE_UDEV environment * variable or udev rules are switched off. */ cmd->default_settings.udev_fallback = !cmd->default_settings.udev_rules || udev_disabled ? 1 : find_config_tree_bool(cmd, activation_verify_udev_operations_CFG); /* Do not rely fully on udev if the udev support is known to be incomplete. */ if (!cmd->default_settings.udev_fallback && !_dm_driver_has_stable_udev_support()) { log_very_verbose("Kernel driver has incomplete udev support so " "LVM will check and perform some operations itself."); cmd->default_settings.udev_fallback = 1; } #else /* We must use old node/symlink creation code if not compiled with udev support at all! */ cmd->default_settings.udev_fallback = 1; #endif cmd->use_linear_target = find_config_tree_bool(cmd, activation_use_linear_target_CFG); cmd->stripe_filler = find_config_tree_str(cmd, activation_missing_stripe_filler_CFG); /* FIXME Missing error code checks from the stats, not log_warn?, notify if setting overridden, delay message/check till it is actually used (eg consider if lvm shell - file could appear later after this check)? */ if (!strcmp(cmd->stripe_filler, "/dev/ioerror") && stat(cmd->stripe_filler, &st)) cmd->stripe_filler = "error"; if (strcmp(cmd->stripe_filler, "error")) { if (stat(cmd->stripe_filler, &st)) { log_warn("WARNING: activation/missing_stripe_filler = \"%s\" " "is invalid,", cmd->stripe_filler); log_warn(" stat failed: %s", strerror(errno)); log_warn("Falling back to \"error\" missing_stripe_filler."); cmd->stripe_filler = "error"; } else if (!S_ISBLK(st.st_mode)) { log_warn("WARNING: activation/missing_stripe_filler = \"%s\" " "is not a block device.", cmd->stripe_filler); log_warn("Falling back to \"error\" missing_stripe_filler."); cmd->stripe_filler = "error"; } } cmd->si_unit_consistency = find_config_tree_bool(cmd, global_si_unit_consistency_CFG); if ((cn = find_config_tree_node(cmd, activation_mlock_filter_CFG))) for (cv = cn->v; cv; cv = cv->next) if ((cv->type != DM_CFG_STRING) || !cv->v.str[0]) log_error("Ignoring invalid activation/mlock_filter entry in config file"); cmd->metadata_read_only = find_config_tree_bool(cmd, global_metadata_read_only_CFG); pv_min_kb = find_config_tree_int64(cmd, devices_pv_min_size_CFG); if (pv_min_kb < PV_MIN_SIZE_KB) { log_warn("Ignoring too small pv_min_size %" PRId64 "KB, using default %dKB.", pv_min_kb, PV_MIN_SIZE_KB); pv_min_kb = PV_MIN_SIZE_KB; } /* LVM stores sizes internally in units of 512-byte sectors. */ init_pv_min_size((uint64_t)pv_min_kb * (1024 >> SECTOR_SHIFT)); init_detect_internal_vg_cache_corruption (find_config_tree_bool(cmd, global_detect_internal_vg_cache_corruption_CFG)); lvmetad_disconnect(); lvmetad_socket = getenv("LVM_LVMETAD_SOCKET"); if (!lvmetad_socket) lvmetad_socket = DEFAULT_RUN_DIR "/lvmetad.socket"; /* TODO? lvmetad_socket = find_config_tree_str(cmd, "lvmetad/socket_path", DEFAULT_RUN_DIR "/lvmetad.socket"); */ lvmetad_set_socket(lvmetad_socket); cn = find_config_tree_node(cmd, devices_global_filter_CFG); lvmetad_set_token(cn ? cn->v : NULL); if (find_config_tree_int(cmd, global_locking_type_CFG) == 3 && find_config_tree_bool(cmd, global_use_lvmetad_CFG)) { log_warn("WARNING: configuration setting use_lvmetad overriden to 0 due to locking_type 3. " "Clustered environment not supported by lvmetad yet."); lvmetad_set_active(0); } else lvmetad_set_active(find_config_tree_bool(cmd, global_use_lvmetad_CFG)); lvmetad_init(cmd); return 1; } static int _set_tag(struct cmd_context *cmd, const char *tag) { log_very_verbose("Setting host tag: %s", dm_pool_strdup(cmd->libmem, tag)); if (!str_list_add(cmd->libmem, &cmd->tags, tag)) { log_error("_set_tag: str_list_add %s failed", tag); return 0; } return 1; } static int _check_host_filters(struct cmd_context *cmd, const struct dm_config_node *hn, int *passes) { const struct dm_config_node *cn; const struct dm_config_value *cv; *passes = 1; for (cn = hn; cn; cn = cn->sib) { if (!cn->v) continue; if (!strcmp(cn->key, "host_list")) { *passes = 0; if (cn->v->type == DM_CFG_EMPTY_ARRAY) continue; for (cv = cn->v; cv; cv = cv->next) { if (cv->type != DM_CFG_STRING) { log_error("Invalid hostname string " "for tag %s", cn->key); return 0; } if (!strcmp(cv->v.str, cmd->hostname)) { *passes = 1; return 1; } } } if (!strcmp(cn->key, "host_filter")) { log_error("host_filter not supported yet"); return 0; } } return 1; } static int _init_tags(struct cmd_context *cmd, struct dm_config_tree *cft) { const struct dm_config_node *tn, *cn; const char *tag; int passes; if (!(tn = find_config_tree_node(cmd, tags_CFG_SECTION)) || !tn->child) return 1; /* NB hosttags 0 when already 1 intentionally does not delete the tag */ if (!cmd->hosttags && find_config_tree_bool(cmd, tags_hosttags_CFG)) { /* FIXME Strip out invalid chars: only A-Za-z0-9_+.- */ if (!_set_tag(cmd, cmd->hostname)) return_0; cmd->hosttags = 1; } for (cn = tn->child; cn; cn = cn->sib) { if (cn->v) continue; tag = cn->key; if (*tag == '@') tag++; if (!validate_name(tag)) { log_error("Invalid tag in config file: %s", cn->key); return 0; } if (cn->child) { passes = 0; if (!_check_host_filters(cmd, cn->child, &passes)) return_0; if (!passes) continue; } if (!_set_tag(cmd, tag)) return_0; } return 1; } static int _load_config_file(struct cmd_context *cmd, const char *tag) { static char config_file[PATH_MAX] = ""; const char *filler = ""; struct stat info; struct config_tree_list *cfl; if (*tag) filler = "_"; if (dm_snprintf(config_file, sizeof(config_file), "%s/lvm%s%s.conf", cmd->system_dir, filler, tag) < 0) { log_error("LVM_SYSTEM_DIR or tag was too long"); return 0; } if (!(cfl = dm_pool_alloc(cmd->libmem, sizeof(*cfl)))) { log_error("config_tree_list allocation failed"); return 0; } if (!(cfl->cft = config_file_open(config_file, 0))) { log_error("config_tree allocation failed"); return 0; } /* Is there a config file? */ if (stat(config_file, &info) == -1) { if (errno == ENOENT) { dm_list_add(&cmd->config_files, &cfl->list); goto out; } log_sys_error("stat", config_file); config_file_destroy(cfl->cft); return 0; } log_very_verbose("Loading config file: %s", config_file); if (!config_file_read(cfl->cft)) { log_error("Failed to load config file %s", config_file); config_file_destroy(cfl->cft); return 0; } dm_list_add(&cmd->config_files, &cfl->list); out: if (*tag) { if (!_init_tags(cmd, cfl->cft)) return_0; } else /* Use temporary copy of lvm.conf while loading other files */ cmd->cft = cfl->cft; return 1; } /* Find and read first config file */ static int _init_lvm_conf(struct cmd_context *cmd) { /* No config file if LVM_SYSTEM_DIR is empty */ if (!*cmd->system_dir) { if (!(cmd->cft = config_file_open(NULL, 0))) { log_error("Failed to create config tree"); return 0; } return 1; } if (!_load_config_file(cmd, "")) return_0; return 1; } /* Read any additional config files */ static int _init_tag_configs(struct cmd_context *cmd) { struct str_list *sl; /* Tag list may grow while inside this loop */ dm_list_iterate_items(sl, &cmd->tags) { if (!_load_config_file(cmd, sl->str)) return_0; } return 1; } static struct dm_config_tree *_merge_config_files(struct cmd_context *cmd, struct dm_config_tree *cft) { struct config_tree_list *cfl; /* Replace temporary duplicate copy of lvm.conf */ if (cft->root) { if (!(cft = config_file_open(NULL, 0))) { log_error("Failed to create config tree"); return 0; } } dm_list_iterate_items(cfl, &cmd->config_files) { /* Merge all config trees into cmd->cft using merge/tag rules */ if (!merge_config_tree(cmd, cft, cfl->cft)) return_0; } return cft; } static void _destroy_tags(struct cmd_context *cmd) { struct dm_list *slh, *slht; dm_list_iterate_safe(slh, slht, &cmd->tags) { dm_list_del(slh); } } int config_files_changed(struct cmd_context *cmd) { struct config_tree_list *cfl; dm_list_iterate_items(cfl, &cmd->config_files) { if (config_file_changed(cfl->cft)) return 1; } return 0; } /* * Returns cmdline config_tree that overrides all others, if present. */ static struct dm_config_tree *_destroy_tag_configs(struct cmd_context *cmd) { struct config_tree_list *cfl; struct dm_config_tree *cft_cmdline = NULL, *cft; cft = dm_config_remove_cascaded_tree(cmd->cft); if (cft) { cft_cmdline = cmd->cft; cmd->cft = cft; } dm_list_iterate_items(cfl, &cmd->config_files) { if (cfl->cft == cmd->cft) cmd->cft = NULL; config_file_destroy(cfl->cft); } if (cmd->cft) { config_file_destroy(cmd->cft); cmd->cft = NULL; } dm_list_init(&cmd->config_files); return cft_cmdline; } static int _init_dev_cache(struct cmd_context *cmd) { const struct dm_config_node *cn; const struct dm_config_value *cv; size_t len, udev_dir_len = strlen(DM_UDEV_DEV_DIR); int len_diff; int device_list_from_udev; init_dev_disable_after_error_count( find_config_tree_int(cmd, devices_disable_after_error_count_CFG)); if (!dev_cache_init(cmd)) return_0; /* * Override existing config and hardcode device_list_from_udev = 0 if: * - udev is not running * - udev is disabled using DM_DISABLE_UDEV environment variable */ if (_check_disable_udev("obtain device list by scanning device directory")) device_list_from_udev = 0; else device_list_from_udev = udev_is_running() ? find_config_tree_bool(cmd, devices_obtain_device_list_from_udev_CFG) : 0; init_obtain_device_list_from_udev(device_list_from_udev); if (!(cn = find_config_tree_node(cmd, devices_scan_CFG))) { if (!dev_cache_add_dir("/dev")) { log_error("Failed to add /dev to internal " "device cache"); return 0; } log_verbose("device/scan not in config file: " "Defaulting to /dev"); return 1; } for (cv = cn->v; cv; cv = cv->next) { if (cv->type != DM_CFG_STRING) { log_error("Invalid string in config file: " "devices/scan"); return 0; } if (device_list_from_udev) { len = strlen(cv->v.str); /* * DM_UDEV_DEV_DIR always has '/' at its end. * If the item in the conf does not have it, be sure * to make the right comparison without the '/' char! */ len_diff = len && cv->v.str[len - 1] != '/' ? udev_dir_len - 1 != len : udev_dir_len != len; if (len_diff || strncmp(DM_UDEV_DEV_DIR, cv->v.str, len)) { log_very_verbose("Non standard udev dir %s, resetting " "devices/obtain_device_list_from_udev.", cv->v.str); device_list_from_udev = 0; init_obtain_device_list_from_udev(0); } } if (!dev_cache_add_dir(cv->v.str)) { log_error("Failed to add %s to internal device cache", cv->v.str); return 0; } } if (!(cn = find_config_tree_node(cmd, devices_loopfiles_CFG))) return 1; for (cv = cn->v; cv; cv = cv->next) { if (cv->type != DM_CFG_STRING) { log_error("Invalid string in config file: " "devices/loopfiles"); return 0; } if (!dev_cache_add_loopfile(cv->v.str)) { log_error("Failed to add loopfile %s to internal " "device cache", cv->v.str); return 0; } } return 1; } #define MAX_FILTERS 5 static struct dev_filter *_init_filter_components(struct cmd_context *cmd) { int nr_filt = 0; const struct dm_config_node *cn; struct dev_filter *filters[MAX_FILTERS] = { 0 }; struct dev_filter *composite; /* * Filters listed in order: top one gets applied first. * Failure to initialise some filters is not fatal. * Update MAX_FILTERS definition above when adding new filters. */ /* * sysfs filter. Only available on 2.6 kernels. Non-critical. * Listed first because it's very efficient at eliminating * unavailable devices. */ if (find_config_tree_bool(cmd, devices_sysfs_scan_CFG)) { if ((filters[nr_filt] = sysfs_filter_create(cmd->sysfs_dir))) nr_filt++; } /* regex filter. Optional. */ if (!(cn = find_config_tree_node(cmd, devices_filter_CFG))) log_very_verbose("devices/filter not found in config file: " "no regex filter installed"); else if (!(filters[nr_filt] = regex_filter_create(cn->v))) { log_error("Failed to create regex device filter"); goto bad; } else nr_filt++; /* device type filter. Required. */ cn = find_config_tree_node(cmd, devices_types_CFG); if (!(filters[nr_filt] = lvm_type_filter_create(cmd->proc_dir, cn))) { log_error("Failed to create lvm type filter"); goto bad; } nr_filt++; /* md component filter. Optional, non-critical. */ if (find_config_tree_bool(cmd, devices_md_component_detection_CFG)) { init_md_filtering(1); if ((filters[nr_filt] = md_filter_create())) nr_filt++; } /* mpath component filter. Optional, non-critical. */ if (find_config_tree_bool(cmd, devices_multipath_component_detection_CFG)) { if ((filters[nr_filt] = mpath_filter_create(cmd->sysfs_dir))) nr_filt++; } /* Only build a composite filter if we really need it. */ if (nr_filt == 1) return filters[0]; if (!(composite = composite_filter_create(nr_filt, filters))) goto_bad; return composite; bad: while (--nr_filt >= 0) filters[nr_filt]->destroy(filters[nr_filt]); return NULL; } static int _init_filters(struct cmd_context *cmd, unsigned load_persistent_cache) { static char cache_file[PATH_MAX]; const char *dev_cache = NULL, *cache_dir, *cache_file_prefix; struct dev_filter *f3 = NULL, *f4 = NULL, *toplevel_components[2] = { 0 }; struct stat st; const struct dm_config_node *cn; cmd->dump_filter = 0; if (!(f3 = _init_filter_components(cmd))) goto_bad; init_ignore_suspended_devices(find_config_tree_bool(cmd, devices_ignore_suspended_devices_CFG)); /* * If 'cache_dir' or 'cache_file_prefix' is set, ignore 'cache'. */ cache_dir = find_config_tree_str(cmd, devices_cache_dir_CFG); cache_file_prefix = find_config_tree_str(cmd, devices_cache_file_prefix_CFG); if (cache_dir || cache_file_prefix) { if (dm_snprintf(cache_file, sizeof(cache_file), "%s%s%s/%s.cache", cache_dir ? "" : cmd->system_dir, cache_dir ? "" : "/", cache_dir ? : DEFAULT_CACHE_SUBDIR, cache_file_prefix ? : DEFAULT_CACHE_FILE_PREFIX) < 0) { log_error("Persistent cache filename too long."); goto bad; } } else if (!(dev_cache = find_config_tree_str(cmd, devices_cache_CFG)) && (dm_snprintf(cache_file, sizeof(cache_file), "%s/%s/%s.cache", cmd->system_dir, DEFAULT_CACHE_SUBDIR, DEFAULT_CACHE_FILE_PREFIX) < 0)) { log_error("Persistent cache filename too long."); goto bad; } if (!dev_cache) dev_cache = cache_file; if (!(f4 = persistent_filter_create(f3, dev_cache))) { log_verbose("Failed to create persistent device filter."); f3->destroy(f3); return_0; } /* Should we ever dump persistent filter state? */ if (find_config_tree_bool(cmd, devices_write_cache_state_CFG)) cmd->dump_filter = 1; if (!*cmd->system_dir) cmd->dump_filter = 0; /* * Only load persistent filter device cache on startup if it is newer * than the config file and this is not a long-lived process. */ if (load_persistent_cache && !cmd->is_long_lived && !stat(dev_cache, &st) && (st.st_ctime > config_file_timestamp(cmd->cft)) && !persistent_filter_load(f4, NULL)) log_verbose("Failed to load existing device cache from %s", dev_cache); if (!(cn = find_config_tree_node(cmd, devices_global_filter_CFG))) { cmd->filter = f4; } else if (!(cmd->lvmetad_filter = regex_filter_create(cn->v))) goto_bad; else { toplevel_components[0] = cmd->lvmetad_filter; toplevel_components[1] = f4; if (!(cmd->filter = composite_filter_create(2, toplevel_components))) goto_bad; } return 1; bad: if (f3) f3->destroy(f3); if (f4) f4->destroy(f4); if (toplevel_components[0]) toplevel_components[0]->destroy(toplevel_components[0]); return 0; } struct format_type *get_format_by_name(struct cmd_context *cmd, const char *format) { struct format_type *fmt; dm_list_iterate_items(fmt, &cmd->formats) if (!strcasecmp(fmt->name, format) || !strcasecmp(fmt->name + 3, format) || (fmt->alias && !strcasecmp(fmt->alias, format))) return fmt; return NULL; } static int _init_formats(struct cmd_context *cmd) { const char *format; struct format_type *fmt; #ifdef HAVE_LIBDL const struct dm_config_node *cn; #endif #ifdef LVM1_INTERNAL if (!(fmt = init_lvm1_format(cmd))) return 0; fmt->library = NULL; dm_list_add(&cmd->formats, &fmt->list); #endif #ifdef POOL_INTERNAL if (!(fmt = init_pool_format(cmd))) return 0; fmt->library = NULL; dm_list_add(&cmd->formats, &fmt->list); #endif #ifdef HAVE_LIBDL /* Load any formats in shared libs if not static */ if (!is_static() && (cn = find_config_tree_node(cmd, global_format_libraries_CFG))) { const struct dm_config_value *cv; struct format_type *(*init_format_fn) (struct cmd_context *); void *lib; for (cv = cn->v; cv; cv = cv->next) { if (cv->type != DM_CFG_STRING) { log_error("Invalid string in config file: " "global/format_libraries"); return 0; } if (!(lib = load_shared_library(cmd, cv->v.str, "format", 0))) return_0; if (!(init_format_fn = dlsym(lib, "init_format"))) { log_error("Shared library %s does not contain " "format functions", cv->v.str); dlclose(lib); return 0; } if (!(fmt = init_format_fn(cmd))) { dlclose(lib); return_0; } fmt->library = lib; dm_list_add(&cmd->formats, &fmt->list); } } #endif if (!(fmt = create_text_format(cmd))) return 0; fmt->library = NULL; dm_list_add(&cmd->formats, &fmt->list); cmd->fmt_backup = fmt; format = find_config_tree_str(cmd, global_format_CFG); dm_list_iterate_items(fmt, &cmd->formats) { if (!strcasecmp(fmt->name, format) || (fmt->alias && !strcasecmp(fmt->alias, format))) { cmd->default_settings.fmt_name = fmt->name; cmd->fmt = fmt; return 1; } } log_error("_init_formats: Default format (%s) not found", format); return 0; } int init_lvmcache_orphans(struct cmd_context *cmd) { struct format_type *fmt; dm_list_iterate_items(fmt, &cmd->formats) if (!lvmcache_add_orphan_vginfo(fmt->orphan_vg_name, fmt)) return_0; return 1; } struct segtype_library { struct cmd_context *cmd; void *lib; const char *libname; }; int lvm_register_segtype(struct segtype_library *seglib, struct segment_type *segtype) { struct segment_type *segtype2; segtype->library = seglib->lib; segtype->cmd = seglib->cmd; dm_list_iterate_items(segtype2, &seglib->cmd->segtypes) { if (strcmp(segtype2->name, segtype->name)) continue; log_error("Duplicate segment type %s: " "unloading shared library %s", segtype->name, seglib->libname); segtype->ops->destroy(segtype); return 0; } dm_list_add(&seglib->cmd->segtypes, &segtype->list); return 1; } static int _init_single_segtype(struct cmd_context *cmd, struct segtype_library *seglib) { struct segment_type *(*init_segtype_fn) (struct cmd_context *); struct segment_type *segtype; if (!(init_segtype_fn = dlsym(seglib->lib, "init_segtype"))) { log_error("Shared library %s does not contain segment type " "functions", seglib->libname); return 0; } if (!(segtype = init_segtype_fn(seglib->cmd))) return_0; return lvm_register_segtype(seglib, segtype); } static int _init_segtypes(struct cmd_context *cmd) { int i; struct segment_type *segtype; struct segtype_library seglib = { .cmd = cmd, .lib = NULL }; struct segment_type *(*init_segtype_array[])(struct cmd_context *cmd) = { init_striped_segtype, init_zero_segtype, init_error_segtype, init_free_segtype, #ifdef SNAPSHOT_INTERNAL init_snapshot_segtype, #endif #ifdef MIRRORED_INTERNAL init_mirrored_segtype, #endif NULL }; #ifdef HAVE_LIBDL const struct dm_config_node *cn; #endif for (i = 0; init_segtype_array[i]; i++) { if (!(segtype = init_segtype_array[i](cmd))) return 0; segtype->library = NULL; dm_list_add(&cmd->segtypes, &segtype->list); } #ifdef REPLICATOR_INTERNAL if (!init_replicator_segtype(cmd, &seglib)) return 0; #endif #ifdef RAID_INTERNAL if (!init_raid_segtypes(cmd, &seglib)) return 0; #endif #ifdef THIN_INTERNAL if (!init_thin_segtypes(cmd, &seglib)) return 0; #endif #ifdef HAVE_LIBDL /* Load any formats in shared libs unless static */ if (!is_static() && (cn = find_config_tree_node(cmd, global_segment_libraries_CFG))) { const struct dm_config_value *cv; int (*init_multiple_segtypes_fn) (struct cmd_context *, struct segtype_library *); for (cv = cn->v; cv; cv = cv->next) { if (cv->type != DM_CFG_STRING) { log_error("Invalid string in config file: " "global/segment_libraries"); return 0; } seglib.libname = cv->v.str; if (!(seglib.lib = load_shared_library(cmd, seglib.libname, "segment type", 0))) return_0; if ((init_multiple_segtypes_fn = dlsym(seglib.lib, "init_multiple_segtypes"))) { if (dlsym(seglib.lib, "init_segtype")) log_warn("WARNING: Shared lib %s has " "conflicting init fns. Using" " init_multiple_segtypes().", seglib.libname); } else init_multiple_segtypes_fn = _init_single_segtype; if (!init_multiple_segtypes_fn(cmd, &seglib)) { struct dm_list *sgtl, *tmp; log_error("init_multiple_segtypes() failed: " "Unloading shared library %s", seglib.libname); dm_list_iterate_safe(sgtl, tmp, &cmd->segtypes) { segtype = dm_list_item(sgtl, struct segment_type); if (segtype->library == seglib.lib) { dm_list_del(&segtype->list); segtype->ops->destroy(segtype); } } dlclose(seglib.lib); return_0; } } } #endif return 1; } static int _init_hostname(struct cmd_context *cmd) { struct utsname uts; if (uname(&uts)) { log_sys_error("uname", "_init_hostname"); return 0; } if (!(cmd->hostname = dm_pool_strdup(cmd->libmem, uts.nodename))) { log_error("_init_hostname: dm_pool_strdup failed"); return 0; } if (!(cmd->kernel_vsn = dm_pool_strdup(cmd->libmem, uts.release))) { log_error("_init_hostname: dm_pool_strdup kernel_vsn failed"); return 0; } return 1; } static int _init_backup(struct cmd_context *cmd) { static char default_dir[PATH_MAX]; uint32_t days, min; const char *dir; if (!cmd->system_dir[0]) { log_warn("WARNING: Metadata changes will NOT be backed up"); backup_init(cmd, "", 0); archive_init(cmd, "", 0, 0, 0); return 1; } /* set up archiving */ cmd->default_settings.archive = find_config_tree_bool(cmd, backup_archive_CFG); days = (uint32_t) find_config_tree_int(cmd, backup_retain_days_CFG); min = (uint32_t) find_config_tree_int(cmd, backup_retain_min_CFG); if (dm_snprintf (default_dir, sizeof(default_dir), "%s/%s", cmd->system_dir, DEFAULT_ARCHIVE_SUBDIR) == -1) { log_error("Couldn't create default archive path '%s/%s'.", cmd->system_dir, DEFAULT_ARCHIVE_SUBDIR); return 0; } if (!(dir = find_config_tree_str(cmd, backup_archive_dir_CFG))) dir = default_dir; if (!archive_init(cmd, dir, days, min, cmd->default_settings.archive)) { log_debug("archive_init failed."); return 0; } /* set up the backup */ cmd->default_settings.backup = find_config_tree_bool(cmd, backup_backup_CFG); if (dm_snprintf (default_dir, sizeof(default_dir), "%s/%s", cmd->system_dir, DEFAULT_BACKUP_SUBDIR) == -1) { log_error("Couldn't create default backup path '%s/%s'.", cmd->system_dir, DEFAULT_BACKUP_SUBDIR); return 0; } if (!(dir = find_config_tree_str(cmd, backup_backup_dir_CFG))) dir = default_dir; if (!backup_init(cmd, dir, cmd->default_settings.backup)) { log_debug("backup_init failed."); return 0; } return 1; } static void _init_rand(struct cmd_context *cmd) { if (read_urandom(&cmd->rand_seed, sizeof(cmd->rand_seed))) { reset_lvm_errno(1); return; } cmd->rand_seed = (unsigned) time(NULL) + (unsigned) getpid(); reset_lvm_errno(1); } static void _init_globals(struct cmd_context *cmd) { init_full_scan_done(0); init_mirror_in_sync(0); } /* * Close and reopen stream on file descriptor fd. */ static int _reopen_stream(FILE *stream, int fd, const char *mode, const char *name, FILE **new_stream) { int fd_copy, new_fd; if ((fd_copy = dup(fd)) < 0) { log_sys_error("dup", name); return 0; } if (fclose(stream)) log_sys_error("fclose", name); if ((new_fd = dup2(fd_copy, fd)) < 0) log_sys_error("dup2", name); else if (new_fd != fd) log_error("dup2(%d, %d) returned %d", fd_copy, fd, new_fd); if (close(fd_copy) < 0) log_sys_error("close", name); if (!(*new_stream = fdopen(fd, mode))) { log_sys_error("fdopen", name); return 0; } return 1; } /* Entry point */ struct cmd_context *create_toolcontext(unsigned is_long_lived, const char *system_dir, unsigned set_buffering, unsigned threaded) { struct cmd_context *cmd; FILE *new_stream; #ifdef M_MMAP_MAX mallopt(M_MMAP_MAX, 0); #endif if (!setlocale(LC_ALL, "")) log_very_verbose("setlocale failed"); #ifdef INTL_PACKAGE bindtextdomain(INTL_PACKAGE, LOCALEDIR); #endif init_syslog(DEFAULT_LOG_FACILITY); if (!(cmd = dm_zalloc(sizeof(*cmd)))) { log_error("Failed to allocate command context"); return NULL; } cmd->is_long_lived = is_long_lived; cmd->threaded = threaded ? 1 : 0; cmd->handles_missing_pvs = 0; cmd->handles_unknown_segments = 0; cmd->independent_metadata_areas = 0; cmd->hosttags = 0; dm_list_init(&cmd->arg_value_groups); dm_list_init(&cmd->formats); dm_list_init(&cmd->segtypes); dm_list_init(&cmd->tags); dm_list_init(&cmd->config_files); label_init(); /* FIXME Make this configurable? */ reset_lvm_errno(1); #ifndef VALGRIND_POOL /* Set in/out stream buffering before glibc */ if (set_buffering) { /* Allocate 2 buffers */ if (!(cmd->linebuffer = dm_malloc(2 * linebuffer_size))) { log_error("Failed to allocate line buffer."); goto out; } if (is_valid_fd(STDIN_FILENO)) { if (!_reopen_stream(stdin, STDIN_FILENO, "r", "stdin", &new_stream)) goto_out; stdin = new_stream; if (setvbuf(stdin, cmd->linebuffer, _IOLBF, linebuffer_size)) { log_sys_error("setvbuf", ""); goto out; } } if (is_valid_fd(STDOUT_FILENO)) { if (!_reopen_stream(stdout, STDOUT_FILENO, "w", "stdout", &new_stream)) goto_out; stdout = new_stream; if (setvbuf(stdout, cmd->linebuffer + linebuffer_size, _IOLBF, linebuffer_size)) { log_sys_error("setvbuf", ""); goto out; } } /* Buffers are used for lines without '\n' */ } else /* Without buffering, must not use stdin/stdout */ init_silent(1); #endif /* * Environment variable LVM_SYSTEM_DIR overrides this below. */ if (system_dir) strncpy(cmd->system_dir, system_dir, sizeof(cmd->system_dir) - 1); else strcpy(cmd->system_dir, DEFAULT_SYS_DIR); if (!_get_env_vars(cmd)) goto_out; /* Create system directory if it doesn't already exist */ if (*cmd->system_dir && !dm_create_dir(cmd->system_dir)) { log_error("Failed to create LVM2 system dir for metadata backups, config " "files and internal cache."); log_error("Set environment variable LVM_SYSTEM_DIR to alternative location " "or empty string."); goto out; } if (!(cmd->libmem = dm_pool_create("library", 4 * 1024))) { log_error("Library memory pool creation failed"); goto out; } if (!_init_lvm_conf(cmd)) goto_out; _init_logging(cmd); if (!_init_hostname(cmd)) goto_out; if (!_init_tags(cmd, cmd->cft)) goto_out; if (!_init_tag_configs(cmd)) goto_out; if (!(cmd->cft = _merge_config_files(cmd, cmd->cft))) goto_out; if (!_process_config(cmd)) goto_out; if (!_init_dev_cache(cmd)) goto_out; if (!_init_filters(cmd, 1)) goto_out; if (!(cmd->mem = dm_pool_create("command", 4 * 1024))) { log_error("Command memory pool creation failed"); goto out; } memlock_init(cmd); if (!_init_formats(cmd)) goto_out; if (!init_lvmcache_orphans(cmd)) goto_out; if (!_init_segtypes(cmd)) goto_out; if (!_init_backup(cmd)) goto_out; _init_rand(cmd); _init_globals(cmd); cmd->default_settings.cache_vgmetadata = 1; cmd->current_settings = cmd->default_settings; cmd->config_valid = 1; out: if (cmd->config_valid != 1) { destroy_toolcontext(cmd); cmd = NULL; } return cmd; } static void _destroy_formats(struct cmd_context *cmd, struct dm_list *formats) { struct dm_list *fmtl, *tmp; struct format_type *fmt; void *lib; dm_list_iterate_safe(fmtl, tmp, formats) { fmt = dm_list_item(fmtl, struct format_type); dm_list_del(&fmt->list); lib = fmt->library; fmt->ops->destroy(fmt); #ifdef HAVE_LIBDL if (lib) dlclose(lib); #endif } cmd->independent_metadata_areas = 0; } static void _destroy_segtypes(struct dm_list *segtypes) { struct dm_list *sgtl, *tmp; struct segment_type *segtype; void *lib; dm_list_iterate_safe(sgtl, tmp, segtypes) { segtype = dm_list_item(sgtl, struct segment_type); dm_list_del(&segtype->list); lib = segtype->library; segtype->ops->destroy(segtype); #ifdef HAVE_LIBDL /* * If no segtypes remain from this library, close it. */ if (lib) { struct segment_type *segtype2; dm_list_iterate_items(segtype2, segtypes) if (segtype2->library == lib) goto skip_dlclose; dlclose(lib); skip_dlclose: ; } #endif } } int refresh_filters(struct cmd_context *cmd) { int r, saved_ignore_suspended_devices = ignore_suspended_devices(); if (cmd->filter) { cmd->filter->destroy(cmd->filter); cmd->filter = NULL; } cmd->lvmetad_filter = NULL; if (!(r = _init_filters(cmd, 0))) stack; /* * During repair code must not reset suspended flag. */ init_ignore_suspended_devices(saved_ignore_suspended_devices); return r; } int refresh_toolcontext(struct cmd_context *cmd) { struct dm_config_tree *cft_cmdline, *cft_tmp; log_verbose("Reloading config files"); /* * Don't update the persistent filter cache as we will * perform a full rescan. */ activation_release(); lvmcache_destroy(cmd, 0); label_exit(); _destroy_segtypes(&cmd->segtypes); _destroy_formats(cmd, &cmd->formats); if (cmd->filter) { cmd->filter->destroy(cmd->filter); cmd->filter = NULL; } dev_cache_exit(); _destroy_tags(cmd); cft_cmdline = _destroy_tag_configs(cmd); cmd->config_valid = 0; cmd->hosttags = 0; if (!_init_lvm_conf(cmd)) return 0; /* Temporary duplicate cft pointer holding lvm.conf - replaced later */ cft_tmp = cmd->cft; if (cft_cmdline) cmd->cft = dm_config_insert_cascaded_tree(cft_cmdline, cft_tmp); /* Uses cmd->cft i.e. cft_cmdline + lvm.conf */ _init_logging(cmd); /* Init tags from lvm.conf. */ if (!_init_tags(cmd, cft_tmp)) return 0; /* Doesn't change cmd->cft */ if (!_init_tag_configs(cmd)) return 0; /* Merge all the tag config files with lvm.conf, returning a * fresh cft pointer in place of cft_tmp. */ if (!(cmd->cft = _merge_config_files(cmd, cft_tmp))) return 0; /* Finally we can make the proper, fully-merged, cmd->cft */ if (cft_cmdline) cmd->cft = dm_config_insert_cascaded_tree(cft_cmdline, cmd->cft); if (!_process_config(cmd)) return 0; if (!_init_dev_cache(cmd)) return 0; if (!_init_filters(cmd, 0)) return 0; if (!_init_formats(cmd)) return 0; if (!init_lvmcache_orphans(cmd)) return 0; if (!_init_segtypes(cmd)) return 0; if (!_init_backup(cmd)) return 0; cmd->config_valid = 1; reset_lvm_errno(1); return 1; } void destroy_toolcontext(struct cmd_context *cmd) { struct dm_config_tree *cft_cmdline; FILE *new_stream; if (cmd->dump_filter) persistent_filter_dump(cmd->filter, 1); archive_exit(cmd); backup_exit(cmd); lvmcache_destroy(cmd, 0); label_exit(); _destroy_segtypes(&cmd->segtypes); _destroy_formats(cmd, &cmd->formats); if (cmd->filter) cmd->filter->destroy(cmd->filter); if (cmd->mem) dm_pool_destroy(cmd->mem); dev_cache_exit(); _destroy_tags(cmd); if ((cft_cmdline = _destroy_tag_configs(cmd))) dm_config_destroy(cft_cmdline); if (cmd->libmem) dm_pool_destroy(cmd->libmem); #ifndef VALGRIND_POOL if (cmd->linebuffer) { /* Reset stream buffering to defaults */ if (is_valid_fd(STDIN_FILENO)) { if (_reopen_stream(stdin, STDIN_FILENO, "r", "stdin", &new_stream)) { stdin = new_stream; setlinebuf(stdin); } else cmd->linebuffer = NULL; /* Leave buffer in place (deliberate leak) */ } if (is_valid_fd(STDOUT_FILENO)) { if (_reopen_stream(stdout, STDOUT_FILENO, "w", "stdout", &new_stream)) { stdout = new_stream; setlinebuf(stdout); } else cmd->linebuffer = NULL; /* Leave buffer in place (deliberate leak) */ } dm_free(cmd->linebuffer); } #endif if (cmd->cft_def_hash) dm_hash_destroy(cmd->cft_def_hash); dm_free(cmd); lvmetad_release_token(); lvmetad_disconnect(); release_log_memory(); activation_exit(); reset_log_duplicated(); fin_log(); fin_syslog(); reset_lvm_errno(0); }