From 1ebfefa565692ca033ba2d156c771d9e2c4017fc Mon Sep 17 00:00:00 2001 From: Colin Walters <walters@verbum.org> Date: Fri, 20 Feb 2015 11:28:47 -0500 Subject: [PATCH] fdio: New APIs to read/write on fds, fd-relative We don't have this really in GLib, unfortunately. We do want GCancellable, but we also want to operate on raw fds where possible. The "read a file and validate as UTF-8" is a common use case of mine, and this combines that with openat(). --- Makefile-libglnx.am | 2 + glnx-fdio.c | 225 ++++++++++++++++++++++++++++++++++++++++++++ glnx-fdio.h | 50 ++++++++++ libglnx.h | 1 + 4 files changed, 278 insertions(+) create mode 100644 glnx-fdio.c create mode 100644 glnx-fdio.h diff --git a/Makefile-libglnx.am b/Makefile-libglnx.am index 7463395a9..5b4411f8d 100644 --- a/Makefile-libglnx.am +++ b/Makefile-libglnx.am @@ -28,6 +28,8 @@ libglnx_la_SOURCES = \ $(libglnx_srcpath)/glnx-console.c \ $(libglnx_srcpath)/glnx-dirfd.h \ $(libglnx_srcpath)/glnx-dirfd.c \ + $(libglnx_srcpath)/glnx-fdio.h \ + $(libglnx_srcpath)/glnx-fdio.c \ $(libglnx_srcpath)/glnx-xattrs.h \ $(libglnx_srcpath)/glnx-xattrs.c \ $(libglnx_srcpath)/glnx-shutil.h \ diff --git a/glnx-fdio.c b/glnx-fdio.c new file mode 100644 index 000000000..67653c26e --- /dev/null +++ b/glnx-fdio.c @@ -0,0 +1,225 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> + +#include <glnx-fdio.h> +#include <glnx-errors.h> +#include <glnx-local-alloc.h> + +static guint8* +glnx_fd_readall_malloc (int fd, + gsize *out_len, + gboolean nul_terminate, + GCancellable *cancellable, + GError **error) +{ + gboolean success = FALSE; + const guint maxreadlen = 4096; + int res; + struct stat stbuf; + guint8* buf = NULL; + gsize buf_allocated; + gsize buf_size = 0; + gssize bytes_read; + + do + res = fstat (fd, &stbuf); + while (G_UNLIKELY (res == -1 && errno == EINTR)); + if (res == -1) + { + glnx_set_error_from_errno (error); + goto out; + } + + if (S_ISREG (stbuf.st_mode) && stbuf.st_size > 0) + buf_allocated = stbuf.st_size; + else + buf_allocated = 16; + + buf = g_malloc (buf_allocated); + + while (TRUE) + { + gsize readlen = MIN (buf_allocated - buf_size, maxreadlen); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto out; + + do + bytes_read = read (fd, buf + buf_size, readlen); + while (G_UNLIKELY (bytes_read == -1 && errno == EINTR)); + if (G_UNLIKELY (bytes_read == -1)) + { + glnx_set_error_from_errno (error); + goto out; + } + if (bytes_read == 0) + break; + + buf_size += bytes_read; + if (buf_allocated - buf_size < maxreadlen) + buf = g_realloc (buf, buf_allocated *= 2); + } + + if (nul_terminate) + { + if (buf_allocated - buf_size == 0) + buf = g_realloc (buf, buf_allocated + 1); + buf[buf_size] = '\0'; + } + + success = TRUE; + out: + if (success) + { + *out_len = buf_size; + return buf; + } + g_free (buf); + return NULL; +} + +/** + * glnx_fd_readall_bytes: + * @fd: A file descriptor + * @cancellable: Cancellable: + * @error: Error + * + * Read all data from file descriptor @fd into a #GBytes. It's + * recommended to only use this for small files. + * + * Returns: (transfer full): A newly allocated #GBytes + */ +GBytes * +glnx_fd_readall_bytes (int fd, + GCancellable *cancellable, + GError **error) +{ + guint8 *buf; + gsize len; + + buf = glnx_fd_readall_malloc (fd, &len, FALSE, cancellable, error); + if (!buf) + return NULL; + + return g_bytes_new_take (buf, len); +} + +/** + * glnx_fd_readall_utf8: + * @fd: A file descriptor + * @out_len: (out): Returned length + * @cancellable: Cancellable: + * @error: Error + * + * Read all data from file descriptor @fd, validating + * the result as UTF-8. + * + * Returns: (transfer full): A string validated as UTF-8, or %NULL on error. + */ +char * +glnx_fd_readall_utf8 (int fd, + gsize *out_len, + GCancellable *cancellable, + GError **error) +{ + gboolean success = FALSE; + guint8 *buf; + gsize len; + + buf = glnx_fd_readall_malloc (fd, &len, TRUE, cancellable, error); + if (!buf) + goto out; + + if (!g_utf8_validate ((char*)buf, len, NULL)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "Invalid UTF-8"); + goto out; + } + + success = TRUE; + out: + if (success) + { + if (out_len) + *out_len = len; + return (char*)buf; + } + g_free (buf); + return NULL; +} + +/** + * glnx_file_get_contents_utf8_at: + * @dfd: Directory file descriptor + * @subpath: Path relative to @dfd + * @out_len: (out) (allow-none): Optional length + * @cancellable: Cancellable + * @error: Error + * + * Read the entire contents of the file referred + * to by @dfd and @subpath, validate the result as UTF-8. + * The length is optionally stored in @out_len. + * + * Returns: (transfer full): UTF-8 validated text, or %NULL on error + */ +char * +glnx_file_get_contents_utf8_at (int dfd, + const char *subpath, + gsize *out_len, + GCancellable *cancellable, + GError **error) +{ + gboolean success = FALSE; + glnx_fd_close int fd = -1; + char *buf; + gsize len; + + do + fd = openat (dfd, subpath, O_RDONLY | O_NOCTTY | O_CLOEXEC); + while (G_UNLIKELY (fd == -1 && errno == EINTR)); + if (G_UNLIKELY (fd == -1)) + { + glnx_set_error_from_errno (error); + goto out; + } + + buf = glnx_fd_readall_utf8 (fd, &len, cancellable, error); + if (G_UNLIKELY(!buf)) + goto out; + + success = TRUE; + out: + if (success) + { + if (out_len) + *out_len = len; + return buf; + } + g_free (buf); + return NULL; +} diff --git a/glnx-fdio.h b/glnx-fdio.h new file mode 100644 index 000000000..91e6aa602 --- /dev/null +++ b/glnx-fdio.h @@ -0,0 +1,50 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#pragma once + +#include <glnx-backport-autocleanups.h> +#include <limits.h> +#include <dirent.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <attr/xattr.h> + +G_BEGIN_DECLS + +GBytes * +glnx_fd_readall_bytes (int fd, + GCancellable *cancellable, + GError **error); + +char * +glnx_fd_readall_utf8 (int fd, + gsize *out_len, + GCancellable *cancellable, + GError **error); + +char * +glnx_file_get_contents_utf8_at (int dfd, + const char *subpath, + gsize *out_len, + GCancellable *cancellable, + GError **error); + +G_END_DECLS diff --git a/libglnx.h b/libglnx.h index 64c1ff1c6..3e815851c 100644 --- a/libglnx.h +++ b/libglnx.h @@ -31,5 +31,6 @@ G_BEGIN_DECLS #include <glnx-shutil.h> #include <glnx-xattrs.h> #include <glnx-console.h> +#include <glnx-fdio.h> G_END_DECLS -- GitLab