Logo Search packages:      
Sourcecode: linux version File versions  Download package

umid.c

/*
 * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
 * Licensed under the GPL
 */

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include "init.h"
#include "kern_constants.h"
#include "os.h"
#include "user.h"

#define UML_DIR "~/.uml/"

#define UMID_LEN 64

/* Changed by set_umid, which is run early in boot */
static 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(UM_KERN_ERR "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(UM_KERN_ERR "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(UM_KERN_ERR "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(UM_KERN_ERR "is_umdir_used : couldn't read pid file "
                   "'%s', err = %d\n", file, errno);
            goto out_close;
      } else if (n == 0) {
            printk(UM_KERN_ERR "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(UM_KERN_ERR "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(UM_KERN_ERR "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(UM_KERN_ERR "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(UM_KERN_ERR "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(UM_KERN_ERR "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;
}

/* Changed in make_umid, which is called during early boot */
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(UM_KERN_ERR "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(UM_KERN_ERR "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(UM_KERN_ERR "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(UM_KERN_ERR "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);

Generated by  Doxygen 1.6.0   Back to index