Newer
Older
/*
* Contains code taken from Flatpak.
*
* Copyright © 2014-2019 Red Hat, Inc
* Copyright © 2017-2019 Collabora Ltd.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This program 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.1 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, see <http://www.gnu.org/licenses/>.
*/
#include "utils.h"
#include <ftw.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include "glib-backports.h"
#include "flatpak-bwrap-private.h"
#include "flatpak-utils-base-private.h"
#include "flatpak-utils-private.h"
static void
child_setup_cb (gpointer user_data)
{
flatpak_close_fds_workaround (3);
}
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
* pv_avoid_gvfs:
*
* Disable gvfs. This function must be called from main() before
* starting any threads.
*/
void
pv_avoid_gvfs (void)
{
g_autofree gchar *old_env = NULL;
/* avoid gvfs (http://bugzilla.gnome.org/show_bug.cgi?id=526454) */
old_env = g_strdup (g_getenv ("GIO_USE_VFS"));
g_setenv ("GIO_USE_VFS", "local", TRUE);
g_vfs_get_default ();
if (old_env)
g_setenv ("GIO_USE_VFS", old_env, TRUE);
else
g_unsetenv ("GIO_USE_VFS");
}
/**
* pv_envp_cmp:
* @p1: a `const char * const *`
* @p2: a `const char * const *`
*
* Compare two environment variables, given as pointers to pointers
* to the actual `KEY=value` string.
*
* In particular this is suitable for sorting a #GStrv using `qsort`.
*
* Returns: negative, 0 or positive if `*p1` compares before, equal to
* or after `*p2`
*/
int
pv_envp_cmp (const void *p1,
const void *p2)
{
const char * const * s1 = p1;
const char * const * s2 = p2;
size_t l1 = strlen (*s1);
size_t l2 = strlen (*s2);
size_t min;
const char *tmp;
int ret;
tmp = strchr (*s1, '=');
if (tmp != NULL)
l1 = tmp - *s1;
tmp = strchr (*s2, '=');
if (tmp != NULL)
l2 = tmp - *s2;
min = MIN (l1, l2);
ret = strncmp (*s1, *s2, min);
/* If they differ before the first '=' (if any) in either s1 or s2,
* then they are certainly different */
if (ret != 0)
return ret;
ret = strcmp (*s1, *s2);
/* If they do not differ at all, then they are equal */
if (ret == 0)
return ret;
/* FOO < FOO=..., and FOO < FOOBAR */
if ((*s1)[min] == '\0')
return -1;
/* FOO=... > FOO, and FOOBAR > FOO */
if ((*s2)[min] == '\0')
return 1;
/* FOO= < FOOBAR */
if ((*s1)[min] == '=' && (*s2)[min] != '=')
/* FOOBAR > FOO= */
if ((*s2)[min] == '=' && (*s1)[min] != '=')
/* Fall back to plain string comparison */
return ret;
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
}
/**
* pv_get_current_dirs:
* @cwd_p: (out) (transfer full) (optional): Used to return the
* current physical working directory, equivalent to `$(pwd -P)`
* in a shell
* @cwd_l: (out) (transfer full) (optional): Used to return the
* current logical working directory, equivalent to `$(pwd -L)`
* in a shell
*
* Return the physical and/or logical working directory.
*
* Equivalent to `$(pwd -L)` in a shell.
*/
void
pv_get_current_dirs (gchar **cwd_p,
gchar **cwd_l)
{
g_autofree gchar *cwd = NULL;
const gchar *pwd;
g_return_if_fail (cwd_p == NULL || *cwd_p == NULL);
g_return_if_fail (cwd_l == NULL || *cwd_l == NULL);
cwd = g_get_current_dir ();
if (cwd_p != NULL)
*cwd_p = flatpak_canonicalize_filename (cwd);
if (cwd_l != NULL)
{
pwd = g_getenv ("PWD");
if (pwd != NULL && pv_is_same_file (pwd, cwd))
*cwd_l = g_strdup (pwd);
else
*cwd_l = g_strdup (cwd);
}
}
/**
* pv_is_same_file:
* @a: a path
* @b: a path
*
* Returns: %TRUE if a and b are names for the same inode.
*/
gboolean
pv_is_same_file (const gchar *a,
const gchar *b)
{
GStatBuf a_buffer, b_buffer;
g_return_val_if_fail (a != NULL, FALSE);
g_return_val_if_fail (b != NULL, FALSE);
if (strcmp (a, b) == 0)
return TRUE;
return (stat (a, &a_buffer) == 0
&& stat (b, &b_buffer) == 0
&& a_buffer.st_dev == b_buffer.st_dev
&& a_buffer.st_ino == b_buffer.st_ino);
}
void
pv_search_path_append (GString *search_path,
const gchar *item)
{
g_return_if_fail (search_path != NULL);
if (item == NULL || item[0] == '\0')
return;
if (search_path->len != 0)
g_string_append (search_path, ":");
g_string_append (search_path, item);
}
gchar *
pv_capture_output (const char * const * argv,
GError **error)
{
gsize len;
gint wait_status;
g_autofree gchar *output = NULL;
g_autofree gchar *errors = NULL;
gsize i;
g_autoptr(GString) command = g_string_new ("");
g_return_val_if_fail (argv != NULL, NULL);
g_return_val_if_fail (argv[0] != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
for (i = 0; argv[i] != NULL; i++)
{
g_autofree gchar *quoted = g_shell_quote (argv[i]);
g_string_append_printf (command, " %s", quoted);
}
g_debug ("run:%s", command->str);
/* We use LEAVE_DESCRIPTORS_OPEN to work around a deadlock in older GLib,
* see flatpak_close_fds_workaround */
if (!g_spawn_sync (NULL, /* cwd */
(char **) argv,
NULL, /* env */
(G_SPAWN_SEARCH_PATH |
G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
child_setup_cb, NULL,
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
&output,
&errors,
&wait_status,
error))
return NULL;
g_printerr ("%s", errors);
if (!g_spawn_check_exit_status (wait_status, error))
return NULL;
len = strlen (output);
/* Emulate shell $() */
if (len > 0 && output[len - 1] == '\n')
output[len - 1] = '\0';
g_debug ("-> %s", output);
return g_steal_pointer (&output);
}
/*
* Returns: (transfer none): The first key in @table in iteration order,
* or %NULL if @table is empty.
*/
gpointer
pv_hash_table_get_arbitrary_key (GHashTable *table)
{
GHashTableIter iter;
gpointer key = NULL;
g_hash_table_iter_init (&iter, table);
if (g_hash_table_iter_next (&iter, &key, NULL))
return key;
else
return NULL;
}
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/* nftw() doesn't have a user_data argument so we need to use a global
* variable :-( */
static struct
{
gchar *source_root;
gchar *dest_root;
GError *error;
} nftw_data;
static int
copy_tree_helper (const char *fpath,
const struct stat *sb,
int typeflag,
struct FTW *ftwbuf)
{
size_t len;
const char *suffix;
g_autofree gchar *dest = NULL;
g_autofree gchar *target = NULL;
GError **error = &nftw_data.error;
g_return_val_if_fail (g_str_has_prefix (fpath, nftw_data.source_root), 1);
if (strcmp (fpath, nftw_data.source_root) == 0)
{
if (typeflag != FTW_D)
{
glnx_throw (error, "\"%s\" is not a directory", fpath);
return 1;
}
if (!glnx_shutil_mkdir_p_at (-1, nftw_data.dest_root,
sb->st_mode & 07777, NULL, error))
return 1;
return 0;
}
len = strlen (nftw_data.source_root);
g_return_val_if_fail (fpath[len] == '/', 1);
suffix = &fpath[len + 1];
dest = g_build_filename (nftw_data.dest_root, suffix, NULL);
switch (typeflag)
{
case FTW_D:
if (!glnx_shutil_mkdir_p_at (-1, dest, sb->st_mode & 07777,
NULL, error))
return 1;
break;
case FTW_SL:
target = glnx_readlinkat_malloc (-1, fpath, NULL, error);
if (target == NULL)
return 1;
if (symlink (target, dest) != 0)
{
glnx_throw_errno_prefix (error,
"Unable to create symlink at \"%s\"",
dest);
return 1;
}
break;
case FTW_F:
/* Fast path: try to make a hard link. */
if (link (fpath, dest) == 0)
break;
/* Slow path: fall back to copying.
*
* This does a FICLONE or copy_file_range to get btrfs reflinks
* if possible, making the copy as cheap as cp --reflink=auto.
*
* Rather than second-guessing which errno values would result
* in link() failing but a copy succeeding, we just try it
* unconditionally - the worst that can happen is that this
* fails too. */
if (!glnx_file_copy_at (AT_FDCWD, fpath, sb,
AT_FDCWD, dest,
GLNX_FILE_COPY_OVERWRITE,
NULL, error))
glnx_prefix_error (error, "Unable to copy \"%s\" to \"%s\"",
fpath, dest);
return 1;
}
break;
default:
glnx_throw (&nftw_data.error,
"Don't know how to handle ftw type flag %d at %s",
typeflag, fpath);
return 1;
}
return 0;
}
gboolean
pv_cheap_tree_copy (const char *source_root,
const char *dest_root,
GError **error)
{
int res;
g_return_val_if_fail (source_root != NULL, FALSE);
g_return_val_if_fail (dest_root != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* Can't run concurrently */
g_return_val_if_fail (nftw_data.source_root == NULL, FALSE);
nftw_data.source_root = flatpak_canonicalize_filename (source_root);
nftw_data.dest_root = flatpak_canonicalize_filename (dest_root);
nftw_data.error = NULL;
res = nftw (nftw_data.source_root, copy_tree_helper, 100, FTW_PHYS);
if (res == -1)
{
g_assert (nftw_data.error == NULL);
glnx_throw_errno_prefix (error, "Unable to copy \"%s\" to \"%s\"",
source_root, dest_root);
}
else if (res != 0)
{
g_propagate_error (error, g_steal_pointer (&nftw_data.error));
}
g_clear_pointer (&nftw_data.source_root, g_free);
g_clear_pointer (&nftw_data.dest_root, g_free);
g_assert (nftw_data.error == NULL);
return (res == 0);
}
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
static gint
ftw_remove (const gchar *path,
const struct stat *sb,
gint typeflags,
struct FTW *ftwbuf)
{
if (remove (path) < 0)
return -1;
return 0;
}
/*
* @directory: (type filename): The directory to remove.
*
* Recursively delete @directory within the same file system and
* without following symbolic links.
*
* Returns: %TRUE if the removal was successful
*/
gboolean
pv_rm_rf (const char *directory)
{
g_return_val_if_fail (directory != NULL, FALSE);
if (nftw (directory, ftw_remove, 10, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) < 0)
return FALSE;
return TRUE;
}
gboolean
pv_boolean_environment (const gchar *name,
gboolean def)
{
const gchar *value = g_getenv (name);
if (g_strcmp0 (value, "1") == 0)
return TRUE;
if (g_strcmp0 (value, "") == 0 || g_strcmp0 (value, "0") == 0)
return FALSE;
if (value != NULL)
g_warning ("Unrecognised value \"%s\" for $%s", value, name);
return def;
}
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
/**
* pv_divert_stdout_to_stderr:
* @error: Used to raise an error on failure
*
* Duplicate file descriptors so that functions that would write to
* `stdout` instead write to a copy of the original `stderr`. Return
* a file handle that can be used to print structured output to the
* original `stdout`.
*
* Returns: (transfer full): A libc file handle for the original `stdout`,
* or %NULL on error. Free with `fclose()`.
*/
FILE *
pv_divert_stdout_to_stderr (GError **error)
{
g_autoptr(FILE) original_stdout = NULL;
glnx_autofd int original_stdout_fd = -1;
int flags;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* Duplicate the original stdout so that we still have a way to write
* machine-readable output. */
original_stdout_fd = dup (STDOUT_FILENO);
if (original_stdout_fd < 0)
return glnx_null_throw_errno_prefix (error,
"Unable to duplicate fd %d",
STDOUT_FILENO);
flags = fcntl (original_stdout_fd, F_GETFD, 0);
if (flags < 0)
return glnx_null_throw_errno_prefix (error,
"Unable to get flags of new fd");
fcntl (original_stdout_fd, F_SETFD, flags|FD_CLOEXEC);
/* If something like g_debug writes to stdout, make it come out of
* our original stderr. */
if (dup2 (STDERR_FILENO, STDOUT_FILENO) != STDOUT_FILENO)
return glnx_null_throw_errno_prefix (error,
"Unable to make fd %d a copy of fd %d",
STDOUT_FILENO, STDERR_FILENO);
original_stdout = fdopen (original_stdout_fd, "w");
if (original_stdout == NULL)
return glnx_null_throw_errno_prefix (error,
"Unable to create a stdio wrapper for fd %d",
original_stdout_fd);
else
original_stdout_fd = -1; /* ownership taken, do not close */
return g_steal_pointer (&original_stdout);
}
/**
* pv_async_signal_safe_error:
* @message: A human-readable message
* @exit_status: Call `_exit` with this status
*
* Exit with a fatal error, like g_error(), but async-signal-safe
* (see signal-safety(7)).
*/
void
pv_async_signal_safe_error (const char *message,
int exit_status)
{
if (write (2, message, strlen (message)) < 0)
{
/* Ignore - there's nothing we can do about it anyway - but
* suppress -Wunused-result. */
}
_exit (exit_status);
}
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
#define PROC_SYS_KERNEL_RANDOM_UUID "/proc/sys/kernel/random/uuid"
/**
* pv_get_random_uuid:
* @error: Used to raise an error on failure
*
* Return a random UUID (RFC 4122 version 4) as a string.
* It is a 128-bit quantity, with 122 bits of entropy, and 6 fixed bits
* indicating the "variant" (type, 0b10) and "version" (subtype, 0b0100).
*
* Returns: (transfer full): A random UUID, or %NULL on error
*/
gchar *
pv_get_random_uuid (GError **error)
{
g_autofree gchar *contents = NULL;
if (!g_file_get_contents (PROC_SYS_KERNEL_RANDOM_UUID,
&contents, NULL, error))
return NULL;
g_strchomp (contents); /* delete trailing newline */
/* Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx */
if (strlen (contents) != 36)
return glnx_null_throw (error, "%s not in expected format",
PROC_SYS_KERNEL_RANDOM_UUID);
return g_steal_pointer (&contents);
}