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

fakeroot.c

/*
 * Copyright (c) 2003, 2004, 2005 Nokia
 * Author: Timo Savola <tsavola@movial.fi>
 *
 * This program is licensed under GPL (see COPYING for details)
 */

#include "fakeroot.h"
#include "types.h"
#include "common.h"
#include "daemon.h"
#include "mount.h"

#define _GNU_SOURCE

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/stat.h>

extern char **environ;

typedef enum {
      DAEMON_TO_CLIENT,
      CLIENT_TO_DAEMON
} direction_t;

struct node_s;
typedef struct node_s {
      struct node_s *next;
      int client;
      int daemon;
      fake_dev_t stored_dev;
} node_t;

static volatile bool_t parent_alive = TRUE;
static volatile int listen_sd = -1;
static uint32_t relay_id;

static void append_node(handler_t *data, node_t **head, int client_sd, int daemon_sd)
{
      node_t *node, *n;

      node = calloc(1, sizeof (node_t));
      if (!node) {
            errno = 0;
            send_error(data, oom);
            exit(1);
      }

      node->client = client_sd;
      node->daemon = daemon_sd;

      if (*head) {
            for (n = *head; n->next; n = n->next);
            n->next = node;
      } else {
            *head = node;
      }
}

static void remove_node(node_t **head, node_t *node)
{
      node_t *n;

      if (*head == node) {
            *head = node->next;
      } else {
            for (n = *head; n->next != node; n = n->next);
            n->next = node->next;
      }

      debug("Removing node: client=%d, daemon=%d", node->client, node->daemon);

      close(node->client);
      close(node->daemon);

      free(node);
}

static void get_device_numbers(handler_t *data)
{
      mount_t **ptr;

      if (!data->mounts) {
            debug("No mounts");
            return;
      }

      for (ptr = data->mounts; *ptr; ptr++) {
            mount_t *m = *ptr;

            if (m->info.type != MTYPE_BIND && m->point_dev == 0LL) {
                  struct stat buf;

                  if (stat(m->info.point, &buf) < 0) {
                        send_error(data, "Can't stat %s", m->info.point);
                        exit(1);
                  }

                  m->point_dev = buf.st_dev;

                  debug("Device number of %s is %lld", m->info.device, m->info.device_dev);
                  debug("Device number of %s is %lld", m->info.point, m->point_dev);
            }
      }
}

static bool_t translate_dev(handler_t *data, node_t *node, struct fakestat *st)
{
      mount_t **ptr;
      const fake_dev_t old = ntohll(st->dev);

      node->stored_dev = st->dev;

      if (!data->mounts) {
            debug("No mounts");
            return FALSE;
      }

      for (ptr = data->mounts; *ptr; ptr++) {
            const mount_t *m = *ptr;

            if (old == m->point_dev) {
                  debug("Translating device number: %lld -> %lld", old, m->info.device_dev);

                  st->dev = htonll(m->info.device_dev);
                  return TRUE;
            }
      }

      debug("No match for device number %lld", old);
      return FALSE;
}

static void restore_dev(node_t *node, struct fakestat *st)
{
      debug("Restoring device number");

      st->dev = node->stored_dev;
}

static int copy_msg(handler_t *data, node_t *node, direction_t direction)
{
      int source, destination;
      struct fake_msg buf;
      ssize_t len;

      if (direction == CLIENT_TO_DAEMON) {
            source = node->client;
            destination = node->daemon;

            debug("Processing message from client=%d", source);
      } else {
            source = node->daemon;
            destination = node->client;

            debug("Processing message from daemon=%d", source);
      }

      while (1) {
            len = read(source, &buf, sizeof (buf));
            if (len >= 0)
                  break;

            if (errno != EINTR) {
                  send_error(data, "Can't read from socket");
                  exit(1);
            }
      }

      if (len == 0) {
            debug("No message");
            return -1;
      }

      if (direction == CLIENT_TO_DAEMON) {
            if (translate_dev(data, node, &buf.st)) {
                  buf.remote = htonl(0);
            } else if (ntohl(buf.remote) == 0) {
                  debug("Setting remote ID");
                  buf.remote = htonl(relay_id);
            }
      } else {
            restore_dev(node, &buf.st);
      }

      if (direction == CLIENT_TO_DAEMON) {
            debug("Forwarding message to daemon=%d", destination);
      } else {
            debug("Forwarding message to client=%d", destination);
      }

      while (1) {
            if (write(destination, &buf, sizeof (buf)) >= 0)
                  break;

            if (errno != EINTR) {
                  send_error(data, "Can't write to socket");
                  exit(1);
            }
      }

      return 0;
}

static int get_daemon(handler_t *data, struct sockaddr_in *addr)
{
      int sd;

      sd = socket(PF_INET, SOCK_STREAM, 0);
      if (sd < 0) {
            send_error(data, "Can't create socket");
            exit(1);
      }

      if (connect(sd, (struct sockaddr *) addr, sizeof (struct sockaddr_in)) < 0) {
            if (errno == EINTR) {
                  debug("Connect interrupted");
                  exit(0);
            }

            send_error(data, "Connect failed");
            exit(1);
      }

      debug("Connected to daemon=%d", sd);

      return sd;
}

static int get_client(handler_t *data)
{
      struct sockaddr_in addr;
      socklen_t len = sizeof (addr);
      int sd;

      sd = accept(listen_sd, (struct sockaddr *) &addr, &len);
      if (sd < 0) {
            if (errno == EINTR) {
                  debug("Accept interrupted");
                  exit(0);
            }

            send_error(data, "Accept failed");
            exit(1);
      }

      debug("Connection from client=%d", sd);

      return sd;
}

