summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2020-11-29 20:16:36 +0300
committerEvgeny Zinoviev <me@ch1p.io>2020-11-29 20:16:36 +0300
commite42df368840ce96a4d561b4fec4d4ea07c30dbb6 (patch)
tree56b6cbd0b5a430edfdfd67850bdd9449d0194055
parentdba45aae98e0378b90125b50d1c4572c021948ed (diff)
change name to voidnsrun, add command-line arguments, rewrite readme
-rw-r--r--Makefile4
-rw-r--r--README.md119
-rw-r--r--glibcrun.c101
-rw-r--r--voidnsrun.c174
4 files changed, 265 insertions, 133 deletions
diff --git a/Makefile b/Makefile
index 4e8c506..050d4ef 100644
--- a/Makefile
+++ b/Makefile
@@ -1,13 +1,13 @@
CC := gcc
-PROGRAM = glibcrun
+PROGRAM = voidnsrun
CFLAGS = -O2 -std=c99 -Wall -W
LDFLAGS =
INSTALL = /usr/bin/env install
PREFIX = /usr/local
-OBJS = glibcrun.o
+OBJS = voidnsrun.o
all: $(PROGRAM)
diff --git a/README.md b/README.md
index 4e136d7..6363c9c 100644
--- a/README.md
+++ b/README.md
@@ -1,58 +1,117 @@
-# glibcrun
+# voidnsrun
-`glibcrun` is utility for launching glibc linked binaries in isolated namespaces
-in musl-libc Void Linux installations.
+`voidnsrun` is utility for launching programs in an isolated namespace with
+alternative `/usr` tree. Its primary goal is to run glibc-linked binaries in
+musl-libc Void Linux environments or vice-versa.
-It creates new private mount namespace for the running process, "replacing" `/usr`
-and `/var/db/xbps` with directories from your glibc basedir using bind
-mounts, and launches your glibc program.
+It creates a new private mount namespace, transparently substitutes `/usr` and
+some other directories with directories from your alternative root using bind
+mounts, and launches your program.
-## Creating glibc chroot
+## Installation
-I will use `/glibc` directory name for an example, you can use any other path you
-want.
+Just clone the repo, and then:
```
-# mkdir /glibc
-# XBPS_ARCH=x86_64 xbps-install --repository=http://alpha.de.repo.voidlinux.org/current -r /glibc -S base-voidstrap
+$ make
+$ sudo make install
```
-When it's done you may want to chroot into it, e.g. to install some dependencies
-for your glibc software.
+Note that installed binary must be owned by root and have suid bit. `make install`
+should handle it.
-## Installing glibcrun
+## Usage
-Just clone the repo, and then:
+```
+voidnsrun [OPTIONS] [PROGRAM [ARGS]]
+
+Options:
+ -h: print this help
+ -m <path>: add bind mount
+ -r <path>: altroot path. If this option is not present,
+ VOIDNSRUN_DIR environment variable is used.
+ -v: print version
+```
+
+`voidnsrun` needs to know the path to your alternative root directory and it can
+read it from the `VOIDNSRUN_DIR` environment variable or you can use `-r`
+argument to specify it.
+
+You may want to add something like this to your `~/.bashrc` or similar script:
```
-$ make
-$ sudo make install
+export VOIDNSRUN_DIR=/glibc
```
-Note that installed binary must be owned as root and have suid bit. `make install`
-should handle it, but anyway.
+By default, `voidnsrun` binds these paths from alternative root to the new
+namespace:
+- `/usr`
+- `/var/db/xbps`
+- `/etc/xbps.d`
-## Usage
+If you want to mount something else, use `-m` argument.
+
+## Example
-`glibcrun` needs to know the path to your glibc base directory and it reads it from
-the `GLIBCRUN_DIR` environment variable. You may want to add something like this
-to your `~/.bashrc` or similar script:
+Let's imagine you want to use some proprietary glibc-linked app on your
+musl-libc Void Linux box. Let it be Vivaldi browser for the example. You
+unpacked it to `/opt/vivaldi` and it doesn't work, obviously.
+First, you need to perform an alternative glibc base system installation to a
+separate new directory:
```
-export GLIBCRUN_DIR=/glibc
+# mkdir /glibc
+# XBPS_ARCH=x86_64 xbps-install --repository=http://alpha.de.repo.voidlinux.org/current -r /glibc -S base-voidstrap
+```
+
+Export path to this installation for `voidnsrun`:
+```
+export VOIDNSRUN_DIR=/glibc
```
-When `glibcrun` is run without arguments it will attempt to launch a shell from your
-`SHELL` variable, otherwise it will treat the first argument as a path to an executable
-and the rest as a list of arguments.
+Try launching your app:
+```
+voidnsrun /opt/vivaldi/vivaldi
+```
-Example:
+It won't work just yet:
+```
+/opt/vivaldi/vivaldi: error while loading shared libraries: libgobject-2.0.so.0: cannot open shared object file: No such file or directory
+```
+
+Now you need to install all its dependencies into your glibc installation. Use
+`xlocate` from `xtools` package to find a package responsible for a file (or
+just guess it):
+```
+$ xlocate libgobject-2.0.so.0
+Signal-Desktop-1.38.1_1 /usr/lib/signal-desktop/resources/app.asar.unpacked/node_modules/sharp/vendor/lib/libgobject-2.0.so -> /usr/lib/signal-desktop/resources/app.asar.unpacked/node_modules/sharp/vendor/lib/libgobject-2.0.so.0.5600.4
+Signal-Desktop-1.38.1_1 /usr/lib/signal-desktop/resources/app.asar.unpacked/node_modules/sharp/vendor/lib/libgobject-2.0.so.0 -> /usr/lib/signal-desktop/resources/app.asar.unpacked/node_modules/sharp/vendor/lib/libgobject-2.0.so.0.5600.4
+Signal-Desktop-1.38.1_1 /usr/lib/signal-desktop/resources/app.asar.unpacked/node_modules/sharp/vendor/lib/libgobject-2.0.so.0.5600.4
+glib-2.66.2_1 /usr/lib/libgobject-2.0.so.0 -> /usr/lib/libgobject-2.0.so.0.6600.2
+glib-2.66.2_1 /usr/lib/libgobject-2.0.so.0.6600.2
+glib-devel-2.66.2_1 /usr/share/gdb/auto-load/usr/lib/libgobject-2.0.so.0.6600.2-gdb.py
+libglib-devel-2.66.2_1 /usr/lib/libgobject-2.0.so -> /usr/lib/libgobject-2.0.so.0
+```
+
+Sync repos and install `glib`. You can use `voidnsrun` for this purpose too.
+Also, I think you should bind `/etc` and `/var` while using `xbps-install` via
+`voidnsrun`, to not mess with your main system files.
+```
+$ sudo voidnsrun -r /glibc -m /etc -m /var xbps-install -Su
+$ sudo voidnsrun -r /glibc -m /etc -m /var xbps-install glib
+```
+Try launching vivaldi again:
```
-glibcrun /opt/palemoon/palemoon -ProfileManager
+$ voidnsrun /opt/vivaldi/vivaldi
+/opt/vivaldi/vivaldi: error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory
```
-will launch `/opt/palemoon/palemoon -ProfileManager`.
+As you can see, it no longer complains about missing `libgobject-2.0.so.0`, now
+it's `libnss3.so`. Repeat steps above for all missing dependencies, and in the
+end, it will work. (If it's not, then something's still missing. In particular,
+make sure to install fonts related packages: `xorg-fonts`, `freetype`,
+`fontconfig`, `libXft`.)
## License
diff --git a/glibcrun.c b/glibcrun.c
deleted file mode 100644
index efedba1..0000000
--- a/glibcrun.c
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * Copyright (c) 2020 Evgeny Zinoviev <me@ch1p.io>.
- *
- * This program is licensed under the BSD 2-Clause License.
- * https://opensource.org/licenses/BSD-2-Clause
- */
-
-#define _GNU_SOURCE
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <errno.h>
-#include <sched.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <linux/limits.h>
-
-const char *var_name = "GLIBCRUN_DIR";
-
-#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
-#define ERROR(f_, ...) fprintf(stderr, (f_), ##__VA_ARGS__)
-
-int main(int argc, char *argv[]) {
- int result;
-
- /* Get glibc base path */
- const char *dir = getenv(var_name);
- if (!dir) {
- ERROR("error: environment variable %s not found.\n", var_name);
- return 1;
- }
-
- /* Validate it */
- struct stat st;
- result = stat(dir, &st);
- if (result != 0 || !S_ISDIR(st.st_mode)) {
- ERROR("error: %s is not a directory.\n", dir);
- return 1;
- }
-
- /* Get shell from current env to reuse it if needed */
- const char *shell = getenv("SHELL");
- if (!shell)
- shell = "/bin/sh";
-
- /* Do the unshare magic */
- result = unshare(CLONE_NEWNS);
- if (result == -1) {
- ERROR("unshare: %s\n", strerror(errno));
- return 1;
- }
-
- /* Mount glibc stuff to our private namespace */
- const char *const mountpoints[] = {"/usr", "/var/db/xbps"};
- char buf[PATH_MAX];
-
- for (int i = 0; i < (int)ARRAY_SIZE(mountpoints); i++) {
- strcpy(buf, dir);
- strcat(buf, mountpoints[i]);
- result = mount(buf, mountpoints[i], NULL, MS_BIND|MS_REC, NULL);
- if (result == -1) {
- ERROR("mount(%s): %s\n", mountpoints[i], strerror(errno));
- return 1;
- }
- }
-
- /* Drop root */
- uid_t uid = getuid();
- gid_t gid = getgid();
-
- result = setreuid(uid, uid);
- if (result == -1) {
- ERROR("setreuid: %s\n", strerror(errno));
- return 1;
- }
-
- result = setregid(gid, gid);
- if (result == -1) {
- ERROR("setregid: %s\n", strerror(errno));
- return 1;
- }
-
- /* Launch program or shell */
- const char *exec_cmd;
- char *const *exec_args = NULL;
- if (argc < 2)
- exec_cmd = shell;
- else {
- exec_cmd = argv[1];
- exec_args = (char *const *)argv+1;
- }
-
- result = execvp(exec_cmd, exec_args);
- if (result == -1) {
- ERROR("execvp(%s): %s\n", exec_cmd, strerror(errno));
- return 1;
- }
-
- return 0;
-}
diff --git a/voidnsrun.c b/voidnsrun.c
new file mode 100644
index 0000000..e121c0f
--- /dev/null
+++ b/voidnsrun.c
@@ -0,0 +1,174 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <linux/limits.h>
+
+const char *var_name = "VOIDNSRUN_DIR";
+const char *prog_version = "1.0";
+
+#define USERMOUNTS_MAX 8
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#define ERROR(f_, ...) fprintf(stderr, (f_), ##__VA_ARGS__)
+
+bool isdir(const char *s)
+{
+ struct stat st;
+ int result = stat(s, &st);
+ if (result == -1)
+ ERROR("stat: %s\n", strerror(errno));
+ return result == 0 && S_ISDIR(st.st_mode);
+}
+
+void usage(const char *progname)
+{
+ printf("Usage: %s [OPTIONS] [PROGRAM [ARGS]]\n", progname);
+ printf("\n"
+ "Options:\n"
+ " -h: print this help\n"
+ " -m <path>: add bind mount\n"
+ " -r <path>: altroot path. If this option is not present,\n"
+ " %s environment variable is used.\n"
+ " -v: print version\n",
+ var_name);
+}
+
+bool mount_list(
+ const char *dirptr,
+ size_t dirlen,
+ const char **mountpoints,
+ size_t len,
+ bool ignore_missing)
+{
+ char buf[PATH_MAX];
+ for (size_t i = 0; i < len; i++) {
+ /* Check if it's safe to proceed. */
+ if (dirlen + strlen(mountpoints[i]) >= PATH_MAX) {
+ ERROR("error: path %s%s is too large.\n", dirptr, mountpoints[i]);
+ return false;
+ }
+ strcpy(buf, dirptr);
+ strcat(buf, mountpoints[i]);
+ if (!isdir(buf)) {
+ if (!ignore_missing) {
+ ERROR("error: source mount dir %s does not exists.\n", buf);
+ return false;
+ } else
+ continue;
+ }
+ if (!isdir(mountpoints[i])) {
+ ERROR("error: mountpoint %s does not exists.\n", mountpoints[i]);
+ return false;
+ }
+ if (mount(buf, mountpoints[i], NULL, MS_BIND|MS_REC, NULL) == -1) {
+ ERROR("mount: failed to mount %s: %s\n",
+ mountpoints[i], strerror(errno));
+ return false;
+ }
+ }
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 2) {
+ usage(argv[0]);
+ return 0;
+ }
+
+ int result;
+ char *dir = NULL;
+ size_t dirlen;
+ const char *usermounts[USERMOUNTS_MAX] = {0};
+ int usermounts_count = 0;
+ int c;
+ while ((c = getopt(argc, argv, "vhm:r:")) != -1) {
+ switch (c) {
+ case 'v':
+ printf("%s\n", prog_version);
+ return 0;
+ case 'h':
+ usage(argv[0]);
+ return 0;
+ case 'r':
+ dir = optarg;
+ break;
+ case 'm':
+ if (usermounts_count < USERMOUNTS_MAX) {
+ usermounts[usermounts_count++] = optarg;
+ break;
+ } else {
+ ERROR("error: only up to %d user mounts allowed.\n",
+ USERMOUNTS_MAX);
+ return 1;
+ }
+ case '?':
+ return 1;
+ }
+ }
+
+ /* Get alternative root dir. */
+ if (!dir)
+ dir = getenv(var_name);
+ if (!dir) {
+ ERROR("error: environment variable %s not found.\n", var_name);
+ return 1;
+ }
+
+ /* Validate it. */
+ if (!isdir(dir)) {
+ ERROR("error: %s is not a directory.\n", dir);
+ return 1;
+ }
+
+ dirlen = strlen(dir);
+
+ /* Do the unshare magic. */
+ result = unshare(CLONE_NEWNS);
+ if (result == -1) {
+ ERROR("unshare: %s\n", strerror(errno));
+ return 1;
+ }
+
+ /* Mount stuff from altroot to our private namespace. */
+ const char *mountpoints[] = {"/usr", "/var/db/xbps", "/etc/xbps.d"};
+
+ if (!mount_list(dir, dirlen, mountpoints, ARRAY_SIZE(mountpoints), true))
+ return 1;
+
+ if (usermounts_count > 0 &&
+ !mount_list(dir, dirlen, usermounts, usermounts_count, false))
+ return 1;
+
+ /* Drop root. */
+ uid_t uid = getuid();
+ gid_t gid = getgid();
+
+ result = setreuid(uid, uid);
+ if (result == -1) {
+ ERROR("setreuid: %s\n", strerror(errno));
+ return 1;
+ }
+
+ result = setregid(gid, gid);
+ if (result == -1) {
+ ERROR("setregid: %s\n", strerror(errno));
+ return 1;
+ }
+
+ /* Launch program. */
+ result = execvp(argv[optind], (char *const *)argv+optind);
+ if (result == -1) {
+ ERROR("execvp(%s): %s\n", argv[optind], strerror(errno));
+ return 1;
+ }
+
+ return 0;
+}