1
linux/arch/um/os-Linux/umid.c
Paolo 'Blaisorblade' Giarrusso eb28931e4a [PATCH] uml: rename and improve actually_do_remove()
Rename actually_do_remove() to remove_files_and_dir(), make it call
closedir(), make it ignore ENOENT (I see it frequently enough).

ENOENT is probably due to multiple threads calling the exitcall functions
together*, but fixing that is non-trivial; and ignoring it is perfectly ok
in any case.

* it can surely happen: last_ditch_exit() is installed as SIGTERM handler
  at boot, and it's not removed on thread creation.  So killall vmlinux
  (which I do) surely causes that.  I've seen also a crash which seems to
  do the same.

Installing the handler on only the main thread would make UML do no cleanup
when another thread exits, and we're not sure we want that.  And mutual
exclusion in that context is tricky - we can't use spinlock in code not on
a kernel stack (spinlock debugging uses "current" a lot).

Signed-off-by: Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it>
Cc: Jeff Dike <jdike@addtoit.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-07-01 09:56:03 -07:00

385 lines
7.6 KiB
C

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <dirent.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/param.h>
#include "init.h"
#include "os.h"
#include "user.h"
#include "mode.h"
#define UML_DIR "~/.uml/"
#define UMID_LEN 64
/* Changed by set_umid, which is run early in boot */
char umid[UMID_LEN] = { 0 };
/* Changed by set_uml_dir and make_uml_dir, which are run early in boot */
static char *uml_dir = UML_DIR;
static int __init make_uml_dir(void)
{
char dir[512] = { '\0' };
int len, err;
if(*uml_dir == '~'){
char *home = getenv("HOME");
err = -ENOENT;
if(home == NULL){
printk("make_uml_dir : no value in environment for "
"$HOME\n");
goto err;
}
strlcpy(dir, home, sizeof(dir));
uml_dir++;
}
strlcat(dir, uml_dir, sizeof(dir));
len = strlen(dir);
if (len > 0 && dir[len - 1] != '/')
strlcat(dir, "/", sizeof(dir));
err = -ENOMEM;
uml_dir = malloc(strlen(dir) + 1);
if (uml_dir == NULL) {
printf("make_uml_dir : malloc failed, errno = %d\n", errno);
goto err;
}
strcpy(uml_dir, dir);
if((mkdir(uml_dir, 0777) < 0) && (errno != EEXIST)){
printf("Failed to mkdir '%s': %s\n", uml_dir, strerror(errno));
err = -errno;
goto err_free;
}
return 0;
err_free:
free(uml_dir);
err:
uml_dir = NULL;
return err;
}
/*
* Unlinks the files contained in @dir and then removes @dir.
* Doesn't handle directory trees, so it's not like rm -rf, but almost such. We
* ignore ENOENT errors for anything (they happen, strangely enough - possibly due
* to races between multiple dying UML threads).
*/
static int remove_files_and_dir(char *dir)
{
DIR *directory;
struct dirent *ent;
int len;
char file[256];
int ret;
directory = opendir(dir);
if (directory == NULL) {
if (errno != ENOENT)
return -errno;
else
return 0;
}
while ((ent = readdir(directory)) != NULL) {
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
continue;
len = strlen(dir) + sizeof("/") + strlen(ent->d_name) + 1;
if (len > sizeof(file)) {
ret = -E2BIG;
goto out;
}
sprintf(file, "%s/%s", dir, ent->d_name);
if (unlink(file) < 0 && errno != ENOENT) {
ret = -errno;
goto out;
}
}
if (rmdir(dir) < 0 && errno != ENOENT) {
ret = -errno;
goto out;
}
ret = 0;
out:
closedir(directory);
return ret;
}
/* This says that there isn't already a user of the specified directory even if
* there are errors during the checking. This is because if these errors
* happen, the directory is unusable by the pre-existing UML, so we might as
* well take it over. This could happen either by
* the existing UML somehow corrupting its umid directory
* something other than UML sticking stuff in the directory
* this boot racing with a shutdown of the other UML
* In any of these cases, the directory isn't useful for anything else.
*
* Boolean return: 1 if in use, 0 otherwise.
*/
static inline int is_umdir_used(char *dir)
{
char file[strlen(uml_dir) + UMID_LEN + sizeof("/pid\0")];
char pid[sizeof("nnnnn\0")], *end;
int dead, fd, p, n, err;
n = snprintf(file, sizeof(file), "%s/pid", dir);
if(n >= sizeof(file)){
printk("is_umdir_used - pid filename too long\n");
err = -E2BIG;
goto out;
}
dead = 0;
fd = open(file, O_RDONLY);
if(fd < 0) {
fd = -errno;
if(fd != -ENOENT){
printk("is_umdir_used : couldn't open pid file '%s', "
"err = %d\n", file, -fd);
}
goto out;
}
err = 0;
n = read(fd, pid, sizeof(pid));
if(n < 0){
printk("is_umdir_used : couldn't read pid file '%s', "
"err = %d\n", file, errno);
goto out_close;
} else if(n == 0){
printk("is_umdir_used : couldn't read pid file '%s', "
"0-byte read\n", file);
goto out_close;
}
p = strtoul(pid, &end, 0);
if(end == pid){
printk("is_umdir_used : couldn't parse pid file '%s', "
"errno = %d\n", file, errno);
goto out_close;
}
if((kill(p, 0) == 0) || (errno != ESRCH)){
printk("umid \"%s\" is already in use by pid %d\n", umid, p);
return 1;
}
out_close:
close(fd);
out:
return 0;
}
/*
* Try to remove the directory @dir unless it's in use.
* Precondition: @dir exists.
* Returns 0 for success, < 0 for failure in removal or if the directory is in
* use.
*/
static int umdir_take_if_dead(char *dir)
{
int ret;
if (is_umdir_used(dir))
return -EEXIST;
ret = remove_files_and_dir(dir);
if (ret) {
printk("is_umdir_used - remove_files_and_dir failed with "
"err = %d\n", ret);
}
return ret;
}
static void __init create_pid_file(void)
{
char file[strlen(uml_dir) + UMID_LEN + sizeof("/pid\0")];
char pid[sizeof("nnnnn\0")];
int fd, n;
if(umid_file_name("pid", file, sizeof(file)))
return;
fd = open(file, O_RDWR | O_CREAT | O_EXCL, 0644);
if(fd < 0){
printk("Open of machine pid file \"%s\" failed: %s\n",
file, strerror(errno));
return;
}
snprintf(pid, sizeof(pid), "%d\n", getpid());
n = write(fd, pid, strlen(pid));
if(n != strlen(pid))
printk("Write of pid file failed - err = %d\n", errno);
close(fd);
}
int __init set_umid(char *name)
{
if(strlen(name) > UMID_LEN - 1)
return -E2BIG;
strlcpy(umid, name, sizeof(umid));
return 0;
}
static int umid_setup = 0;
int __init make_umid(void)
{
int fd, err;
char tmp[256];
if(umid_setup)
return 0;
make_uml_dir();
if(*umid == '\0'){
strlcpy(tmp, uml_dir, sizeof(tmp));
strlcat(tmp, "XXXXXX", sizeof(tmp));
fd = mkstemp(tmp);
if(fd < 0){
printk("make_umid - mkstemp(%s) failed: %s\n",
tmp, strerror(errno));
err = -errno;
goto err;
}
close(fd);
set_umid(&tmp[strlen(uml_dir)]);
/* There's a nice tiny little race between this unlink and
* the mkdir below. It'd be nice if there were a mkstemp
* for directories.
*/
if(unlink(tmp)){
err = -errno;
goto err;
}
}
snprintf(tmp, sizeof(tmp), "%s%s", uml_dir, umid);
err = mkdir(tmp, 0777);
if(err < 0){
err = -errno;
if(err != -EEXIST)
goto err;
if (umdir_take_if_dead(tmp) < 0)
goto err;
err = mkdir(tmp, 0777);
}
if(err){
err = -errno;
printk("Failed to create '%s' - err = %d\n", umid, -errno);
goto err;
}
umid_setup = 1;
create_pid_file();
err = 0;
err:
return err;
}
static int __init make_umid_init(void)
{
if(!make_umid())
return 0;
/* If initializing with the given umid failed, then try again with
* a random one.
*/
printk("Failed to initialize umid \"%s\", trying with a random umid\n",
umid);
*umid = '\0';
make_umid();
return 0;
}
__initcall(make_umid_init);
int __init umid_file_name(char *name, char *buf, int len)
{
int n, err;
err = make_umid();
if(err)
return err;
n = snprintf(buf, len, "%s%s/%s", uml_dir, umid, name);
if(n >= len){
printk("umid_file_name : buffer too short\n");
return -E2BIG;
}
return 0;
}
char *get_umid(void)
{
return umid;
}
static int __init set_uml_dir(char *name, int *add)
{
if(*name == '\0'){
printf("uml_dir can't be an empty string\n");
return 0;
}
if(name[strlen(name) - 1] == '/'){
uml_dir = name;
return 0;
}
uml_dir = malloc(strlen(name) + 2);
if(uml_dir == NULL){
printf("Failed to malloc uml_dir - error = %d\n", errno);
/* Return 0 here because do_initcalls doesn't look at
* the return value.
*/
return 0;
}
sprintf(uml_dir, "%s/", name);
return 0;
}
__uml_setup("uml_dir=", set_uml_dir,
"uml_dir=<directory>\n"
" The location to place the pid and umid files.\n\n"
);
static void remove_umid_dir(void)
{
char dir[strlen(uml_dir) + UMID_LEN + 1], err;
sprintf(dir, "%s%s", uml_dir, umid);
err = remove_files_and_dir(dir);
if(err)
printf("remove_umid_dir - remove_files_and_dir failed with "
"err = %d\n", err);
}
__uml_exitcall(remove_umid_dir);