Logo Search packages:      
Sourcecode: nautilus-cd-burner version File versions  Download package

mapping-daemon.c

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
 *
 * mapping-daemon.c: central daemon to keep track of file mappings
 *
 * Copyright (C) 2002 Red Hat, Inc.
 * Copyright (C) 2005 William Jon McCann <mccann@jhu.edu>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Authors: Alexander Larsson <alexl@redhat.com>
 *          William Jon McCann <mccann@jhu.edu>
 */

#include "config.h"

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>

#include <glib/glist.h>
#include <glib/gstdio.h>
#include <libgnomevfs/gnome-vfs.h>

#ifndef HAVE_MKDTEMP
#include "mkdtemp.h"
#endif

#include "mapping-daemon.h"
#include "mapping-protocol.h"

/* Global daemon */
GHashTable *roots;
GList      *connections = NULL;
time_t      last_disconnect = 0;

#define KEEP_DATA_TIME 60*60*2

#undef DEBUG_ENABLE

#ifdef DEBUG_ENABLE
#ifdef G_HAVE_ISO_VARARGS
#  define DEBUG_PRINT(...) debug_print (__VA_ARGS__);
#elif defined(G_HAVE_GNUC_VARARGS)
#  define DEBUG_PRINT(args...) debug_print (args);
#endif
#else
#ifdef G_HAVE_ISO_VARARGS
#  define DEBUG_PRINT(...)
#elif defined(G_HAVE_GNUC_VARARGS)
#  define DEBUG_PRINT(args...)
#endif
#endif

typedef struct {
      MappingProtocolChannel *source;
      GList                  *subscriptions;
      guint                   ref_count;
} MappingConnection;

typedef struct {
      MappingConnection *connection;
      char              *rootname;
      char              *path;
      gboolean           cancelled;
      void              *userdata;
      guint              ref_count;
} MappingSubscription;

typedef enum {
      VIRTUAL_NODE_FILE,
      VIRTUAL_NODE_DIRECTORY
} VirtualNodeType;

typedef struct {
      char           *filename;
      VirtualNodeType type;
      
      /* for files: */
      char           *backing_file; /* local filename */
      gboolean        owned_file;

      GList          *subscriptions;

      /* for directories: */
      GList          *children;
} VirtualNode;

typedef struct {
      char        *method;
      char        *tempdir;
      VirtualNode *root_node;
} VirtualRoot;


static GnomeVFSResult monitor_cancel (const char        *rootname,
                              const char        *path,
                              MappingConnection *connection,
                              void              *userdata);

#ifdef DEBUG_ENABLE
static FILE *debug_out = NULL;

static void
debug_init (void)
{
      const char path [50] = "mapping_daemon_debug_XXXXXX";
      int  fd = g_file_open_tmp (path, NULL, NULL);
      if (fd >= 0) {
            debug_out = fdopen (fd, "a");
      }
}

static void
debug_print (const char *format, ...)
{
      va_list args;

      va_start (args, format);
      vfprintf ((debug_out ? debug_out : stderr), format, args);
      va_end (args);
      if (debug_out)
            fflush (debug_out);
}
#endif

typedef struct {
      gint32               type;
      char                *filename;
      MappingSubscription *subscription;
} DispatchData;

static void
subscription_free (MappingSubscription *sub)
{
      g_return_if_fail (sub != NULL);

      DEBUG_PRINT ("Freeing subscription %p\n", sub);

      g_free (sub->rootname);
      g_free (sub->path);
      g_free (sub);
      sub = NULL;
}

static void
subscription_ref (MappingSubscription *subscription)
{
      g_return_if_fail (subscription != NULL);
      g_return_if_fail (subscription->ref_count > 0);

      subscription->ref_count += 1;
}

static void
subscription_unref (MappingSubscription *subscription)
{
      g_return_if_fail (subscription != NULL);
      g_return_if_fail (subscription->ref_count > 0);

      if (subscription->ref_count > 1)
            subscription->ref_count -= 1;
      else
            subscription_free (subscription);
}

static MappingSubscription *
subscription_new (const char        *rootname,
              const char        *path,
              MappingConnection *connection,
              gpointer           userdata)
{
      MappingSubscription *sub;

      g_return_val_if_fail (rootname != NULL, NULL);
      g_return_val_if_fail (path != NULL, NULL);
      g_return_val_if_fail (connection != NULL, NULL);

      sub = g_new0 (MappingSubscription, 1);
      sub->rootname = g_strdup (rootname);
      sub->path = g_strdup (path);
      sub->connection = connection;
      sub->userdata = userdata;
      sub->ref_count = 1;
      return sub;
}

static void
subscription_cancel (MappingSubscription *sub)
{
      g_return_if_fail (sub != NULL);

      sub->cancelled = TRUE;
}

static const char *
subscription_get_path (MappingSubscription *sub)
{
      g_return_val_if_fail (sub != NULL, NULL);

      return sub->path;
}

