shared: move file-get-contents and file-set-contents helper to shared/
These functions are not specific to "src/". Also, they will be needed by outside of "src/" soon.
This commit is contained in:
@@ -23,4 +23,408 @@
|
|||||||
|
|
||||||
#include "nm-io-utils.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;
|
||||||
|
}
|
||||||
|
@@ -26,4 +26,38 @@
|
|||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NMUtilsFileGetContentsFlags:
|
||||||
|
* @NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE: no flag
|
||||||
|
* @NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET: if present, ensure that no
|
||||||
|
* data is left in memory. Essentially, it means to call explicity_bzero()
|
||||||
|
* to not leave key material on the heap (when reading secrets).
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE = 0,
|
||||||
|
NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET = (1 << 0),
|
||||||
|
} NMUtilsFileGetContentsFlags;
|
||||||
|
|
||||||
|
int nm_utils_fd_get_contents (int fd,
|
||||||
|
gboolean close_fd,
|
||||||
|
gsize max_length,
|
||||||
|
NMUtilsFileGetContentsFlags flags,
|
||||||
|
char **contents,
|
||||||
|
gsize *length,
|
||||||
|
GError **error);
|
||||||
|
|
||||||
|
int nm_utils_file_get_contents (int dirfd,
|
||||||
|
const char *filename,
|
||||||
|
gsize max_length,
|
||||||
|
NMUtilsFileGetContentsFlags flags,
|
||||||
|
char **contents,
|
||||||
|
gsize *length,
|
||||||
|
GError **error);
|
||||||
|
|
||||||
|
gboolean nm_utils_file_set_contents (const char *filename,
|
||||||
|
const char *contents,
|
||||||
|
gssize length,
|
||||||
|
mode_t mode,
|
||||||
|
GError **error);
|
||||||
|
|
||||||
#endif /* __NM_IO_UTILS_H__ */
|
#endif /* __NM_IO_UTILS_H__ */
|
||||||
|
@@ -38,7 +38,7 @@
|
|||||||
#include <net/ethernet.h>
|
#include <net/ethernet.h>
|
||||||
|
|
||||||
#include "nm-utils/nm-random-utils.h"
|
#include "nm-utils/nm-random-utils.h"
|
||||||
#include "nm-utils/nm-secret-utils.h"
|
#include "nm-utils/nm-io-utils.h"
|
||||||
#include "nm-utils.h"
|
#include "nm-utils.h"
|
||||||
#include "nm-core-internal.h"
|
#include "nm-core-internal.h"
|
||||||
#include "nm-setting-connection.h"
|
#include "nm-setting-connection.h"
|
||||||
@@ -2561,301 +2561,6 @@ nm_utils_machine_id_read (void)
|
|||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
_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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*****************************************************************************/
|
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
_secret_key_read (guint8 **out_secret_key,
|
_secret_key_read (guint8 **out_secret_key,
|
||||||
gsize *out_key_len)
|
gsize *out_key_len)
|
||||||
@@ -4028,110 +3733,6 @@ nm_utils_get_reverse_dns_domains_ip6 (const struct in6_addr *ip, guint8 plen, GP
|
|||||||
#undef N_SHIFT
|
#undef N_SHIFT
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct plugin_info {
|
struct plugin_info {
|
||||||
char *path;
|
char *path;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
@@ -264,40 +264,6 @@ gboolean nm_utils_sysctl_ip_conf_is_path (int addr_family, const char *path, con
|
|||||||
|
|
||||||
gboolean nm_utils_is_specific_hostname (const char *name);
|
gboolean nm_utils_is_specific_hostname (const char *name);
|
||||||
|
|
||||||
/**
|
|
||||||
* NMUtilsFileGetContentsFlags:
|
|
||||||
* @NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE: no flag
|
|
||||||
* @NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET: if present, ensure that no
|
|
||||||
* data is left in memory. Essentially, it means to call explicity_bzero()
|
|
||||||
* to not leave key material on the heap (when reading secrets).
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE = 0,
|
|
||||||
NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET = (1 << 0),
|
|
||||||
} NMUtilsFileGetContentsFlags;
|
|
||||||
|
|
||||||
int nm_utils_fd_get_contents (int fd,
|
|
||||||
gboolean close_fd,
|
|
||||||
gsize max_length,
|
|
||||||
NMUtilsFileGetContentsFlags flags,
|
|
||||||
char **contents,
|
|
||||||
gsize *length,
|
|
||||||
GError **error);
|
|
||||||
|
|
||||||
int nm_utils_file_get_contents (int dirfd,
|
|
||||||
const char *filename,
|
|
||||||
gsize max_length,
|
|
||||||
NMUtilsFileGetContentsFlags flags,
|
|
||||||
char **contents,
|
|
||||||
gsize *length,
|
|
||||||
GError **error);
|
|
||||||
|
|
||||||
gboolean nm_utils_file_set_contents (const char *filename,
|
|
||||||
const char *contents,
|
|
||||||
gssize length,
|
|
||||||
mode_t mode,
|
|
||||||
GError **error);
|
|
||||||
|
|
||||||
char *nm_utils_machine_id_read (void);
|
char *nm_utils_machine_id_read (void);
|
||||||
gboolean nm_utils_machine_id_parse (const char *id_str, /*uuid_t*/ guchar *out_uuid);
|
gboolean nm_utils_machine_id_parse (const char *id_str, /*uuid_t*/ guchar *out_uuid);
|
||||||
|
|
||||||
|
@@ -54,6 +54,7 @@
|
|||||||
#include "wifi/nm-wifi-utils-wext.h"
|
#include "wifi/nm-wifi-utils-wext.h"
|
||||||
#include "wpan/nm-wpan-utils.h"
|
#include "wpan/nm-wpan-utils.h"
|
||||||
#include "nm-utils/unaligned.h"
|
#include "nm-utils/unaligned.h"
|
||||||
|
#include "nm-utils/nm-io-utils.h"
|
||||||
#include "nm-utils/nm-udev-utils.h"
|
#include "nm-utils/nm-udev-utils.h"
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <linux/if_tun.h>
|
#include <linux/if_tun.h>
|
||||||
|
|
||||||
|
#include "nm-utils/nm-io-utils.h"
|
||||||
#include "platform/nmp-object.h"
|
#include "platform/nmp-object.h"
|
||||||
#include "platform/nmp-netns.h"
|
#include "platform/nmp-netns.h"
|
||||||
#include "platform/nm-platform-utils.h"
|
#include "platform/nm-platform-utils.h"
|
||||||
|
@@ -33,6 +33,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "nm-utils/nm-enum-utils.h"
|
#include "nm-utils/nm-enum-utils.h"
|
||||||
|
#include "nm-utils/nm-io-utils.h"
|
||||||
#include "nm-manager.h"
|
#include "nm-manager.h"
|
||||||
#include "nm-setting-connection.h"
|
#include "nm-setting-connection.h"
|
||||||
#include "nm-setting-wired.h"
|
#include "nm-setting-wired.h"
|
||||||
|
@@ -39,6 +39,7 @@
|
|||||||
#include "nm-core-internal.h"
|
#include "nm-core-internal.h"
|
||||||
#include "nm-core-utils.h"
|
#include "nm-core-utils.h"
|
||||||
#include "nm-utils/nm-enum-utils.h"
|
#include "nm-utils/nm-enum-utils.h"
|
||||||
|
#include "nm-utils/nm-io-utils.h"
|
||||||
#include "c-list/src/c-list.h"
|
#include "c-list/src/c-list.h"
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
@@ -34,6 +34,8 @@
|
|||||||
#include "nms-keyfile-utils.h"
|
#include "nms-keyfile-utils.h"
|
||||||
#include "nms-keyfile-reader.h"
|
#include "nms-keyfile-reader.h"
|
||||||
|
|
||||||
|
#include "nm-utils/nm-io-utils.h"
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
Reference in New Issue
Block a user