static void do_relay(handler_t *data, int port)
{
      struct hostent *host;
      struct sockaddr_in addr = { 0 };
      node_t *sd_head = NULL;
      fd_set fds;

      host = gethostbyname(data->host);
      if (!host) {
            send_error(data, "Can't resolve host: %s", data->host);
            exit(1);
      }

      addr.sin_family = AF_INET;
      addr.sin_addr.s_addr = *(unsigned int *) host->h_addr;
      addr.sin_port = htons(port);

      debug("Fakeroot daemon assumed to be at %s:%d", data->host, port);

      while (parent_alive) {
            int maxfd, count;
            node_t *n, *next;

            FD_ZERO(&fds);

            FD_SET(listen_sd, &fds);
            maxfd = listen_sd;

            for (n = sd_head, count = 0; n; n = n->next, count++) {
                  FD_SET(n->daemon, &fds);
                  FD_SET(n->client, &fds);
                  maxfd = MAX(maxfd, n->daemon);
                  maxfd = MAX(maxfd, n->client);
            }

            debug("Selecting (%d nodes)", count);

            if (select(maxfd + 1, &fds, NULL, NULL, NULL) < 0) {
                  if (!parent_alive) {
                        debug("SIGEXIT caught during select call");
                        break;
                  }

                  if (errno == EINTR) {
                        debug("Select interrupted");
                        continue;
                  }

                  send_error(data, "Select failed");
                  exit(1);
            }

            for (n = sd_head; n; n = next) {
                  next = n->next;

                  if (FD_ISSET(n->daemon, &fds) && copy_msg(data, n, DAEMON_TO_CLIENT) < 0) {
                        remove_node(&sd_head, n);
                        continue;
                  }

                  if (FD_ISSET(n->client, &fds) && copy_msg(data, n, CLIENT_TO_DAEMON) < 0) {
                        remove_node(&sd_head, n);
                  }
            }

            if (FD_ISSET(listen_sd, &fds)) {
                  int cli, dae;

                  cli = get_client(data);
                  dae = get_daemon(data, &addr);

                  append_node(data, &sd_head, cli, dae);
            }
      }

      debug("Exiting");
}

static void sig_exit(int sig)
{
      int stored_errno = errno;

      parent_alive = FALSE;

      if (listen_sd >= 0)
            close(listen_sd);

      errno = stored_errno;
}

static pid_t fork_relay(handler_t *data, uint16_t *portp)
{
      int faked_port, sd;
      struct sockaddr_in addr;
      socklen_t len;
      uint32_t id;
      pid_t pid;
      struct sigaction act_exit;

      faked_port = *portp;

      sd = socket(PF_INET, SOCK_STREAM, 0);
      if (sd < 0) {
            send_error(data, "Can't create socket");
            return -1;
      }

      if (setsockopt_bool(sd, SOL_SOCKET, SO_REUSEADDR, TRUE)) {
            send_error(data, "Can't set socket option: SO_REUSEADDR");
            goto _error;
      }

      if (listen(sd, SOMAXCONN) < 0) {
            send_error(data, "Can't listen to socket");
            goto _error;
      }

      len = sizeof (addr);
      if (getsockname(sd, (struct sockaddr *) &addr, &len) < 0) {
            send_error(data, "Can't get name of relay listening socket");
            goto _error;
      }

      *portp = ntohs(addr.sin_port);

      len = sizeof (addr);
      if (getsockname(data->sd, (struct sockaddr *) &addr, &len) < 0) {
            send_error(data, "Can't get name of client connection socket");
            goto _error;
      }

      id = ntohl(addr.sin_addr.s_addr);

      pid = fork();
      if (pid < 0) {
            send_error(data, "Can't fork");
            goto _error;
      }

      if (pid > 0) {
            /* Parent: */
            close(sd);
            return pid;
      }

      /* Child: */

      set_debug_name("RELAY");
      debug("Relaying fakeroot messages at port %d", *portp);
      debug("Remote ID of this relay is 0x%lx", id);

      daemonize(sd);

      act_exit.sa_handler = sig_exit;
      sigemptyset(&act_exit.sa_mask);
      act_exit.sa_flags = 0;

      sigaction(SIGHUP, &act_exit, NULL);
      sigaction(SIGTERM, &act_exit, NULL);

      get_device_numbers(data);

      listen_sd = sd;
      relay_id = id;

      do_relay(data, faked_port);
      exit(0);

_error:
      close(sd);
      return -1;
}

pid_t fakeroot_relay(handler_t *data)
{
      char **old_environ, *str;
      uint16_t port;
      pid_t pid;

      /* TODO: better. */
      old_environ = environ;
      environ = data->param.environ;
      str = getenv(FAKEROOTKEY_ENV);
      environ = old_environ;

      if (!str || !*str)
            return 0;

      debug("Creating relay process");

      port = atoi(str);
      if (port == 0) {
            send_error(data, "Invalid " FAKEROOTKEY_ENV ": %s", str);
            return -1;
      }

      pid = fork_relay(data, &port);
      if (pid < 0)
            return -1;

      data->fakerootkey = malloc(strlen(FAKEROOTKEY_ENV) + 1 + 5 + 1);
      if (!data->fakerootkey) {
            errno = 0;
            send_error(data, oom);
            goto _kill;
      }

      sprintf(data->fakerootkey, FAKEROOTKEY_ENV "=%d", port);

      return pid;

_kill:
      kill(pid, SIGTERM);
      return -1;
}

Generated by  Doxygen 1.6.0   Back to index