static void *
subscription_get_userdata (MappingSubscription *sub)
{
      g_return_val_if_fail (sub != NULL, NULL);

      return sub->userdata;
}

static gboolean
subscription_send_event (MappingSubscription *sub,
                   const char          *filename,
                   gint32               type)
{
      MappingConnection           *connection;
      MappingProtocolMonitorEvent *event;
      int                          res;

      g_return_val_if_fail (sub != NULL, FALSE);
      g_return_val_if_fail (filename != NULL, FALSE);

      if (sub->cancelled)
            return FALSE;

      connection = sub->connection;

      g_return_val_if_fail (connection != NULL, FALSE);

      /* if there is data to be read then delay sending event */
      if (mapping_protocol_data_available (connection->source))
            return TRUE;

      event = g_new0 (MappingProtocolMonitorEvent, 1);
      event->type = type;
      event->path = g_strdup (filename);
      event->userdata = sub->userdata;

      DEBUG_PRINT ("Encoding event type %d userdata %p for %s sub: %p conn: %p\n",
                 type, sub->userdata, filename, sub, connection);

      res = mapping_protocol_monitor_event_encode (connection->source, event);
      if (res == -1) {
            subscription_cancel (sub);
      }

      g_free (event->path);
      g_free (event);

      return FALSE;
}

static gboolean
actually_dispatch_event (gpointer data)
{
      DispatchData *ddata = data;
      gboolean      res;

      res = subscription_send_event (ddata->subscription, ddata->filename, ddata->type);

      return res;
}

static void
dispatch_data_free (gpointer data)
{
      DispatchData *ddata = data;

      subscription_unref (ddata->subscription);
      g_free (ddata->filename);
      ddata->filename = NULL;
      g_free (ddata);
      ddata = NULL;
}

static void
virtual_node_add_subscription (VirtualNode         *node,
                         MappingSubscription *sub)
{
      g_return_if_fail (node != NULL);
      g_return_if_fail (sub != NULL);

      node->subscriptions = g_list_append (node->subscriptions, sub);
      subscription_ref (sub);
}

static void
virtual_node_remove_subscription (VirtualNode         *node,
                          MappingSubscription *sub)
{
      g_return_if_fail (node != NULL);
      g_return_if_fail (sub != NULL);

      node->subscriptions = g_list_remove_all (node->subscriptions, sub);
      subscription_unref (sub);
}

static void
virtual_node_subscription_event (VirtualNode *node,
                         const char  *filename,
                         gint32       type)
{
      GList *l;

      g_return_if_fail (node != NULL);
      g_return_if_fail (filename != NULL);

      for (l = node->subscriptions; l != NULL; l = l->next) {
            int                  timeout;
            DispatchData        *ddata;
            MappingSubscription *sub = l->data;

            if (!sub || sub->cancelled)
                  continue;

            ddata = g_new (DispatchData, 1);
            ddata->subscription = sub;
            subscription_ref (sub);
            ddata->filename = g_strdup (filename);
            ddata->type = type;

            timeout = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
                                 actually_dispatch_event,
                                 ddata,
                                 (GDestroyNotify)dispatch_data_free);
      }
}

static VirtualNode *
virtual_node_new (const char     *filename,
              VirtualNodeType type)
{
      VirtualNode *node;

      node = g_new0 (VirtualNode, 1);
      node->filename = g_strdup (filename);
      node->type = type;
      
      return node;
}


static void
virtual_node_free (VirtualNode *node,
               gboolean     deep)
{
      GList *l;
      
      g_free (node->filename);
      switch (node->type) {
      case VIRTUAL_NODE_FILE:
            if (node->backing_file) {
                  if (node->owned_file) {
                        g_unlink (node->backing_file);
                  }
                  g_free (node->backing_file);
            }
            break;
      case VIRTUAL_NODE_DIRECTORY:
            if (deep) {
                  for (l = node->children; l != NULL; l = l->next) {
                        virtual_node_free ((VirtualNode *)l->data, TRUE);
                  }
            }
            break;
      }

      while ((l = g_list_first (node->subscriptions)) != NULL) {
            MappingSubscription *sub = l->data;
            virtual_node_remove_subscription (node, sub);
      }

      g_free (node);
}


static void
virtual_root_free (VirtualRoot *root)
{
      virtual_node_free (root->root_node, TRUE);
      g_free (root->method);
      g_rmdir (root->tempdir);
      g_free (root->tempdir);
}

static VirtualRoot *
virtual_root_new (const char *method)
{
      VirtualRoot *root;
      char        *tempdir, *filename;
      char        *dir;

      filename = g_strdup_printf ("virtual-%s.XXXXXX", g_get_user_name ());
      tempdir = g_build_filename (g_get_tmp_dir (), filename, NULL);
      g_free (filename);

      root = g_new (VirtualRoot, 1);
      
      dir = mkdtemp (tempdir);
      
      if (dir == NULL) {
            g_free (tempdir);
            g_free (root);
            root = NULL;
            return NULL;
      }
      
      root->method = g_strdup (method);
      root->tempdir = g_strdup (dir);
      root->root_node = virtual_node_new (NULL, VIRTUAL_NODE_DIRECTORY);

      g_free (tempdir);
      
      return root;
}

