mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-03 22:10:14 +01:00
core: add nm_utils_file_get_contents() and nm_utils_fd_get_contents()
A reimplementation of g_file_get_contents() to overcome two limitations: - nm_utils_file_get_contents() accepts a @dirfd argument to open the file relative using openat(). - nm_utils_fd_get_contents() allows to read the content from a file filedescriptor. - both support a max_length argument, to fail gracefully if we get tricked into loading a huge file.
This commit is contained in:
parent
76876e896c
commit
1d9bdad1df
2 changed files with 259 additions and 6 deletions
|
|
@ -2802,6 +2802,246 @@ nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll)
|
|||
return 0;
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* nm_utils_fd_get_contents:
|
||||
* @fd: open file descriptor to read. The fd will not be closed,
|
||||
* but don't rely on it's state afterwards.
|
||||
* @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.
|
||||
*
|
||||
* @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,
|
||||
gsize max_length,
|
||||
char **contents,
|
||||
gsize *length,
|
||||
GError **error)
|
||||
{
|
||||
struct stat stat_buf;
|
||||
gs_free char *str = NULL;
|
||||
|
||||
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)
|
||||
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) {
|
||||
char *tmp;
|
||||
|
||||
tmp = g_try_realloc (str, n_read + 1);
|
||||
if (!tmp)
|
||||
return _get_contents_error (error, ENOMEM, "failure to reallocate buffer with %zu bytes", n_read + 1);
|
||||
str = tmp;
|
||||
}
|
||||
NM_SET_OUT (length, n_read);
|
||||
} else {
|
||||
nm_auto_fclose FILE *f = NULL;
|
||||
char buf[4096];
|
||||
gsize n_have, n_alloc;
|
||||
|
||||
if (!(f = fdopen (fd, "r")))
|
||||
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))
|
||||
return _get_contents_error (error, errsv, "error during fread");
|
||||
|
||||
if ( n_have > G_MAXSIZE - 1 - n_read
|
||||
|| n_have + n_read + 1 > max_length) {
|
||||
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) {
|
||||
char *tmp;
|
||||
|
||||
if (str) {
|
||||
if (n_alloc >= max_length / 2)
|
||||
n_alloc = max_length;
|
||||
else
|
||||
n_alloc *= 2;
|
||||
} else
|
||||
n_alloc = NM_MIN (n_read + 1, sizeof (buf));
|
||||
|
||||
tmp = g_try_realloc (str, n_alloc);
|
||||
if (!tmp)
|
||||
return _get_contents_error (error, ENOMEM, "failure to allocate buffer of %zu bytes", n_alloc);
|
||||
str = tmp;
|
||||
}
|
||||
|
||||
memcpy (str + n_have, buf, n_read);
|
||||
n_have += n_read;
|
||||
}
|
||||
|
||||
if (n_alloc == 0)
|
||||
str = g_new0 (gchar, 1);
|
||||
else {
|
||||
str[n_have] = '\0';
|
||||
if (n_have + 1 < n_alloc) {
|
||||
char *tmp;
|
||||
|
||||
tmp = g_try_realloc (str, n_have + 1);
|
||||
if (!tmp)
|
||||
return _get_contents_error (error, ENOMEM, "failure to truncate buffer to %zu bytes", n_have + 1);
|
||||
str = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
* @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,
|
||||
char **contents,
|
||||
gsize *length,
|
||||
GError **error)
|
||||
{
|
||||
nm_auto_close int fd = -1;
|
||||
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,
|
||||
max_length,
|
||||
contents,
|
||||
length,
|
||||
error);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* taken from systemd's dev_urandom(). */
|
||||
int
|
||||
nm_utils_read_urandom (void *p, size_t nbytes)
|
||||
|
|
|
|||
|
|
@ -313,6 +313,25 @@ int nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns);
|
|||
ssize_t nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll);
|
||||
int nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll);
|
||||
|
||||
int nm_utils_fd_get_contents (int fd,
|
||||
gsize max_length,
|
||||
char **contents,
|
||||
gsize *length,
|
||||
GError **error);
|
||||
|
||||
int nm_utils_file_get_contents (int dirfd,
|
||||
const char *filename,
|
||||
gsize max_length,
|
||||
char **contents,
|
||||
gsize *length,
|
||||
GError **error);
|
||||
|
||||
gboolean nm_utils_file_set_contents (const gchar *filename,
|
||||
const gchar *contents,
|
||||
gssize length,
|
||||
mode_t mode,
|
||||
GError **error);
|
||||
|
||||
int nm_utils_read_urandom (void *p, size_t n);
|
||||
|
||||
char *nm_utils_machine_id_read (void);
|
||||
|
|
@ -445,12 +464,6 @@ const char *nm_utils_dnsmasq_status_to_string (int status, char *dest, gsize siz
|
|||
void nm_utils_get_reverse_dns_domains_ip4 (guint32 ip, guint8 plen, GPtrArray *domains);
|
||||
void nm_utils_get_reverse_dns_domains_ip6 (const struct in6_addr *ip, guint8 plen, GPtrArray *domains);
|
||||
|
||||
gboolean nm_utils_file_set_contents (const gchar *filename,
|
||||
const gchar *contents,
|
||||
gssize length,
|
||||
mode_t mode,
|
||||
GError **error);
|
||||
|
||||
struct stat;
|
||||
|
||||
gboolean nm_utils_validate_plugin (const char *path, struct stat *stat, GError **error);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue