commit a640cd365bd21743165adedb00bd9fac981687b2 Author: Alexander Larsson Date: Wed Dec 17 14:05:44 2014 +0100 Initial version diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1f53c4d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +*.la +*.o +*.lo +.deps +.libs +INSTALL +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +compile +config.guess +config.h +config.log +config.status +config.sub +configure +depcomp +install-sh +libtool +ltmain.sh +m4 +missing +stamp-h1 +config.h.in +stamp-* +xdg-app +xdg-app-helper diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..eab7c235 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,24 @@ +NULL = + +AM_CPPFLAGS = \ + -DXDG_APP_BASEDIR=\"$(datadir)/xdg-app\" \ + $(NULL) + +bin_PROGRAMS = \ + xdg-app-helper \ + xdg-app \ + $(NULL) + +xdg_app_helper_SOURCES = xdg-app-helper.c + +xdg_app_SOURCES = \ + xdg-app-main.c \ + xdg-app-builtins.h \ + xdg-app-builtins-add-repo.c \ + $(NULL) +xdg_app_LDADD = $(OSTREE_LIBS) +xdg_app_CFLAGS = $(OSTREE_CFLAGS) + +install-exec-hook: + chown root $(bindir)/xdg-app-helper + chmod u+s $(bindir)/xdg-app-helper diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..52843e5d --- /dev/null +++ b/autogen.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. + +test -n "$srcdir" || srcdir=`dirname "$0"` +test -n "$srcdir" || srcdir=. + +olddir=`pwd` +cd "$srcdir" + +AUTORECONF=`which autoreconf` +if test -z $AUTORECONF; then + echo "*** No autoreconf found, please install it ***" + exit 1 +fi + +# INSTALL are required by automake, but may be deleted by clean +# up rules. to get automake to work, simply touch these here, they will be +# regenerated from their corresponding *.in files by ./configure anyway. +touch INSTALL + +autoreconf --force --install --verbose || exit $? + +cd "$olddir" +test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..7d084572 --- /dev/null +++ b/configure.ac @@ -0,0 +1,35 @@ +AC_PREREQ([2.63]) + +AC_INIT([xdg-app],[0.0.1]) + +AC_PROG_CC +AM_PROG_CC_C_O +AC_DISABLE_STATIC + +LT_PREREQ([2.2.6]) +LT_INIT([disable-static]) + +AC_CONFIG_SRCDIR([xdg-app-helper.c]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) +AM_INIT_AUTOMAKE([1.11 no-define no-dist-gzip dist-bzip2 tar-ustar foreign]) + +# Enable silent rules is available +AM_SILENT_RULES([yes]) +AM_MAINTAINER_MODE([enable]) + +if test "x$GCC" = "xyes"; then + case " $CFLAGS " in + *[[\ \ ]]-Wall[[\ \ ]]*) ;; + *) CFLAGS="$CFLAGS -Wall" ;; + esac +fi + +PKG_CHECK_MODULES(OSTREE, [glib-2.0 libgsystem gio-2.0 ostree-1]) +AC_SUBST(OSTREE_CFLAGS) +AC_SUBST(OSTREE_LIBS) + +AC_CONFIG_FILES([ +Makefile +]) +AC_OUTPUT diff --git a/xdg-app-builtins-add-repo.c b/xdg-app-builtins-add-repo.c new file mode 100644 index 00000000..b6da0aab --- /dev/null +++ b/xdg-app-builtins-add-repo.c @@ -0,0 +1,75 @@ +#include "config.h" + +#include +#include +#include + +#include "libgsystem.h" + +#include "xdg-app-builtins.h" + +static gboolean opt_no_gpg_verify; +static gboolean opt_if_not_exists; + +static GOptionEntry options[] = { + { "no-gpg-verify", 0, 0, G_OPTION_ARG_NONE, &opt_no_gpg_verify, "Disable GPG verification", NULL }, + { "if-not-exists", 0, 0, G_OPTION_ARG_NONE, &opt_if_not_exists, "Do nothing if the provided remote exists", NULL }, + { NULL } +}; + +static void +usage_error (GOptionContext *context, const char *message, GError **error) +{ + gs_free gchar *help = g_option_context_get_help (context, TRUE, NULL); + g_printerr ("%s", help); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, message); +} + +gboolean +xdg_app_builtin_add_repo (int argc, char **argv, GCancellable *cancellable, GError **error) +{ + GOptionContext *context; + gboolean ret = FALSE; + gs_unref_object OstreeRepo *repo = NULL; + gs_unref_object GFile *basedir = NULL; + gs_unref_variant_builder GVariantBuilder *optbuilder = NULL; + const char *remote_name; + const char *remote_url; + + context = g_option_context_new ("NAME URL - Add a remote repository"); + + if (!xdg_app_option_context_parse (context, options, &argc, &argv, 0, &repo, &basedir, cancellable, error)) + goto out; + + if (argc < 3) + { + usage_error (context, "NAME and URL must be specified", error); + goto out; + } + + remote_name = argv[1]; + remote_url = argv[2]; + + optbuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + + if (opt_no_gpg_verify) + g_variant_builder_add (optbuilder, "{s@v}", + "gpg-verify", + g_variant_new_variant (g_variant_new_boolean (FALSE))); + + + if (!ostree_repo_remote_change (repo, NULL, + opt_if_not_exists ? OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS : + OSTREE_REPO_REMOTE_CHANGE_ADD, + remote_name, remote_url, + g_variant_builder_end (optbuilder), + cancellable, error)) + goto out; + + ret = TRUE; + + out: + if (context) + g_option_context_free (context); + return ret; +} diff --git a/xdg-app-builtins.h b/xdg-app-builtins.h new file mode 100644 index 00000000..57066845 --- /dev/null +++ b/xdg-app-builtins.h @@ -0,0 +1,32 @@ +#ifndef __XDG_APP_BUILTINS_H__ +#define __XDG_APP_BUILTINS_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef enum { + XDG_APP_BUILTIN_FLAG_NO_USER = 1 << 0, + XDG_APP_BUILTIN_FLAG_NO_REPO = 1 << 1, +} XdgAppBuiltinFlags; + +gboolean xdg_app_option_context_parse (GOptionContext *context, + const GOptionEntry *main_entries, + int *argc, + char ***argv, + XdgAppBuiltinFlags flags, + OstreeRepo **repo, + GFile **basedir, + GCancellable *cancellable, + GError **error); + +#define BUILTINPROTO(name) gboolean xdg_app_builtin_ ## name (int argc, char **argv, GCancellable *cancellable, GError **error) + +BUILTINPROTO(add_repo); + +#undef BUILTINPROTO + +G_END_DECLS + +#endif /* __XDG_APP_BUILTINS_H__ */ diff --git a/xdg-app-helper.c b/xdg-app-helper.c new file mode 100644 index 00000000..92ce4298 --- /dev/null +++ b/xdg-app-helper.c @@ -0,0 +1,1160 @@ +/* xdg-app-helper + * Copyright (C) 2014 Alexander Larsson + * + * This probram 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 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 . + * + */ + +#define _GNU_SOURCE /* Required for CLONE_NEWNS */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define __debug__(x) printf x +#else +#define __debug__(x) +#endif + +#define N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) + +#define READ_END 0 +#define WRITE_END 1 + +static void +die_with_error (const char *format, ...) +{ + va_list args; + int errsv; + + errsv = errno; + + va_start (args, format); + vfprintf (stderr, format, args); + va_end (args); + + fprintf (stderr, ": %s\n", strerror (errsv)); + + exit (1); +} + +static void +die (const char *format, ...) +{ + va_list args; + + va_start (args, format); + vfprintf (stderr, format, args); + va_end (args); + + fprintf (stderr, "\n"); + + exit (1); +} + +static void * +xmalloc (size_t size) +{ + void *res = malloc (size); + if (res == NULL) + die ("oom"); + return res; +} + +static char * +xstrdup (const char *str) +{ + char *res; + + assert (str != NULL); + + res = strdup (str); + if (res == NULL) + die ("oom"); + + return res; +} + +static void +xsetenv (const char *name, const char *value, int overwrite) +{ + if (setenv (name, value, overwrite)) + die ("setenv failed"); +} + +static void +xunsetenv (const char *name) +{ + if (unsetenv(name)) + die ("unsetenv failed"); +} + +char * +strconcat (const char *s1, + const char *s2) +{ + size_t len = 0; + char *res; + + if (s1) + len += strlen (s1); + if (s2) + len += strlen (s2); + + res = xmalloc (len + 1); + *res = 0; + if (s1) + strcat (res, s1); + if (s2) + strcat (res, s2); + + return res; +} + +char * +strconcat_len (const char *s1, + const char *s2, + size_t s2_len) +{ + size_t len = 0; + char *res; + + if (s1) + len += strlen (s1); + if (s2) + len += s2_len; + + res = xmalloc (len + 1); + *res = 0; + if (s1) + strcat (res, s1); + if (s2) + strncat (res, s2, s2_len); + + return res; +} + +char* +strdup_printf (const char *format, + ...) +{ + char *buffer = NULL; + va_list args; + + va_start (args, format); + vasprintf (&buffer, format, args); + va_end (args); + + if (buffer == NULL) + die ("oom"); + + return buffer; +} + +void +usage (char **argv) +{ + fprintf (stderr, "usage: %s [-n] [-i] [-p ] [-x X11 socket] [-w] [-W] [-a ] [-v ] \n", argv[0]); + exit (1); +} + +static int +pivot_root (const char * new_root, const char * put_old) +{ +#ifdef __NR_pivot_root + return syscall(__NR_pivot_root, new_root, put_old); +#else + errno = ENOSYS; + return -1; +#endif +} + +typedef enum { + FILE_TYPE_REGULAR, + FILE_TYPE_DIR, + FILE_TYPE_SYMLINK, + FILE_TYPE_BIND, + FILE_TYPE_BIND_RO, + FILE_TYPE_MOUNT, + FILE_TYPE_DEVICE, + FILE_TYPE_SHM, +} file_type_t; + +typedef enum { + FILE_FLAGS_NONE = 0, + FILE_FLAGS_USER_OWNED = 1 << 0, + FILE_FLAGS_NON_FATAL = 1 << 1, + FILE_FLAGS_IF_LAST_FAILED = 1 << 2, + FILE_FLAGS_DEVICES = 1 << 3, +} file_flags_t; + +typedef struct { + file_type_t type; + const char *name; + mode_t mode; + const char *data; + file_flags_t flags; +} create_table_t; + +typedef struct { + const char *what; + const char *where; + const char *type; + const char *options; + unsigned long flags; +} mount_table_t; + +int +ascii_isdigit (char c) +{ + return c >= '0' && c <= '9'; +} + +static const create_table_t create[] = { + { FILE_TYPE_DIR, ".oldroot", 0755 }, + { FILE_TYPE_DIR, "usr", 0755 }, + { FILE_TYPE_DIR, "tmp", 01777 }, + { FILE_TYPE_DIR, "self", 0755}, + { FILE_TYPE_DIR, "run", 0755}, + { FILE_TYPE_DIR, "run/dbus", 0755}, + { FILE_TYPE_DIR, "run/user", 0755}, + { FILE_TYPE_DIR, "run/user/%1$d", 0700, NULL, FILE_FLAGS_USER_OWNED }, + { FILE_TYPE_DIR, "run/user/%1$d/pulse", 0700, NULL, FILE_FLAGS_USER_OWNED }, + { FILE_TYPE_REGULAR, "run/user/%1$d/pulse/native", 0700, NULL, FILE_FLAGS_USER_OWNED }, + { FILE_TYPE_DIR, "var", 0755}, + { FILE_TYPE_SYMLINK, "var/tmp", 0755, "/tmp"}, + { FILE_TYPE_SYMLINK, "var/run", 0755, "/run"}, + { FILE_TYPE_SYMLINK, "lib", 0755, "usr/lib"}, + { FILE_TYPE_SYMLINK, "bin", 0755, "usr/bin" }, + { FILE_TYPE_SYMLINK, "sbin", 0755, "usr/sbin"}, + { FILE_TYPE_SYMLINK, "etc", 0755, "usr/etc"}, + { FILE_TYPE_DIR, "tmp/.X11-unix", 0755 }, + { FILE_TYPE_REGULAR, "tmp/.X11-unix/X99", 0755 }, + { FILE_TYPE_DIR, "proc", 0755}, + { FILE_TYPE_MOUNT, "proc"}, + { FILE_TYPE_BIND_RO, "proc/sys", 0755, "proc/sys"}, + { FILE_TYPE_DIR, "sys", 0755}, + { FILE_TYPE_MOUNT, "sys"}, + { FILE_TYPE_DIR, "dev", 0755}, + { FILE_TYPE_MOUNT, "dev"}, + { FILE_TYPE_DIR, "dev/pts", 0755}, + { FILE_TYPE_MOUNT, "dev/pts"}, + { FILE_TYPE_DIR, "dev/shm", 0755}, + { FILE_TYPE_SHM, "dev/shm"}, + { FILE_TYPE_DEVICE, "dev/null", S_IFCHR|0666, "/dev/null"}, + { FILE_TYPE_DEVICE, "dev/zero", S_IFCHR|0666, "/dev/zero"}, + { FILE_TYPE_DEVICE, "dev/full", S_IFCHR|0666, "/dev/full"}, + { FILE_TYPE_DEVICE, "dev/random", S_IFCHR|0666, "/dev/random"}, + { FILE_TYPE_DEVICE, "dev/urandom", S_IFCHR|0666, "/dev/urandom"}, + { FILE_TYPE_DEVICE, "dev/tty", S_IFCHR|0666, "/dev/tty"}, + { FILE_TYPE_DIR, "dev/dri", 0755}, + { FILE_TYPE_BIND, "dev/dri", 0755, "/dev/dri", FILE_FLAGS_NON_FATAL|FILE_FLAGS_DEVICES}, +}; + +static const create_table_t create_post[] = { + { FILE_TYPE_BIND, "usr/etc/machine-id", 0444, "/etc/machine-id", FILE_FLAGS_NON_FATAL}, + { FILE_TYPE_BIND, "usr/etc/machine-id", 0444, "/var/lib/dbus/machine-id", FILE_FLAGS_NON_FATAL | FILE_FLAGS_IF_LAST_FAILED}, +}; + +static const mount_table_t mount_table[] = { + { "proc", "proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, + { "sysfs", "sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV }, + { "tmpfs", "dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME }, + { "devpts", "dev/pts", "devpts","newinstance,ptmxmode=0666,mode=620,gid=5", MS_NOSUID|MS_NOEXEC }, + { "tmpfs", "dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME }, +}; + +const char *dont_mount_in_root[] = { + ".", "..", "lib", "lib64", "bin", "sbin", "usr", "boot", + "tmp", "etc", "self", "run", "proc", "sys", "dev", "var" +}; + +typedef enum { + BIND_READONLY = (1<<0), + BIND_PRIVATE = (1<<1), + BIND_DEVICES = (1<<2), + BIND_RECURSIVE = (1<<3), +} bind_option_t; + +static int +bind_mount (const char *src, const char *dest, bind_option_t options) +{ + int readonly = (options & BIND_READONLY) != 0; + int private = (options & BIND_PRIVATE) != 0; + int devices = (options & BIND_DEVICES) != 0; + int recursive = (options & BIND_RECURSIVE) != 0; + + if (mount (src, dest, NULL, MS_MGC_VAL|MS_BIND|(recursive?MS_REC:0), NULL) != 0) + return 1; + + if (private) + { + if (mount ("none", dest, + NULL, MS_REC|MS_PRIVATE, NULL) != 0) + return 2; + } + + if (mount ("none", dest, + NULL, MS_MGC_VAL|MS_BIND|MS_REMOUNT|(devices?0:MS_NODEV)|MS_NOSUID|(readonly?MS_RDONLY:0), NULL) != 0) + return 3; + + return 0; +} + +static int +mkdir_with_parents (const char *pathname, + int mode, + int uid) +{ + char *fn, *p; + struct stat buf; + + if (pathname == NULL || *pathname == '\0') + { + errno = EINVAL; + return 1; + } + + fn = xstrdup (pathname); + + p = fn; + while (*p == '/') + p++; + + do + { + while (*p && *p != '/') + p++; + + if (!*p) + p = NULL; + else + *p = '\0'; + + if (stat (fn, &buf) != 0) + { + if (mkdir (fn, mode) == -1 && errno != EEXIST) + { + int errsave = errno; + free (fn); + errno = errsave; + return -1; + } + if (chown (fn, uid, -1)) + return -1; + } + else if (!S_ISDIR (buf.st_mode)) + { + free (fn); + errno = ENOTDIR; + return -1; + } + + if (p) + { + *p++ = '/'; + while (*p && *p == '/') + p++; + } + } + while (p); + + free (fn); + + return 0; +} + + +static int +create_file (const char *path, mode_t mode, const char *content) +{ + int fd; + + fd = creat (path, mode); + if (fd == -1) + return -1; + + if (content) + { + ssize_t len = strlen (content); + ssize_t res; + + while (len > 0) + { + res = write (fd, content, len); + if (res < 0 && errno == EINTR) + continue; + if (res <= 0) + { + close (fd); + return -1; + } + len -= res; + content += res; + } + } + + close (fd); + + return 0; +} + +static void +create_files (const create_table_t *create, int n_create, int ignore_shm) +{ + int last_failed = 0; + int i; + + for (i = 0; i < n_create; i++) + { + char *name = strdup_printf (create[i].name, getuid()); + mode_t mode = create[i].mode; + const char *data = create[i].data; + file_flags_t flags = create[i].flags; + struct stat st; + int k; + int found; + int res; + + if ((flags & FILE_FLAGS_IF_LAST_FAILED) && + !last_failed) + continue; + + last_failed = 0; + + switch (create[i].type) + { + case FILE_TYPE_DIR: + if (mkdir (name, mode) != 0) + die_with_error ("creating dir %s", name); + break; + + case FILE_TYPE_REGULAR: + if (create_file (name, mode, NULL)) + die_with_error ("creating file %s", name); + break; + + case FILE_TYPE_SYMLINK: + if (symlink (data, name) != 0) + die_with_error ("creating symlink %s", name); + break; + + case FILE_TYPE_BIND: + case FILE_TYPE_BIND_RO: + if ((res = bind_mount (data, name, + 0 | + ((create[i].type == FILE_TYPE_BIND_RO) ? BIND_READONLY : 0) | + ((flags & FILE_FLAGS_DEVICES) ? BIND_DEVICES : 0)))) + { + if (res > 1 || (flags & FILE_FLAGS_NON_FATAL) == 0) + die_with_error ("mounting bindmount %s", name); + last_failed = 1; + } + + break; + + case FILE_TYPE_SHM: + if (ignore_shm) + break; + + /* NOTE: Fall through, treat as mount */ + case FILE_TYPE_MOUNT: + found = 0; + for (k = 0; k < N_ELEMENTS(mount_table); k++) + { + if (strcmp (mount_table[k].where, name) == 0) + { + if (mount(mount_table[k].what, + mount_table[k].where, + mount_table[k].type, + mount_table[k].flags, + mount_table[k].options) < 0) + die_with_error ("Mounting %s", name); + found = 1; + } + } + + if (!found) + die ("Unable to find mount %s\n", name); + + break; + + case FILE_TYPE_DEVICE: + if (stat (data, &st) < 0) + die_with_error ("stat node %s", data); + + if (!S_ISCHR (st.st_mode) && !S_ISBLK (st.st_mode)) + die_with_error ("node %s is not a device", data); + + if (mknod (name, mode, st.st_rdev) < 0) + die_with_error ("mknod %s", name); + + break; + + default: + die ("Unknown create type %d\n", create[i].type); + } + + if (flags & FILE_FLAGS_USER_OWNED) + { + if (chown (name, getuid(), -1)) + die_with_error ("chown to user"); + } + + free (name); + } +} + +static void +mount_extra_root_dirs (void) +{ + DIR *dir; + struct dirent *dirent; + int i; + + /* Bind mount most dirs in / into the new root */ + dir = opendir("/"); + if (dir != NULL) + { + while ((dirent = readdir(dir))) + { + int dont_mount = 0; + char *path; + struct stat st; + + for (i = 0; i < N_ELEMENTS(dont_mount_in_root); i++) + { + if (strcmp (dirent->d_name, dont_mount_in_root[i]) == 0) + { + dont_mount = 1; + break; + } + } + + if (dont_mount) + continue; + + path = strconcat ("/", dirent->d_name); + + if (stat (path, &st) != 0) + { + free (path); + continue; + } + + if (S_ISDIR(st.st_mode)) + { + if (mkdir (dirent->d_name, 0755) != 0) + die_with_error (dirent->d_name); + + if (bind_mount (path, dirent->d_name, BIND_RECURSIVE)) + die_with_error ("mount root subdir %s", dirent->d_name); + } + + free (path); + } + } +} + +static void +create_homedir (int do_mount) +{ + const char *home; + const char *relative_home; + + home = getenv("HOME"); + if (home == NULL) + return; + + relative_home = home; + while (*relative_home == '/') + relative_home++; + + if (mkdir_with_parents (relative_home, 0700, getuid())) + die_with_error ("unable to create %s", relative_home); + + if (do_mount) + { + if (bind_mount (home, relative_home, BIND_RECURSIVE)) + die_with_error ("unable to mount %s", home); + } +} + +static void * +add_rta (struct nlmsghdr *header, int type, size_t size) +{ + struct rtattr *rta; + size_t rta_size = RTA_LENGTH(size); + + rta = (struct rtattr*)((char *)header + NLMSG_ALIGN(header->nlmsg_len)); + rta->rta_type = type; + rta->rta_len = rta_size; + + header->nlmsg_len = NLMSG_ALIGN(header->nlmsg_len) + rta_size; + + return RTA_DATA(rta); +} + +static int +rtnl_send_request (int rtnl_fd, struct nlmsghdr *header) +{ + struct sockaddr_nl dst_addr = { AF_NETLINK, 0 }; + ssize_t sent; + + sent = sendto (rtnl_fd, (void *)header, header->nlmsg_len, 0, + (struct sockaddr *)&dst_addr, sizeof (dst_addr)); + if (sent < 0) + return 1; + + return 0; +} + +static int +rtnl_read_reply (int rtnl_fd, int seq_nr) +{ + char buffer[1024]; + ssize_t received; + struct nlmsghdr *rheader; + + while (1) + { + received = recv (rtnl_fd, buffer, sizeof(buffer), 0); + if (received < 0) + return 1; + + rheader = (struct nlmsghdr *)buffer; + while (received >= NLMSG_HDRLEN) + { + if (rheader->nlmsg_seq != seq_nr) + return 1; + if (rheader->nlmsg_pid != getpid ()) + return 1; + if (rheader->nlmsg_type == NLMSG_ERROR) + { + uint32_t err = NLMSG_DATA(rheader); + if (err == 0) + return 0; + + return 1; + } + if (rheader->nlmsg_type == NLMSG_DONE) + return 0; + + rheader = NLMSG_NEXT(rheader, received); + } + } +} + +static int +rtnl_do_request (int rtnl_fd, struct nlmsghdr *header) +{ + if (!rtnl_send_request (rtnl_fd, header)) + return 1; + + if (!rtnl_read_reply (rtnl_fd, header->nlmsg_seq)) + return 1; + + return 0; +} + +static struct nlmsghdr * +rtnl_setup_request (char *buffer, int type, int flags, size_t size) +{ + struct nlmsghdr *header; + size_t len = NLMSG_LENGTH (size); + static uint32_t counter = 0; + + memset (buffer, 0, len); + + header = (struct nlmsghdr *)buffer; + header->nlmsg_len = len; + header->nlmsg_type = type; + header->nlmsg_flags = flags | NLM_F_REQUEST; + header->nlmsg_seq = counter++; + header->nlmsg_pid = getpid (); + + return (struct nlmsghdr *)header; +} + +static int +loopback_setup (void) +{ + int r, if_loopback; + int rtnl_fd = -1; + char buffer[1024]; + struct sockaddr_nl src_addr = { AF_NETLINK, 0 }; + struct nlmsghdr *header; + struct ifaddrmsg *addmsg; + struct ifinfomsg *infomsg; + struct in_addr *ip_addr; + int res = 1; + + src_addr.nl_pid = getpid (); + + if_loopback = (int) if_nametoindex ("lo"); + if (if_loopback <= 0) + goto error; + + rtnl_fd = socket (PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE); + if (rtnl_fd < 0) + goto error; + + r = bind (rtnl_fd, (struct sockaddr *)&src_addr, sizeof (src_addr)); + if (r < 0) + goto error; + + header = rtnl_setup_request (buffer, RTM_NEWADDR, + NLM_F_CREATE|NLM_F_EXCL|NLM_F_ACK, + sizeof (struct ifaddrmsg)); + addmsg = NLMSG_DATA(header); + + addmsg->ifa_family = AF_INET; + addmsg->ifa_prefixlen = 8; + addmsg->ifa_flags = IFA_F_PERMANENT; + addmsg->ifa_scope = RT_SCOPE_HOST; + addmsg->ifa_index = if_loopback; + + ip_addr = add_rta (header, IFA_LOCAL, sizeof (*ip_addr)); + ip_addr->s_addr = htonl(INADDR_LOOPBACK); + + ip_addr = add_rta (header, IFA_ADDRESS, sizeof (*ip_addr)); + ip_addr->s_addr = htonl(INADDR_LOOPBACK); + + assert (header->nlmsg_len < sizeof (buffer)); + + if (rtnl_do_request (rtnl_fd, header)) + goto error; + + header = rtnl_setup_request (buffer, RTM_NEWLINK, + NLM_F_ACK, + sizeof (struct ifinfomsg)); + infomsg = NLMSG_DATA(header); + + infomsg->ifi_family = AF_UNSPEC; + infomsg->ifi_type = 0; + infomsg->ifi_index = if_loopback; + infomsg->ifi_flags = IFF_UP; + infomsg->ifi_change = IFF_UP; + + assert (header->nlmsg_len < sizeof (buffer)); + + if (rtnl_do_request (rtnl_fd, header)) + goto error; + + res = 0; + + error: + if (rtnl_fd != -1) + close (rtnl_fd); + + return res; +} + +int +main (int argc, + char **argv) +{ + int res; + mode_t old_umask; + char *newroot; + int pipefd[2]; + uid_t saved_euid; + pid_t pid; + char *runtime_path = NULL; + char *app_path = NULL; + char *var_path = NULL; + char *pulseaudio_socket = NULL; + char *x11_socket = NULL; + char *system_dbus_socket = NULL; + char *session_dbus_socket = NULL; + char *xdg_runtime_dir; + char **args; + int n_args; + int share_shm = 0; + int network = 0; + int ipc = 0; + int mount_host_fs = 0; + int mount_home = 0; + int writable = 0; + int writable_app = 0; + char old_cwd[256]; + + char tmpdir[] = "/tmp/run-app.XXXXXX"; + + args = &argv[1]; + n_args = argc - 1; + + while (n_args > 0 && args[0][0] == '-') + { + switch (args[0][1]) + { + case 'i': + ipc = 1; + args += 1; + n_args -= 1; + break; + + case 'n': + network = 1; + args += 1; + n_args -= 1; + break; + + case 'W': + writable = 1; + args += 1; + n_args -= 1; + break; + + case 'w': + writable_app = 1; + args += 1; + n_args -= 1; + break; + + case 's': + share_shm = 1; + args += 1; + n_args -= 1; + break; + + case 'f': + mount_host_fs = 1; + args += 1; + n_args -= 1; + break; + + case 'H': + mount_home = 1; + args += 1; + n_args -= 1; + break; + + case 'a': + if (n_args < 2) + usage (argv); + + app_path = args[1]; + args += 2; + n_args -= 2; + break; + + case 'p': + if (n_args < 2) + usage (argv); + + pulseaudio_socket = args[1]; + args += 2; + n_args -= 2; + break; + + case 'x': + if (n_args < 2) + usage (argv); + + x11_socket = args[1]; + args += 2; + n_args -= 2; + break; + + case 'd': + if (n_args < 2) + usage (argv); + + session_dbus_socket = args[1]; + args += 2; + n_args -= 2; + break; + + case 'D': + if (n_args < 2) + usage (argv); + + system_dbus_socket = args[1]; + args += 2; + n_args -= 2; + break; + + case 'v': + if (n_args < 2) + usage (argv); + + var_path = args[1]; + args += 2; + n_args -= 2; + break; + + default: + usage (argv); + } + } + + if (n_args < 2) + usage (argv); + + runtime_path = args[0]; + args++; + n_args--; + + /* The initial code is run with a high permission euid + (at least CAP_SYS_ADMIN), so take lots of care. */ + + __debug__(("Creating temporary dir\n")); + + saved_euid = geteuid (); + + /* First switch to the real user id so we can have the + temp directories owned by the user */ + + if (seteuid (getuid ())) + die_with_error ("seteuid to user"); + + if (mkdtemp (tmpdir) == NULL) + die_with_error ("Creating %s", tmpdir); + + newroot = strconcat (tmpdir, "/root"); + + if (mkdir (newroot, 0755)) + die_with_error ("Creating new root failed"); + + /* Now switch back to the root user */ + if (seteuid (saved_euid)) + die_with_error ("seteuid to privileged"); + + /* We want to make the temp directory a bind mount so that + we can ensure that it is MS_PRIVATE, so mount don't leak out + of the namespace, and also so that pivot_root() succeeds. However + this means if /tmp is MS_SHARED the bind-mount will be propagated + to the parent namespace. In order to handle this we spawn a child + in the original namespace and unmount the bind mount from that at + the right time. */ + + if (pipe (pipefd) != 0) + die_with_error ("pipe failed"); + + pid = fork(); + if (pid == -1) + die_with_error ("fork failed"); + + if (pid == 0) + { + char c; + + /* In child */ + close (pipefd[WRITE_END]); + + /* Don't die when the parent closes pipe */ + signal (SIGPIPE, SIG_IGN); + + /* Wait for parent */ + read (pipefd[READ_END], &c, 1); + + /* Unmount tmpdir bind mount */ + umount2 (tmpdir, MNT_DETACH); + + exit (0); + } + + close (pipefd[READ_END]); + + __debug__(("creating new namespace\n")); + res = unshare (CLONE_NEWNS | + (network ? 0 : CLONE_NEWNET) | + (ipc ? 0 : CLONE_NEWIPC)); + if (res != 0) + die_with_error ("Creating new namespace failed"); + + old_umask = umask (0); + + /* make it tmpdir rprivate to avoid leaking mounts */ + if (mount (tmpdir, tmpdir, NULL, MS_BIND, NULL) != 0) + die_with_error ("Failed to make bind mount on tmpdir"); + if (mount (tmpdir, tmpdir, NULL, MS_REC|MS_PRIVATE, NULL) != 0) + die_with_error ("Failed to make tmpdir rprivate"); + + /* Create a tmpfs which we will use as / in the namespace */ + if (mount ("", newroot, "tmpfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) != 0) + die_with_error ("Failed to mount tmpfs"); + + getcwd (old_cwd, sizeof (old_cwd)); + + if (chdir (newroot) != 0) + die_with_error ("chdir"); + + create_files (create, N_ELEMENTS (create), share_shm); + + if (share_shm) + { + if (bind_mount ("/dev/shm", "dev/shm", BIND_DEVICES)) + die_with_error ("mount /dev/shm"); + } + + if (bind_mount (runtime_path, "usr", BIND_PRIVATE | (writable?0:BIND_READONLY))) + die_with_error ("mount usr"); + + if (app_path != NULL) + { + if (bind_mount (app_path, "self", BIND_PRIVATE | (writable_app?0:BIND_READONLY))) + die_with_error ("mount self"); + } + + if (var_path != NULL) + { + if (bind_mount (var_path, "var", BIND_PRIVATE)) + die_with_error ("mount var"); + } + + create_files (create_post, N_ELEMENTS (create_post), share_shm); + + /* /usr now mounted private inside the namespace, tell child process to unmount the tmpfs in the parent namespace. */ + close (pipefd[WRITE_END]); + + if (bind_mount ("etc/passwd", "etc/passwd", BIND_READONLY)) + die_with_error ("mount passwd"); + + if (bind_mount ("etc/group", "etc/group", BIND_READONLY)) + die_with_error ("mount group"); + + /* Bind mount in X socket + * This is a bit iffy, as Xlib typically uses abstract unix domain sockets + * to connect to X, but that is not namespaced. We instead set DISPLAY=99 + * and point /tmp/.X11-unix/X99 to the right X socket. Any Xserver listening + * to global abstract unix domain sockets are still accessible to the app + * though... + */ + if (x11_socket) + { + struct stat st; + + if (stat (x11_socket, &st) == 0 && S_ISSOCK (st.st_mode)) + { + if (bind_mount (x11_socket, "tmp/.X11-unix/X99", 0)) + die ("can't bind X11 socket"); + + xsetenv ("DISPLAY", ":99.0", 1); + } + else + { + xunsetenv ("DISPLAY"); + } + } + + if (pulseaudio_socket != NULL) + { + char *pulse_path_relative = strdup_printf ("run/user/%d/pulse/native", getuid()); + char *pulse_server = strdup_printf ("unix:/run/user/%d/pulse/native", getuid()); + char *config_path_relative = strdup_printf ("run/user/%d/pulse/config", getuid()); + char *config_path_absolute = strdup_printf ("/run/user/%d/pulse/config", getuid()); + char *client_config = strdup_printf ("enable-shm=%s\n", share_shm ? "yes" : "no"); + + if (create_file (config_path_relative, 0666, client_config) == 0 && + bind_mount (pulseaudio_socket, pulse_path_relative, BIND_READONLY) == 0) + { + xsetenv ("PULSE_SERVER", pulse_server, 1); + xsetenv ("PULSE_CLIENTCONFIG", config_path_absolute, 1); + } + else + { + xunsetenv ("PULSE_SERVER"); + } + + free (pulse_path_relative); + free (pulse_server); + free (config_path_relative); + free (config_path_absolute); + free (client_config); + } + + if (system_dbus_socket != NULL) + { + if (create_file ("run/dbus/system_bus_socket", 0666, NULL) == 0 && + bind_mount (system_dbus_socket, "run/dbus/system_bus_socket", 0) == 0) + xsetenv ("DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/var/run/dbus/system_bus_socket", 1); + else + xunsetenv ("DBUS_SYSTEM_BUS_ADDRESS"); + } + + if (session_dbus_socket != NULL) + { + char *session_dbus_socket_path_relative = strdup_printf ("run/user/%d/bus", getuid()); + char *session_dbus_address = strdup_printf ("unix:path=/run/user/%d/bus", getuid()); + + if (create_file (session_dbus_socket_path_relative, 0666, NULL) == 0 && + bind_mount (session_dbus_socket, session_dbus_socket_path_relative, 0) == 0) + xsetenv ("DBUS_SESSION_BUS_ADDRESS", session_dbus_address, 1); + else + xunsetenv ("DBUS_SESSION_BUS_ADDRESS"); + + free (session_dbus_socket_path_relative); + free (session_dbus_address); + } + + if (mount_host_fs) + mount_extra_root_dirs (); + + create_homedir (!mount_host_fs && mount_home); + + if (!network) + loopback_setup (); + + if (pivot_root (newroot, ".oldroot")) + die_with_error ("pivot_root"); + + chdir ("/"); + + /* The old root better be rprivate or we will send unmount events to the parent namespace */ + if (mount (".oldroot", ".oldroot", NULL, MS_REC|MS_PRIVATE, NULL) != 0) + die_with_error ("Failed to make old root rprivate"); + + if (umount2 (".oldroot", MNT_DETACH)) + die_with_error ("unmount oldroot"); + + umask (old_umask); + + /* Now we have everything we need CAP_SYS_ADMIN for, so drop setuid */ + setuid (getuid ()); + + chdir (old_cwd); + + xsetenv ("PATH", "/self/bin:/usr/bin", 1); + xsetenv ("LD_LIBRARY_PATH", "/self/lib", 1); + xsetenv ("XDG_CONFIG_DIRS","/self/etc/xdg:/etc/xdg", 1); + xsetenv ("XDG_DATA_DIRS", "/self/share:/usr/share", 1); + xdg_runtime_dir = strdup_printf ("/run/user/%d", getuid()); + xsetenv ("XDG_RUNTIME_DIR", xdg_runtime_dir, 1); + free (xdg_runtime_dir); + + __debug__(("launch executable %s\n", args[0])); + + return execvp (args[0], args); +} diff --git a/xdg-app-main.c b/xdg-app-main.c new file mode 100644 index 00000000..54e424fc --- /dev/null +++ b/xdg-app-main.c @@ -0,0 +1,317 @@ +#include "config.h" + +#include +#include +#include + +#include +#include "libgsystem.h" + +#include "xdg-app-builtins.h" + +static gboolean opt_verbose; +static gboolean opt_version; +static gboolean opt_user; + +typedef struct { + const char *name; + gboolean (*fn) (int argc, char **argv, GCancellable *cancellable, GError **error); +} XdgAppCommand; + +static XdgAppCommand commands[] = { + { "add-repo", xdg_app_builtin_add_repo }, + { NULL } +}; + +static GOptionEntry global_entries[] = { + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Print debug information during command processing", NULL }, + { "version", 0, 0, G_OPTION_ARG_NONE, &opt_version, "Print version information and exit", NULL }, + { NULL } +}; + +static GOptionEntry user_entries[] = { + { "user", 0, 0, G_OPTION_ARG_NONE, &opt_user, "Work on user installed apps", NULL }, + { NULL } +}; + +static void +message_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + /* Make this look like normal console output */ + if (log_level & G_LOG_LEVEL_DEBUG) + g_printerr ("XA: %s\n", message); + else + g_printerr ("%s: %s\n", g_get_prgname (), message); +} + +GOptionContext * +xdg_app_option_context_new_with_commands (XdgAppCommand *commands) +{ + GOptionContext *context; + GString *summary; + + context = g_option_context_new ("COMMAND"); + + summary = g_string_new ("Builtin Commands:"); + + while (commands->name != NULL) + { + g_string_append_printf (summary, "\n %s", commands->name); + commands++; + } + + g_option_context_set_summary (context, summary->str); + + g_string_free (summary, TRUE); + + return context; +} + +int +xdg_app_usage (XdgAppCommand *commands, + gboolean is_error) +{ + GOptionContext *context; + gs_free char *help; + + context = xdg_app_option_context_new_with_commands (commands); + + g_option_context_add_main_entries (context, global_entries, NULL); + + help = g_option_context_get_help (context, FALSE, NULL); + + if (is_error) + g_printerr ("%s", help); + else + g_print ("%s", help); + + g_option_context_free (context); + + return (is_error ? 1 : 0); +} + +gboolean +xdg_app_option_context_parse (GOptionContext *context, + const GOptionEntry *main_entries, + int *argc, + char ***argv, + XdgAppBuiltinFlags flags, + OstreeRepo **out_repo, + GFile **out_basedir, + GCancellable *cancellable, + GError **error) +{ + gboolean success = FALSE; + gs_unref_object GFile *basedir = NULL; + gs_unref_object GFile *repodir = NULL; + gs_unref_object OstreeRepo *repo = NULL; + + if (!(flags & XDG_APP_BUILTIN_FLAG_NO_USER)) + g_option_context_add_main_entries (context, user_entries, NULL); + + if (main_entries != NULL) + g_option_context_add_main_entries (context, main_entries, NULL); + + g_option_context_add_main_entries (context, global_entries, NULL); + + if (!g_option_context_parse (context, argc, argv, error)) + return FALSE; + + if (opt_version) + { + g_print ("%s\n", PACKAGE_STRING); + exit (EXIT_SUCCESS); + } + + if (opt_user) + { + gs_free char *base = g_build_filename (g_get_user_data_dir (), "xdg-app", NULL); + basedir = g_file_new_for_path (base); + } + else + { + basedir = g_file_new_for_path (XDG_APP_BASEDIR); + } + + if (!(flags & XDG_APP_BUILTIN_FLAG_NO_USER)) + { + if (!gs_file_ensure_directory (basedir, TRUE, cancellable, error)) + goto out; + + if (!(flags & XDG_APP_BUILTIN_FLAG_NO_REPO)) + { + repodir = g_file_get_child (basedir, "repo"); + repo = ostree_repo_new (repodir); + + if (!g_file_query_exists (repodir, cancellable)) + { + if (!ostree_repo_create (repo, + opt_user ? OSTREE_REPO_MODE_BARE_USER : OSTREE_REPO_MODE_BARE, + cancellable, error)) + { + gs_shutil_rm_rf (repodir, cancellable, NULL); + goto out; + } + } + else + { + if (!ostree_repo_open (repo, cancellable, error)) + goto out; + } + } + } + + if (opt_verbose) + g_log_set_handler (NULL, G_LOG_LEVEL_DEBUG, message_handler, NULL); + + gs_transfer_out_value (out_repo, &repo); + gs_transfer_out_value (out_basedir, &basedir); + + success = TRUE; + out: + return success; +} + +int +xdg_app_run (int argc, + char **argv, + XdgAppCommand *commands, + GError **res_error) +{ + XdgAppCommand *command; + GError *error = NULL; + GCancellable *cancellable = NULL; + const char *command_name = NULL; + gs_free char *prgname = NULL; + gboolean success = FALSE; + int in, out; + + /* + * Parse the global options. We rearrange the options as + * necessary, in order to pass relevant options through + * to the commands, but also have them take effect globally. + */ + for (in = 1, out = 1; in < argc; in++, out++) + { + /* The non-option is the command, take it out of the arguments */ + if (argv[in][0] != '-') + { + if (command_name == NULL) + { + command_name = argv[in]; + out--; + continue; + } + } + else if (g_str_equal (argv[in], "--")) + { + break; + } + + argv[out] = argv[in]; + } + + argc = out; + + command = commands; + while (command->name) + { + if (g_strcmp0 (command_name, command->name) == 0) + break; + command++; + } + + if (!command->fn) + { + GOptionContext *context; + gs_free char *help; + + context = xdg_app_option_context_new_with_commands (commands); + + /* This will not return for some options (e.g. --version). */ + if (xdg_app_option_context_parse (context, NULL, &argc, &argv, XDG_APP_BUILTIN_FLAG_NO_USER, NULL, NULL, cancellable, &error)) + { + if (command_name == NULL) + { + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, + "No command specified"); + } + else + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unknown command '%s'", command_name); + } + } + + help = g_option_context_get_help (context, FALSE, NULL); + g_printerr ("%s", help); + + g_option_context_free (context); + + goto out; + } + + prgname = g_strdup_printf ("%s %s", g_get_prgname (), command_name); + g_set_prgname (prgname); + + if (!command->fn (argc, argv, cancellable, &error)) + goto out; + + success = TRUE; + out: + g_assert (success || error); + + if (error) + { + g_propagate_error (res_error, error); + return 1; + } + return 0; +} + +int +main (int argc, + char **argv) +{ + GError *error = NULL; + gs_free const char *old_env = NULL; + int ret; + + setlocale (LC_ALL, ""); + + g_log_set_handler (NULL, G_LOG_LEVEL_MESSAGE, message_handler, NULL); + + g_set_prgname (argv[0]); + + /* 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"); + + ret = xdg_app_run (argc, argv, commands, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) + xdg_app_usage (commands, TRUE); + + if (error != NULL) + { + int is_tty = isatty (1); + const char *prefix = ""; + const char *suffix = ""; + if (is_tty) + { + prefix = "\x1b[31m\x1b[1m"; /* red, bold */ + suffix = "\x1b[22m\x1b[0m"; /* bold off, color reset */ + } + g_printerr ("%serror: %s%s\n", prefix, suffix, error->message); + g_error_free (error); + } + + return ret; +}