static VirtualRoot *
lookup_root (const char *method,
           gboolean    create)
{
      VirtualRoot *root;
      
      root = g_hash_table_lookup (roots, method);

      if (root == NULL && create) {
            root = virtual_root_new (method);
            g_hash_table_replace (roots,
                              root->method,
                              root);
      }
      
      return root;
}

static VirtualNode *
virtual_dir_lookup (VirtualNode *dir,
                const char  *filename)
{
      GList       *l;
      VirtualNode *node;
      
      g_assert (dir->type == VIRTUAL_NODE_DIRECTORY);

      for (l = dir->children; l != NULL; l = l->next) {
            node = (VirtualNode *)l->data;
            if (strcmp (node->filename, filename) == 0) {
                  return node;
            }
      }
      
      return NULL;
}

static VirtualNode *
virtual_node_lookup (VirtualRoot  *root,
                 const char   *path,
                 VirtualNode **parent)
{
      char        *copy, *next, *copy_orig;
      VirtualNode *node;

      copy_orig = g_strdup (path);
      copy = copy_orig;

      if (parent != NULL) {
            *parent = NULL;
      }
      node = root->root_node;
      while (copy != NULL) {
            /* Skip initial/multiple slashes */
            while (G_IS_DIR_SEPARATOR (*copy)) {
                  ++copy;
            }

            if (*copy == 0) {
                  break;
            }
            
            next = strchr (copy, G_DIR_SEPARATOR);
            if (next) {
                  *next = 0;
                  next++;
            }

            if (node->type != VIRTUAL_NODE_DIRECTORY) {
                  /* Found a file in the middle of the path */
                  node = NULL;
                  break;
            }
                  
            if (parent != NULL) {
                  *parent = node;
            }
            node = virtual_dir_lookup (node, copy);
            if (node == NULL) {
                  break;
            }

            copy = next;
      }

      g_free (copy_orig);

      return node;
}

static VirtualNode *
virtual_mkdir (VirtualNode *node,
             const char  *name)
{
      VirtualNode *subdir;

      g_assert (node->type == VIRTUAL_NODE_DIRECTORY);

      if (virtual_dir_lookup (node, name) != NULL) {
            return NULL;
      }
      
      subdir = virtual_node_new (name, VIRTUAL_NODE_DIRECTORY);
      node->children = g_list_append (node->children, subdir);

      return subdir;
}

static void
virtual_unlink (VirtualNode *dir,
            VirtualNode *node)
{
      g_assert (dir->type == VIRTUAL_NODE_DIRECTORY);
      
      dir->children = g_list_remove (dir->children, node);
}

static VirtualNode *
virtual_create (VirtualRoot *root,
            VirtualNode *dir,
            const char  *name,
            const char  *backing_file)
{
      VirtualNode *file;
      char        *template;
      int          fd;

      g_assert (dir->type == VIRTUAL_NODE_DIRECTORY);

      if (virtual_dir_lookup (dir, name) != NULL) {
            return NULL;
      }
      
      file = virtual_node_new (name, VIRTUAL_NODE_FILE);

      if (backing_file != NULL) {
            file->backing_file = g_strdup (backing_file);
            file->owned_file = FALSE;
      } else {
            template = g_build_filename (root->tempdir, "file.XXXXXX", NULL);

            fd = g_mkstemp (template);

            if (fd < 0) {
                  g_free (template);
                  virtual_node_free (file, FALSE);
                  return NULL;
            }
            close (fd);
            g_unlink (template);
            
            file->backing_file = template;
            file->owned_file = TRUE;
      }

      dir->children = g_list_append (dir->children, file);

      return file;
}

static GnomeVFSResult
get_backing_file (const char    *rootname,
              const char    *path,
              const gboolean write,
              char         **backing_file)
{
      VirtualRoot *root;
      VirtualNode *node;

      root = lookup_root (rootname, TRUE);
      
      *backing_file = NULL;
      node = virtual_node_lookup (root, path, NULL);
      if (node == NULL) {
            return GNOME_VFS_ERROR_NOT_FOUND;
      }
      if (node->type != VIRTUAL_NODE_FILE) {
            return GNOME_VFS_ERROR_IS_DIRECTORY;
      }
      if (write && !node->owned_file) {
            return GNOME_VFS_ERROR_READ_ONLY;
      }
      
      *backing_file = g_strdup (node->backing_file);

      return GNOME_VFS_OK;
}

