/* * Copyright (C) 2014-2015 Red Hat, Inc. * * 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. */ #include "tool.h" #include "lvmlockd-client.h" #include #include #include #include #include #include #include #include static int quit = 0; static int info = 0; static int dump = 0; static int wait_opt = 0; static int force_opt = 0; static int kill_vg = 0; static int drop_vg = 0; static int gl_enable = 0; static int gl_disable = 0; static int stop_lockspaces = 0; static char *arg_vg_name = NULL; #define DUMP_SOCKET_NAME "lvmlockd-dump.sock" #define DUMP_BUF_SIZE (1024 * 1024) static char dump_buf[DUMP_BUF_SIZE+1]; static int dump_len; static struct sockaddr_un dump_addr; static socklen_t dump_addrlen; daemon_handle _lvmlockd; #define log_error(fmt, args...) \ do { \ printf(fmt "\n", ##args); \ } while (0) #define MAX_LINE 512 /* copied from lvmlockd-internal.h */ #define MAX_NAME 64 #define MAX_ARGS 64 /* * lvmlockd dumps the client info before the lockspaces, * so we can look up client info when printing lockspace info. */ #define MAX_CLIENTS 100 struct client_info { uint32_t client_id; int pid; char name[MAX_NAME+1]; }; static struct client_info clients[MAX_CLIENTS]; static int num_clients; static void save_client_info(char *line) { uint32_t pid = 0; int fd = 0; int pi = 0; uint32_t client_id = 0; char name[MAX_NAME+1] = { 0 }; (void) sscanf(line, "info=client pid=%u fd=%d pi=%d id=%u name=%s", &pid, &fd, &pi, &client_id, name); clients[num_clients].client_id = client_id; clients[num_clients].pid = pid; strcpy(clients[num_clients].name, name); num_clients++; } static void find_client_info(uint32_t client_id, uint32_t *pid, char *cl_name) { int i; for (i = 0; i < num_clients; i++) { if (clients[i].client_id == client_id) { *pid = clients[i].pid; strcpy(cl_name, clients[i].name); return; } } } static int first_ls = 1; static void format_info_ls(char *line) { char ls_name[MAX_NAME+1] = { 0 }; char vg_name[MAX_NAME+1] = { 0 }; char vg_uuid[MAX_NAME+1] = { 0 }; char vg_sysid[MAX_NAME+1] = { 0 }; char lock_args[MAX_ARGS+1] = { 0 }; char lock_type[MAX_NAME+1] = { 0 }; (void) sscanf(line, "info=ls ls_name=%s vg_name=%s vg_uuid=%s vg_sysid=%s vg_args=%s lm_type=%s", ls_name, vg_name, vg_uuid, vg_sysid, lock_args, lock_type); if (!first_ls) printf("\n"); first_ls = 0; printf("VG %s lock_type=%s %s\n", vg_name, lock_type, vg_uuid); printf("LS %s %s\n", lock_type, ls_name); } static void format_info_ls_action(char *line) { uint32_t client_id = 0; char flags[MAX_NAME+1] = { 0 }; char version[MAX_NAME+1] = { 0 }; char op[MAX_NAME+1] = { 0 }; uint32_t pid = 0; char cl_name[MAX_NAME+1] = { 0 }; (void) sscanf(line, "info=ls_action client_id=%u %s %s op=%s", &client_id, flags, version, op); find_client_info(client_id, &pid, cl_name); printf("OP %s pid %u (%s)\n", op, pid, cl_name); } static void format_info_r(char *line, char *r_name_out, char *r_type_out) { char r_name[MAX_NAME+1] = { 0 }; char r_type[4] = { 0 }; char mode[4] = { 0 }; char sh_count[MAX_NAME+1] = { 0 }; uint32_t ver = 0; (void) sscanf(line, "info=r name=%s type=%s mode=%s %s version=%u", r_name, r_type, mode, sh_count, &ver); strcpy(r_name_out, r_name); strcpy(r_type_out, r_type); /* when mode is not un, wait and print each lk line */ if (strcmp(mode, "un")) return; /* when mode is un, there will be no lk lines, so print now */ if (!strcmp(r_type, "gl")) { printf("LK GL un ver %u\n", ver); } else if (!strcmp(r_type, "vg")) { printf("LK VG un ver %u\n", ver); } else if (!strcmp(r_type, "lv")) { printf("LK LV un %s\n", r_name); } } static void format_info_lk(char *line, char *r_name, char *r_type) { char mode[4] = { 0 }; uint32_t ver = 0; char flags[MAX_NAME+1] = { 0 }; uint32_t client_id = 0; uint32_t pid = 0; char cl_name[MAX_NAME+1] = { 0 }; if (!r_name[0] || !r_type[0]) { printf("format_info_lk error r_name %s r_type %s\n", r_name, r_type); printf("%s\n", line); return; } (void) sscanf(line, "info=lk mode=%s version=%u %s client_id=%u", mode, &ver, flags, &client_id); find_client_info(client_id, &pid, cl_name); if (!strcmp(r_type, "gl")) { printf("LK GL %s ver %u pid %u (%s)\n", mode, ver, pid, cl_name); } else if (!strcmp(r_type, "vg")) { printf("LK VG %s ver %u pid %u (%s)\n", mode, ver, pid, cl_name); } else if (!strcmp(r_type, "lv")) { printf("LK LV %s %s\n", mode, r_name); } } static void format_info_r_action(char *line, char *r_name, char *r_type) { uint32_t client_id = 0; char flags[MAX_NAME+1] = { 0 }; char version[MAX_NAME+1] = { 0 }; char op[MAX_NAME+1] = { 0 }; char rt[4] = { 0 }; char mode[4] = { 0 }; char lm[MAX_NAME+1] = { 0 }; char result[MAX_NAME+1] = { 0 }; char lm_rv[MAX_NAME+1] = { 0 }; uint32_t pid = 0; char cl_name[MAX_NAME+1] = { 0 }; if (!r_name[0] || !r_type[0]) { printf("format_info_r_action error r_name %s r_type %s\n", r_name, r_type); printf("%s\n", line); return; } (void) sscanf(line, "info=r_action client_id=%u %s %s op=%s rt=%s mode=%s %s %s %s", &client_id, flags, version, op, rt, mode, lm, result, lm_rv); find_client_info(client_id, &pid, cl_name); if (strcmp(op, "lock")) { printf("OP %s pid %u (%s)\n", op, pid, cl_name); return; } if (!strcmp(r_type, "gl")) { printf("LW GL %s ver %u pid %u (%s)\n", mode, 0, pid, cl_name); } else if (!strcmp(r_type, "vg")) { printf("LW VG %s ver %u pid %u (%s)\n", mode, 0, pid, cl_name); } else if (!strcmp(r_type, "lv")) { printf("LW LV %s %s\n", mode, r_name); } } static void format_info_line(char *line, char *r_name, char *r_type) { if (!strncmp(line, "info=structs ", strlen("info=structs "))) { /* only print this in the raw info dump */ } else if (!strncmp(line, "info=client ", strlen("info=client "))) { save_client_info(line); } else if (!strncmp(line, "info=ls ", strlen("info=ls "))) { format_info_ls(line); } else if (!strncmp(line, "info=ls_action ", strlen("info=ls_action "))) { format_info_ls_action(line); } else if (!strncmp(line, "info=r ", strlen("info=r "))) { /* * r_name/r_type are reset when a new resource is found. * They are reused for the lock and action lines that * follow a resource line. */ memset(r_name, 0, MAX_NAME+1); memset(r_type, 0, MAX_NAME+1); format_info_r(line, r_name, r_type); } else if (!strncmp(line, "info=lk ", strlen("info=lk "))) { /* will use info from previous r */ format_info_lk(line, r_name, r_type); } else if (!strncmp(line, "info=r_action ", strlen("info=r_action "))) { /* will use info from previous r */ format_info_r_action(line, r_name, r_type); } else { printf("UN %s\n", line); } } static void format_info(void) { char line[MAX_LINE]; char r_name[MAX_NAME+1]; char r_type[MAX_NAME+1]; int i, j; j = 0; memset(line, 0, sizeof(line)); for (i = 0; i < dump_len; i++) { line[j++] = dump_buf[i]; if ((line[j-1] == '\n') || (line[j-1] == '\0')) { format_info_line(line, r_name, r_type); j = 0; memset(line, 0, sizeof(line)); } } } static daemon_reply _lvmlockd_send(const char *req_name, ...) { va_list ap; daemon_reply repl; daemon_request req; req = daemon_request_make(req_name); va_start(ap, req_name); daemon_request_extend_v(req, ap); va_end(ap); repl = daemon_send(_lvmlockd, req); daemon_request_destroy(req); return repl; } /* See the same in lib/locking/lvmlockd.c */ #define NO_LOCKD_RESULT -1000 static int _lvmlockd_result(daemon_reply reply, int *result) { int reply_result; if (reply.error) { log_error("lvmlockd_result reply error %d", reply.error); return 0; } if (strcmp(daemon_reply_str(reply, "response", ""), "OK")) { log_error("lvmlockd_result bad response"); return 0; } reply_result = daemon_reply_int(reply, "op_result", NO_LOCKD_RESULT); if (reply_result == -1000) { log_error("lvmlockd_result no op_result"); return 0; } *result = reply_result; return 1; } static int do_quit(void) { daemon_reply reply; int rv = 0; reply = daemon_send_simple(_lvmlockd, "quit", NULL); if (reply.error) { log_error("reply error %d", reply.error); rv = reply.error; } daemon_reply_destroy(reply); return rv; } static int setup_dump_socket(void) { int s, rv; s = socket(AF_LOCAL, SOCK_DGRAM, 0); if (s < 0) return s; memset(&dump_addr, 0, sizeof(dump_addr)); dump_addr.sun_family = AF_LOCAL; strcpy(&dump_addr.sun_path[1], DUMP_SOCKET_NAME); dump_addrlen = sizeof(sa_family_t) + strlen(dump_addr.sun_path+1) + 1; rv = bind(s, (struct sockaddr *) &dump_addr, dump_addrlen); if (rv < 0) { rv = -errno; if (close(s)) log_error("failed to close dump socket"); return rv; } return s; } static int do_dump(const char *req_name) { daemon_reply reply; int result; int fd, rv = 0; int count = 0; fd = setup_dump_socket(); if (fd < 0) { log_error("socket error %d", fd); return fd; } reply = daemon_send_simple(_lvmlockd, req_name, NULL); if (reply.error) { log_error("reply error %d", reply.error); rv = reply.error; goto out; } result = daemon_reply_int(reply, "result", 0); dump_len = daemon_reply_int(reply, "dump_len", 0); daemon_reply_destroy(reply); if (result < 0) { rv = result; log_error("result %d", result); } if (!dump_len) goto out; memset(dump_buf, 0, sizeof(dump_buf)); retry: rv = recvfrom(fd, dump_buf + count, dump_len - count, MSG_WAITALL, (struct sockaddr *)&dump_addr, &dump_addrlen); if (rv < 0) { log_error("recvfrom error %d %d", rv, errno); rv = -errno; goto out; } count += rv; if (count < dump_len) goto retry; rv = 0; if ((info && dump) || !strcmp(req_name, "dump")) printf("%s\n", dump_buf); else format_info(); out: if (close(fd)) log_error("failed to close dump socket %d", fd); return rv; } static int do_able(const char *req_name) { daemon_reply reply; int result; int rv; reply = _lvmlockd_send(req_name, "cmd = %s", "lvmlockctl", "pid = " FMTd64, (int64_t) getpid(), "vg_name = %s", arg_vg_name, NULL); if (!_lvmlockd_result(reply, &result)) { log_error("lvmlockd result %d", result); rv = result; } else { rv = 0; } daemon_reply_destroy(reply); return rv; } static int do_stop_lockspaces(void) { daemon_reply reply; char opts[32]; int result; int rv; memset(opts, 0, sizeof(opts)); if (wait_opt) strcat(opts, "wait "); if (force_opt) strcat(opts, "force "); reply = _lvmlockd_send("stop_all", "cmd = %s", "lvmlockctl", "pid = " FMTd64, (int64_t) getpid(), "opts = %s", opts[0] ? opts : "none", NULL); if (!_lvmlockd_result(reply, &result)) { log_error("lvmlockd result %d", result); rv = result; } else { rv = 0; } daemon_reply_destroy(reply); return rv; } static int do_kill(void) { daemon_reply reply; int result; int rv; syslog(LOG_EMERG, "Lost access to sanlock lease storage in VG %s.", arg_vg_name); /* These two lines explain the manual alternative to the FIXME below. */ syslog(LOG_EMERG, "Immediately deactivate LVs in VG %s.", arg_vg_name); syslog(LOG_EMERG, "Once VG is unused, run lvmlockctl --drop %s.", arg_vg_name); /* * It may not be strictly necessary to notify lvmlockd of the kill, but * lvmlockd can use this information to avoid attempting any new lock * requests in the VG (which would fail anyway), and can return an * error indicating that the VG has been killed. */ reply = _lvmlockd_send("kill_vg", "cmd = %s", "lvmlockctl", "pid = " FMTd64, (int64_t) getpid(), "vg_name = %s", arg_vg_name, NULL); if (!_lvmlockd_result(reply, &result)) { log_error("lvmlockd result %d", result); rv = result; } else { rv = 0; } daemon_reply_destroy(reply); /* * FIXME: here is where we should implement a strong form of * blkdeactivate, and if it completes successfully, automatically call * do_drop() afterward. (The drop step may not always be necessary * if the lvm commands run while shutting things down release all the * leases.) * * run_strong_blkdeactivate(); * do_drop(); */ return rv; } static int do_drop(void) { daemon_reply reply; int result; int rv; syslog(LOG_WARNING, "Dropping locks for VG %s.", arg_vg_name); /* * Check for misuse by looking for any active LVs in the VG * and refusing this operation if found? One possible way * to kill LVs (e.g. if fs cannot be unmounted) is to suspend * them, or replace them with the error target. In that * case the LV will still appear to be active, but it is * safe to release the lock. */ reply = _lvmlockd_send("drop_vg", "cmd = %s", "lvmlockctl", "pid = " FMTd64, (int64_t) getpid(), "vg_name = %s", arg_vg_name, NULL); if (!_lvmlockd_result(reply, &result)) { log_error("lvmlockd result %d", result); rv = result; } else { rv = 0; } daemon_reply_destroy(reply); return rv; } static void print_usage(void) { printf("lvmlockctl options\n"); printf("Options:\n"); printf("--help | -h\n"); printf(" Show this help information.\n"); printf("--quit | -q\n"); printf(" Tell lvmlockd to quit.\n"); printf("--info | -i\n"); printf(" Print lock state information from lvmlockd.\n"); printf("--dump | -d\n"); printf(" Print log buffer from lvmlockd.\n"); printf("--wait | -w 0|1\n"); printf(" Wait option for other commands.\n"); printf("--force | -f 0|1>\n"); printf(" Force option for other commands.\n"); printf("--kill | -k \n"); printf(" Kill access to the VG when sanlock cannot renew lease.\n"); printf("--drop | -r \n"); printf(" Clear locks for the VG when it is unused after kill (-k).\n"); printf("--gl-enable | -E \n"); printf(" Tell lvmlockd to enable the global lock in a sanlock VG.\n"); printf("--gl-disable | -D \n"); printf(" Tell lvmlockd to disable the global lock in a sanlock VG.\n"); printf("--stop-lockspaces | -S\n"); printf(" Stop all lockspaces.\n"); } static int read_options(int argc, char *argv[]) { int option_index = 0; int c; static struct option long_options[] = { {"help", no_argument, 0, 'h' }, {"quit", no_argument, 0, 'q' }, {"info", no_argument, 0, 'i' }, {"dump", no_argument, 0, 'd' }, {"wait", required_argument, 0, 'w' }, {"force", required_argument, 0, 'f' }, {"kill", required_argument, 0, 'k' }, {"drop", required_argument, 0, 'r' }, {"gl-enable", required_argument, 0, 'E' }, {"gl-disable", required_argument, 0, 'D' }, {"stop-lockspaces", no_argument, 0, 'S' }, {0, 0, 0, 0 } }; if (argc == 1) { print_usage(); exit(0); } while (1) { c = getopt_long(argc, argv, "hqidE:D:w:k:r:S", long_options, &option_index); if (c == -1) break; switch (c) { case 'h': /* --help */ print_usage(); exit(0); case 'q': /* --quit */ quit = 1; break; case 'i': /* --info */ info = 1; break; case 'd': /* --dump */ dump = 1; break; case 'w': wait_opt = atoi(optarg); break; case 'k': kill_vg = 1; arg_vg_name = strdup(optarg); break; case 'r': drop_vg = 1; arg_vg_name = strdup(optarg); break; case 'E': gl_enable = 1; arg_vg_name = strdup(optarg); break; case 'D': gl_disable = 1; arg_vg_name = strdup(optarg); break; case 'S': stop_lockspaces = 1; break; default: print_usage(); exit(1); } } return 0; } int main(int argc, char **argv) { int rv = 0; rv = read_options(argc, argv); if (rv < 0) return rv; _lvmlockd = lvmlockd_open(NULL); if (_lvmlockd.socket_fd < 0 || _lvmlockd.error) { log_error("Cannot connect to lvmlockd."); return -1; } if (quit) { rv = do_quit(); goto out; } if (info) { rv = do_dump("info"); goto out; } if (dump) { rv = do_dump("dump"); goto out; } if (kill_vg) { rv = do_kill(); goto out; } if (drop_vg) { rv = do_drop(); goto out; } if (gl_enable) { syslog(LOG_INFO, "Enabling global lock in VG %s.", arg_vg_name); rv = do_able("enable_gl"); goto out; } if (gl_disable) { syslog(LOG_INFO, "Disabling global lock in VG %s.", arg_vg_name); rv = do_able("disable_gl"); goto out; } if (stop_lockspaces) { rv = do_stop_lockspaces(); goto out; } out: lvmlockd_close(_lvmlockd); return rv; }