check against all pending requests with same hash, not just first; this is a true...
[oweals/gnunet.git] / src / fs / fs_namespace.c
index 873494c4f4e184381834a4ad2507ccfdc492763c..5d52265b74c9c05916bb28053d9f33d9e5de0abf 100644 (file)
@@ -1,10 +1,10 @@
 /*
      This file is part of GNUnet
 /*
      This file is part of GNUnet
-     (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Christian Grothoff (and other contributing authors)
+     Copyright (C) 2003-2013 Christian Grothoff (and other contributing authors)
 
      GNUnet is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published
 
      GNUnet 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, or (at your
+     by the Free Software Foundation; either version 3, or (at your
      option) any later version.
 
      GNUnet is distributed in the hope that it will be useful, but
      option) any later version.
 
      GNUnet is distributed in the hope that it will be useful, but
 
      You should have received a copy of the GNU General Public License
      along with GNUnet; see the file COPYING.  If not, write to the
 
      You should have received a copy of the GNU General Public License
      along with GNUnet; see the file COPYING.  If not, write to the
-     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-     Boston, MA 02111-1307, USA.
+     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+     Boston, MA 02110-1301, USA.
 */
 
 /**
  * @file fs/fs_namespace.c
 */
 
 /**
  * @file fs/fs_namespace.c
- * @brief create and destroy namespaces
+ * @brief publishing to namespaces, and tracking updateable entries
+ *        for our namespaces
  * @author Christian Grothoff
  */
 #include "platform.h"
  * @author Christian Grothoff
  */
 #include "platform.h"
 #include "gnunet_signatures.h"
 #include "gnunet_util_lib.h"
 #include "gnunet_fs_service.h"
 #include "gnunet_signatures.h"
 #include "gnunet_util_lib.h"
 #include "gnunet_fs_service.h"
-#include "fs.h"
+#include "fs_api.h"
+#include "fs_publish_ublock.h"
+
+
+/**
+ * Information about an (updateable) node in the
+ * namespace.
+ */
+struct NamespaceUpdateNode
+{
+  /**
+   * Identifier for this node.
+   */
+  char *id;
+
+  /**
+   * Identifier of children of this node.
+   */
+  char *update;
+
+  /**
+   * Metadata for this entry.
+   */
+  struct GNUNET_CONTAINER_MetaData *md;
+
+  /**
+   * URI of this entry in the namespace.
+   */
+  struct GNUNET_FS_Uri *uri;
+
+  /**
+   * Namespace update generation ID.  Used to ensure
+   * freshness of the tree_id.
+   */
+  unsigned int nug;
+
+  /**
+   * TREE this entry belongs to (if nug is current).
+   */
+  unsigned int tree_id;
+
+};
+
+
+/**
+ * Handle to update information for a namespace.
+ */
+struct GNUNET_FS_UpdateInformationGraph
+{
+
+  /**
+   * Handle to the FS service context.
+   */
+  struct GNUNET_FS_Handle *h;
+
+  /**
+   * Array with information about nodes in the namespace.
+   */
+  struct NamespaceUpdateNode **update_nodes;
+
+  /**
+   * Private key for the namespace.
+   */
+  struct GNUNET_CRYPTO_EcdsaPrivateKey ns;
+
+  /**
+   * Hash map mapping identifiers of update nodes
+   * to the update nodes (initialized on-demand).
+   */
+  struct GNUNET_CONTAINER_MultiHashMap *update_map;
+
+  /**
+   * Size of the update nodes array.
+   */
+  unsigned int update_node_count;
+
+  /**
+   * Reference counter.
+   */
+  unsigned int rc;
+
+  /**
+   * Generator for unique nug numbers.
+   */
+  unsigned int nug_gen;
+};
+
 
 /**
  * Return the name of the directory in which we store
 
 /**
  * Return the name of the directory in which we store
- * our local namespaces (or rather, their public keys).
+ * the update information graph for the given local namespace.
  *
  *
- * @param h global fs handle 
+ * @param h file-sharing handle
+ * @param ns namespace handle
  * @return NULL on error, otherwise the name of the directory
  */
 static char *
  * @return NULL on error, otherwise the name of the directory
  */
 static char *