static GnomeVFSResult
create_dir (const char *rootname,
          const char *path)
{
      VirtualRoot *root;
      char        *dirname;
      char        *basename;
      VirtualNode *dir;
      VirtualNode *file;

      root = lookup_root (rootname, TRUE);

      dirname = g_path_get_dirname (path);
      
      dir = virtual_node_lookup (root, dirname, NULL);
      g_free (dirname);
      if (dir == NULL) {
            return GNOME_VFS_ERROR_NOT_FOUND;
      }
      
      basename = g_path_get_basename (path);
      file = virtual_dir_lookup (dir, basename);
      if (file != NULL) {
            g_free (basename);
            return GNOME_VFS_ERROR_FILE_EXISTS;
      }

      file = virtual_mkdir (dir, basename);
      g_free (basename);

      /* on create only send event to parent dir */
      virtual_node_subscription_event (dir, path, GNOME_VFS_MONITOR_EVENT_CREATED);

      return GNOME_VFS_OK;
}

static GnomeVFSResult
remove_dir (const char *rootname,
          const char *path)
{
      VirtualRoot *root;
      VirtualNode *dir;
      VirtualNode *file;
      
      root = lookup_root (rootname, TRUE);

      file = virtual_node_lookup (root, path, &dir);
      if (file == NULL) {
            return GNOME_VFS_ERROR_NOT_FOUND;
      }
      if (dir == NULL) {
            /* Don't allow you to remove root */
            return GNOME_VFS_ERROR_NOT_FOUND;
      }
      
      if (file->type != VIRTUAL_NODE_DIRECTORY) {
            return GNOME_VFS_ERROR_NOT_A_DIRECTORY;
      }

      if (file->children != NULL) {
            return GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY;
      }

      virtual_unlink (dir, file);

      virtual_node_subscription_event (file, path, GNOME_VFS_MONITOR_EVENT_DELETED);
      virtual_node_subscription_event (dir, path, GNOME_VFS_MONITOR_EVENT_DELETED);

      virtual_node_free (file, FALSE);

      return GNOME_VFS_OK;
}

static GnomeVFSResult
remove_file (const char *rootname,
           const char *path)
{
      VirtualRoot *root;
      VirtualNode *dir;
      VirtualNode *file;
      
      root = lookup_root (rootname, TRUE);

      file = virtual_node_lookup (root, path, &dir);
      if (file == NULL) {
            return GNOME_VFS_ERROR_NOT_FOUND;
      }
      
      if (file->type != VIRTUAL_NODE_FILE) {
            return GNOME_VFS_ERROR_IS_DIRECTORY;
      }

      virtual_unlink (dir, file);

      virtual_node_subscription_event (file, path, GNOME_VFS_MONITOR_EVENT_DELETED);
      virtual_node_subscription_event (dir, path, GNOME_VFS_MONITOR_EVENT_DELETED);

      virtual_node_free (file, FALSE);

      return GNOME_VFS_OK;
}

static GnomeVFSResult
create_file (const char    *rootname,
           const char    *path,
           const gboolean exclusive,
           char         **backing_file,
           gboolean      *newly_created)
{
      VirtualRoot *root;
      char        *dirname;
      char        *basename;
      VirtualNode *dir;
      VirtualNode *file;
      
      *backing_file = NULL;
      *newly_created = FALSE;
      
      root = lookup_root (rootname, TRUE);

      dirname = g_path_get_dirname (path);
      
      dir = virtual_node_lookup (root, dirname, NULL);
      g_free (dirname);
      if (dir == NULL) {
            return GNOME_VFS_ERROR_NOT_FOUND;
      }
      
      basename = g_path_get_basename (path);
      file = virtual_dir_lookup (dir, basename);
      if (exclusive && file != NULL) {
            g_free (basename);
            return GNOME_VFS_ERROR_FILE_EXISTS;
      }

      *newly_created = FALSE;
      if (file == NULL) {
            file = virtual_create (root, dir, basename, NULL);
            *newly_created = TRUE;
      }
      
      g_free (basename);

      if (file == NULL) {
            return GNOME_VFS_ERROR_NO_SPACE;
      }

      *backing_file = g_strdup (file->backing_file);

      /* on create only send event to parent dir */
      virtual_node_subscription_event (dir, path, GNOME_VFS_MONITOR_EVENT_CREATED);

      return GNOME_VFS_OK;
}

static void
create_dir_link (VirtualRoot *root,
             VirtualNode *parent,
             const char  *dirname,
             const char  *target_path)
{
      VirtualNode   *dir;
      GDir          *d; 
      const char    *d_name;
      char          *path;
      struct stat    stat_buf;

      DEBUG_PRINT ("create_dir_link %s %s\n", dirname, target_path);

      dir = virtual_mkdir (parent, dirname);
      if (dir == NULL) {
            return;
      }

      d = g_dir_open (target_path, 0, NULL);
      if (d == NULL) {
            return;
      }

      while ((d_name = g_dir_read_name (d)) != NULL) {
      
            path = g_build_filename (target_path, d_name, NULL);

            if (g_lstat (path, &stat_buf) == 0) {
                  if (S_ISDIR (stat_buf.st_mode)) {
                        create_dir_link (root, dir, d_name, path);
                  } else {
                        virtual_create (root, dir, d_name, path);
                  }
            }
            g_free (path);
      }

      g_dir_close (d);
}

