on master help sign in
nix-bundle/AppRun.c
280 lines 8.2 kB history raw
1/**************************************************************************
3Copyright (c) 2004-16 Simon Peter
4Portions Copyright (c) 2010 RazZziel
6All Rights Reserved.
8Permission is hereby granted, free of charge, to any person obtaining a copy
9of this software and associated documentation files (the "Software"), to deal
10in the Software without restriction, including without limitation the rights
11to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12copies of the Software, and to permit persons to whom the Software is
13furnished to do so, subject to the following conditions:
15The above copyright notice and this permission notice shall be included in
16all copies or substantial portions of the Software.
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24THE SOFTWARE.
26**************************************************************************/
28#define _GNU_SOURCE
29#include <stdio.h>
30#include <stdlib.h>
31#include <unistd.h>
32#include <libgen.h>
33#include <dirent.h>
34#include <string.h>
35#include <sys/stat.h>
36#include <sched.h>
37#include <fcntl.h>
38#include <sys/mount.h>
39#include <errno.h>
41#define die(...) \
42 do { \
43 fprintf(stderr, "Error: " __VA_ARGS__); \
44 exit(1); \
45 } while (0);
47#define PATH_MAX 4096
49#define LINE_SIZE 255
51#define err_exit(format, ...) { fprintf(stderr, format ": %s\n", ##__VA_ARGS__, strerror(errno)); exit(EXIT_FAILURE); }
53int filter (const struct dirent *dir) {
54 char *p = (char*) &dir->d_name;
55 p = strrchr(p, '.');
56 return p && !strcmp(p, ".desktop");
59static void update_map(char *mapping, char *map_file) {
60 int fd;
62 fd = open(map_file, O_WRONLY);
63 if (fd < 0) {
64 err_exit("map open");
65 }
67 int map_len = strlen(mapping);
68 if (write(fd, mapping, map_len) != map_len) {
69 err_exit("map write");
70 }
72 close(fd);
75static void add_path(const char* name, const char* rootdir) {
76 char path_buf[PATH_MAX];
77 snprintf(path_buf, sizeof(path_buf), "/%s", name);
79 struct stat statbuf;
80 if (stat(path_buf, &statbuf) < 0) {
81 fprintf(stderr, "Cannot stat %s: %s\n", path_buf, strerror(errno));
82 return;
83 }
85 char path_buf2[PATH_MAX];
86 snprintf(path_buf2, sizeof(path_buf2), "%s/%s", rootdir, name);
88 mkdir(path_buf2, statbuf.st_mode & ~S_IFMT);
89 if (mount(path_buf, path_buf2, "none", MS_BIND | MS_REC, NULL) < 0) {
90 fprintf(stderr, "Cannot bind mount %s to %s: %s\n", path_buf, path_buf2, strerror(errno));
91 }
94#define SAVE_ENV_VAR(x) char *x = getenv(#x)
95#define LOAD_ENV_VAR(x) do { if (x != NULL) setenv(#x, x, 1); } while(0)
97int main(int argc, char *argv[]) {
98 char *appdir = dirname(realpath("/proc/self/exe", NULL));
99 if (!appdir)
100 die("Could not access /proc/self/exe\n");
102 char *tmpdir = getenv("TMPDIR");
103 if (!tmpdir) {
104 tmpdir = "/tmp";
105 }
107 char template[PATH_MAX];
108 int needed = snprintf(template, PATH_MAX, "%s/nixXXXXXX", tmpdir);
109 if (needed < 0) {
110 err_exit("TMPDIR too long: '%s'", tmpdir);
111 }
113 char *rootdir = mkdtemp(template);
114 if (!rootdir) {
115 err_exit("mkdtemp(%s)", template);
116 }
118 int ret;
120 struct dirent **namelist;
122 ret = scandir(appdir, &namelist, filter, NULL);
124 if (ret == 0) {
125 die("No .desktop files found\n");
126 } else if(ret == -1) {
127 die("Could not scan directory %s\n", appdir);
128 }
130 /* Extract executable from .desktop file */
132 FILE *f;
133 char *desktop_file = malloc(LINE_SIZE);
134 snprintf(desktop_file, LINE_SIZE-1, "%s/%s", appdir, namelist[0]->d_name);
135 f = fopen(desktop_file, "r");
137 char *line = malloc(LINE_SIZE);
138 size_t n = LINE_SIZE;
139 int found = 0;
141 while (getline(&line, &n, f) != -1)
142 {
143 if (!strncmp(line,"Exec=",5))
144 {
145 char *p = line+5;
146 while (*++p && *p != ' ' && *p != '%' && *p != '\n');
147 *p = 0;
148 found = 1;
149 break;
150 }
151 }
153 fclose(f);
155 if (!found)
156 die("Executable not found, make sure there is a line starting with 'Exec='\n");
158 /* Execution */
159 char *executable = basename(line+5);
161 char full_exec[PATH_MAX];
162 snprintf(full_exec, sizeof(full_exec), "/usr/bin/%s", executable);
164 // get uid, gid before going to new namespace
165 uid_t uid = getuid();
166 gid_t gid = getgid();
168 // "unshare" into new namespace
169 if (unshare(CLONE_NEWNS | CLONE_NEWUSER) < 0) {
170 err_exit("unshare()");
171 }
173 // add necessary system stuff to rootdir namespace
174 add_path("dev", rootdir);
175 add_path("proc", rootdir);
176 add_path("sys", rootdir);
177 add_path("run", rootdir);
178 add_path("etc", rootdir);
179 add_path("home", rootdir);
181 // setup skeleton
182 char path_buf[PATH_MAX];
183 snprintf(path_buf, sizeof(path_buf), "%s/tmp", rootdir);
184 mkdir(path_buf, ~0);
185 snprintf(path_buf, sizeof(path_buf), "%s/var", rootdir);
186 mkdir(path_buf, ~0);
188 // make sure nixdir exists
189 struct stat statbuf2;
190 if (stat(appdir, &statbuf2) < 0) {
191 err_exit("stat(%s)", appdir);
192 }
194 char nixdir[PATH_MAX];
195 snprintf(nixdir, sizeof(nixdir), "%s/nix", appdir);
196 snprintf(path_buf, sizeof(path_buf), "%s/nix", rootdir);
197 mkdir(path_buf, statbuf2.st_mode & ~S_IFMT);
198 if (mount(nixdir, path_buf, "none", MS_BIND | MS_REC, NULL) < 0) {
199 err_exit("mount(%s, %s)", nixdir, path_buf);
200 }
202 char usrdir[PATH_MAX];
203 snprintf(usrdir, sizeof(usrdir), "%s/usr", appdir);
204 snprintf(path_buf, sizeof(path_buf), "%s/usr", rootdir);
205 mkdir(path_buf, statbuf2.st_mode & ~S_IFMT);
206 if (mount(usrdir, path_buf, "none", MS_BIND | MS_REC, NULL) < 0) {
207 err_exit("mount(%s, %s)", usrdir, path_buf);
208 }
210 snprintf(path_buf, sizeof(path_buf), "%s/bin", rootdir);
211 if (symlink("/usr/bin", path_buf) < 0) {
212 err_exit("symlink(/usr/bin, %s)", path_buf);
213 }
215 // fixes issue #1 where writing to /proc/self/gid_map fails
216 // see user_namespaces(7) for more documentation
217 int fd_setgroups = open("/proc/self/setgroups", O_WRONLY);
218 if (fd_setgroups > 0) {
219 write(fd_setgroups, "deny", 4);
220 }
222 // map the original uid/gid in the new ns
223 char map_buf[1024];
224 snprintf(map_buf, sizeof(map_buf), "%d %d 1", uid, uid);
225 update_map(map_buf, "/proc/self/uid_map");
226 snprintf(map_buf, sizeof(map_buf), "%d %d 1", gid, gid);
227 update_map(map_buf, "/proc/self/gid_map");
229 // chroot to rootdir
230 if (chroot(rootdir) < 0) {
231 err_exit("chroot(%s)", rootdir);
232 }
234 char *pwddir = getenv("PWD");
235 chdir(pwddir);
237 SAVE_ENV_VAR(PWD);
238 SAVE_ENV_VAR(DBUS_SESSION_BUS_ADDRESS);
239 SAVE_ENV_VAR(USER);
240 SAVE_ENV_VAR(HOSTNAME);
241 SAVE_ENV_VAR(LANG);
242 SAVE_ENV_VAR(LC_ALL);
243 SAVE_ENV_VAR(TERM);
244 SAVE_ENV_VAR(DISPLAY);
245 SAVE_ENV_VAR(XDG_RUNTIME_DIR);
246 SAVE_ENV_VAR(XAUTHORITY);
247 SAVE_ENV_VAR(XDG_SESSION_ID);
248 SAVE_ENV_VAR(XDG_SEAT);
249 SAVE_ENV_VAR(HOME);
251 clearenv();
253 LOAD_ENV_VAR(PWD);
254 LOAD_ENV_VAR(DBUS_SESSION_BUS_ADDRESS);
255 LOAD_ENV_VAR(USER);
256 LOAD_ENV_VAR(HOSTNAME);
257 LOAD_ENV_VAR(LANG);
258 LOAD_ENV_VAR(LC_ALL);
259 LOAD_ENV_VAR(TERM);
260 LOAD_ENV_VAR(DISPLAY);
261 LOAD_ENV_VAR(XDG_RUNTIME_DIR);
262 LOAD_ENV_VAR(XAUTHORITY);
263 LOAD_ENV_VAR(XDG_SESSION_ID);
264 LOAD_ENV_VAR(XDG_SEAT);
265 LOAD_ENV_VAR(HOME);
267 setenv("PATH", ENV_PATH, 1);
268 setenv("TMPDIR", "/tmp", 1);
270 /* Run */
271 // FIXME: What about arguments in the Exec= line of the desktop file?
272 ret = execvp(full_exec, argv);
274 if (ret == -1)
275 die("Error executing '%s'; return code: %d\n", full_exec, ret);
277 free(line);
278 free(desktop_file);
279 return 0;