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