mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
After the previous refactoring, we are able to link the daemon to
common/utils, and also remove some of the "duplicate" functions that
the daemon carried ("duplicate" in quotes because they were often not
exact duplicates).
Also this removes the duplicate reimplementation of (most) cleanup
functions in the daemon, since those are provided by libutils now.
It also allows us in future (but not in this commit) to move utility
functions from the daemon into libutils.
1123 lines
22 KiB
C
1123 lines
22 KiB
C
/* libguestfs - the guestfsd daemon
|
|
* Copyright (C) 2009-2017 Red Hat Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
|
|
#include "daemon.h"
|
|
#include "c-ctype.h"
|
|
#include "actions.h"
|
|
#include "optgroups.h"
|
|
|
|
GUESTFSD_EXT_CMD(str_lvm, lvm);
|
|
|
|
int
|
|
optgroup_lvm2_available (void)
|
|
{
|
|
return prog_exists (str_lvm);
|
|
}
|
|
|
|
/* LVM actions. Keep an eye on liblvm, although at the time
|
|
* of writing it hasn't progressed very far.
|
|
*/
|
|
|
|
static char **
|
|
convert_lvm_output (char *out, const char *prefix)
|
|
{
|
|
char *p, *pend;
|
|
CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret);
|
|
size_t len;
|
|
char buf[256];
|
|
char *str;
|
|
|
|
p = out;
|
|
while (p) {
|
|
pend = strchr (p, '\n'); /* Get the next line of output. */
|
|
if (pend) {
|
|
*pend = '\0';
|
|
pend++;
|
|
}
|
|
|
|
while (*p && c_isspace (*p)) /* Skip any leading whitespace. */
|
|
p++;
|
|
|
|
/* Sigh, skip trailing whitespace too. "pvs", I'm looking at you. */
|
|
len = strlen (p)-1;
|
|
while (*p && c_isspace (p[len]))
|
|
p[len--] = '\0';
|
|
|
|
if (!*p) { /* Empty line? Skip it. */
|
|
p = pend;
|
|
continue;
|
|
}
|
|
|
|
/* Prefix? */
|
|
if (prefix) {
|
|
snprintf (buf, sizeof buf, "%s%s", prefix, p);
|
|
str = buf;
|
|
} else
|
|
str = p;
|
|
|
|
/* Ignore "unknown device" message (RHBZ#1054761). */
|
|
if (STRNEQ (str, "unknown device")) {
|
|
if (add_string (&ret, str) == -1) {
|
|
free (out);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
p = pend;
|
|
}
|
|
|
|
free (out);
|
|
|
|
if (ret.size > 0)
|
|
sort_strings (ret.argv, ret.size);
|
|
|
|
if (end_stringsbuf (&ret) == -1)
|
|
return NULL;
|
|
|
|
return take_stringsbuf (&ret);
|
|
}
|
|
|
|
/* Filter a colon-separated output of
|
|
* lvs -o lv_attr,vg_name,lv_name
|
|
* removing thin layouts, and building the device path as we expect it.
|
|
*
|
|
* This is used only when lvm has no -S.
|
|
*/
|
|
static char **
|
|
filter_convert_old_lvs_output (char *out)
|
|
{
|
|
char *p, *pend;
|
|
CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret);
|
|
|
|
p = out;
|
|
while (p) {
|
|
size_t len;
|
|
char *saveptr;
|
|
char *lv_attr, *vg_name, *lv_name;
|
|
|
|
pend = strchr (p, '\n'); /* Get the next line of output. */
|
|
if (pend) {
|
|
*pend = '\0';
|
|
pend++;
|
|
}
|
|
|
|
while (*p && c_isspace (*p)) /* Skip any leading whitespace. */
|
|
p++;
|
|
|
|
/* Sigh, skip trailing whitespace too. "pvs", I'm looking at you. */
|
|
len = strlen (p)-1;
|
|
while (*p && c_isspace (p[len]))
|
|
p[len--] = '\0';
|
|
|
|
if (!*p) { /* Empty line? Skip it. */
|
|
skip_line:
|
|
p = pend;
|
|
continue;
|
|
}
|
|
|
|
lv_attr = strtok_r (p, ":", &saveptr);
|
|
if (!lv_attr)
|
|
goto skip_line;
|
|
|
|
vg_name = strtok_r (NULL, ":", &saveptr);
|
|
if (!vg_name)
|
|
goto skip_line;
|
|
|
|
lv_name = strtok_r (NULL, ":", &saveptr);
|
|
if (!lv_name)
|
|
goto skip_line;
|
|
|
|
/* Ignore thin layouts (RHBZ#1278878). */
|
|
if (lv_attr[0] == 't')
|
|
goto skip_line;
|
|
|
|
/* Ignore activationskip (RHBZ#1306666). */
|
|
if (strlen (lv_attr) >= 10 && lv_attr[9] == 'k')
|
|
goto skip_line;
|
|
|
|
/* Ignore "unknown device" message (RHBZ#1054761). */
|
|
if (STRNEQ (p, "unknown device")) {
|
|
char buf[256];
|
|
|
|
snprintf (buf, sizeof buf, "/dev/%s/%s", vg_name, lv_name);
|
|
if (add_string (&ret, buf) == -1) {
|
|
free (out);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
p = pend;
|
|
}
|
|
|
|
free (out);
|
|
|
|
if (ret.size > 0)
|
|
sort_strings (ret.argv, ret.size);
|
|
|
|
if (end_stringsbuf (&ret) == -1)
|
|
return NULL;
|
|
|
|
return take_stringsbuf (&ret);
|
|
}
|
|
|
|
char **
|
|
do_pvs (void)
|
|
{
|
|
char *out;
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (&out, &err,
|
|
str_lvm, "pvs", "-o", "pv_name", "--noheadings", NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
free (out);
|
|
return NULL;
|
|
}
|
|
|
|
return convert_lvm_output (out, NULL);
|
|
}
|
|
|
|
char **
|
|
do_vgs (void)
|
|
{
|
|
char *out;
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (&out, &err,
|
|
str_lvm, "vgs", "-o", "vg_name", "--noheadings", NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
free (out);
|
|
return NULL;
|
|
}
|
|
|
|
return convert_lvm_output (out, NULL);
|
|
}
|
|
|
|
/* Check whether lvs has -S to filter its output.
|
|
* It is available only in lvm2 >= 2.02.107.
|
|
*/
|
|
static int
|
|
test_lvs_has_S_opt (void)
|
|
{
|
|
static int result = -1;
|
|
if (result != -1)
|
|
return result;
|
|
|
|
CLEANUP_FREE char *out = NULL;
|
|
CLEANUP_FREE char *err = NULL;
|
|
|
|
int r = command (&out, &err, str_lvm, "lvs", "--help", NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("lvm lvs --help: %s", err);
|
|
return -1;
|
|
}
|
|
|
|
if (strstr (out, "-S") == NULL)
|
|
result = 0;
|
|
else
|
|
result = 1;
|
|
|
|
return result;
|
|
}
|
|
|
|
char **
|
|
do_lvs (void)
|
|
{
|
|
char *out;
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
const int has_S = test_lvs_has_S_opt ();
|
|
|
|
if (has_S < 0)
|
|
return NULL;
|
|
|
|
if (has_S > 0) {
|
|
r = command (&out, &err,
|
|
str_lvm, "lvs",
|
|
"-o", "vg_name,lv_name",
|
|
"-S", "lv_role=public && lv_skip_activation!=yes",
|
|
"--noheadings",
|
|
"--separator", "/", NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
free (out);
|
|
return NULL;
|
|
}
|
|
|
|
return convert_lvm_output (out, "/dev/");
|
|
} else {
|
|
r = command (&out, &err,
|
|
str_lvm, "lvs",
|
|
"-o", "lv_attr,vg_name,lv_name",
|
|
"--noheadings",
|
|
"--separator", ":", NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
free (out);
|
|
return NULL;
|
|
}
|
|
|
|
return filter_convert_old_lvs_output (out);
|
|
}
|
|
}
|
|
|
|
/* These were so complex to implement that I ended up auto-generating
|
|
* the code. That code is in stubs.c, and it is generated as usual
|
|
* by generator.ml.
|
|
*/
|
|
guestfs_int_lvm_pv_list *
|
|
do_pvs_full (void)
|
|
{
|
|
return parse_command_line_pvs ();
|
|
}
|
|
|
|
guestfs_int_lvm_vg_list *
|
|
do_vgs_full (void)
|
|
{
|
|
return parse_command_line_vgs ();
|
|
}
|
|
|
|
guestfs_int_lvm_lv_list *
|
|
do_lvs_full (void)
|
|
{
|
|
return parse_command_line_lvs ();
|
|
}
|
|
|
|
int
|
|
do_pvcreate (const char *device)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "pvcreate", "--force", device, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_vgcreate (const char *volgroup, char *const *physvols)
|
|
{
|
|
int r, argc, i;
|
|
CLEANUP_FREE char *err = NULL;
|
|
CLEANUP_FREE const char **argv = NULL;
|
|
|
|
argc = guestfs_int_count_strings (physvols) + 3;
|
|
argv = malloc (sizeof (char *) * (argc + 1));
|
|
if (argv == NULL) {
|
|
reply_with_perror ("malloc");
|
|
return -1;
|
|
}
|
|
argv[0] = str_lvm;
|
|
argv[1] = "vgcreate";
|
|
argv[2] = volgroup;
|
|
for (i = 3; i < argc+1; ++i)
|
|
argv[i] = physvols[i-3];
|
|
|
|
r = commandv (NULL, &err, (const char * const*) argv);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_lvcreate (const char *logvol, const char *volgroup, int mbytes)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
char size[64];
|
|
|
|
snprintf (size, sizeof size, "%d", mbytes);
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "lvcreate",
|
|
"-L", size, "-n", logvol, volgroup, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_lvcreate_free (const char *logvol, const char *volgroup, int percent)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
if (percent < 0 || percent > 100) {
|
|
reply_with_error ("percentage must be [0..100] (was %d)", percent);
|
|
return -1;
|
|
}
|
|
|
|
char size[64];
|
|
snprintf (size, sizeof size, "%d%%FREE", percent);
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "lvcreate",
|
|
"-l", size, "-n", logvol, volgroup, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* The lvresize command unnecessarily gives an error if you don't
|
|
* change the size of the LV. Suppress this error.
|
|
* https://bugzilla.redhat.com/show_bug.cgi?id=834712
|
|
*/
|
|
static int
|
|
ignore_same_size_error (const char *err)
|
|
{
|
|
return strstr (err, "New size (") != NULL &&
|
|
strstr (err, "extents) matches existing size (") != NULL;
|
|
}
|
|
|
|
int
|
|
do_lvresize (const char *logvol, int mbytes)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
char size[64];
|
|
|
|
snprintf (size, sizeof size, "%d", mbytes);
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "lvresize",
|
|
"--force", "-L", size, logvol, NULL);
|
|
if (r == -1) {
|
|
if (!ignore_same_size_error (err)) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_lvresize_free (const char *logvol, int percent)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
if (percent < 0 || percent > 100) {
|
|
reply_with_error ("percentage must be [0..100] (was %d)", percent);
|
|
return -1;
|
|
}
|
|
|
|
char size[64];
|
|
snprintf (size, sizeof size, "+%d%%FREE", percent);
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "lvresize", "-l", size, logvol, NULL);
|
|
if (r == -1) {
|
|
if (!ignore_same_size_error (err)) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Super-dangerous command used for testing. It removes all
|
|
* LVs, VGs and PVs permanently.
|
|
*/
|
|
int
|
|
do_lvm_remove_all (void)
|
|
{
|
|
size_t i;
|
|
int r;
|
|
|
|
{
|
|
/* Remove LVs. */
|
|
CLEANUP_FREE_STRING_LIST char **xs = do_lvs ();
|
|
if (xs == NULL)
|
|
return -1;
|
|
|
|
for (i = 0; xs[i] != NULL; ++i) {
|
|
CLEANUP_FREE char *err = NULL;
|
|
|
|
/* Deactivate the LV first. On Ubuntu, lvremove '-f' option
|
|
* does not remove active LVs reliably.
|
|
*/
|
|
(void) command (NULL, NULL, str_lvm, "lvchange", "-an", xs[i], NULL);
|
|
udev_settle ();
|
|
|
|
r = command (NULL, &err, str_lvm, "lvremove", "-f", xs[i], NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("lvremove: %s: %s", xs[i], err);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
/* Remove VGs. */
|
|
CLEANUP_FREE_STRING_LIST char **xs = do_vgs ();
|
|
if (xs == NULL)
|
|
return -1;
|
|
|
|
for (i = 0; xs[i] != NULL; ++i) {
|
|
CLEANUP_FREE char *err = NULL;
|
|
|
|
/* Deactivate the VG first, see note above. */
|
|
(void) command (NULL, NULL, str_lvm, "vgchange", "-an", xs[i], NULL);
|
|
udev_settle ();
|
|
|
|
r = command (NULL, &err, str_lvm, "vgremove", "-f", xs[i], NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("vgremove: %s: %s", xs[i], err);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
/* Remove PVs. */
|
|
CLEANUP_FREE_STRING_LIST char **xs = do_pvs ();
|
|
if (xs == NULL)
|
|
return -1;
|
|
|
|
for (i = 0; xs[i] != NULL; ++i) {
|
|
CLEANUP_FREE char *err = NULL;
|
|
|
|
r = command (NULL, &err, str_lvm, "pvremove", "-f", xs[i], NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("pvremove: %s: %s", xs[i], err);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
/* There, that was easy, sorry about your data. */
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_lvremove (const char *device)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "lvremove", "-f", device, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_vgremove (const char *device)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "vgremove", "-f", device, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_pvremove (const char *device)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "pvremove", "-ff", device, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_pvresize (const char *device)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "pvresize", device, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s: %s", device, err);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_pvresize_size (const char *device, int64_t size)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
char buf[32];
|
|
snprintf (buf, sizeof buf, "%" PRIi64 "b", size);
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "pvresize",
|
|
"--yes",
|
|
"--setphysicalvolumesize", buf,
|
|
device, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s: %s", device, err);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_vg_activate (int activate, char *const *volgroups)
|
|
{
|
|
int r, i, argc;
|
|
CLEANUP_FREE char *err = NULL;
|
|
CLEANUP_FREE const char **argv = NULL;
|
|
|
|
argc = guestfs_int_count_strings (volgroups) + 4;
|
|
argv = malloc (sizeof (char *) * (argc+1));
|
|
if (argv == NULL) {
|
|
reply_with_perror ("malloc");
|
|
return -1;
|
|
}
|
|
|
|
argv[0] = str_lvm;
|
|
argv[1] = "vgchange";
|
|
argv[2] = "-a";
|
|
argv[3] = activate ? "y" : "n";
|
|
for (i = 4; i < argc+1; ++i)
|
|
argv[i] = volgroups[i-4];
|
|
|
|
r = commandv (NULL, &err, (const char * const*) argv);
|
|
if (r == -1) {
|
|
reply_with_error ("vgchange: %s", err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_vg_activate_all (int activate)
|
|
{
|
|
char *empty[] = { NULL };
|
|
return do_vg_activate (activate, empty);
|
|
}
|
|
|
|
int
|
|
do_lvrename (const char *logvol, const char *newlogvol)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "lvrename",
|
|
logvol, newlogvol, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s -> %s: %s", logvol, newlogvol, err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_vgrename (const char *volgroup, const char *newvolgroup)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "vgrename",
|
|
volgroup, newvolgroup, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s -> %s: %s", volgroup, newvolgroup, err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char *
|
|
get_lvm_field (const char *cmd, const char *field, const char *device)
|
|
{
|
|
char *out;
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r = command (&out, &err,
|
|
str_lvm, cmd,
|
|
"--unbuffered", "--noheadings", "-o", field,
|
|
device, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s: %s", device, err);
|
|
free (out);
|
|
return NULL;
|
|
}
|
|
|
|
trim (out);
|
|
return out; /* Caller frees. */
|
|
}
|
|
|
|
char *
|
|
do_pvuuid (const char *device)
|
|
{
|
|
return get_lvm_field ("pvs", "pv_uuid", device);
|
|
}
|
|
|
|
char *
|
|
do_vguuid (const char *vgname)
|
|
{
|
|
return get_lvm_field ("vgs", "vg_uuid", vgname);
|
|
}
|
|
|
|
char *
|
|
do_lvuuid (const char *device)
|
|
{
|
|
return get_lvm_field ("lvs", "lv_uuid", device);
|
|
}
|
|
|
|
static char **
|
|
get_lvm_fields (const char *cmd, const char *field, const char *device)
|
|
{
|
|
CLEANUP_FREE char *out = NULL, *err = NULL;
|
|
|
|
int r = command (&out, &err,
|
|
str_lvm, cmd,
|
|
"--unbuffered", "--noheadings", "-o", field,
|
|
device, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s: %s", device, err);
|
|
return NULL;
|
|
}
|
|
|
|
char **ret = split_lines (out);
|
|
|
|
if (ret == NULL)
|
|
return NULL;
|
|
|
|
size_t i;
|
|
for (i = 0; ret[i] != NULL; ++i)
|
|
trim (ret[i]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
char **
|
|
do_vgpvuuids (const char *vgname)
|
|
{
|
|
return get_lvm_fields ("vgs", "pv_uuid", vgname);
|
|
}
|
|
|
|
char **
|
|
do_vglvuuids (const char *vgname)
|
|
{
|
|
return get_lvm_fields ("vgs", "lv_uuid", vgname);
|
|
}
|
|
|
|
int
|
|
do_vgscan (void)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "vgscan", NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Convert a non-canonical LV path like /dev/mapper/vg-lv or /dev/dm-0
|
|
* to a canonical one.
|
|
*
|
|
* This is harder than it should be. A LV device like /dev/VG/LV is
|
|
* really a symlink to a device-mapper device like /dev/dm-0. However
|
|
* at the device-mapper (kernel) level, nothing is really known about
|
|
* LVM (a userspace concept). Therefore we use a convoluted method to
|
|
* determine this, by listing out known LVs and checking whether the
|
|
* rdev (major/minor) of the device we are passed matches any of them.
|
|
*
|
|
* Note use of 'stat' instead of 'lstat' so that symlinks are fully
|
|
* resolved.
|
|
*
|
|
* Returns:
|
|
* 1 = conversion was successful, path is an LV
|
|
* '*ret' is set to the updated path if 'ret' is non-NULL.
|
|
* 0 = path is not an LV
|
|
* -1 = error, reply_with_* has been called
|
|
*
|
|
*/
|
|
int
|
|
lv_canonical (const char *device, char **ret)
|
|
{
|
|
struct stat stat1, stat2;
|
|
|
|
int r = stat (device, &stat1);
|
|
if (r == -1) {
|
|
reply_with_perror ("stat: %s", device);
|
|
return -1;
|
|
}
|
|
|
|
CLEANUP_FREE_STRING_LIST char **lvs = do_lvs ();
|
|
if (lvs == NULL)
|
|
return -1;
|
|
|
|
size_t i;
|
|
for (i = 0; lvs[i] != NULL; ++i) {
|
|
r = stat (lvs[i], &stat2);
|
|
if (r == -1) {
|
|
reply_with_perror ("stat: %s", lvs[i]);
|
|
return -1;
|
|
}
|
|
if (stat1.st_rdev == stat2.st_rdev) { /* found it */
|
|
if (ret) {
|
|
*ret = strdup (lvs[i]);
|
|
if (*ret == NULL) {
|
|
reply_with_perror ("strdup");
|
|
return -1;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* not found */
|
|
return 0;
|
|
}
|
|
|
|
/* Test if a device is a logical volume (RHBZ#619793). */
|
|
int
|
|
do_is_lv (const mountable_t *mountable)
|
|
{
|
|
if (mountable->type != MOUNTABLE_DEVICE)
|
|
return 0;
|
|
return lv_canonical (mountable->device, NULL);
|
|
}
|
|
|
|
/* Return canonical name of LV to caller (RHBZ#638899). */
|
|
char *
|
|
do_lvm_canonical_lv_name (const char *device)
|
|
{
|
|
char *canonical;
|
|
int r = lv_canonical (device, &canonical);
|
|
if (r == -1)
|
|
return NULL;
|
|
|
|
if (r == 0) {
|
|
reply_with_error ("%s: not a logical volume", device);
|
|
return NULL;
|
|
}
|
|
|
|
return canonical; /* caller frees */
|
|
}
|
|
|
|
/* List everything in /dev/mapper which *isn't* an LV (RHBZ#688062). */
|
|
char **
|
|
do_list_dm_devices (void)
|
|
{
|
|
CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret);
|
|
struct dirent *d;
|
|
DIR *dir;
|
|
int r;
|
|
|
|
dir = opendir ("/dev/mapper");
|
|
if (!dir) {
|
|
reply_with_perror ("opendir: /dev/mapper");
|
|
return NULL;
|
|
}
|
|
|
|
while (1) {
|
|
CLEANUP_FREE char *devname = NULL;
|
|
|
|
errno = 0;
|
|
d = readdir (dir);
|
|
if (d == NULL) break;
|
|
|
|
/* Ignore . and .. */
|
|
if (STREQ (d->d_name, ".") || STREQ (d->d_name, ".."))
|
|
continue;
|
|
|
|
/* Ignore /dev/mapper/control which is used internally by dm. */
|
|
if (STREQ (d->d_name, "control"))
|
|
continue;
|
|
|
|
if (asprintf (&devname, "/dev/mapper/%s", d->d_name) == -1) {
|
|
reply_with_perror ("asprintf");
|
|
closedir (dir);
|
|
return NULL;
|
|
}
|
|
|
|
/* Ignore dm devices which are LVs. */
|
|
r = lv_canonical (devname, NULL);
|
|
if (r == -1) {
|
|
closedir (dir);
|
|
return NULL;
|
|
}
|
|
if (r)
|
|
continue;
|
|
|
|
/* Not an LV, so add it. */
|
|
if (add_string (&ret, devname) == -1) {
|
|
closedir (dir);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Did readdir fail? */
|
|
if (errno != 0) {
|
|
reply_with_perror ("readdir: /dev/mapper");
|
|
closedir (dir);
|
|
return NULL;
|
|
}
|
|
|
|
/* Close the directory handle. */
|
|
if (closedir (dir) == -1) {
|
|
reply_with_perror ("closedir: /dev/mapper");
|
|
return NULL;
|
|
}
|
|
|
|
/* Sort the output (may be empty). */
|
|
if (ret.size > 0)
|
|
sort_strings (ret.argv, ret.size);
|
|
|
|
/* NULL-terminate the list. */
|
|
if (end_stringsbuf (&ret) == -1)
|
|
return NULL;
|
|
|
|
return take_stringsbuf (&ret);
|
|
}
|
|
|
|
char *
|
|
do_vgmeta (const char *vg, size_t *size_r)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int fd, r;
|
|
char tmp[] = "/tmp/vgmetaXXXXXX";
|
|
size_t alloc, size, max;
|
|
ssize_t rs;
|
|
char *buf, *buf2;
|
|
|
|
/* Make a temporary file. */
|
|
fd = mkstemp (tmp);
|
|
if (fd == -1) {
|
|
reply_with_perror ("mkstemp");
|
|
return NULL;
|
|
}
|
|
|
|
close (fd);
|
|
|
|
r = command (NULL, &err, str_lvm, "vgcfgbackup", "-f", tmp, vg, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("vgcfgbackup: %s", err);
|
|
return NULL;
|
|
}
|
|
|
|
/* Now read back the temporary file. */
|
|
fd = open (tmp, O_RDONLY|O_CLOEXEC);
|
|
if (fd == -1) {
|
|
reply_with_error ("%s", tmp);
|
|
return NULL;
|
|
}
|
|
|
|
/* Read up to GUESTFS_MESSAGE_MAX - <overhead> bytes. If it's
|
|
* larger than that, we need to return an error instead (for
|
|
* correctness).
|
|
*/
|
|
max = GUESTFS_MESSAGE_MAX - 1000;
|
|
buf = NULL;
|
|
size = alloc = 0;
|
|
|
|
for (;;) {
|
|
if (size >= alloc) {
|
|
alloc += 8192;
|
|
if (alloc > max) {
|
|
reply_with_error ("metadata is too large for message buffer");
|
|
free (buf);
|
|
close (fd);
|
|
return NULL;
|
|
}
|
|
buf2 = realloc (buf, alloc);
|
|
if (buf2 == NULL) {
|
|
reply_with_perror ("realloc");
|
|
free (buf);
|
|
close (fd);
|
|
return NULL;
|
|
}
|
|
buf = buf2;
|
|
}
|
|
|
|
rs = read (fd, buf + size, alloc - size);
|
|
if (rs == -1) {
|
|
reply_with_perror ("read: %s", tmp);
|
|
free (buf);
|
|
close (fd);
|
|
return NULL;
|
|
}
|
|
if (rs == 0)
|
|
break;
|
|
if (rs > 0)
|
|
size += rs;
|
|
}
|
|
|
|
if (close (fd) == -1) {
|
|
reply_with_perror ("close: %s", tmp);
|
|
free (buf);
|
|
return NULL;
|
|
}
|
|
|
|
unlink (tmp);
|
|
|
|
*size_r = size;
|
|
|
|
return buf; /* caller will free */
|
|
}
|
|
|
|
int
|
|
do_pvchange_uuid (const char *device)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "pvchange", "-u", device, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s: %s", device, err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_pvchange_uuid_all (void)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "pvchange", "-u", "-a", NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_vgchange_uuid (const char *vg)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "vgchange", "-u", vg, NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s: %s", vg, err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
do_vgchange_uuid_all (void)
|
|
{
|
|
CLEANUP_FREE char *err = NULL;
|
|
int r;
|
|
|
|
r = command (NULL, &err,
|
|
str_lvm, "vgchange", "-u", NULL);
|
|
if (r == -1) {
|
|
reply_with_error ("%s", err);
|
|
return -1;
|
|
}
|
|
|
|
udev_settle ();
|
|
|
|
return 0;
|
|
}
|