-get_namespace_directory (struct GNUNET_FS_Handle *h)
+get_update_information_directory (struct GNUNET_FS_Handle *h,
+                                 const struct GNUNET_CRYPTO_EcdsaPrivateKey *ns)
 {
   char *dn;
 {
   char *dn;
+  char *ret;
+  struct GNUNET_CRYPTO_EcdsaPublicKey pub;
+  struct GNUNET_HashCode hc;
+  struct GNUNET_CRYPTO_HashAsciiEncoded enc;
 
   if (GNUNET_OK !=
 
   if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_filename (h->cfg,
-                                              "FS",
-                                              "IDENTITY_DIR",
-                                              &dn))
+      GNUNET_CONFIGURATION_get_value_filename (h->cfg, "FS", "UPDATE_DIR",
+                                               &dn))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                              "fs", "UPDATE_DIR");
+    return NULL;
+  }
+  GNUNET_CRYPTO_ecdsa_key_get_public (ns, &pub);
+  GNUNET_CRYPTO_hash (&pub, sizeof (pub), &hc);
+  GNUNET_CRYPTO_hash_to_enc (&hc,
+                            &enc);
+  GNUNET_asprintf (&ret, "%s%s%s",
+                  dn,
+                  DIR_SEPARATOR_STR,
+                  (const char *) enc.encoding);
+  GNUNET_free (dn);
+  return ret;
+}
+
+
+/**
+ * Release memory occupied by UIG datastructure.
+ *
+ * @param uig data structure to free
+ */
+static void
+free_update_information_graph (struct GNUNET_FS_UpdateInformationGraph *uig)
+{
+  unsigned int i;
+  struct NamespaceUpdateNode *nsn;
+
+  for (i = 0; i < uig->update_node_count; i++)
+  {
+    nsn = uig->update_nodes[i];
+    GNUNET_CONTAINER_meta_data_destroy (nsn->md);
+    GNUNET_FS_uri_destroy (nsn->uri);
+    GNUNET_free (nsn->id);
+    GNUNET_free (nsn->update);
+    GNUNET_free (nsn);
+  }
+  GNUNET_array_grow (uig->update_nodes, uig->update_node_count,
+                    0);
+  if (NULL != uig->update_map)
+    GNUNET_CONTAINER_multihashmap_destroy (uig->update_map);
+  GNUNET_free (uig);
+}
+
+
+/**
+ * Write a namespace's update node graph to a file.
+ *
+ * @param uig update information graph to dump
+ */
+static void
+write_update_information_graph (struct GNUNET_FS_UpdateInformationGraph *uig)
+{
+  char *fn;
+  struct GNUNET_BIO_WriteHandle *wh;
+  unsigned int i;
+  struct NamespaceUpdateNode *n;
+  char *uris;
+
+  fn = get_update_information_directory (uig->h,
+                                        &uig->ns);
+  wh = GNUNET_BIO_write_open (fn);
+  if (NULL == wh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                _("Failed to open `%s' for writing: %s\n"), STRERROR (errno));
+    GNUNET_free (fn);
+    return;
+  }
+  if (GNUNET_OK != GNUNET_BIO_write_int32 (wh, uig->update_node_count))
+    goto END;
+  for (i = 0; i < uig->update_node_count; i++)
+  {
+    n = uig->update_nodes[i];
+    uris = GNUNET_FS_uri_to_string (n->uri);
+    if ((GNUNET_OK != GNUNET_BIO_write_string (wh, n->id)) ||
+        (GNUNET_OK != GNUNET_BIO_write_meta_data (wh, n->md)) ||
+        (GNUNET_OK != GNUNET_BIO_write_string (wh, n->update)) ||
+        (GNUNET_OK != GNUNET_BIO_write_string (wh, uris)))
     {
     {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                 _("Configuration fails to specify `%s' in section `%s'\n"),
-                 "IDENTITY_DIR",
-                 "fs");
-      return NULL;
+      GNUNET_free (uris);
+      break;
+    }
+    GNUNET_free (uris);
+  }
+END:
+  if (GNUNET_OK != GNUNET_BIO_write_close (wh))
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Failed to write `%s': %s\n"),
+                STRERROR (errno));
+  GNUNET_free (fn);
+}
+
+
+/**
+ * Read the namespace update node graph from a file.
+ *
+ * @param h FS handle to use
+ * @param ns namespace to read
+ * @return update graph, never NULL
+ */
+static struct GNUNET_FS_UpdateInformationGraph *
+read_update_information_graph (struct GNUNET_FS_Handle *h,
+                              const struct GNUNET_CRYPTO_EcdsaPrivateKey *ns)
+{
+  struct GNUNET_FS_UpdateInformationGraph *uig;
+  char *fn;
+  struct GNUNET_BIO_ReadHandle *rh;
+  unsigned int i;
+  struct NamespaceUpdateNode *n;
+  char *uris;
+  uint32_t count;
+  char *emsg;
+
+  uig = GNUNET_new (struct GNUNET_FS_UpdateInformationGraph);
+  uig->h = h;
+  uig->ns = *ns;
+  fn = get_update_information_directory (h, ns);
+  if (GNUNET_YES != GNUNET_DISK_file_test (fn))
+  {
+    GNUNET_free (fn);
+    return uig;
+  }
+  rh = GNUNET_BIO_read_open (fn);
+  if (NULL == rh)
+  {
+    GNUNET_free (fn);
+    return uig;
+  }
+  if (GNUNET_OK != GNUNET_BIO_read_int32 (rh, &count))
+  {
+    GNUNET_break (0);
+    goto END;
+  }
+  if (count > 1024 * 1024)
+  {
+    GNUNET_break (0);
+    goto END;
+  }
+  if (0 == count)
+    goto END;
+  uig->update_nodes =
+    GNUNET_malloc (count * sizeof (struct NamespaceUpdateNode *));
+
+  for (i = 0; i < count; i++)
+  {
+    n = GNUNET_new (struct NamespaceUpdateNode);
+    if ((GNUNET_OK != GNUNET_BIO_read_string (rh, "identifier", &n->id, 1024))
+        || (GNUNET_OK != GNUNET_BIO_read_meta_data (rh, "meta", &n->md)) ||
+        (GNUNET_OK !=
+         GNUNET_BIO_read_string (rh, "update-id", &n->update, 1024)) ||
+        (GNUNET_OK != GNUNET_BIO_read_string (rh, "uri", &uris, 1024 * 2)))
+    {
+      GNUNET_break (0);
+      GNUNET_free_non_null (n->id);
+      GNUNET_free_non_null (n->update);
+      if (n->md != NULL)
+        GNUNET_CONTAINER_meta_data_destroy (n->md);
+      GNUNET_free (n);
+      break;
+    }
+    n->uri = GNUNET_FS_uri_parse (uris, &emsg);
+    GNUNET_free (uris);
+    if (n->uri == NULL)
+    {
+      GNUNET_break (0);
+      GNUNET_free (emsg);
+      GNUNET_free (n->id);
+      GNUNET_free_non_null (n->update);
+      GNUNET_CONTAINER_meta_data_destroy (n->md);
+      GNUNET_free (n);
+      break;
     }
     }
-  return dn;
+    uig->update_nodes[i] = n;
+  }
+  uig->update_node_count = i;
+ END:
+  if (GNUNET_OK != GNUNET_BIO_read_close (rh, &emsg))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Failed to read `%s': %s\n"),
+               fn, emsg);
+    GNUNET_free (emsg);
+  }
+  GNUNET_free (fn);
+  return uig;
 }
 
 
 /**
 }
 
 
 /**
- * Context for advertising a namespace.
+ * Context for the SKS publication.
  */
  */
