1 /*
2  * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
3  * Licensed under the GPL
4  */
5 
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <dirent.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <signal.h>
12 #include <string.h>
13 #include <unistd.h>
14 #include <sys/stat.h>
15 #include "init.h"
16 #include "kern_constants.h"
17 #include "os.h"
18 #include "user.h"
19 
20 #define UML_DIR "~/.uml/"
21 
22 #define UMID_LEN 64
23 
24 /* Changed by set_umid, which is run early in boot */
25 static char umid[UMID_LEN] = { 0 };
26 
27 /* Changed by set_uml_dir and make_uml_dir, which are run early in boot */
28 static char *uml_dir = UML_DIR;
29 
make_uml_dir(void)30 static int __init make_uml_dir(void)
31 {
32 	char dir[512] = { '\0' };
33 	int len, err;
34 
35 	if (*uml_dir == '~') {
36 		char *home = getenv("HOME");
37 
38 		err = -ENOENT;
39 		if (home == NULL) {
40 			printk(UM_KERN_ERR "make_uml_dir : no value in "
41 			       "environment for $HOME\n");
42 			goto err;
43 		}
44 		strlcpy(dir, home, sizeof(dir));
45 		uml_dir++;
46 	}
47 	strlcat(dir, uml_dir, sizeof(dir));
48 	len = strlen(dir);
49 	if (len > 0 && dir[len - 1] != '/')
50 		strlcat(dir, "/", sizeof(dir));
51 
52 	err = -ENOMEM;
53 	uml_dir = malloc(strlen(dir) + 1);
54 	if (uml_dir == NULL) {
55 		printf("make_uml_dir : malloc failed, errno = %d\n", errno);
56 		goto err;
57 	}
58 	strcpy(uml_dir, dir);
59 
60 	if ((mkdir(uml_dir, 0777) < 0) && (errno != EEXIST)) {
61 	        printf("Failed to mkdir '%s': %s\n", uml_dir, strerror(errno));
62 		err = -errno;
63 		goto err_free;
64 	}
65 	return 0;
66 
67 err_free:
68 	free(uml_dir);
69 err:
70 	uml_dir = NULL;
71 	return err;
72 }
73 
74 /*
75  * Unlinks the files contained in @dir and then removes @dir.
76  * Doesn't handle directory trees, so it's not like rm -rf, but almost such. We
77  * ignore ENOENT errors for anything (they happen, strangely enough - possibly
78  * due to races between multiple dying UML threads).
79  */
remove_files_and_dir(char * dir)80 static int remove_files_and_dir(char *dir)
81 {
82 	DIR *directory;
83 	struct dirent *ent;
84 	int len;
85 	char file[256];
86 	int ret;
87 
88 	directory = opendir(dir);
89 	if (directory == NULL) {
90 		if (errno != ENOENT)
91 			return -errno;
92 		else
93 			return 0;
94 	}
95 
96 	while ((ent = readdir(directory)) != NULL) {
97 		if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
98 			continue;
99 		len = strlen(dir) + sizeof("/") + strlen(ent->d_name) + 1;
100 		if (len > sizeof(file)) {
101 			ret = -E2BIG;
102 			goto out;
103 		}
104 
105 		sprintf(file, "%s/%s", dir, ent->d_name);
106 		if (unlink(file) < 0 && errno != ENOENT) {
107 			ret = -errno;
108 			goto out;
109 		}
110 	}
111 
112 	if (rmdir(dir) < 0 && errno != ENOENT) {
113 		ret = -errno;
114 		goto out;
115 	}
116 
117 	ret = 0;
118 out:
119 	closedir(directory);
120 	return ret;
121 }
122 
123 /*
124  * This says that there isn't already a user of the specified directory even if
125  * there are errors during the checking.  This is because if these errors
126  * happen, the directory is unusable by the pre-existing UML, so we might as
127  * well take it over.  This could happen either by
128  * 	the existing UML somehow corrupting its umid directory
129  * 	something other than UML sticking stuff in the directory
130  *	this boot racing with a shutdown of the other UML
131  * In any of these cases, the directory isn't useful for anything else.
132  *
133  * Boolean return: 1 if in use, 0 otherwise.
134  */
is_umdir_used(char * dir)135 static inline int is_umdir_used(char *dir)
136 {
137 	char file[strlen(uml_dir) + UMID_LEN + sizeof("/pid\0")];
138 	char pid[sizeof("nnnnn\0")], *end;
139 	int dead, fd, p, n, err;
140 
141 	n = snprintf(file, sizeof(file), "%s/pid", dir);
142 	if (n >= sizeof(file)) {
143 		printk(UM_KERN_ERR "is_umdir_used - pid filename too long\n");
144 		err = -E2BIG;
145 		goto out;
146 	}
147 
148 	dead = 0;
149 	fd = open(file, O_RDONLY);
150 	if (fd < 0) {
151 		fd = -errno;
152 		if (fd != -ENOENT) {
153 			printk(UM_KERN_ERR "is_umdir_used : couldn't open pid "
154 			       "file '%s', err = %d\n", file, -fd);
155 		}
156 		goto out;
157 	}
158 
159 	err = 0;
160 	n = read(fd, pid, sizeof(pid));
161 	if (n < 0) {
162 		printk(UM_KERN_ERR "is_umdir_used : couldn't read pid file "
163 		       "'%s', err = %d\n", file, errno);
164 		goto out_close;
165 	} else if (n == 0) {
166 		printk(UM_KERN_ERR "is_umdir_used : couldn't read pid file "
167 		       "'%s', 0-byte read\n", file);
168 		goto out_close;
169 	}
170 
171 	p = strtoul(pid, &end, 0);
172 	if (end == pid) {
173 		printk(UM_KERN_ERR "is_umdir_used : couldn't parse pid file "
174 		       "'%s', errno = %d\n", file, errno);
175 		goto out_close;
176 	}
177 
178 	if ((kill(p, 0) == 0) || (errno != ESRCH)) {
179 		printk(UM_KERN_ERR "umid \"%s\" is already in use by pid %d\n",
180 		       umid, p);
181 		return 1;
182 	}
183 
184 out_close:
185 	close(fd);
186 out:
187 	return 0;
188 }
189 
190 /*
191  * Try to remove the directory @dir unless it's in use.
192  * Precondition: @dir exists.
193  * Returns 0 for success, < 0 for failure in removal or if the directory is in
194  * use.
195  */
umdir_take_if_dead(char * dir)196 static int umdir_take_if_dead(char *dir)
197 {
198 	int ret;
199 	if (is_umdir_used(dir))
200 		return -EEXIST;
201 
202 	ret = remove_files_and_dir(dir);
203 	if (ret) {
204 		printk(UM_KERN_ERR "is_umdir_used - remove_files_and_dir "
205 		       "failed with err = %d\n", ret);
206 	}
207 	return ret;
208 }
209 
create_pid_file(void)210 static void __init create_pid_file(void)
211 {
212 	char file[strlen(uml_dir) + UMID_LEN + sizeof("/pid\0")];
213 	char pid[sizeof("nnnnn\0")];
214 	int fd, n;
215 
216 	if (umid_file_name("pid", file, sizeof(file)))
217 		return;
218 
219 	fd = open(file, O_RDWR | O_CREAT | O_EXCL, 0644);
220 	if (fd < 0) {
221 		printk(UM_KERN_ERR "Open of machine pid file \"%s\" failed: "
222 		       "%s\n", file, strerror(errno));
223 		return;
224 	}
225 
226 	snprintf(pid, sizeof(pid), "%d\n", getpid());
227 	n = write(fd, pid, strlen(pid));
228 	if (n != strlen(pid))
229 		printk(UM_KERN_ERR "Write of pid file failed - err = %d\n",
230 		       errno);
231 
232 	close(fd);
233 }
234 
set_umid(char * name)235 int __init set_umid(char *name)
236 {
237 	if (strlen(name) > UMID_LEN - 1)
238 		return -E2BIG;
239 
240 	strlcpy(umid, name, sizeof(umid));
241 
242 	return 0;
243 }
244 
245 /* Changed in make_umid, which is called during early boot */
246 static int umid_setup = 0;
247 
make_umid(void)248 static int __init make_umid(void)
249 {
250 	int fd, err;
251 	char tmp[256];
252 
253 	if (umid_setup)
254 		return 0;
255 
256 	make_uml_dir();
257 
258 	if (*umid == '\0') {
259 		strlcpy(tmp, uml_dir, sizeof(tmp));
260 		strlcat(tmp, "XXXXXX", sizeof(tmp));
261 		fd = mkstemp(tmp);
262 		if (fd < 0) {
263 			printk(UM_KERN_ERR "make_umid - mkstemp(%s) failed: "
264 			       "%s\n", tmp, strerror(errno));
265 			err = -errno;
266 			goto err;
267 		}
268 
269 		close(fd);
270 
271 		set_umid(&tmp[strlen(uml_dir)]);
272 
273 		/*
274 		 * There's a nice tiny little race between this unlink and
275 		 * the mkdir below.  It'd be nice if there were a mkstemp
276 		 * for directories.
277 		 */
278 		if (unlink(tmp)) {
279 			err = -errno;
280 			goto err;
281 		}
282 	}
283 
284 	snprintf(tmp, sizeof(tmp), "%s%s", uml_dir, umid);
285 	err = mkdir(tmp, 0777);
286 	if (err < 0) {
287 		err = -errno;
288 		if (err != -EEXIST)
289 			goto err;
290 
291 		if (umdir_take_if_dead(tmp) < 0)
292 			goto err;
293 
294 		err = mkdir(tmp, 0777);
295 	}
296 	if (err) {
297 		err = -errno;
298 		printk(UM_KERN_ERR "Failed to create '%s' - err = %d\n", umid,
299 		       errno);
300 		goto err;
301 	}
302 
303 	umid_setup = 1;
304 
305 	create_pid_file();
306 
307 	err = 0;
308  err:
309 	return err;
310 }
311 
make_umid_init(void)312 static int __init make_umid_init(void)
313 {
314 	if (!make_umid())
315 		return 0;
316 
317 	/*
318 	 * If initializing with the given umid failed, then try again with
319 	 * a random one.
320 	 */
321 	printk(UM_KERN_ERR "Failed to initialize umid \"%s\", trying with a "
322 	       "random umid\n", umid);
323 	*umid = '\0';
324 	make_umid();
325 
326 	return 0;
327 }
328 
329 __initcall(make_umid_init);
330 
umid_file_name(char * name,char * buf,int len)331 int __init umid_file_name(char *name, char *buf, int len)
332 {
333 	int n, err;
334 
335 	err = make_umid();
336 	if (err)
337 		return err;
338 
339 	n = snprintf(buf, len, "%s%s/%s", uml_dir, umid, name);
340 	if (n >= len) {
341 		printk(UM_KERN_ERR "umid_file_name : buffer too short\n");
342 		return -E2BIG;
343 	}
344 
345 	return 0;
346 }
347 
get_umid(void)348 char *get_umid(void)
349 {
350 	return umid;
351 }
352 
set_uml_dir(char * name,int * add)353 static int __init set_uml_dir(char *name, int *add)
354 {
355 	if (*name == '\0') {
356 		printf("uml_dir can't be an empty string\n");
357 		return 0;
358 	}
359 
360 	if (name[strlen(name) - 1] == '/') {
361 		uml_dir = name;
362 		return 0;
363 	}
364 
365 	uml_dir = malloc(strlen(name) + 2);
366 	if (uml_dir == NULL) {
367 		printf("Failed to malloc uml_dir - error = %d\n", errno);
368 
369 		/*
370 		 * Return 0 here because do_initcalls doesn't look at
371 		 * the return value.
372 		 */
373 		return 0;
374 	}
375 	sprintf(uml_dir, "%s/", name);
376 
377 	return 0;
378 }
379 
380 __uml_setup("uml_dir=", set_uml_dir,
381 "uml_dir=<directory>\n"
382 "    The location to place the pid and umid files.\n\n"
383 );
384 
remove_umid_dir(void)385 static void remove_umid_dir(void)
386 {
387 	char dir[strlen(uml_dir) + UMID_LEN + 1], err;
388 
389 	sprintf(dir, "%s%s", uml_dir, umid);
390 	err = remove_files_and_dir(dir);
391 	if (err)
392 		printf("remove_umid_dir - remove_files_and_dir failed with "
393 		       "err = %d\n", err);
394 }
395 
396 __uml_exitcall(remove_umid_dir);
397