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

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-2006 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

/*
#define MEMLEAK_DEBUG_TIME 120
*/

#undef DEBUG_ENABLE

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

typedef struct {
      MappingConnection *connection;
      char              *rootname;
      char              *path;
      gboolean           cancelled;
      void              *userdata;
      volatile gint      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;
      volatile gint   ref_count;
} VirtualNode;

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

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

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


#ifdef DEBUG_ENABLE
#ifdef G_HAVE_ISO_VARARGS
#  define DEBUG_PRINT(...) profile_log (G_STRFUNC, NULL, __VA_ARGS__);
#elif defined(G_HAVE_GNUC_VARARGS)
#  define DEBUG_PRINT(args...) profile_log (G_STRFUNC, NULL, args);
#endif
#else
#ifdef G_HAVE_ISO_VARARGS
#  define DEBUG_PRINT(...)
#elif defined(G_HAVE_GNUC_VARARGS)
#  define DEBUG_PRINT(args...)
#endif
#endif

#ifdef DEBUG_ENABLE
static void
profile_log (const char *func,
           const char *note,
           const char *format,
           ...)
{
        va_list args;
        char   *str;
        char   *formatted;

        if (format == NULL) {
                formatted = g_strdup ("");
        } else {
            va_start (args, format);
            formatted = g_strdup_vprintf (format, args);
            va_end (args);
        }

        if (func != NULL) {
                str = g_strdup_printf ("MARK: %s %s: %s %s", g_get_prgname(), func, note ? note : "", formatted);
        } else {
                str = g_strdup_printf ("MARK: %s: %s %s", g_get_prgname(), note ? note : "", formatted);
        }

        g_free (formatted);

        g_access (str, F_OK);
        g_free (str);
}
#endif

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

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

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

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

      g_atomic_int_inc (&subscription->ref_count);

      return subscription;
}

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

      if (g_atomic_int_dec_and_test (&subscription->ref_count)) {
            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;
      MappingProtocolMessage      *message;
      MappingProtocolMonitorEvent *event;
      gboolean                     res;

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

      if (sub->cancelled) {
            DEBUG_PRINT ("Skipping event for cancelled subscription: %p %s %d", sub, filename, type);

            return FALSE;
      }

      connection = sub->connection;

      g_return_val_if_fail (connection != NULL, FALSE);

      message = mapping_protocol_message_new_monitor_event ();
      event = MAPPING_PROTOCOL_MONITOR_EVENT (message);

      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",
                 type, sub->userdata, filename, sub, connection);

      res = mapping_protocol_channel_send (connection->source, message);

      mapping_protocol_message_unref (message);

      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;

      virtual_node_unref (ddata->node);
      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);

      DEBUG_PRINT ("subscription event for node %p %s %d", node, filename, type);

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

            if (!sub || sub->cancelled) {
                  DEBUG_PRINT ("subscription cancelled, skipping");
                  continue;
            }

            ddata = g_new (DispatchData, 1);
            ddata->node = virtual_node_ref (node);
            ddata->subscription = 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;
      node->ref_count = 1;

      return node;
}