static GnomeVFSResult
create_link (const char *rootname,
           const char *path,
           const char *target_path)
{
      VirtualRoot *root;
      char        *dirname;
      char        *basename;
      VirtualNode *dir;
      VirtualNode *file;
      struct stat  stat_buf;
      
      root = lookup_root (rootname, TRUE);

      dirname = g_path_get_dirname (path);

      DEBUG_PRINT ("create_link %s %s\n", path, target_path);

      dir = virtual_node_lookup (root, dirname, NULL);
      g_free (dirname);
      if (dir == NULL) {
            return GNOME_VFS_ERROR_NOT_FOUND;
      }
      
      basename = g_path_get_basename (path);
      
      file = virtual_dir_lookup (dir, basename);
      if (file != NULL) {
            g_free (basename);
            return GNOME_VFS_ERROR_FILE_EXISTS;
      }

      if (g_lstat (target_path, &stat_buf) < 0) {
            g_free (basename);
            return GNOME_VFS_ERROR_NOT_FOUND;
      }

      if (S_ISDIR (stat_buf.st_mode)) {
            create_dir_link (root, dir, basename, target_path);
      } else {
            file = virtual_create (root, dir, basename, target_path);
            g_free (basename);
            
            if (file == NULL) {
                  return GNOME_VFS_ERROR_NO_SPACE;
            }
      }

      /* on create only send event to parent dir */
      virtual_node_subscription_event (dir, path, GNOME_VFS_MONITOR_EVENT_CREATED);

      return GNOME_VFS_OK;
}


static GnomeVFSResult
move_file (const char *rootname,
         const char *old_path,
         const char *new_path)
{
      VirtualRoot *root;
      char        *dirname;
      VirtualNode *old_node, *new_node;
      VirtualNode *old_dir, *new_dir;
      
      root = lookup_root (rootname, TRUE);

      old_node = virtual_node_lookup (root, old_path, &old_dir);
      if (old_node == NULL) {
            return GNOME_VFS_ERROR_NOT_FOUND;
      }

      new_node = virtual_node_lookup (root, new_path, &new_dir);
      if (new_node != NULL) {
            if (new_node->type == VIRTUAL_NODE_DIRECTORY &&
                old_node->type != VIRTUAL_NODE_DIRECTORY) {
                  return GNOME_VFS_ERROR_IS_DIRECTORY;
            }
            if (new_node->type == VIRTUAL_NODE_DIRECTORY &&
                new_node->children != NULL) {
                  return GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY;
            }
            g_free (old_node->filename);
            old_node->filename = g_strdup (new_node->filename);
            virtual_unlink (new_dir, new_node);
            virtual_unlink (old_dir, old_node);
            new_dir->children = g_list_append (new_dir->children, old_node);
            virtual_node_free (new_node, FALSE);
      } else {
            dirname = g_path_get_dirname (new_path);
            new_dir = virtual_node_lookup (root, dirname, NULL);
            g_free (dirname);
            if (new_dir == NULL) {
                  return GNOME_VFS_ERROR_NOT_FOUND;
            }
            g_free (old_node->filename);
            old_node->filename = g_path_get_basename (new_path);
            virtual_unlink (old_dir, old_node);
            new_dir->children = g_list_append (new_dir->children, old_node);
      }

      virtual_node_subscription_event (new_dir, new_path, GNOME_VFS_MONITOR_EVENT_CREATED);
      virtual_node_subscription_event (old_node, old_path, GNOME_VFS_MONITOR_EVENT_DELETED);
      virtual_node_subscription_event (old_dir, old_path, GNOME_VFS_MONITOR_EVENT_DELETED);

      return GNOME_VFS_OK;
      
}

static GnomeVFSResult
list_dir (const char *rootname,
        const char *path,
        gint32     *n_elements,
         char    ***listing_out)
{
      VirtualRoot *root;
      VirtualNode *node;
      int          len, i;
      GList       *l;
      char       **listing;

      *n_elements = 0;
      
      root = lookup_root (rootname, TRUE);
      
      node = virtual_node_lookup (root, path, NULL);
      if (node == NULL) {
            return GNOME_VFS_ERROR_NOT_FOUND;
      }
      if (node->type != VIRTUAL_NODE_DIRECTORY) {
            return GNOME_VFS_ERROR_NOT_A_DIRECTORY;
      }

      len = g_list_length (node->children);
      listing = g_new (char *, len * 2);
      *listing_out = listing;
      *n_elements = (gint32) len * 2;

      for (i=0, l = node->children; l != NULL; l = l->next, i++) {
            node = (VirtualNode *)l->data;

            listing[i*2] = node->filename;
            if (node->type == VIRTUAL_NODE_FILE) {
                  listing [i*2 + 1] = node->backing_file;
            } else {
                  listing [i*2 + 1] = NULL;
            }
      }
      
      return GNOME_VFS_OK;
}

