mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
668 lines
16 KiB
C
668 lines
16 KiB
C
/* libguestfs
|
|
* Copyright (C) 2012 Red Hat Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; 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 <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <string.h>
|
|
|
|
#include "full-read.h"
|
|
#include "full-write.h"
|
|
|
|
#include "guestfs.h"
|
|
#include "guestfs-internal.h"
|
|
#include "guestfs-internal-actions.h"
|
|
|
|
static int
|
|
compare (const void *vp1, const void *vp2)
|
|
{
|
|
char * const *p1 = (char * const *) vp1;
|
|
char * const *p2 = (char * const *) vp2;
|
|
return strcmp (*p1, *p2);
|
|
}
|
|
|
|
static void
|
|
sort_strings (char **argv, size_t len)
|
|
{
|
|
qsort (argv, len, sizeof (char *), compare);
|
|
}
|
|
|
|
/* Take the first 'n' names, returning a newly allocated list. The
|
|
* strings themselves are not duplicated. If 'lastp' is not NULL,
|
|
* then it is updated with the pointer to the list of remaining names.
|
|
*/
|
|
static char **
|
|
take_strings (guestfs_h *g, char *const *names, size_t n, char *const **lastp)
|
|
{
|
|
size_t i;
|
|
|
|
char **ret = safe_malloc (g, (n+1) * sizeof (char *));
|
|
|
|
for (i = 0; names[i] != NULL && i < n; ++i)
|
|
ret[i] = names[i];
|
|
|
|
ret[i] = NULL;
|
|
|
|
if (lastp)
|
|
*lastp = &names[i];
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *
|
|
guestfs_impl_cat (guestfs_h *g, const char *path)
|
|
{
|
|
size_t size;
|
|
|
|
return guestfs_read_file (g, path, &size);
|
|
}
|
|
|
|
char *
|
|
guestfs_impl_read_file (guestfs_h *g, const char *path, size_t *size_r)
|
|
{
|
|
int fd = -1;
|
|
size_t size;
|
|
CLEANUP_UNLINK_FREE char *tmpfile = NULL;
|
|
char *ret = NULL;
|
|
struct stat statbuf;
|
|
|
|
tmpfile = guestfs_int_make_temp_path (g, "cat");
|
|
if (tmpfile == NULL)
|
|
goto err;
|
|
|
|
if (guestfs_download (g, path, tmpfile) == -1)
|
|
goto err;
|
|
|
|
fd = open (tmpfile, O_RDONLY|O_CLOEXEC);
|
|
if (fd == -1) {
|
|
perrorf (g, "open: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
|
|
/* Read the whole file into memory. */
|
|
if (fstat (fd, &statbuf) == -1) {
|
|
perrorf (g, "stat: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
|
|
/* Don't use safe_malloc, because we want to return an errno to the caller. */
|
|
size = statbuf.st_size;
|
|
ret = malloc (size + 1);
|
|
if (!ret) {
|
|
perrorf (g, "malloc: %zu bytes", size + 1);
|
|
goto err;
|
|
}
|
|
|
|
if (full_read (fd, ret, size) != size) {
|
|
perrorf (g, "full-read: %s: %zu bytes", tmpfile, size + 1);
|
|
goto err;
|
|
}
|
|
|
|
ret[size] = '\0';
|
|
|
|
if (close (fd) == -1) {
|
|
perrorf (g, "close: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
|
|
/* Mustn't touch *size_r until we are sure that we won't return any
|
|
* error (RHBZ#589039).
|
|
*/
|
|
*size_r = size;
|
|
return ret;
|
|
|
|
err:
|
|
free (ret);
|
|
if (fd >= 0)
|
|
close (fd);
|
|
return NULL;
|
|
}
|
|
|
|
char **
|
|
guestfs_impl_read_lines (guestfs_h *g, const char *file)
|
|
{
|
|
size_t i, count, size, len;
|
|
CLEANUP_FREE char *buf = NULL;
|
|
char **ret = NULL;
|
|
|
|
/* Read the whole file into memory. */
|
|
buf = guestfs_read_file (g, file, &size);
|
|
if (buf == NULL)
|
|
return NULL;
|
|
|
|
/* 'buf' contains the list of strings, separated by LF or CRLF
|
|
* characters. Convert this to a list of lines. Note we have to
|
|
* handle the cases where the buffer is zero length and where the
|
|
* final string is not terminated.
|
|
*/
|
|
count = 0;
|
|
for (i = 0; i < size; ++i)
|
|
if (buf[i] == '\n')
|
|
count++;
|
|
if (size > 0 && buf[size-1] != '\n')
|
|
count++;
|
|
|
|
ret = malloc ((count + 1) * sizeof (char *));
|
|
if (!ret) {
|
|
perrorf (g, "malloc");
|
|
goto err;
|
|
}
|
|
|
|
count = 0;
|
|
if (size > 0) {
|
|
ret[count++] = buf;
|
|
for (i = 0; i < size; ++i) {
|
|
if (buf[i] == '\n') {
|
|
buf[i] = '\0';
|
|
if (i+1 < size)
|
|
ret[count++] = &buf[i+1];
|
|
}
|
|
}
|
|
}
|
|
ret[count] = NULL;
|
|
|
|
/* Duplicate the strings, and remove the trailing \r characters if any. */
|
|
for (i = 0; ret[i] != NULL; ++i) {
|
|
ret[i] = strdup (ret[i]);
|
|
if (ret[i] == NULL) {
|
|
perrorf (g, "strdup");
|
|
while (i > 0)
|
|
free (ret[--i]);
|
|
goto err;
|
|
}
|
|
len = strlen (ret[i]);
|
|
if (len > 0 && ret[i][len-1] == '\r')
|
|
ret[i][len-1] = '\0';
|
|
}
|
|
|
|
return ret;
|
|
|
|
err:
|
|
free (ret);
|
|
return NULL;
|
|
}
|
|
|
|
char **
|
|
guestfs_impl_find (guestfs_h *g, const char *directory)
|
|
{
|
|
int fd = -1;
|
|
struct stat statbuf;
|
|
CLEANUP_UNLINK_FREE char *tmpfile = NULL;
|
|
CLEANUP_FREE char *buf = NULL;
|
|
char **ret = NULL;
|
|
size_t i, count, size;
|
|
|
|
tmpfile = guestfs_int_make_temp_path (g, "find");
|
|
if (tmpfile == NULL)
|
|
goto err;
|
|
|
|
if (guestfs_find0 (g, directory, tmpfile) == -1)
|
|
goto err;
|
|
|
|
fd = open (tmpfile, O_RDONLY|O_CLOEXEC);
|
|
if (fd == -1) {
|
|
perrorf (g, "open: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
|
|
/* Read the whole file into memory. */
|
|
if (fstat (fd, &statbuf) == -1) {
|
|
perrorf (g, "stat: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
|
|
/* Don't use safe_malloc, because we want to return an errno to the caller. */
|
|
size = statbuf.st_size;
|
|
buf = malloc (size);
|
|
if (!buf) {
|
|
perrorf (g, "malloc: %zu bytes", size);
|
|
goto err;
|
|
}
|
|
|
|
if (full_read (fd, buf, size) != size) {
|
|
perrorf (g, "full-read: %s: %zu bytes", tmpfile, size);
|
|
goto err;
|
|
}
|
|
|
|
if (close (fd) == -1) {
|
|
perrorf (g, "close: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
fd = -1;
|
|
|
|
/* 'buf' contains the list of strings, separated (and terminated) by
|
|
* '\0' characters. Convert this to a list of lines. Note we
|
|
* handle the case where buf is completely empty (size == 0), even
|
|
* though it is probably impossible.
|
|
*/
|
|
count = 0;
|
|
for (i = 0; i < size; ++i)
|
|
if (buf[i] == '\0')
|
|
count++;
|
|
|
|
ret = malloc ((count + 1) * sizeof (char *));
|
|
if (!ret) {
|
|
perrorf (g, "malloc");
|
|
goto err;
|
|
}
|
|
|
|
count = 0;
|
|
ret[count++] = buf;
|
|
for (i = 0; i < size; ++i) {
|
|
if (buf[i] == '\0')
|
|
ret[count++] = &buf[i+1];
|
|
}
|
|
ret[--count] = NULL;
|
|
|
|
/* Finally we have to duplicate and sort the strings, since that's
|
|
* what the caller is expecting.
|
|
*/
|
|
for (i = 0; ret[i] != NULL; ++i) {
|
|
ret[i] = strdup (ret[i]);
|
|
if (ret[i] == NULL) {
|
|
perrorf (g, "strdup");
|
|
while (i > 0)
|
|
free (ret[--i]);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
sort_strings (ret, count);
|
|
|
|
return ret; /* caller frees */
|
|
|
|
err:
|
|
free (ret);
|
|
if (fd >= 0)
|
|
close (fd);
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
write_or_append (guestfs_h *g, const char *path,
|
|
const char *content, size_t size,
|
|
int append)
|
|
{
|
|
CLEANUP_UNLINK_FREE char *tmpfile = NULL;
|
|
int fd = -1;
|
|
int64_t filesize;
|
|
|
|
/* If the content is small enough, use guestfs_internal_write{,_append}
|
|
* since that call is more efficient.
|
|
*/
|
|
if (size <= 2*1024*1024)
|
|
return
|
|
(!append ? guestfs_internal_write : guestfs_internal_write_append)
|
|
(g, path, content, size);
|
|
|
|
/* Write the content out to a temporary file. */
|
|
tmpfile = guestfs_int_make_temp_path (g, "write");
|
|
if (tmpfile == NULL)
|
|
goto err;
|
|
|
|
fd = open (tmpfile, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0600);
|
|
if (fd == -1) {
|
|
perrorf (g, "open: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
|
|
if (full_write (fd, content, size) != size) {
|
|
perrorf (g, "write: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
|
|
if (close (fd) == -1) {
|
|
perrorf (g, "close: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
fd = -1;
|
|
|
|
if (!append) {
|
|
if (guestfs_upload (g, tmpfile, path) == -1)
|
|
goto err;
|
|
}
|
|
else {
|
|
/* XXX Should have an 'upload-append' call to make this atomic. */
|
|
filesize = guestfs_filesize (g, path);
|
|
if (filesize == -1)
|
|
goto err;
|
|
if (guestfs_upload_offset (g, tmpfile, path, filesize) == -1)
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
if (fd >= 0)
|
|
close (fd);
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
guestfs_impl_write (guestfs_h *g, const char *path,
|
|
const char *content, size_t size)
|
|
{
|
|
return write_or_append (g, path, content, size, 0);
|
|
}
|
|
|
|
int
|
|
guestfs_impl_write_append (guestfs_h *g, const char *path,
|
|
const char *content, size_t size)
|
|
{
|
|
return write_or_append (g, path, content, size, 1);
|
|
}
|
|
|
|
#define LSTATNSLIST_MAX 1000
|
|
|
|
struct guestfs_statns_list *
|
|
guestfs_impl_lstatnslist (guestfs_h *g, const char *dir, char * const*names)
|
|
{
|
|
size_t len = guestfs_int_count_strings (names);
|
|
size_t old_len;
|
|
struct guestfs_statns_list *ret;
|
|
|
|
ret = safe_malloc (g, sizeof *ret);
|
|
ret->len = 0;
|
|
ret->val = NULL;
|
|
|
|
while (len > 0) {
|
|
CLEANUP_FREE_STATNS_LIST struct guestfs_statns_list *stats = NULL;
|
|
|
|
/* Note we don't need to free up the strings because take_strings
|
|
* does not do a deep copy.
|
|
*/
|
|
CLEANUP_FREE char **first = take_strings (g, names, LSTATNSLIST_MAX, &names);
|
|
|
|
len = len <= LSTATNSLIST_MAX ? 0 : len - LSTATNSLIST_MAX;
|
|
|
|
stats = guestfs_internal_lstatnslist (g, dir, first);
|
|
|
|
if (stats == NULL) {
|
|
guestfs_free_statns_list (ret);
|
|
return NULL;
|
|
}
|
|
|
|
/* Append stats to ret. */
|
|
old_len = ret->len;
|
|
ret->len += stats->len;
|
|
ret->val = safe_realloc (g, ret->val,
|
|
ret->len * sizeof (struct guestfs_statns));
|
|
memcpy (&ret->val[old_len], stats->val,
|
|
stats->len * sizeof (struct guestfs_statns));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define LXATTRLIST_MAX 1000
|
|
|
|
struct guestfs_xattr_list *
|
|
guestfs_impl_lxattrlist (guestfs_h *g, const char *dir, char *const *names)
|
|
{
|
|
size_t len = guestfs_int_count_strings (names);
|
|
size_t i, old_len;
|
|
struct guestfs_xattr_list *ret;
|
|
|
|
ret = safe_malloc (g, sizeof *ret);
|
|
ret->len = 0;
|
|
ret->val = NULL;
|
|
|
|
while (len > 0) {
|
|
CLEANUP_FREE_XATTR_LIST struct guestfs_xattr_list *xattrs = NULL;
|
|
|
|
/* Note we don't need to free up the strings because take_strings
|
|
* does not do a deep copy.
|
|
*/
|
|
CLEANUP_FREE char **first = take_strings (g, names, LXATTRLIST_MAX, &names);
|
|
len = len <= LXATTRLIST_MAX ? 0 : len - LXATTRLIST_MAX;
|
|
|
|
xattrs = guestfs_internal_lxattrlist (g, dir, first);
|
|
|
|
if (xattrs == NULL) {
|
|
guestfs_free_xattr_list (ret);
|
|
return NULL;
|
|
}
|
|
|
|
/* Append xattrs to ret. */
|
|
old_len = ret->len;
|
|
ret->len += xattrs->len;
|
|
ret->val = safe_realloc (g, ret->val,
|
|
ret->len * sizeof (struct guestfs_xattr));
|
|
for (i = 0; i < xattrs->len; ++i, ++old_len) {
|
|
/* We have to make a deep copy of the attribute name and value.
|
|
*/
|
|
ret->val[old_len].attrname = safe_strdup (g, xattrs->val[i].attrname);
|
|
ret->val[old_len].attrval = safe_malloc (g, xattrs->val[i].attrval_len);
|
|
ret->val[old_len].attrval_len = xattrs->val[i].attrval_len;
|
|
memcpy (ret->val[old_len].attrval, xattrs->val[i].attrval,
|
|
xattrs->val[i].attrval_len);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define READLINK_MAX 1000
|
|
|
|
char **
|
|
guestfs_impl_readlinklist (guestfs_h *g, const char *dir, char *const *names)
|
|
{
|
|
size_t len = guestfs_int_count_strings (names);
|
|
size_t old_len, ret_len = 0;
|
|
char **ret = NULL;
|
|
|
|
while (len > 0) {
|
|
/* Note we don't need to free up the strings because the 'links'
|
|
* strings are copied to ret, and 'take_strings' does not do a
|
|
* deep copy.
|
|
*/
|
|
CLEANUP_FREE char **links = NULL;
|
|
CLEANUP_FREE char **first = take_strings (g, names, READLINK_MAX, &names);
|
|
len = len <= READLINK_MAX ? 0 : len - READLINK_MAX;
|
|
|
|
links = guestfs_internal_readlinklist (g, dir, first);
|
|
|
|
if (links == NULL) {
|
|
if (ret)
|
|
guestfs_int_free_string_list (ret);
|
|
return NULL;
|
|
}
|
|
|
|
/* Append links to ret. */
|
|
old_len = ret_len;
|
|
ret_len += guestfs_int_count_strings (links);
|
|
ret = safe_realloc (g, ret, ret_len * sizeof (char *));
|
|
memcpy (&ret[old_len], links, (ret_len-old_len) * sizeof (char *));
|
|
}
|
|
|
|
/* NULL-terminate the list. */
|
|
ret = safe_realloc (g, ret, (ret_len+1) * sizeof (char *));
|
|
ret[ret_len] = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
char **
|
|
guestfs_impl_ls (guestfs_h *g, const char *directory)
|
|
{
|
|
int fd = -1;
|
|
struct stat statbuf;
|
|
CLEANUP_UNLINK_FREE char *tmpfile = NULL;
|
|
CLEANUP_FREE char *buf = NULL;
|
|
char **ret = NULL;
|
|
size_t i, count, size;
|
|
|
|
tmpfile = guestfs_int_make_temp_path (g, "ls");
|
|
if (tmpfile == NULL)
|
|
goto err;
|
|
|
|
if (guestfs_ls0 (g, directory, tmpfile) == -1)
|
|
goto err;
|
|
|
|
fd = open (tmpfile, O_RDONLY|O_CLOEXEC);
|
|
if (fd == -1) {
|
|
perrorf (g, "open: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
|
|
/* Read the whole file into memory. */
|
|
if (fstat (fd, &statbuf) == -1) {
|
|
perrorf (g, "stat: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
|
|
/* Don't use safe_malloc, because we want to return an errno to the caller. */
|
|
size = statbuf.st_size;
|
|
buf = malloc (size);
|
|
if (!buf) {
|
|
perrorf (g, "malloc: %zu bytes", size);
|
|
goto err;
|
|
}
|
|
|
|
if (full_read (fd, buf, size) != size) {
|
|
perrorf (g, "full-read: %s: %zu bytes", tmpfile, size);
|
|
goto err;
|
|
}
|
|
|
|
if (close (fd) == -1) {
|
|
perrorf (g, "close: %s", tmpfile);
|
|
goto err;
|
|
}
|
|
fd = -1;
|
|
|
|
/* 'buf' contains the list of strings, separated (and terminated) by
|
|
* '\0' characters. Convert this to a list of lines. Note we
|
|
* handle the case where buf is completely empty (size == 0).
|
|
*/
|
|
count = 0;
|
|
for (i = 0; i < size; ++i)
|
|
if (buf[i] == '\0')
|
|
count++;
|
|
|
|
ret = malloc ((count + 1) * sizeof (char *));
|
|
if (!ret) {
|
|
perrorf (g, "malloc");
|
|
goto err;
|
|
}
|
|
|
|
count = 0;
|
|
ret[count++] = buf;
|
|
for (i = 0; i < size; ++i) {
|
|
if (buf[i] == '\0')
|
|
ret[count++] = &buf[i+1];
|
|
}
|
|
ret[--count] = NULL;
|
|
|
|
/* Finally we have to duplicate and sort the strings, since that's
|
|
* what the caller is expecting.
|
|
*/
|
|
for (i = 0; ret[i] != NULL; ++i) {
|
|
ret[i] = strdup (ret[i]);
|
|
if (ret[i] == NULL) {
|
|
perrorf (g, "strdup");
|
|
while (i > 0)
|
|
free (ret[--i]);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
sort_strings (ret, count);
|
|
|
|
return ret; /* caller frees */
|
|
|
|
err:
|
|
free (ret);
|
|
if (fd >= 0)
|
|
close (fd);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
statns_to_old_stat (struct guestfs_statns *a, struct guestfs_stat *r)
|
|
{
|
|
r->dev = a->st_dev;
|
|
r->ino = a->st_ino;
|
|
r->mode = a->st_mode;
|
|
r->nlink = a->st_nlink;
|
|
r->uid = a->st_uid;
|
|
r->gid = a->st_gid;
|
|
r->rdev = a->st_rdev;
|
|
r->size = a->st_size;
|
|
r->blksize = a->st_blksize;
|
|
r->blocks = a->st_blocks;
|
|
r->atime = a->st_atime_sec;
|
|
r->mtime = a->st_mtime_sec;
|
|
r->ctime = a->st_ctime_sec;
|
|
}
|
|
|
|
struct guestfs_stat *
|
|
guestfs_impl_stat (guestfs_h *g, const char *path)
|
|
{
|
|
CLEANUP_FREE_STATNS struct guestfs_statns *r;
|
|
struct guestfs_stat *ret;
|
|
|
|
r = guestfs_statns (g, path);
|
|
if (r == NULL)
|
|
return NULL;
|
|
|
|
ret = safe_malloc (g, sizeof *ret);
|
|
statns_to_old_stat (r, ret);
|
|
return ret; /* caller frees */
|
|
}
|
|
|
|
struct guestfs_stat *
|
|
guestfs_impl_lstat (guestfs_h *g, const char *path)
|
|
{
|
|
CLEANUP_FREE_STATNS struct guestfs_statns *r;
|
|
struct guestfs_stat *ret;
|
|
|
|
r = guestfs_lstatns (g, path);
|
|
if (r == NULL)
|
|
return NULL;
|
|
|
|
ret = safe_malloc (g, sizeof *ret);
|
|
statns_to_old_stat (r, ret);
|
|
return ret; /* caller frees */
|
|
}
|
|
|
|
struct guestfs_stat_list *
|
|
guestfs_impl_lstatlist (guestfs_h *g, const char *dir, char * const*names)
|
|
{
|
|
CLEANUP_FREE_STATNS_LIST struct guestfs_statns_list *r;
|
|
struct guestfs_stat_list *ret;
|
|
size_t i;
|
|
|
|
r = guestfs_lstatnslist (g, dir, names);
|
|
if (r == NULL)
|
|
return NULL;
|
|
|
|
ret = safe_malloc (g, sizeof *ret);
|
|
ret->len = r->len;
|
|
ret->val = safe_calloc (g, r->len, sizeof (struct guestfs_stat));
|
|
|
|
for (i = 0; i < r->len; ++i)
|
|
statns_to_old_stat (&r->val[i], &ret->val[i]);
|
|
|
|
return ret;
|
|
}
|