Merge git://git.infradead.org/~kmpark/onenand-mtd-2.6
[linux-2.6] / arch / um / os-Linux / umid.c
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
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  */
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  */
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  */
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 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
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
248 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
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
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
348 char *get_umid(void)
349 {
350         return umid;
351 }
352
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
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);