static void
connection_add_subscription (MappingConnection   *connection,
                       MappingSubscription *sub)
{
      g_return_if_fail (connection != NULL);
      g_return_if_fail (sub != NULL);

      DEBUG_PRINT ("Adding subscription %p to connection %p\n", sub, connection);
      connection->subscriptions = g_list_append (connection->subscriptions, sub);
      subscription_ref (sub);
}

static void
connection_remove_subscription (MappingConnection   *connection,
                        MappingSubscription *sub)
{
      g_return_if_fail (connection != NULL);
      g_return_if_fail (sub != NULL);

      DEBUG_PRINT ("Removing subscription %p from connection %p\n", sub, connection);
      connection->subscriptions = g_list_remove_all (connection->subscriptions, sub);
      subscription_unref (sub);
}

static GnomeVFSResult
remove_subscription (MappingConnection   *connection,
                 MappingSubscription *sub)
{
      VirtualRoot *root;
      VirtualNode *node;

      root = lookup_root (sub->rootname, TRUE);

      node = virtual_node_lookup (root, sub->path, NULL);

      subscription_cancel (sub);

      if (node != NULL) {
            virtual_node_remove_subscription (node, sub);
      }

      connection_remove_subscription (connection, sub);
      subscription_unref (sub);

      return GNOME_VFS_OK;
}

static void
connection_free (MappingConnection *connection)
{
      GList *cur;

      g_return_if_fail (connection != NULL);

      DEBUG_PRINT ("Freeing connection %p\n", connection);

      /* release subscriptions that weren't released by cancelling the monitor */
      while ((cur = g_list_first (connection->subscriptions)) != NULL) {
            GnomeVFSResult       res;
            MappingSubscription *sub = cur->data;

            DEBUG_PRINT ("Removing subscription %p\n", sub);
            res = remove_subscription (connection, sub);
            if (GNOME_VFS_OK != res) {
                  DEBUG_PRINT ("Removing connection failed: %d\n", res);
            }
      }

      mapping_protocol_channel_unref (connection->source);

      g_free (connection);
      connection = NULL;
}

static void
connection_ref (MappingConnection *connection)
{
      g_return_if_fail (connection != NULL);
      g_return_if_fail (connection->ref_count > 0);

      connection->ref_count += 1;
}

static void
connection_unref (MappingConnection *connection)
{
      g_return_if_fail (connection != NULL);
      g_return_if_fail (connection->ref_count > 0);

      if (connection->ref_count > 1)
            connection->ref_count -= 1;
      else
            connection_free (connection);
}

static MappingConnection *
connection_new (MappingProtocolChannel *source)
{
      MappingConnection *connection;

      g_return_val_if_fail (source != NULL, NULL);

      connection = g_new0 (MappingConnection, 1);
      connection->ref_count = 1;
      connection->source = source;

      mapping_protocol_channel_ref (source);

      DEBUG_PRINT ("New connection %p\n", connection);
      return connection;
}

static MappingSubscription *
connection_get_subscription (MappingConnection *connection,
                       const char        *path,
                       void              *userdata)
{
      GList *l;

      g_return_val_if_fail (connection != NULL, NULL);

      for (l = connection->subscriptions; l; l = l->next) {
            MappingSubscription *sub = l->data;

            if ((strcmp (subscription_get_path (sub), path) == 0)
                && (subscription_get_userdata (sub) == userdata)) {
                  return sub;
            }
      }

      return NULL;
}

static void
connection_add (MappingConnection *connection)
{
      g_return_if_fail (connection != NULL);

      connections = g_list_append (connections, connection);
      connection_ref (connection);
}

static void
connection_remove (MappingConnection *connection)
{
      g_return_if_fail (connection != NULL);

      connections = g_list_remove_all (connections, connection);
      connection_unref (connection);

      DEBUG_PRINT ("Connection died: %d connections remain\n", g_list_length (connections));

      last_disconnect = time (NULL);
}

static gboolean
connection_error (MappingConnection *connection,
              GIOCondition       condition,
              gpointer           data)
{
      DEBUG_PRINT ("connection_error: source %p\n", connection->source);

      connection_remove (connection);
      connection_unref (connection);

      return FALSE;
}

static GnomeVFSResult
monitor_add (const char        *rootname,
           const char        *path,
           MappingConnection *connection,
           void              *userdata)
{
      VirtualRoot         *root;
      VirtualNode         *node;
      MappingSubscription *sub;

      DEBUG_PRINT ("Adding monitor for %s userdata %p\n", path, userdata);

      root = lookup_root (rootname, TRUE);

      node = virtual_node_lookup (root, path, NULL);

      if (node == NULL) {
            return GNOME_VFS_ERROR_NOT_FOUND;
      }

      sub = subscription_new (rootname, path, connection, userdata);

      virtual_node_add_subscription (node, sub);
      connection_add_subscription (connection, sub);

      return GNOME_VFS_OK;
}

