Files
libguestfs/lib/readdir.c
Laszlo Ersek 45b7f1736b guestfs_readdir(): rewrite with FileOut transfer, to lift protocol limit
Currently the guestfs_readdir() API can not list long directories, due to
it sending back the whole directory listing in a single guestfs protocol
response, which is limited to GUESTFS_MESSAGE_MAX (approx. 4MB) in size.

Introduce the "internal_readdir" action, for transferring the directory
listing from the daemon to the library through a FileOut parameter.
Rewrite guestfs_readdir() on top of this new internal function:

- The new "internal_readdir" action is a daemon action. Do not repurpose
  the "readdir" proc_nr (138) for "internal_readdir", as some distros ship
  the binary appliance to their users, and reusing the proc_nr could
  create a mismatch between library & appliance with obscure symptoms.
  Replace the old proc_nr (138) with a new proc_nr (511) instead; a
  mismatch would then produce a clear error message. Assume the new action
  will first be released in libguestfs-1.48.2.

- Turn "readdir" from a daemon action into a non-daemon one. Call the
  daemon action guestfs_internal_readdir() manually, receive the FileOut
  parameter into a temp file, then deserialize the dirents array from the
  temp file.

This patch sneakily fixes an independent bug, too. In the pre-patch
do_readdir() function [daemon/readdir.c], when readdir() returns NULL, we
don't distinguish "end of directory stream" from "readdir() failed". This
rewrite fixes this problem -- I didn't see much value separating out the
fix for the original do_readdir().

Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1674392
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20220502085601.15012-2-lersek@redhat.com>
Reviewed-by: Richard W.M. Jones <rjones@redhat.com>
2022-05-03 10:53:48 +02:00

132 lines
3.5 KiB
C

/* libguestfs
* Copyright (C) 2016-2022 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> /* UNIX_PATH_MAX, needed by "guestfs-internal.h" */
#include <rpc/xdr.h> /* xdrstdio_create() */
#include <stdint.h> /* UINT32_MAX */
#include <stdio.h> /* fopen() */
#include <string.h> /* memset() */
#include "guestfs.h" /* guestfs_internal_readdir() */
#include "guestfs_protocol.h" /* guestfs_int_dirent */
#include "guestfs-internal.h" /* guestfs_int_make_temp_path() */
#include "guestfs-internal-actions.h" /* guestfs_impl_readdir */
struct guestfs_dirent_list *
guestfs_impl_readdir (guestfs_h *g, const char *dir)
{
struct guestfs_dirent_list *ret;
char *tmpfn;
FILE *f;
off_t fsize;
XDR xdr;
struct guestfs_dirent_list *dirents;
uint32_t alloc_entries;
size_t alloc_bytes;
/* Prepare to fail. */
ret = NULL;
tmpfn = guestfs_int_make_temp_path (g, "readdir", NULL);
if (tmpfn == NULL)
return ret;
if (guestfs_internal_readdir (g, dir, tmpfn) == -1)
goto drop_tmpfile;
f = fopen (tmpfn, "r");
if (f == NULL) {
perrorf (g, "fopen: %s", tmpfn);
goto drop_tmpfile;
}
if (fseeko (f, 0, SEEK_END) == -1) {
perrorf (g, "fseeko");
goto close_tmpfile;
}
fsize = ftello (f);
if (fsize == -1) {
perrorf (g, "ftello");
goto close_tmpfile;
}
if (fseeko (f, 0, SEEK_SET) == -1) {
perrorf (g, "fseeko");
goto close_tmpfile;
}
xdrstdio_create (&xdr, f, XDR_DECODE);
dirents = safe_malloc (g, sizeof *dirents);
dirents->len = 0;
alloc_entries = 8;
alloc_bytes = alloc_entries * sizeof *dirents->val;
dirents->val = safe_malloc (g, alloc_bytes);
while (xdr_getpos (&xdr) < fsize) {
guestfs_int_dirent v;
struct guestfs_dirent *d;
if (dirents->len == alloc_entries) {
if (alloc_entries > UINT32_MAX / 2 || alloc_bytes > (size_t)-1 / 2) {
error (g, "integer overflow");
goto free_dirents;
}
alloc_entries *= 2u;
alloc_bytes *= 2u;
dirents->val = safe_realloc (g, dirents->val, alloc_bytes);
}
/* Decoding does not work unless the target buffer is zero-initialized. */
memset (&v, 0, sizeof v);
if (!xdr_guestfs_int_dirent (&xdr, &v)) {
error (g, "xdr_guestfs_int_dirent failed");
goto free_dirents;
}
d = &dirents->val[dirents->len];
d->ino = v.ino;
d->ftyp = v.ftyp;
d->name = v.name; /* transfer malloc'd string to "d" */
dirents->len++;
}
/* Success; transfer "dirents" to "ret". */
ret = dirents;
dirents = NULL;
/* Clean up. */
xdr_destroy (&xdr);
free_dirents:
guestfs_free_dirent_list (dirents);
close_tmpfile:
fclose (f);
drop_tmpfile:
/* In case guestfs_internal_readdir() failed, it may or may not have created
* the temporary file.
*/
unlink (tmpfn);
free (tmpfn);
return ret;
}