Files
libguestfs/fish/glob.c
Richard W.M. Jones 05d4fcb64d Update copyright dates for 2019.
This command run over the source:

perl -pi.bak -e 's/(20[01][0-9])-2018/$1-2019/g' `git ls-files`
2019-01-08 11:58:30 +00:00

353 lines
8.6 KiB
C

/* guestfish - guest filesystem shell
* Copyright (C) 2009-2019 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.
*/
/**
* This file implements the guestfish C<glob> command.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fnmatch.h>
#include <libintl.h>
#include "fish.h"
/* A bit tricky because in the case where there are multiple
* paths we have to perform a Cartesian product.
*/
static char **expand_pathname (guestfs_h *g, const char *path);
static char **expand_devicename (guestfs_h *g, const char *device);
static int add_strings_matching (char **pp, const char *glob, char ***ret, size_t *size_r);
static int add_string (const char *str, char ***ret, size_t *size_r);
static char **single_element_list (const char *element);
static int glob_issue (char *cmd, size_t argc, char ***globs, size_t *posn, size_t *count, int *r);
int
run_glob (const char *cmd, size_t argc, char *argv[])
{
/* For 'glob cmd foo /s* /usr/s*' this could be:
*
* (globs[0]) globs[1] globs[1] globs[2]
* (cmd) foo /sbin /usr/sbin
* /srv /usr/share
* /sys /usr/src
*
* and then we call every combination (ie. 1x3x3) of
* argv[1-].
*/
CLEANUP_FREE char ***globs = NULL;
CLEANUP_FREE size_t *posn = NULL;
CLEANUP_FREE size_t *count = NULL;
size_t i;
int r = 0;
globs = malloc (sizeof (char **) * argc);
posn = malloc (sizeof (size_t) * argc);
count = malloc (sizeof (size_t) * argc);
if (globs == NULL || posn == NULL || count == NULL) {
perror ("malloc");
return -1;
}
if (argc < 1) {
fprintf (stderr, _("use 'glob command [args...]'\n"));
return -1;
}
/* This array will record the current execution position
* in the Cartesian product.
* NB. globs[0], posn[0], count[0] are ignored.
*/
for (i = 1; i < argc; ++i)
posn[i] = 0;
for (i = 1; i < argc; ++i)
globs[i] = NULL;
for (i = 1; i < argc; ++i) {
char **pp;
/* If it begins with "/dev/" then treat it as a globbable device
* name.
*/
if (STRPREFIX (argv[i], "/dev/")) {
pp = expand_devicename (g, argv[i]);
if (pp == NULL) {
r = -1;
goto error;
}
}
/* If it begins with "/" it might be a globbable pathname. */
else if (argv[i][0] == '/') {
pp = expand_pathname (g, argv[i]);
if (pp == NULL) {
r = -1;
goto error;
}
}
/* Doesn't begin with '/' */
else {
pp = single_element_list (argv[i]);
if (pp == NULL) {
r = -1;
goto error;
}
}
globs[i] = pp;
count[i] = guestfs_int_count_strings (pp);
}
/* Issue the commands. */
if (glob_issue (argv[0], argc, globs, posn, count, &r) == -1) {
r = -1;
goto error;
}
/* Free resources. */
error:
for (i = 1; i < argc; ++i)
if (globs[i])
guestfs_int_free_string_list (globs[i]);
return r;
}
static char **
expand_pathname (guestfs_h *g, const char *path)
{
char **pp;
pp = guestfs_glob_expand (g, path);
if (pp == NULL) { /* real error in glob_expand */
fprintf (stderr, _("glob: guestfs_glob_expand call failed: %s\n"), path);
return NULL;
}
if (pp[0] != NULL)
return pp; /* Return the non-empty list of matches. */
/* If there were no matches, then we add a single element list
* containing just the original string.
*/
free (pp);
return single_element_list (path);
}
/**
* Glob-expand device patterns, such as C</dev/sd*>
* (L<https://bugzilla.redhat.com/635971>).
*
* There is no C<guestfs_glob_expand_device> function because the
* equivalent can be implemented using functions like
* C<guestfs_list_devices>.
*
* It's not immediately clear what it means to expand a pattern like
* C</dev/sd*>. Should that include device name translation? Should
* the result include partitions as well as devices?
*
* Should C<"/dev/"> + C<"*"> return every possible device and
* filesystem? How about VGs? LVs?
*
* To solve this what we do is build up a list of every device,
* partition, etc., then glob against that list.
*
* Notes for future work (XXX):
*
* =over 4
*
* =item *
*
* This doesn't handle device name translation. It wouldn't be
* too hard to add.
*
* =item *
*
* Could have an API function for returning all device-like things.
*
* =back
*/
static char **
expand_devicename (guestfs_h *g, const char *device)
{
char **pp = NULL;
char **ret = NULL;
size_t size = 0;
const char *lvm2[] = { "lvm2", NULL };
pp = guestfs_list_devices (g);
if (pp == NULL) goto error;
if (add_strings_matching (pp, device, &ret, &size) == -1) goto error;
guestfs_int_free_string_list (pp);
pp = guestfs_list_partitions (g);
if (pp == NULL) goto error;
if (add_strings_matching (pp, device, &ret, &size) == -1) goto error;
guestfs_int_free_string_list (pp);
pp = guestfs_list_md_devices (g);
if (pp == NULL) goto error;
if (add_strings_matching (pp, device, &ret, &size) == -1) goto error;
guestfs_int_free_string_list (pp);
if (guestfs_feature_available (g, (char **) lvm2)) {
pp = guestfs_lvs (g);
if (pp == NULL) goto error;
if (add_strings_matching (pp, device, &ret, &size) == -1) goto error;
guestfs_int_free_string_list (pp);
pp = NULL;
}
/* None matched? Add the original glob pattern. */
if (ret == NULL)
ret = single_element_list (device);
return ret;
error:
if (pp)
guestfs_int_free_string_list (pp);
if (ret)
guestfs_int_free_string_list (ret);
return NULL;
}
/**
* Using POSIX L<fnmatch(3)>, find strings in the list C<pp> which
* match pattern C<glob>. Add strings which match to the C<ret>
* array. C<*size_r> is the current size of the C<ret> array, which
* is updated with the new size.
*/
static int
add_strings_matching (char **pp, const char *glob,
char ***ret, size_t *size_r)
{
size_t i;
int r;
for (i = 0; pp[i] != NULL; ++i) {
errno = 0;
r = fnmatch (glob, pp[i], FNM_PATHNAME);
if (r == 0) { /* matches - add it */
if (add_string (pp[i], ret, size_r) == -1)
return -1;
}
else if (r != FNM_NOMATCH) { /* error */
/* I checked the glibc impl and it returns random negative
* numbers for errors. It doesn't always set errno. Do our
* best here to record the error state.
*/
fprintf (stderr, "glob: fnmatch: error (r = %d, errno = %d)\n",
r, errno);
return -1;
}
}
return 0;
}
static int
add_string (const char *str, char ***ret, size_t *size_r)
{
char **new_ret = *ret;
size_t size = *size_r;
new_ret = realloc (new_ret, (size + 2) * (sizeof (char *)));
if (!new_ret) {
perror ("realloc");
return -1;
}
*ret = new_ret;
new_ret[size] = strdup (str);
if (new_ret[size] == NULL) {
perror ("strdup");
return -1;
}
size++;
new_ret[size] = NULL;
*size_r = size;
return 0;
}
/**
* Return a single element list containing C<element>.
*/
static char **
single_element_list (const char *element)
{
char **pp;
pp = malloc (sizeof (char *) * 2);
if (pp == NULL) {
perror ("malloc");
return NULL;
}
pp[0] = strdup (element);
if (pp[0] == NULL) {
perror ("strdup");
free (pp);
return NULL;
}
pp[1] = NULL;
return pp;
}
static int
glob_issue (char *cmd, size_t argc,
char ***globs, size_t *posn, size_t *count,
int *r)
{
size_t i;
CLEANUP_FREE char **argv = NULL;
argv = malloc (sizeof (char *) * (argc+1));
if (argv == NULL) {
perror ("malloc");
return -1;
}
argv[0] = cmd;
argv[argc] = NULL;
again:
for (i = 1; i < argc; ++i)
argv[i] = globs[i][posn[i]];
if (issue_command (argv[0], &argv[1], NULL, 0) == -1)
*r = -1; /* ... but don't exit */
for (i = argc-1; i >= 1; --i) {
posn[i]++;
if (posn[i] < count[i])
break;
posn[i] = 0;
}
if (i == 0) /* All done. */
return 0;
goto again;
}