static GnomeVFSResult
monitor_cancel (const char        *rootname,
            const char        *path,
            MappingConnection *connection,
            void              *userdata)
{
      MappingSubscription *sub;
      GnomeVFSResult       res;

      DEBUG_PRINT ("Cancelling monitor for %s userdata %p\n", path, userdata);

      sub = connection_get_subscription (connection, path, userdata);

      if (!sub) {
            return GNOME_VFS_ERROR_NOT_FOUND;
      }

      res = remove_subscription (connection, sub);

      return res;
}

static void
init_roots (void)
{
      roots = g_hash_table_new (g_str_hash, g_str_equal);
}

static const char *
operation_string (gint32 op)
{
      const char *str;

      switch (op) {
      case MAPPING_PROTOCOL_OP_GET_BACKING_FILE:
            str = "MAPPING_PROTOCOL_OP_GET_BACKING_FILE";
            break;
      case MAPPING_PROTOCOL_OP_LIST_DIR:
            str = "MAPPING_PROTOCOL_OP_LIST_DIR";
            break;
      case MAPPING_PROTOCOL_OP_CREATE_DIR:
            str = "MAPPING_PROTOCOL_OP_CREATE_DIR";
            break;
      case MAPPING_PROTOCOL_OP_REMOVE_DIR:
            str = "MAPPING_PROTOCOL_OP_REMOVE_DIR";
            break;
      case MAPPING_PROTOCOL_OP_REMOVE_FILE:
            str = "MAPPING_PROTOCOL_OP_REMOVE_FILE";
            break;
      case MAPPING_PROTOCOL_OP_CREATE_FILE:
            str = "MAPPING_PROTOCOL_OP_CREATE_FILE";
            break;
      case MAPPING_PROTOCOL_OP_CREATE_LINK:
            str = "MAPPING_PROTOCOL_OP_CREATE_LINK";
            break;
      case MAPPING_PROTOCOL_OP_MOVE_FILE:
            str = "MAPPING_PROTOCOL_OP_MOVE_FILE";
            break;
      case MAPPING_PROTOCOL_OP_MONITOR_ADD:
            str = "MAPPING_PROTOCOL_OP_MONITOR_ADD";
            break;
      case MAPPING_PROTOCOL_OP_MONITOR_CANCEL:
            str = "MAPPING_PROTOCOL_OP_MONITOR_CANCEL";
            break;
      default:
            str = "Unknown";
            break;
      }

      return str;
}

static gboolean
handle_request (GIOChannel   *source,
            GIOCondition  condition,
            gpointer      data)
{
      MappingConnection     *connection;
      int                    res;
      MappingProtocolRequest req;
      MappingProtocolReply   reply;

      connection = (MappingConnection *)data;
      
      if ((condition & G_IO_ERR) || (condition & G_IO_HUP)) {
            connection_error (connection, condition, NULL);
            return FALSE;
      }

      res = mapping_protocol_request_decode (connection->source, &req);

      if (res != 0) {
            connection_error (connection, condition, NULL);
            return FALSE;
      }

      memset (&reply, 0, sizeof (MappingProtocolReply));

      DEBUG_PRINT ("Decoded new request type %s\n", operation_string (req.operation));

      switch (req.operation) {
      case MAPPING_PROTOCOL_OP_GET_BACKING_FILE:
            reply.result = get_backing_file (req.root,
                                     req.path1,
                                     req.option,
                                     &reply.path);
            break;
      case MAPPING_PROTOCOL_OP_LIST_DIR:
            reply.result = list_dir (req.root,
                               req.path1,
                               &reply.n_strings,
                               &reply.strings);
            break;
      case MAPPING_PROTOCOL_OP_CREATE_DIR:
            reply.result = create_dir (req.root,
                                 req.path1);
            break;
      case MAPPING_PROTOCOL_OP_REMOVE_DIR:
            reply.result = remove_dir (req.root,
                                 req.path1);
            break;
      case MAPPING_PROTOCOL_OP_REMOVE_FILE:
            reply.result = remove_file (req.root,
                                  req.path1);
            break;
      case MAPPING_PROTOCOL_OP_CREATE_FILE:
            reply.result = create_file (req.root,
                                  req.path1,
                                  req.option,
                                  &reply.path,
                                  &reply.option);
            break;
      case MAPPING_PROTOCOL_OP_CREATE_LINK:
            reply.result = create_link (req.root,
                                  req.path1,
                                  req.path2);
            break;
      case MAPPING_PROTOCOL_OP_MOVE_FILE:
            reply.result = move_file (req.root,
                                req.path1,
                                req.path2);
            break;
      case MAPPING_PROTOCOL_OP_MONITOR_ADD:
            reply.result = monitor_add (req.root,
                                  req.path1,
                                  connection,
                                  req.userdata);
            break;
      case MAPPING_PROTOCOL_OP_MONITOR_CANCEL:
            reply.result = monitor_cancel (req.root,
                                     req.path1,
                                     connection,
                                     req.userdata);
            break;
      default:
            g_warning ("Unimplemented op %s\n", operation_string (req.operation));
            break;
      }

      DEBUG_PRINT ("Encoding reply to request op %s path1: %s result: %d path: '%s' n_strings: %d\n",
                 operation_string (req.operation), req.path1, reply.result, reply.path, reply.n_strings);

      res = mapping_protocol_reply_encode (connection->source, &reply);

      mapping_protocol_request_destroy (&req);
      mapping_protocol_reply_destroy (&reply);

      if (res != 0) {
            DEBUG_PRINT ("Could not encode reply: %d\n", res);
            connection_error (connection, condition, NULL);
            return FALSE;
      }

      DEBUG_PRINT ("Finished handling request type %s\n", operation_string (req.operation));

      return TRUE;
}