-struct AdvertisementContext
+struct GNUNET_FS_PublishSksContext
 {
 {
+
   /**
   /**
-   * Function to call with the result.
+   * URI of the new entry in the namespace.
    */
    */
-  GNUNET_FS_PublishContinuation cont;
+  struct GNUNET_FS_Uri *uri;
 
   /**
 
   /**
-   * Closure for cont.
+   * Namespace update node to add to namespace on success (or to be
+   * deleted if publishing failed).
    */
    */
-  void *cont_cls;
+  struct NamespaceUpdateNode *nsn;
+
+  /**
+   * Namespace we're publishing to.
+   */
+  struct GNUNET_CRYPTO_EcdsaPrivateKey ns;
 
   /**
 
   /**
-   * Datastore handle.
+   * Handle to the datastore.
    */
   struct GNUNET_DATASTORE_Handle *dsh;
 
   /**
    */
   struct GNUNET_DATASTORE_Handle *dsh;
 
   /**
-   * URI that was created.
-   */ 
-  struct GNUNET_FS_Uri *uri;
+   * Handle to FS.
+   */
+  struct GNUNET_FS_Handle *h;
+
+  /**
+   * Function to call once we're done.
+   */
+  GNUNET_FS_PublishContinuation cont;
+
+  /**
+   * Closure for cont.
+   */
+  void *cont_cls;
+
+  /**
+   * Handle for our UBlock operation request.
+   */
+  struct GNUNET_FS_PublishUblockContext *uc;
 };
 
 
 /**
 };
 
 
 /**
- * Continuation called to notify client about result of the
- * operation.
+ * Function called by the UBlock construction with
+ * the result from the PUT (UBlock) request.
  *
  *
- * @param cls closure (our struct AdvertismentContext)
- * @param success GNUNET_SYSERR on failure
- * @param msg NULL on success, otherwise an error message
+ * @param cls closure of type "struct GNUNET_FS_PublishSksContext*"
+ * @param msg error message (or NULL)
  */
 static void
  */
 static void
-advertisement_cont (void *cls,
-                   int success,
-                   const char *msg)
+sks_publish_cont (void *cls,
+                 const char *msg)
 {
 {
-  struct AdvertisementContext *ac = cls;
-  
-  if (GNUNET_OK != success)
-    ac->cont (ac->cont_cls, NULL, msg);
-  else
-    ac->cont (ac->cont_cls, ac->uri, NULL);
-  GNUNET_DATASTORE_disconnect (ac->dsh, GNUNET_NO);
-  GNUNET_FS_uri_destroy (ac->uri);
-  GNUNET_free (ac);
+  struct GNUNET_FS_PublishSksContext *psc = cls;
+  struct GNUNET_FS_UpdateInformationGraph *uig;
+
+  psc->uc = NULL;
+  if (NULL != msg)
+  {
+    if (NULL != psc->cont)
+      psc->cont (psc->cont_cls, NULL, msg);
+    GNUNET_FS_publish_sks_cancel (psc);
+    return;
+  }
+  if (NULL != psc->nsn)
+  {
+    /* FIXME: this can be done much more
+     * efficiently by simply appending to the
+     * file and overwriting the 4-byte header */
+    uig = read_update_information_graph (psc->h,
+                                        &psc->ns);
+    GNUNET_array_append (uig->update_nodes,
+                        uig->update_node_count,
+                        psc->nsn);
+    psc->nsn = NULL;
+    write_update_information_graph (uig);
+    free_update_information_graph (uig);
+  }
+  if (NULL != psc->cont)
+    psc->cont (psc->cont_cls, psc->uri, NULL);
+  GNUNET_FS_publish_sks_cancel (psc);
 }
 
 
 /**
 }
 
 
 /**
- * Publish an advertismement for a namespace.  
+ * Publish an SBlock on GNUnet.
  *
  * @param h handle to the file sharing subsystem
  *
  * @param h handle to the file sharing subsystem
- * @param namespace handle for the namespace that should be advertised
- * @param meta meta-data for the namespace advertisement
- * @param anonymity for the namespace advertismement
- * @param priority for the namespace advertisement
- * @param expiration for the namespace advertisement
- * @param rootEntry name of the root of the namespace
+ * @param ns namespace to publish in
+ * @param identifier identifier to use
+ * @param update update identifier to use
+ * @param meta metadata to use
+ * @param uri URI to refer to in the SBlock
+ * @param bo block options
+ * @param options publication options
  * @param cont continuation
  * @param cont_cls closure for cont
  * @param cont continuation
  * @param cont_cls closure for cont
+ * @return NULL on error ('cont' will still be called)
  */
  */
-void
-GNUNET_FS_namespace_advertise (struct GNUNET_FS_Handle *h,
-                              struct GNUNET_FS_Namespace *namespace,
-                              const struct GNUNET_CONTAINER_MetaData *meta,
-                              uint32_t anonymity,
-                              uint32_t priority,
-                              struct GNUNET_TIME_Absolute expiration,
-                              const char *rootEntry,
-                              GNUNET_FS_PublishContinuation cont,
-                              void *cont_cls)
+struct GNUNET_FS_PublishSksContext *
+GNUNET_FS_publish_sks (struct GNUNET_FS_Handle *h,
+                       const struct GNUNET_CRYPTO_EcdsaPrivateKey *ns,
+                       const char *identifier, const char *update,
+                       const struct GNUNET_CONTAINER_MetaData *meta,
+                       const struct GNUNET_FS_Uri *uri,
+                       const struct GNUNET_FS_BlockOptions *bo,
+                       enum GNUNET_FS_PublishOptions options,
+                       GNUNET_FS_PublishContinuation cont, void *cont_cls)
 {
 {
-  size_t reslen;
-  size_t size;
-  ssize_t mdsize;
-  struct NBlock *nb;
-  char *rtgt;
-  char *mdst;
-  struct GNUNET_DATASTORE_Handle *dsh;
-  struct AdvertisementContext *ctx;
+  struct GNUNET_FS_PublishSksContext *psc;
+  struct GNUNET_FS_Uri *sks_uri;
 
 
-  /* create advertisements */
-  mdsize = GNUNET_CONTAINER_meta_data_get_serialized_size (meta);
-  if (-1 == mdsize)
-    {
-      cont (cont_cls, NULL, _("Failed to serialize meta data"));
-      return;
-    }
-  reslen = strlen (rootEntry) + 1;
-  size = mdsize + sizeof (struct NBlock) + reslen;
-  if (size > MAX_NBLOCK_SIZE)
-    {
-      size = MAX_NBLOCK_SIZE;
-      mdsize = size - sizeof (struct NBlock) - reslen;
-    }
-  nb = GNUNET_malloc (size);
-  GNUNET_CRYPTO_rsa_key_get_public (namespace->key, &nb->subspace);
-  rtgt = (char *) &nb[1];
-  memcpy (rtgt, rootEntry, reslen);
-  mdst = &rtgt[reslen];
-  mdsize = GNUNET_CONTAINER_meta_data_serialize (meta,
-                                                &mdst,
-                                                mdsize,
-                                                GNUNET_CONTAINER_META_DATA_SERIALIZE_PART);
-  if (mdsize == -1)
-    {
-      GNUNET_break (0);
-      GNUNET_free (nb);
-      cont (cont_cls, NULL, _("Failed to serialize meta data"));
-      return;
-    }
-  size = mdsize + sizeof (struct NBlock) + reslen;
-  nb->purpose.size = htonl (size - sizeof (struct GNUNET_CRYPTO_RsaSignature));
-  nb->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_FS_NBLOCK);
-  GNUNET_break (GNUNET_OK == 
-               GNUNET_CRYPTO_rsa_sign (namespace->key,
-                                       &nb->purpose,
-                                       &nb->signature));
-  dsh = GNUNET_DATASTORE_connect (h->cfg, h->sched);
-  if (NULL == dsh)
+  sks_uri = GNUNET_new (struct GNUNET_FS_Uri);
+  sks_uri->type = GNUNET_FS_URI_SKS;
+  sks_uri->data.sks.identifier = GNUNET_strdup (identifier);
+  GNUNET_CRYPTO_ecdsa_key_get_public (ns,
+                                   &sks_uri->data.sks.ns);
+
+  psc = GNUNET_new (struct GNUNET_FS_PublishSksContext);
+  psc->h = h;
+  psc->uri = sks_uri;
+  psc->cont = cont;
+  psc->cont_cls = cont_cls;
+  psc->ns = *ns;
+  if (0 == (options & GNUNET_FS_PUBLISH_OPTION_SIMULATE_ONLY))
+  {
+    psc->dsh = GNUNET_DATASTORE_connect (h->cfg);
+    if (NULL == psc->dsh)
     {
     {
-      GNUNET_free (nb);
-      cont (cont_cls, NULL, _("Failed to connect to datastore service"));
-      return;
+      sks_publish_cont (psc,
+                       _("Failed to connect to datastore."));
+      return NULL;
     }
     }
-  ctx = GNUNET_malloc (sizeof (struct AdvertisementContext));
-  ctx->cont = cont;
-  ctx->cont_cls = cont_cls;
-  ctx->dsh = dsh;
-  ctx->uri = GNUNET_malloc (sizeof (struct GNUNET_FS_Uri));
-  ctx->uri->type = sks;
-  ctx->uri->data.sks.identifier = GNUNET_strdup ("");
-  GNUNET_CRYPTO_hash (&nb->subspace,
-                     sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
-                     &ctx->uri->data.sks.namespace); 
-  GNUNET_DATASTORE_put (dsh,
-                       0, 
-                       &ctx->uri->data.sks.namespace,
-                       size,
-                       nb,
-                       GNUNET_DATASTORE_BLOCKTYPE_NBLOCK,
-                       priority,
-                       anonymity,
-                       expiration,
-                       GNUNET_CONSTANTS_SERVICE_TIMEOUT, 
-                       &advertisement_cont,
-                       ctx);
+  }
+  if (NULL != update)
+  {
+    psc->nsn = GNUNET_new (struct NamespaceUpdateNode);
+    psc->nsn->id = GNUNET_strdup (identifier);
+    psc->nsn->update = GNUNET_strdup (update);
+    psc->nsn->md = GNUNET_CONTAINER_meta_data_duplicate (meta);
+    psc->nsn->uri = GNUNET_FS_uri_dup (uri);
+  }
+  psc->uc = GNUNET_FS_publish_ublock_ (h,
+                                      psc->dsh,
+                                      identifier,
+                                      update,
+                                      ns,
+                                      meta,
+                                      uri,
+                                      bo,
+                                      options,
+                                      &sks_publish_cont,
+                                      psc);
+  return psc;
 }
 
 
 /**
 }
 
 
 /**
- * Create a namespace with the given name; if one already
- * exists, return a handle to the existing namespace.
+ * Abort the SKS publishing operation.
  *
  *
- * @param h handle to the file sharing subsystem
- * @param name name to use for the namespace
- * @return handle to the namespace, NULL on error
+ * @param psc context of the operation to abort.
  */
  */
-struct GNUNET_FS_Namespace *
-GNUNET_FS_namespace_create (struct GNUNET_FS_Handle *h,
-                           const char *name)
+void
+GNUNET_FS_publish_sks_cancel (struct GNUNET_FS_PublishSksContext *psc)
 {
 {
-  char *dn;
-  char *fn;
-  struct GNUNET_FS_Namespace *ret;
-
-  dn = get_namespace_directory (h);
-  GNUNET_asprintf (&fn,
-                  "%s%s%s",
-                  dn,
-                  DIR_SEPARATOR_STR,
-                  name);
-  GNUNET_free (dn);
-  ret = GNUNET_malloc (sizeof (struct GNUNET_FS_Namespace));
-  ret->rc = 1;
-  ret->key = GNUNET_CRYPTO_rsa_key_create_from_file (fn);
-  if (ret->key == NULL)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                 _("Failed to create or read private key for namespace `%s'\n"),
-                 name);
-      GNUNET_free (ret);
-      GNUNET_free (fn);
-      return NULL;
-    }
-  ret->filename = fn;
-  return ret;
+  if (NULL != psc->uc)
+  {
+    GNUNET_FS_publish_ublock_cancel_ (psc->uc);
+    psc->uc = NULL;
+  }
+  if (NULL != psc->dsh)
+  {
+    GNUNET_DATASTORE_disconnect (psc->dsh, GNUNET_NO);
+    psc->dsh = NULL;
+  }
+  GNUNET_FS_uri_destroy (psc->uri);
+  if (NULL != psc->nsn)
+  {
+    GNUNET_CONTAINER_meta_data_destroy (psc->nsn->md);
+    GNUNET_FS_uri_destroy (psc->nsn->uri);
+    GNUNET_free (psc->nsn->id);
+    GNUNET_free (psc->nsn->update);
+    GNUNET_free (psc->nsn);
+  }
+  GNUNET_free (psc);
 }
 
 
 /**
 }
 
 
 /**
- * Delete a namespace handle.  Can be used for a clean shutdown (free
- * memory) or also to freeze the namespace to prevent further
- * insertions by anyone.
- *
- * @param namespace handle to the namespace that should be deleted / freed
- * @param freeze prevents future insertions; creating a namespace
- *        with the same name again will create a fresh namespace instead
+ * Closure for 'process_update_node'.
+ */
+struct ProcessUpdateClosure
+{
+  /**
+   * Function to call for each node.
+   */
+  GNUNET_FS_IdentifierProcessor ip;
+
+  /**
+   * Closure for 'ip'.
+   */
+  void *ip_cls;
+};
+
+
+/**
+ * Call the iterator in the closure for each node.
  *
  *
- * @return GNUNET_OK on success, GNUNET_SYSERR on error
+ * @param cls closure (of type 'struct ProcessUpdateClosure *')
+ * @param key current key code
+ * @param value value in the hash map (of type 'struct NamespaceUpdateNode *')
+ * @return GNUNET_YES if we should continue to
+ *         iterate,
+ *         GNUNET_NO if not.
  */
  */
-int 
-GNUNET_FS_namespace_delete (struct GNUNET_FS_Namespace *namespace,
-                           int freeze)
+static int
+process_update_node (void *cls,
+                    const struct GNUNET_HashCode *key,
+                    void *value)
 {
 {
-  namespace->rc--;
-  if (freeze)
-    {
-      if (0 != UNLINK (namespace->filename))
-       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
-                                 "unlink",
-                                 namespace->filename);      
-    }
-  if (0 == namespace->rc)
-    {
-      GNUNET_CRYPTO_rsa_key_free (namespace->key);
-      GNUNET_free (namespace->filename);
-      GNUNET_free (namespace);
-    }
-  return GNUNET_OK;
+  struct ProcessUpdateClosure *pc = cls;
+  struct NamespaceUpdateNode *nsn = value;
+
+  pc->ip (pc->ip_cls,
+         nsn->id,
+         nsn->uri,
+         nsn->md,
+         nsn->update);
+  return GNUNET_YES;
 }
 
 
 /**
 }
 
 
 /**
- * Context for the 'process_namespace' callback.
- * Specifies a function to call on each namespace.
+ * Closure for 'find_trees'.
  */
  */
-struct ProcessNamespaceContext
+struct FindTreeClosure
 {
   /**
 {
   /**
-   * Function to call.
+   * UIG we are operating on.
+   */
+  struct GNUNET_FS_UpdateInformationGraph *uig;
+
+  /**
+   * Array with 'head's of TREEs.
+   */
+  struct NamespaceUpdateNode **tree_array;
+
+  /**
+   * Size of 'tree_array'
+   */
+  unsigned int tree_array_size;
+
+  /**
+   * Current generational ID used.
    */
    */
-  GNUNET_FS_NamespaceInfoProcessor cb;
+  unsigned int nug;
 
   /**
 
   /**
-   * Closure for 'cb'.
+   * Identifier for the current TREE, or UINT_MAX for none yet.
    */
    */
-  void *cb_cls;
+  unsigned int id;
 };
 
 
 /**
 };
 
 
 /**
- * Function called with a filename of a namespace. Reads the key and
- * calls the callback.
+ * Find all nodes reachable from the current node (including the
+ * current node itself).  If they are in no tree, add them to the
+ * current one.   If they are the head of another tree, merge the
+ * trees.  If they are in the middle of another tree, let them be.
+ * We can tell that a node is already in an tree by checking if
+ * its 'nug' field is set to the current 'nug' value.  It is the
+ * head of an tree if it is in the 'tree_array' under its respective
+ * 'tree_id'.
+ *
+ * In short, we're trying to find the smallest number of tree to
+ * cover a directed graph.
  *
  *
- * @param cls closure (struct ProcessNamespaceContext)
- * @param filename complete filename (absolute path)
- * @return GNUNET_OK to continue to iterate,
- *  GNUNET_SYSERR to abort iteration with error!
+ * @param cls closure (of type 'struct FindTreeClosure')
+ * @param key current key code
+ * @param value value in the hash map
+ * @return GNUNET_YES if we should continue to
+ *         iterate,
+ *         GNUNET_NO if not.
  */
 static int
  */
 static int
-process_namespace (void *cls, 
-                  const char *filename)
+find_trees (void *cls,
+           const struct GNUNET_HashCode *key,
+           void *value)
 {
 {
-  struct ProcessNamespaceContext *pnc = cls;
-  struct GNUNET_CRYPTO_RsaPrivateKey *key;
-  struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pk;
-  GNUNET_HashCode id;
-  const char *name;
-  const char *t;
-
-  key = GNUNET_CRYPTO_rsa_key_create_from_file (filename);
-  if (key == NULL)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                 _("Failed to read namespace private key file `%s', deleting it!\n"),
-                 filename);
-      if (0 != UNLINK (filename))
-       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
-                                 "unlink",
-                                 filename);
-      return GNUNET_OK;
-    }
-  GNUNET_CRYPTO_rsa_key_get_public (key, &pk);
-  GNUNET_CRYPTO_rsa_key_free (key);
-  GNUNET_CRYPTO_hash (&pk, sizeof(pk), &id); 
-  name = filename;
-  while (NULL != (t = strstr (name, DIR_SEPARATOR_STR)))
-    name = t + 1;
-  pnc->cb (pnc->cb_cls,
-          name,
-          &id);
-  return GNUNET_OK;
+  struct FindTreeClosure *fc = cls;
+  struct NamespaceUpdateNode *nsn = value;
+  struct GNUNET_HashCode hc;
+
+  if (nsn->nug == fc->nug)
+  {
+    if (UINT_MAX == nsn->tree_id)
+      return GNUNET_YES;        /* circular */
+    GNUNET_assert (nsn->tree_id < fc->tree_array_size);
+    if (fc->tree_array[nsn->tree_id] != nsn)
+      return GNUNET_YES;        /* part of "another" (directed) TREE,
+                                 * and not root of it, end trace */
+    if (nsn->tree_id == fc->id)
+      return GNUNET_YES;        /* that's our own root (can this be?) */
+    /* merge existing TREE, we have a root for both */
+    fc->tree_array[nsn->tree_id] = NULL;
+    if (UINT_MAX == fc->id)
+      fc->id = nsn->tree_id;    /* take over ID */
+  }
+  else
+  {
+    nsn->nug = fc->nug;
+    nsn->tree_id = UINT_MAX;    /* mark as undef */
+    /* trace */
+    GNUNET_CRYPTO_hash (nsn->update, strlen (nsn->update), &hc);
+    GNUNET_CONTAINER_multihashmap_get_multiple (fc->uig->update_map, &hc,
+                                                &find_trees, fc);
+  }
+  return GNUNET_YES;
 }
 
 
 /**
 }
 
 
 /**
- * Build a list of all available local (!) namespaces The returned
- * names are only the nicknames since we only iterate over the local
- * namespaces.
+ * List all of the identifiers in the namespace for which we could
+ * produce an update.  Namespace updates form a graph where each node
+ * has a name.  Each node can have any number of URI/meta-data entries
+ * which can each be linked to other nodes.  Cycles are possible.
  *
  *
- * @param h handle to the file sharing subsystem
- * @param cb function to call on each known namespace
- * @param cb_cls closure for cb
+ * Calling this function with "next_id" NULL will cause the library to
+ * call "ip" with a root for each strongly connected component of the
+ * graph (a root being a node from which all other nodes in the Tree
+ * are reachable).
+ *
+ * Calling this function with "next_id" being the name of a node will
+ * cause the library to call "ip" with all children of the node.  Note
+ * that cycles within the final tree are possible (including self-loops).
+ * I know, odd definition of a tree, but the GUI will display an actual
+ * tree (GtkTreeView), so that's what counts for the term here.
+ *
+ * @param h fs handle to use
+ * @param ns namespace to inspect for updateable content
+ * @param next_id ID to look for; use NULL to look for tree roots
+ * @param ip function to call on each updateable identifier
+ * @param ip_cls closure for ip
  */
  */