static void
virtual_node_free (VirtualNode *node,
               gboolean     deep)
{
      GList *l;

      DEBUG_PRINT ("freeing node %p %s", node, node->filename);

      g_free (node->filename);

      switch (node->type) {
      case VIRTUAL_NODE_FILE:
            if (node->backing_file != NULL) {
                  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_unref ((VirtualNode *)l->data);
                  }
            }
            break;
      default:
            g_assert_not_reached ();
            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_node_unref (VirtualNode *node)
{
      g_return_if_fail (node != NULL);
      g_return_if_fail (node->ref_count > 0);

      if (g_atomic_int_dec_and_test (&node->ref_count)) {
            virtual_node_free (node, TRUE);
      }
}

static VirtualNode *
virtual_node_ref (VirtualNode *node)
{
      g_return_val_if_fail (node != NULL, NULL);
      g_return_val_if_fail (node->ref_count > 0, NULL);

      g_atomic_int_inc (&node->ref_count);
      return node;
}

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

static void
virtual_root_unref (VirtualRoot *node)
{
      g_return_if_fail (node != NULL);
      g_return_if_fail (node->ref_count > 0);

      if (g_atomic_int_dec_and_test (&node->ref_count)) {
            virtual_root_free (node);
      }
}

#if 0
static VirtualRoot *
virtual_root_ref (VirtualRoot *node)
{
      g_return_val_if_fail (node != NULL, NULL);
      g_return_val_if_fail (node->ref_count > 0, NULL);

      g_atomic_int_inc (&node->ref_count);
      return node;
}
#endif

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_new0 (VirtualRoot, 1);

      dir = mkdtemp (tempdir);

      if (dir == NULL) {
            g_free (tempdir);
            g_free (root);
            root = NULL;
            return NULL;
      }

      root->ref_count = 1;
      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);

      /* list takes ownership of ref */
      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);
      virtual_node_unref (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_unref (file);
                  return NULL;
            }
            close (fd);
            g_unlink (template);

            file->backing_file = template;
            file->owned_file = TRUE;
      }

      /* list takes ownership of ref */
      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;
      }

      /* temp ref */
      virtual_node_ref (file);

      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);

      /* drop temp ref */
      virtual_node_unref (file);

      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;
      }

      /* temp ref */
      virtual_node_ref (file);

      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);

      /* drop temp ref */
      virtual_node_unref (file);

      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", 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) {
            VirtualNode *file;

            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 {
                        file = 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", 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);

            /* temp ref */
            virtual_node_ref (old_node);

            virtual_unlink (new_dir, new_node);
            virtual_unlink (old_dir, old_node);

            new_dir->children = g_list_append (new_dir->children, virtual_node_ref (old_node));

            /* drop temp ref */
            virtual_node_unref (old_node);
      } 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);

            /* temp ref */
            virtual_node_ref (old_node);

            virtual_unlink (old_dir, old_node);

            new_dir->children = g_list_append (new_dir->children, virtual_node_ref (old_node));

            /* drop temp ref */
            virtual_node_unref (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] = g_strdup (node->filename);
            if (node->type == VIRTUAL_NODE_FILE) {
                  listing [i * 2 + 1] = g_strdup (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", 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", 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", 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", sub);
            res = remove_subscription (connection, sub);
            if (GNOME_VFS_OK != res) {
                  DEBUG_PRINT ("Removing connection failed: %d", res);
            }
      }

      mapping_protocol_channel_unref (connection->source);

      g_free (connection);
      connection = NULL;
}

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

      g_atomic_int_inc (&connection->ref_count);

      return connection;
}

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

      if (g_atomic_int_dec_and_test (&connection->ref_count)) {
            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", 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", g_list_length (connections));

      last_disconnect = time (NULL);
}

static gboolean
connection_error (MappingConnection *connection)
{
      DEBUG_PRINT ("connection_error: source %p", 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", 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", 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 void
handle_message (MappingProtocolChannel *channel,
            MappingProtocolMessage *message,
            gpointer                data)
{
      MappingConnection      *connection;
      MappingProtocolMessage *reply_message;
      MappingProtocolReply   *reply;
      MappingProtocolRequest *req;
      gboolean                res;

      connection = (MappingConnection *)data;

      if (message == NULL) {
            connection_error (connection);
            return;
      }

      if (message->type != MAPPING_PROTOCOL_MESSAGE_REQUEST) {
            return;
      }

      req = MAPPING_PROTOCOL_REQUEST (message);

      reply_message = mapping_protocol_message_new_reply (message);
      reply = MAPPING_PROTOCOL_REPLY (reply_message);

      DEBUG_PRINT ("Decoded new request type %s", 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",
                 operation_string (req->operation), req->path1, reply->result, reply->path, reply->n_strings);

      res = mapping_protocol_channel_send (channel, reply_message);

        mapping_protocol_message_unref (reply_message);
}

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 takes a reference */
      connection = connection_new (io);
      mapping_protocol_channel_set_message_handler (io, handle_message, connection);

      DEBUG_PRINT ("Handling new client source: %p", 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_unref (root);
      return TRUE;
}

static void
free_roots (void)
{
      g_hash_table_foreach_remove (roots,
                             free_roots_helper, NULL);
      g_hash_table_destroy (roots);
      roots = 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;
}

#ifdef MEMLEAK_DEBUG_TIME
static gboolean
debug_shutdown (GMainLoop *loop)
{
      g_list_foreach (connections, (GFunc)connection_unref, NULL);
      g_list_free (connections);
      connections = NULL;

      g_main_loop_quit (loop);

      return FALSE;
}
#endif

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 ();

      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);

#ifdef MEMLEAK_DEBUG_TIME
        g_timeout_add (MEMLEAK_DEBUG_TIME * 1000,
                       (GSourceFunc)debug_shutdown,
                       loop);
#endif

      g_main_loop_run (loop);

      g_main_loop_unref (loop);

      free_roots ();
      return 0;
}

Generated by  Doxygen 1.6.0   Back to index