static gboolean
handle_new_client (GIOChannel   *source,
               GIOCondition  condition,
               gpointer      data)
{
      MappingConnection      *connection;
      MappingProtocolChannel *io;
      int                     master_fd = g_io_channel_unix_get_fd (source);
      int                     client;

      /* wait for a client to talk to us */
        if ((client = accept (master_fd, 0, 0)) == -1) {
                g_warning ("accept failed");
            return TRUE;
        }

      io = mapping_protocol_channel_new (client);
      connection = connection_new (io);

      g_io_add_watch (io->iochannel, G_IO_IN | G_IO_ERR | G_IO_HUP, handle_request, connection);

      DEBUG_PRINT ("Handling new client source: %p\n", io);

      mapping_protocol_channel_unref (io);

      connection_add (connection);
      
      return TRUE;
}

static void
have_data_helper (gpointer       key,
              gpointer       value,
              gpointer       user_data)
{
      VirtualRoot *root = value;
      gboolean    *res = user_data;

      if (root->root_node->children != NULL) {
            *res = TRUE;
      }
}

static gboolean
have_data (void)
{
      gboolean res = FALSE;
      
      g_hash_table_foreach (roots,
                        have_data_helper, &res);

      return res;
}

static gboolean
free_roots_helper (gpointer       key,
               gpointer       value,
               gpointer       user_data)
{
      VirtualRoot *root = value;

      virtual_root_free (root);
      return TRUE;
}

static void
free_roots (void)
{
      g_hash_table_foreach_remove (roots,
                             free_roots_helper, NULL);
}


static gboolean
cleanup_timeout (gpointer data)
{
      if (g_list_length (connections) == 0 ) {
            if (have_data ()) {
                  time_t now;
                  now = time (NULL);
                  if (now - last_disconnect > KEEP_DATA_TIME) {
                        free_roots ();
                        exit (0);
                  }
            } else {
                  free_roots ();
                  exit (0);
            }
      }
      
      return TRUE;
}

int
main (int   argc,
      char *argv[])
{
      struct sockaddr_un sin;
      int                master_socket;
      GIOChannel        *master_io;
      GMainLoop         *loop;
      int                pipe_fd;
      char              *path;

      /* See if we have a valid pipe to write to */
      pipe_fd = dup (3);
      close (3);

      init_roots ();

#ifdef DEBUG_ENABLE
      debug_init ();
#endif
      
      if ((master_socket = socket (AF_UNIX, SOCK_STREAM, 0)) == -1) {
            perror ("Master socket creation failed");
            exit (1);
      }
      
      path = mapping_protocol_get_unix_name ();

      sin.sun_family = AF_UNIX;     
      g_snprintf (sin.sun_path, sizeof (sin.sun_path), "%s", path);
      g_free (path);

      g_unlink (sin.sun_path);
      if (bind (master_socket, (const struct sockaddr *)&sin, sizeof (sin)) == -1) {
            perror ("Failed to bind master socket");
            close (master_socket);
            exit (1);
      }

      if (listen (master_socket, 5) == -1) {
            perror ("Failed to listen to master socket");
            close (master_socket);
            exit (1);
      }

      /* Trigger launching app */
      if (pipe_fd >= 0) {
            write (pipe_fd, "G", 1);
            close (pipe_fd);
      }
      
      master_io = g_io_channel_unix_new (master_socket);
      g_io_add_watch (master_io, G_IO_IN, handle_new_client, NULL);
      g_io_channel_unref (master_io);

      /* Don't hold up the cwd, allowing unmounts of e.g. /home
       * since we run a while after logout.
       */
      chdir (G_DIR_SEPARATOR_S);

      loop = g_main_loop_new (NULL, TRUE);

      g_timeout_add (5000, &cleanup_timeout, NULL);

      g_main_loop_run (loop);
      
      free_roots ();
      return 0;
}

Generated by  Doxygen 1.6.0   Back to index