Newer
Older
/* Set defaults */
opt_freedesktop_app_id = g_strdup (g_getenv ("PRESSURE_VESSEL_FDO_APP_ID"));
if (opt_freedesktop_app_id != NULL && opt_freedesktop_app_id[0] == '\0')
g_clear_pointer (&opt_freedesktop_app_id, g_free);
opt_home = g_strdup (g_getenv ("PRESSURE_VESSEL_HOME"));
if (opt_home != NULL && opt_home[0] == '\0')
g_clear_pointer (&opt_home, g_free);
opt_share_home = tristate_environment ("PRESSURE_VESSEL_SHARE_HOME");
opt_verbose = boolean_environment ("PRESSURE_VESSEL_VERBOSE", FALSE);
if (!opt_shell_cb ("$PRESSURE_VESSEL_SHELL",
g_getenv ("PRESSURE_VESSEL_SHELL"), NULL, error))
goto out;
if (!opt_terminal_cb ("$PRESSURE_VESSEL_TERMINAL",
g_getenv ("PRESSURE_VESSEL_TERMINAL"), NULL, error))
goto out;
context = g_option_context_new ("[--] COMMAND [ARGS]\n"
"Run COMMAND [ARGS] in a container.\n");
g_option_context_add_main_entries (context, options, NULL);
if (!g_option_context_parse (context, &argc, &argv, error))
goto out;
if (opt_runtime == NULL)
opt_runtime = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME"));
if (opt_runtime_base == NULL)
opt_runtime_base = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME_BASE"));
if (opt_runtime != NULL
&& opt_runtime[0] != '\0'
&& !g_path_is_absolute (opt_runtime)
&& opt_runtime_base != NULL
&& opt_runtime_base[0] != '\0')
{
g_autofree gchar *tmp = g_steal_pointer (&opt_runtime);
opt_runtime = g_build_filename (opt_runtime_base, tmp, NULL);
}
if (opt_version)
{
g_print ("%s:\n"
" Package: pressure-vessel\n"
" Version: %s\n",
argv[0], VERSION);
ret = 0;
goto out;
}
if (argc < 2)
{
g_printerr ("%s: An executable to run is required\n",
g_get_prgname ());
goto out;
}
if (opt_terminal == PV_TERMINAL_AUTO)
if (opt_shell != PV_SHELL_NONE)
opt_terminal = PV_TERMINAL_XTERM;
if (opt_terminal == PV_TERMINAL_NONE && opt_shell != PV_SHELL_NONE)
g_printerr ("%s: --terminal=none is incompatible with --shell\n",
g_get_prgname ());
goto out;
if (strcmp (argv[1], "--") == 0)
{
argv++;
argc--;
}
home = g_get_home_dir ();
if (opt_share_home == TRISTATE_YES)
{
opt_fake_home = NULL;
}
else if (opt_home)
{
opt_fake_home = g_strdup (opt_home);
}
else if (opt_share_home == TRISTATE_MAYBE)
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
{
opt_fake_home = NULL;
}
else if (opt_freedesktop_app_id)
{
opt_fake_home = g_build_filename (home, ".var", "app",
opt_freedesktop_app_id, NULL);
}
else if (opt_steam_app_id)
{
opt_freedesktop_app_id = g_strdup_printf ("com.steampowered.App%s",
opt_steam_app_id);
opt_fake_home = g_build_filename (home, ".var", "app",
opt_freedesktop_app_id, NULL);
}
else if (g_getenv ("SteamAppId") != NULL)
{
opt_freedesktop_app_id = g_strdup_printf ("com.steampowered.App%s",
g_getenv ("SteamAppId"));
opt_fake_home = g_build_filename (home, ".var", "app",
opt_freedesktop_app_id, NULL);
}
else
{
g_printerr ("%s: Either --home, --freedesktop-app-id, --steam-app-id "
"or $SteamAppId is required\n",
g_get_prgname ());
goto out;
}
if (opt_env_if_host != NULL)
{
for (i = 0; opt_env_if_host[i] != NULL; i++)
{
const char *equals = strchr (opt_env_if_host[i], '=');
if (equals == NULL)
g_printerr ("%s: --env-if-host argument must be of the form "
"NAME=VALUE, not \"%s\"\n",
g_get_prgname (), opt_env_if_host[i]);
}
}
/* Finished parsing arguments, so any subsequent failures will make
* us exit 1. */
ret = 1;
if (opt_terminal != PV_TERMINAL_TTY)
{
int fd;
if (!glnx_openat_rdonly (-1, "/dev/null", TRUE, &fd, error))
goto out;
if (dup2 (fd, STDIN_FILENO) < 0)
{
glnx_throw_errno_prefix (error,
"Cannot replace stdin with /dev/null");
goto out;
}
}
pv_get_current_dirs (&cwd_p, &cwd_l);
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
if (opt_verbose)
{
g_auto(GStrv) env = g_get_environ ();
g_log_set_handler (G_LOG_DOMAIN,
G_LOG_LEVEL_DEBUG | G_LOG_LEVEL_INFO,
cli_log_func, (void *) g_get_prgname ());
g_message ("Original argv:");
for (i = 0; i < original_argc; i++)
{
g_autofree gchar *quoted = g_shell_quote (original_argv[i]);
g_message ("\t%" G_GSIZE_FORMAT ": %s", i, quoted);
}
g_message ("Current working directory:");
g_message ("\tPhysical: %s", cwd_p);
g_message ("\tLogical: %s", cwd_l);
g_message ("Environment variables:");
qsort (env, g_strv_length (env), sizeof (char *), pv_envp_cmp);
for (i = 0; env[i] != NULL; i++)
{
g_autofree gchar *quoted = g_shell_quote (env[i]);
g_message ("\t%s", quoted);
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
}
g_message ("Wrapped command:");
for (i = 1; i < argc; i++)
{
g_autofree gchar *quoted = g_shell_quote (argv[i]);
g_message ("\t%" G_GSIZE_FORMAT ": %s", i, quoted);
}
}
tools_dir = find_executable_dir (error);
if (tools_dir == NULL)
goto out;
g_debug ("Found executable directory: %s", tools_dir);
wrapped_command = flatpak_bwrap_new (NULL);
switch (opt_terminal)
g_debug ("Wrapping command to use tty");
if (!pv_bwrap_wrap_tty (wrapped_command, error))
g_debug ("Wrapping command with xterm");
pv_bwrap_wrap_in_xterm (wrapped_command);
case PV_TERMINAL_AUTO:
case PV_TERMINAL_NONE:
default:
/* do nothing */
break;
if (opt_shell != PV_SHELL_NONE || opt_terminal == PV_TERMINAL_XTERM)
/* In the (PV_SHELL_NONE, PV_TERMINAL_XTERM) case, just don't let the
* xterm close before the user has had a chance to see the output */
pv_bwrap_wrap_interactive (wrapped_command, opt_shell);
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
}
if (argv[1][0] == '-')
{
/* Make sure wrapped_command is something we can validly pass to env(1) */
if (strchr (argv[1], '=') != NULL)
flatpak_bwrap_add_args (wrapped_command,
"sh", "-euc", "exec \"$@\"", "sh",
NULL);
/* Make sure bwrap will interpret wrapped_command as the end of its
* options */
flatpak_bwrap_add_arg (wrapped_command, "env");
}
g_debug ("Setting arguments for wrapped command");
flatpak_bwrap_append_argsv (wrapped_command, &argv[1], argc - 1);
g_debug ("Checking for bwrap...");
bwrap_executable = check_bwrap (tools_dir);
if (bwrap_executable == NULL)
{
if (opt_host_fallback)
{
g_message ("Falling back to executing wrapped command directly");
if (opt_env_if_host != NULL)
{
for (i = 0; opt_env_if_host[i] != NULL; i++)
{
char *equals = strchr (opt_env_if_host[i], '=');
g_assert (equals != NULL);
*equals = '\0';
flatpak_bwrap_set_env (wrapped_command, opt_env_if_host[i],
equals + 1, TRUE);
}
}
flatpak_bwrap_finish (wrapped_command);
/* flatpak_bwrap_finish did this */
g_assert (g_ptr_array_index (wrapped_command->argv,
wrapped_command->argv->len - 1) == NULL);
execvpe (g_ptr_array_index (wrapped_command->argv, 0),
(char * const *) wrapped_command->argv->pdata,
wrapped_command->envp);
glnx_throw_errno_prefix (error, "execvpe %s",
(gchar *) g_ptr_array_index (wrapped_command->argv, 0));
goto out;
}
else
{
goto out;
}
}
g_debug ("Checking bwrap features...");
bwrap_help_argv[0] = bwrap_executable;
bwrap_help = capture_output (bwrap_help_argv, error);
if (bwrap_help == NULL)
goto out;
g_debug ("Creating temporary directories...");
tmpdir = g_dir_make_tmp ("pressure-vessel-wrap.XXXXXX", error);
if (tmpdir == NULL)
goto out;
scratch = g_build_filename (tmpdir, "scratch", NULL);
g_mkdir (scratch, 0700);
overrides = g_build_filename (tmpdir, "overrides", NULL);
g_mkdir (overrides, 0700);
overrides_bin = g_build_filename (overrides, "bin", NULL);
g_mkdir (overrides_bin, 0700);
bwrap = flatpak_bwrap_new (NULL);
flatpak_bwrap_add_arg (bwrap, bwrap_executable);
/* Protect the controlling terminal from the app/game, unless we are
* running an interactive shell in which case that would break its
* job control. */
if (opt_terminal != PV_TERMINAL_TTY)
flatpak_bwrap_add_arg (bwrap, "--new-session");
pv_bwrap_add_api_filesystems (bwrap);
if (opt_runtime != NULL && opt_runtime[0] != '\0')
g_autofree gchar *usr = NULL;
g_autofree gchar *files_ref = NULL;
g_debug ("Configuring runtime...");
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
/* Take a lock on the runtime until we're finished with setup,
* to make sure it doesn't get deleted. */
files_ref = g_build_filename (opt_runtime, ".ref", NULL);
runtime_lock = pv_bwrap_lock_new (files_ref,
PV_BWRAP_LOCK_FLAGS_CREATE,
error);
/* If the runtime is being deleted, ... don't use it, I suppose? */
if (runtime_lock == NULL)
goto out;
usr = g_build_filename (opt_runtime, "usr", NULL);
/* Tell bwrap to hold a reference to files/.ref until it exits.
* If files is a merged-/usr, it's mounted as /usr (as if for
* Flatpak); otherwise it's mounted at /, which we need to cope
* with too. */
if (g_file_test (usr, G_FILE_TEST_IS_DIR))
flatpak_bwrap_add_args (bwrap,
"--lock-file", "/.ref",
NULL);
else
flatpak_bwrap_add_args (bwrap,
"--lock-file", "/usr/.ref",
NULL);
search_path_append (bin_path, "/overrides/bin");
search_path_append (bin_path, g_getenv ("PATH"));
flatpak_bwrap_add_args (bwrap,
"--setenv", "PATH",
bin_path->str,
NULL);
/* TODO: Adapt the use_ld_so_cache code from Flatpak instead
* of setting LD_LIBRARY_PATH, for better robustness against
* games that set their own LD_LIBRARY_PATH ignoring what they
* got from the environment */
g_assert (multiarch_tuples[G_N_ELEMENTS (multiarch_tuples) - 1] == NULL);
for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++)
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
{
g_autofree gchar *ld_path = NULL;
ld_path = g_build_filename ("/overrides", "lib",
multiarch_tuples[i], NULL);
search_path_append (ld_library_path, ld_path);
}
/* This would be filtered out by a setuid bwrap, so we have to go
* via --setenv. */
flatpak_bwrap_add_args (bwrap,
"--setenv", "LD_LIBRARY_PATH",
ld_library_path->str,
NULL);
if (!bind_runtime (bwrap, tools_dir, opt_runtime, overrides,
scratch, error))
goto out;
}
else
{
flatpak_bwrap_add_args (bwrap,
"--bind", "/", "/",
NULL);
}
/* Protect other users' homes (but guard against the unlikely
* situation that they don't exist) */
if (g_file_test ("/home", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap,
"--tmpfs", "/home",
NULL);
if (opt_fake_home == NULL)
{
flatpak_bwrap_add_args (bwrap,
"--bind", home, home,
NULL);
}
else
{
if (!use_fake_home (bwrap, opt_fake_home, error))
goto out;
}
g_debug ("Adjusting LD_PRELOAD...");
/* We need the LD_PRELOADs from Steam visible at the paths that were
* used for them, which might be their physical rather than logical
* locations. */
if (opt_ld_preload != NULL)
{
for (i = 0; i < opt_ld_preload->len; i++)
{
const char *preload = g_ptr_array_index (opt_ld_preload, i);
g_assert (preload != NULL);
if (*preload == '\0')
continue;
/* We have the beginnings of infrastructure to set a LD_PRELOAD
* from inside the container, but currently the only thing we
* support is it coming from the host. */
g_assert (g_str_has_prefix (preload, "host:"));
preload = preload + 5;
if (g_file_test (preload, G_FILE_TEST_EXISTS))
{
if (opt_runtime != NULL
&& opt_runtime[0] != '\0'
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
&& (g_str_has_prefix (preload, "/usr/")
|| g_str_has_prefix (preload, "/lib")))
{
g_autofree gchar *in_run_host = g_build_filename ("/run/host",
preload,
NULL);
/* When using a runtime we can't write to /usr/ or /libQUAL/,
* so redirect this preloaded module to the corresponding
* location in /run/host. */
search_path_append (adjusted_ld_preload, in_run_host);
}
else
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", preload, preload,
NULL);
search_path_append (adjusted_ld_preload, preload);
}
}
else
{
g_debug ("LD_PRELOAD module '%s' does not exist", preload);
}
}
}
/* Put the caller's LD_PRELOAD back.
* This would be filtered out by a setuid bwrap, so we have to go
* via --setenv. */
if (adjusted_ld_preload->len != 0)
flatpak_bwrap_add_args (bwrap,
"--setenv", "LD_PRELOAD",
adjusted_ld_preload->str,
NULL);
else
flatpak_bwrap_add_args (bwrap,
"--unsetenv", "LD_PRELOAD",
NULL);
/* Make sure the current working directory (the game we are going to
* run) is available. Some games write here. */
g_debug ("Making home directory available...");
if (pv_is_same_file (home, cwd_p))
{
g_debug ("Not making physical working directory \"%s\" available to "
"container because it is the home directory",
cwd_p);
}
else
{
flatpak_bwrap_add_args (bwrap,
"--bind", cwd_p, cwd_p,
NULL);
}
flatpak_bwrap_add_args (bwrap,
"--chdir", cwd_p,
"--unsetenv", "PWD",
NULL);
/* TODO: Potential future expansion: use --unshare-pid for more isolation */
/* Put Steam Runtime environment variables back, if /usr is mounted
* from the host. */
if (opt_runtime == NULL || opt_runtime[0] == '\0')
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
{
g_debug ("Making Steam Runtime available...");
/* We need libraries from the Steam Runtime, so make sure that's
* visible (it should never need to be read/write though) */
if (opt_env_if_host != NULL)
{
for (i = 0; opt_env_if_host[i] != NULL; i++)
{
char *equals = strchr (opt_env_if_host[i], '=');
g_assert (equals != NULL);
if (g_str_has_prefix (opt_env_if_host[i], "STEAM_RUNTIME=/"))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", equals + 1,
equals + 1,
NULL);
}
*equals = '\0';
/* We do this via --setenv instead of flatpak_bwrap_set_env()
* to make sure they aren't filtered out by a setuid bwrap. */
flatpak_bwrap_add_args (bwrap,
"--setenv", opt_env_if_host[i],
equals + 1,
NULL);
*equals = '=';
}
}
}
if (opt_verbose)
{
g_message ("%s options before bundling:", bwrap_executable);
for (i = 0; i < bwrap->argv->len; i++)
{
g_autofree gchar *quoted = NULL;
quoted = g_shell_quote (g_ptr_array_index (bwrap->argv, i));
g_message ("\t%s", quoted);
}
}
if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error))
goto out;
g_debug ("Adding wrapped command...");
flatpak_bwrap_append_args (bwrap, wrapped_command->argv);
if (opt_verbose)
{
g_message ("Final %s options:", bwrap_executable);
for (i = 0; i < bwrap->argv->len; i++)
{
g_autofree gchar *quoted = NULL;
quoted = g_shell_quote (g_ptr_array_index (bwrap->argv, i));
g_message ("\t%s", quoted);
}
g_message ("%s environment:", bwrap_executable);
for (i = 0; bwrap->envp != NULL && bwrap->envp[i] != NULL; i++)
{
g_autofree gchar *quoted = NULL;
quoted = g_shell_quote (bwrap->envp[i]);
g_message ("\t%s", quoted);
}
/* Clean up temporary directory before running our long-running process */
if (tmpdir != NULL &&
!glnx_shutil_rm_rf_at (-1, tmpdir, NULL, error))
{
g_warning ("Unable to delete temporary directory: %s",
local_error->message);
g_clear_error (&local_error);
}
g_clear_pointer (&tmpdir, g_free);
/* If we are using a runtime, pass the lock fd to the bwrap process.
* We hope it will hold the fd open for as long as it's running.
*
* This is not a complete solution - what we really want is for the
* bwrap process *and every one of its children, recursively* to hold
* this fd open. However, we can't currently use bwrap --lock-file
* or bwrap --sync-fd without --unshare-pid
* (<https://github.com/containers/bubblewrap/issues/336>), and we
* can't do that without breaking gameoverlayrender.so's assumptions. */
if (runtime_lock != NULL)
flatpak_bwrap_add_fd (bwrap, pv_bwrap_lock_steal_fd (runtime_lock));
flatpak_bwrap_finish (bwrap);
pv_bwrap_execve (bwrap, error);
out:
if (local_error != NULL)
g_warning ("%s", local_error->message);
if (tmpdir != NULL &&
!glnx_shutil_rm_rf_at (-1, tmpdir, NULL, error))
{
g_warning ("Unable to delete temporary directory: %s",
local_error->message);
g_clear_error (&local_error);
}
g_clear_pointer (&opt_ld_preload, g_ptr_array_unref);
g_clear_pointer (&opt_env_if_host, g_strfreev);
g_clear_pointer (&opt_freedesktop_app_id, g_free);
g_clear_pointer (&opt_steam_app_id, g_free);
g_clear_pointer (&opt_home, g_free);
g_clear_pointer (&opt_fake_home, g_free);
g_clear_pointer (&opt_runtime_base, g_free);
g_clear_pointer (&opt_runtime, g_free);
return ret;
}