-void 
-GNUNET_FS_namespace_list (struct GNUNET_FS_Handle *h,
-                         GNUNET_FS_NamespaceInfoProcessor cb,
-                         void *cb_cls)
+void
+GNUNET_FS_namespace_list_updateable (struct GNUNET_FS_Handle *h,
+                                    const struct GNUNET_CRYPTO_EcdsaPrivateKey *ns,
+                                     const char *next_id,
+                                     GNUNET_FS_IdentifierProcessor ip,
+                                     void *ip_cls)
 {
 {
-  char *dn;
-  struct ProcessNamespaceContext ctx;
-  
-  dn = get_namespace_directory (h);
-  if (dn == NULL)
+  unsigned int i;
+  unsigned int nug;
+  struct GNUNET_HashCode hc;
+  struct NamespaceUpdateNode *nsn;
+  struct ProcessUpdateClosure pc;
+  struct FindTreeClosure fc;
+  struct GNUNET_FS_UpdateInformationGraph *uig;
+
+  uig = read_update_information_graph (h, ns);
+  if (NULL == uig->update_nodes)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "No updateable nodes found for ID `%s'\n", next_id);
+    free_update_information_graph (uig);
+    return;                     /* no nodes */
+  }
+  uig->update_map =
+    GNUNET_CONTAINER_multihashmap_create (2 +
+                                         3 * uig->update_node_count /
+                                         4,
+                                         GNUNET_NO);
+  for (i = 0; i < uig->update_node_count; i++)
+  {
+    nsn = uig->update_nodes[i];
+    GNUNET_CRYPTO_hash (nsn->id, strlen (nsn->id), &hc);
+    GNUNET_CONTAINER_multihashmap_put (uig->update_map, &hc, nsn,
+                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+  }
+  if (NULL != next_id)
+  {
+    GNUNET_CRYPTO_hash (next_id, strlen (next_id), &hc);
+    pc.ip = ip;
+    pc.ip_cls = ip_cls;
+    GNUNET_CONTAINER_multihashmap_get_multiple (uig->update_map, &hc,
+                                                &process_update_node, &pc);
+    free_update_information_graph (uig);
     return;
     return;
-  ctx.cb = cb;
-  ctx.cb_cls = cb_cls;
-  GNUNET_DISK_directory_scan (dn,
-                             &process_namespace,
-                             &ctx);
-  GNUNET_free (dn);
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Calculating TREEs to find roots of update trees\n");
+  /* Find heads of TREEs in update graph */
+  nug = ++uig->nug_gen;
+  fc.tree_array = NULL;
+  fc.tree_array_size = 0;
+
+  for (i = 0; i < uig->update_node_count; i++)
+  {
+    nsn = uig->update_nodes[i];
+    if (nsn->nug == nug)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "TREE of node `%s' is %u\n", nsn->id,
+                  nsn->nug);
+      continue;                 /* already placed in TREE */
+    }
+    GNUNET_CRYPTO_hash (nsn->update, strlen (nsn->update), &hc);
+    nsn->nug = nug;
+    nsn->tree_id = UINT_MAX;
+    fc.id = UINT_MAX;
+    fc.nug = nug;
+    fc.uig = uig;
+    GNUNET_CONTAINER_multihashmap_get_multiple (uig->update_map, &hc,
+                                                &find_trees, &fc);
+    if (UINT_MAX == fc.id)
+    {
+      /* start new TREE */
+      for (fc.id = 0; fc.id < fc.tree_array_size; fc.id++)
+      {
+        if (NULL == fc.tree_array[fc.id])
+        {
+          fc.tree_array[fc.id] = nsn;
+          nsn->tree_id = fc.id;
+          break;
+        }
+      }
+      if (fc.id == fc.tree_array_size)
+      {
+        GNUNET_array_append (fc.tree_array, fc.tree_array_size, nsn);
+        nsn->tree_id = fc.id;
+      }
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  "Starting new TREE %u with node `%s'\n", nsn->tree_id,
+                  nsn->id);
+      /* put all nodes with same identifier into this TREE */
+      GNUNET_CRYPTO_hash (nsn->id, strlen (nsn->id), &hc);
+      fc.id = nsn->tree_id;
+      fc.nug = nug;
+      fc.uig = uig;
+      GNUNET_CONTAINER_multihashmap_get_multiple (uig->update_map, &hc,
+                                                  &find_trees, &fc);
+    }
+    else
+    {
+      /* make head of TREE "id" */
+      fc.tree_array[fc.id] = nsn;
+      nsn->tree_id = fc.id;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+               "TREE of node `%s' is %u\n", nsn->id,
+                fc.id);
+  }
+  for (i = 0; i < fc.tree_array_size; i++)
+  {
+    nsn = fc.tree_array[i];
+    if (NULL != nsn)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Root of TREE %u is node `%s'\n", i,
+                  nsn->id);
+      ip (ip_cls, nsn->id, nsn->uri, nsn->md, nsn->update);
+    }
+  }
+  GNUNET_array_grow (fc.tree_array, fc.tree_array_size, 0);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Done processing TREEs\n");
+  free_update_information_graph (uig);
 }
 
 }
 
-/* end of fs_namespace.c */
 
 
+/* end of fs_namespace.c */