summary refs log tree commit diff
path: root/shared/nm-utils/nm-io-utils.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-10-20 01:30:31 +0200
committerMichael Biebl <biebl@debian.org>2018-10-20 01:30:31 +0200
commit6518e361171f64bcaaa4bf868139362ed95cc2e0 (patch)
treed2d5b53faf80646a40ec2c0c7f2a42b3959612f5 /shared/nm-utils/nm-io-utils.c
parente126f3e804c35480c4f075777430419d6ece23da (diff)
New upstream version 1.14.2 upstream/1.14.2
Diffstat (limited to 'shared/nm-utils/nm-io-utils.c')
-rw-r--r--shared/nm-utils/nm-io-utils.c430
1 files changed, 430 insertions, 0 deletions
diff --git a/shared/nm-utils/nm-io-utils.c b/shared/nm-utils/nm-io-utils.c
new file mode 100644
index 00000000..88cb13ff
--- /dev/null
+++ b/shared/nm-utils/nm-io-utils.c
@@ -0,0 +1,430 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * 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.
+ *
+ * (C) Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-io-utils.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "nm-shared-utils.h"
+#include "nm-secret-utils.h"
+
+/*****************************************************************************/
+
+_nm_printf (3, 4)
+static int
+_get_contents_error (GError **error, int errsv, const char *format, ...)
+{
+	if (errsv < 0)
+		errsv = -errsv;
+	else if (!errsv)
+		errsv = errno;
+
+	if (error) {
+		char *msg;
+		va_list args;
+
+		va_start (args, format);
+		msg = g_strdup_vprintf (format, args);
+		va_end (args);
+		g_set_error (error,
+		             G_FILE_ERROR,
+		             g_file_error_from_errno (errsv),
+		             "%s: %s",
+		             msg, g_strerror (errsv));
+		g_free (msg);
+	}
+	return -errsv;
+}
+
+static char *
+_mem_realloc (char *old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
+{
+	char *new;
+
+	/* re-allocating to zero bytes is an odd case. We don't need it
+	 * and it's not supported. */
+	nm_assert (new_len > 0);
+
+	/* regardless of success/failure, @old will always be freed/consumed. */
+
+	if (do_bzero_mem && cur_len > 0) {
+		new = g_try_malloc (new_len);
+		if (new)
+			memcpy (new, old, NM_MIN (cur_len, new_len));
+		nm_explicit_bzero (old, cur_len);
+		g_free (old);
+	} else {
+		new = g_try_realloc (old, new_len);
+		if (!new)
+			g_free (old);
+	}
+
+	return new;
+}
+
+/**
+ * nm_utils_fd_get_contents:
+ * @fd: open file descriptor to read. The fd will not be closed,
+ *   but don't rely on its state afterwards.
+ * @close_fd: if %TRUE, @fd will be closed by the function.
+ *  Passing %TRUE here might safe a syscall for dup().
+ * @max_length: allocate at most @max_length bytes. If the
+ *   file is larger, reading will fail. Set to zero to use
+ *   a very large default.
+ *   WARNING: @max_length is here to avoid a crash for huge/unlimited files.
+ *   For example, stat(/sys/class/net/enp0s25/ifindex) gives a filesize of
+ *   4K, although the actual real is small. @max_length is the memory
+ *   allocated in the process of reading the file, thus it must be at least
+ *   the size reported by fstat.
+ *   If you set it to 1K, read will fail because fstat() claims the
+ *   file is larger.
+ * @flags: %NMUtilsFileGetContentsFlags for reading the file.
+ * @contents: the output buffer with the file read. It is always
+ *   NUL terminated. The buffer is at most @max_length long, including
+ *  the NUL byte. That is, it reads only files up to a length of
+ *  @max_length - 1 bytes.
+ * @length: optional output argument of the read file size.
+ *
+ * A reimplementation of g_file_get_contents() with a few differences:
+ *   - accepts an open fd, instead of a path name. This allows you to
+ *     use openat().
+ *   - limits the maxium filesize to max_length.
+ *
+ * Returns: a negative error code on failure.
+ */
+int
+nm_utils_fd_get_contents (int fd,
+                          gboolean close_fd,
+                          gsize max_length,
+                          NMUtilsFileGetContentsFlags flags,
+                          char **contents,
+                          gsize *length,
+                          GError **error)
+{
+	nm_auto_close int fd_keeper = close_fd ? fd : -1;
+	struct stat stat_buf;
+	gs_free char *str = NULL;
+	const bool do_bzero_mem = NM_FLAGS_HAS (flags, NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET);
+
+	g_return_val_if_fail (fd >= 0, -EINVAL);
+	g_return_val_if_fail (contents, -EINVAL);
+	g_return_val_if_fail (!error || !*error, -EINVAL);
+
+	if (fstat (fd, &stat_buf) < 0)
+		return _get_contents_error (error, 0, "failure during fstat");
+
+	if (!max_length) {
+		/* default to a very large size, but not extreme */
+		max_length = 2 * 1024 * 1024;
+	}
+
+	if (   stat_buf.st_size > 0
+	    && S_ISREG (stat_buf.st_mode)) {
+		const gsize n_stat = stat_buf.st_size;
+		ssize_t n_read;
+
+		if (n_stat > max_length - 1)
+			return _get_contents_error (error, EMSGSIZE, "file too large (%zu+1 bytes with maximum %zu bytes)", n_stat, max_length);
+
+		str = g_try_malloc (n_stat + 1);
+		if (!str)
+			return _get_contents_error (error, ENOMEM, "failure to allocate buffer of %zu+1 bytes", n_stat);
+
+		n_read = nm_utils_fd_read_loop (fd, str, n_stat, TRUE);
+		if (n_read < 0) {
+			if (do_bzero_mem)
+				nm_explicit_bzero (str, n_stat);
+			return _get_contents_error (error, n_read, "error reading %zu bytes from file descriptor", n_stat);
+		}
+		str[n_read] = '\0';
+
+		if (n_read < n_stat) {
+			if (!(str = _mem_realloc (str, do_bzero_mem, n_stat + 1, n_read + 1)))
+				return _get_contents_error (error, ENOMEM, "failure to reallocate buffer with %zu bytes", n_read + 1);
+		}
+		NM_SET_OUT (length, n_read);
+	} else {
+		nm_auto_fclose FILE *f = NULL;
+		char buf[4096];
+		gsize n_have, n_alloc;
+		int fd2;
+
+		if (fd_keeper >= 0)
+			fd2 = nm_steal_fd (&fd_keeper);
+		else {
+			fd2 = fcntl (fd, F_DUPFD_CLOEXEC, 0);
+			if (fd2 < 0)
+				return _get_contents_error (error, 0, "error during dup");
+		}
+
+		if (!(f = fdopen (fd2, "r"))) {
+			nm_close (fd2);
+			return _get_contents_error (error, 0, "failure during fdopen");
+		}
+
+		n_have = 0;
+		n_alloc = 0;
+
+		while (!feof (f)) {
+			int errsv;
+			gsize n_read;
+
+			n_read = fread (buf, 1, sizeof (buf), f);
+			errsv = errno;
+			if (ferror (f)) {
+				if (do_bzero_mem)
+					nm_explicit_bzero (buf, sizeof (buf));
+				return _get_contents_error (error, errsv, "error during fread");
+			}
+
+			if (   n_have > G_MAXSIZE - 1 - n_read
+			    || n_have + n_read + 1 > max_length) {
+				if (do_bzero_mem)
+					nm_explicit_bzero (buf, sizeof (buf));
+				return _get_contents_error (error, EMSGSIZE, "file stream too large (%zu+1 bytes with maximum %zu bytes)",
+				                            (n_have > G_MAXSIZE - 1 - n_read) ? G_MAXSIZE : n_have + n_read,
+				                            max_length);
+			}
+
+			if (n_have + n_read + 1 >= n_alloc) {
+				gsize old_n_alloc = n_alloc;
+
+				if (n_alloc != 0) {
+					nm_assert (str);
+					if (n_alloc >= max_length / 2)
+						n_alloc = max_length;
+					else
+						n_alloc *= 2;
+				} else {
+					nm_assert (!str);
+					n_alloc = NM_MIN (n_read + 1, sizeof (buf));
+				}
+
+				if (!(str = _mem_realloc (str, do_bzero_mem, old_n_alloc, n_alloc))) {
+					if (do_bzero_mem)
+						nm_explicit_bzero (buf, sizeof (buf));
+					return _get_contents_error (error, ENOMEM, "failure to allocate buffer of %zu bytes", n_alloc);
+				}
+			}
+
+			memcpy (str + n_have, buf, n_read);
+			n_have += n_read;
+		}
+
+		if (do_bzero_mem)
+			nm_explicit_bzero (buf, sizeof (buf));
+
+		if (n_alloc == 0)
+			str = g_new0 (char, 1);
+		else {
+			str[n_have] = '\0';
+			if (n_have + 1 < n_alloc) {
+				if (!(str = _mem_realloc (str, do_bzero_mem, n_alloc, n_have + 1)))
+					return _get_contents_error (error, ENOMEM, "failure to truncate buffer to %zu bytes", n_have + 1);
+			}
+		}
+
+		NM_SET_OUT (length, n_have);
+	}
+
+	*contents = g_steal_pointer (&str);
+	return 0;
+}
+
+/**
+ * nm_utils_file_get_contents:
+ * @dirfd: optional file descriptor to use openat(). If negative, use plain open().
+ * @filename: the filename to open. Possibly relative to @dirfd.
+ * @max_length: allocate at most @max_length bytes.
+ *   WARNING: see nm_utils_fd_get_contents() hint about @max_length.
+ * @flags: %NMUtilsFileGetContentsFlags for reading the file.
+ * @contents: the output buffer with the file read. It is always
+ *   NUL terminated. The buffer is at most @max_length long, including
+ *  the NUL byte. That is, it reads only files up to a length of
+ *  @max_length - 1 bytes.
+ * @length: optional output argument of the read file size.
+ *
+ * A reimplementation of g_file_get_contents() with a few differences:
+ *   - accepts an @dirfd to open @filename relative to that path via openat().
+ *   - limits the maxium filesize to max_length.
+ *   - uses O_CLOEXEC on internal file descriptor
+ *
+ * Returns: a negative error code on failure.
+ */
+int
+nm_utils_file_get_contents (int dirfd,
+                            const char *filename,
+                            gsize max_length,
+                            NMUtilsFileGetContentsFlags flags,
+                            char **contents,
+                            gsize *length,
+                            GError **error)
+{
+	int fd;
+	int errsv;
+
+	g_return_val_if_fail (filename && filename[0], -EINVAL);
+
+	if (dirfd >= 0) {
+		fd = openat (dirfd, filename, O_RDONLY | O_CLOEXEC);
+		if (fd < 0) {
+			errsv = errno;
+
+			g_set_error (error,
+			             G_FILE_ERROR,
+			             g_file_error_from_errno (errsv),
+			             "Failed to open file \"%s\" with openat: %s",
+			             filename,
+			             g_strerror (errsv));
+			return -errsv;
+		}
+	} else {
+		fd = open (filename, O_RDONLY | O_CLOEXEC);
+		if (fd < 0) {
+			errsv = errno;
+
+			g_set_error (error,
+			             G_FILE_ERROR,
+			             g_file_error_from_errno (errsv),
+			             "Failed to open file \"%s\": %s",
+			             filename,
+			             g_strerror (errsv));
+			return -errsv;
+		}
+	}
+	return nm_utils_fd_get_contents (fd,
+	                                 TRUE,
+	                                 max_length,
+	                                 flags,
+	                                 contents,
+	                                 length,
+	                                 error);
+}
+
+/*****************************************************************************/
+
+/*
+ * Copied from GLib's g_file_set_contents() et al., but allows
+ * specifying a mode for the new file.
+ */
+gboolean
+nm_utils_file_set_contents (const char *filename,
+                            const char *contents,
+                            gssize length,
+                            mode_t mode,
+                            GError **error)
+{
+	gs_free char *tmp_name = NULL;
+	struct stat statbuf;
+	int errsv;
+	gssize s;
+	int fd;
+
+	g_return_val_if_fail (filename, FALSE);
+	g_return_val_if_fail (contents || !length, FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+	g_return_val_if_fail (length >= -1, FALSE);
+
+	if (length == -1)
+		length = strlen (contents);
+
+	tmp_name = g_strdup_printf ("%s.XXXXXX", filename);
+	fd = g_mkstemp_full (tmp_name, O_RDWR, mode);
+	if (fd < 0) {
+		errsv = errno;
+		g_set_error (error,
+		             G_FILE_ERROR,
+		             g_file_error_from_errno (errsv),
+		             "failed to create file %s: %s",
+		             tmp_name,
+		             g_strerror (errsv));
+		return FALSE;
+	}
+
+	while (length > 0) {
+		s = write (fd, contents, length);
+		if (s < 0) {
+			errsv = errno;
+			if (errsv == EINTR)
+				continue;
+
+			nm_close (fd);
+			unlink (tmp_name);
+
+			g_set_error (error,
+			             G_FILE_ERROR,
+			             g_file_error_from_errno (errsv),
+			             "failed to write to file %s: %s",
+			             tmp_name,
+			             g_strerror (errsv));
+			return FALSE;
+		}
+
+		g_assert (s <= length);
+
+		contents += s;
+		length -= s;
+	}
+
+	/* If the final destination exists and is > 0 bytes, we want to sync the
+	 * newly written file to ensure the data is on disk when we rename over
+	 * the destination. Otherwise if we get a system crash we can lose both
+	 * the new and the old file on some filesystems. (I.E. those that don't
+	 * guarantee the data is written to the disk before the metadata.)
+	 */
+	if (   lstat (filename, &statbuf) == 0
+	    && statbuf.st_size > 0
+	    && fsync (fd) != 0) {
+		errsv = errno;
+
+		nm_close (fd);
+		unlink (tmp_name);
+
+		g_set_error (error,
+		             G_FILE_ERROR,
+		             g_file_error_from_errno (errsv),
+		             "failed to fsync %s: %s",
+		             tmp_name,
+		             g_strerror (errsv));
+		return FALSE;
+	}
+
+	nm_close (fd);
+
+	if (rename (tmp_name, filename)) {
+		errsv = errno;
+		unlink (tmp_name);
+		g_set_error (error,
+		             G_FILE_ERROR,
+		             g_file_error_from_errno (errsv),
+		             "failed to rename %s to %s: %s",
+		             tmp_name,
+		             filename,
+		             g_strerror (errsv));
+		return FALSE;
+	}
+
+	return TRUE;
+}