gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gnunet] branch master updated (391794a46 -> 94ea7a1fa)


From: gnunet
Subject: [gnunet] branch master updated (391794a46 -> 94ea7a1fa)
Date: Wed, 28 Jul 2021 15:30:58 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a change to branch master
in repository gnunet.

    from 391794a46 -fix json pack bugs
     new 7615d46b0 implement config inline globbing
     new 94ea7a1fa implement @inline-secret@ directive

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/include/gnunet_disk_lib.h |  26 +++
 src/util/configuration.c      | 407 ++++++++++++++++++++++++++++++++++--------
 src/util/disk.c               | 167 ++++++++++++++++-
 3 files changed, 521 insertions(+), 79 deletions(-)

diff --git a/src/include/gnunet_disk_lib.h b/src/include/gnunet_disk_lib.h
index 3805039fc..7dfd9ccf1 100644
--- a/src/include/gnunet_disk_lib.h
+++ b/src/include/gnunet_disk_lib.h
@@ -266,6 +266,16 @@ GNUNET_DISK_handle_invalid (const struct 
GNUNET_DISK_FileHandle *h);
 enum GNUNET_GenericReturnValue
 GNUNET_DISK_file_test (const char *fil);
 
+/**
+ * Check that fil corresponds to a filename and the file has read permissions.
+ *
+ * @param fil filename to check
+ * @return #GNUNET_YES if yes, #GNUNET_NO if file doesn't exist or
+ *         has no read permissions, #GNUNET_SYSERR if something else
+ *         (will print an error message in that case, too).
+ */
+enum GNUNET_GenericReturnValue
+GNUNET_DISK_file_test_read (const char *fil);
 
 /**
  * Move a file out of the way (create a backup) by renaming it to "orig.NUM~"
@@ -654,6 +664,22 @@ GNUNET_DISK_directory_scan (const char *dir_name,
                             GNUNET_FileNameCallback callback,
                             void *callback_cls);
 
+/**
+ * Find all files matching a glob pattern.
+ *
+ * Currently, the glob_pattern only supports asterisks in the last
+ * path component.
+ *
+ * @param glob_patterb the glob pattern to search for
+ * @param callback the method to call for each file
+ * @param callback_cls closure for @a callback
+ * @return the number of files found, -1 on error
+ */
+int
+GNUNET_DISK_glob (const char *glob_pattern,
+                  GNUNET_FileNameCallback callback,
+                  void *callback_cls);
+
 
 /**
  * Create the directory structure for storing
diff --git a/src/util/configuration.c b/src/util/configuration.c
index 4a1af10d3..3bb08e6c0 100644
--- a/src/util/configuration.c
+++ b/src/util/configuration.c
@@ -75,6 +75,14 @@ struct ConfigSection
    * name of the section
    */
   char *name;
+
+  /**
+   * Is the configuration section marked as inaccessible?
+   *
+   * This can happen if the section name is used in an @inline-secret@
+   * directive, but the referenced file can't be found or accessed.
+   */
+  bool inaccessible;
 };
 
 
@@ -230,35 +238,232 @@ GNUNET_CONFIGURATION_parse_and_run (const char *filename,
   return ret;
 }
 
+struct InlineGlobClosure
+{
+  struct GNUNET_CONFIGURATION_Handle *cfg;
+};
+
+/**
+ * Function called with a filename.
+ *
+ * @param cls closure
+ * @param filename complete filename (absolute path)
+ * @return #GNUNET_OK to continue to iterate,
+ *  #GNUNET_NO to stop iteration with no error,
+ *  #GNUNET_SYSERR to abort iteration with error!
+ */
+static int
+inline_glob_cb (void *cls,
+                const char *filename)
+{
+  struct InlineGlobClosure *igc = cls;
+
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Reading globbed config file '%s'\n",
+       filename);
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_parse (igc->cfg,
+                                  filename))
+  {
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Find a section entry from a configuration.
+ *
+ * @param cfg configuration to search in
+ * @param section name of the section to look for
+ * @return matching entry, NULL if not found
+ */
+static struct ConfigSection *
+find_section (const struct GNUNET_CONFIGURATION_Handle *cfg,
+              const char *section)
+{
+  struct ConfigSection *pos;
+
+  pos = cfg->sections;
+  while ((pos != NULL) && (0 != strcasecmp (section, pos->name)))
+    pos = pos->next;
+  return pos;
+}
+
+static void
+set_section_inaccessible (struct GNUNET_CONFIGURATION_Handle *cfg,
+                          const char *section)
+{
+  struct ConfigSection *sec;
 
+  sec = find_section (cfg, section);
+
+  if (NULL == sec)
+  {
+    sec = GNUNET_new (struct ConfigSection);
+    sec->name = GNUNET_strdup (section);
+    sec->next = cfg->sections;
+    cfg->sections = sec;
+    sec->entries = NULL;
+  }
+
+  sec->inaccessible = true;
+}
+
+/**
+ * Handle an inline directive.
+ *
+ * @returns #GNUNET_SYSERR on error, #GNUNET_OK otherwise
+ */
 enum GNUNET_GenericReturnValue
-GNUNET_CONFIGURATION_deserialize (struct GNUNET_CONFIGURATION_Handle *cfg,
-                                  const char *mem,
-                                  size_t size,
-                                  const char *source_filename)
+handle_inline (struct GNUNET_CONFIGURATION_Handle *cfg,
+               const char *path_or_glob,
+               bool path_is_glob,
+               const char *restrict_section,
+               const char *source_filename)
+{
+  char *inline_path;
+
+  /* We support the section restriction only for non-globs */
+  GNUNET_assert (! (path_is_glob && (NULL != restrict_section)));
+
+  if (NULL == source_filename)
+  {
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+         "Refusing to parse inline configurations, "
+         "not allowed without source filename!\n");
+    return GNUNET_SYSERR;
+  }
+  if ('/' == *path_or_glob)
+    inline_path = GNUNET_strdup (path_or_glob);
+  else
+  {
+    /* We compute the canonical, absolute path first,
+       so that relative imports resolve properly with symlinked
+       config files.  */
+    char *source_realpath;
+    char *endsep;
+
+    source_realpath = realpath (source_filename,
+                                NULL);
+    if (NULL == source_realpath)
+    {
+      /* Couldn't even resolve path of base dir. */
+      GNUNET_break (0);
+      /* failed to parse included config */
+      return GNUNET_SYSERR;
+    }
+    endsep = strrchr (source_realpath, '/');
+    GNUNET_assert (NULL != endsep);
+    *endsep = '\0';
+    GNUNET_asprintf (&inline_path,
+                     "%s/%s",
+                     source_realpath,
+                     path_or_glob);
+    free (source_realpath);
+  }
+  if (path_is_glob)
+  {
+    int nret;
+    struct InlineGlobClosure igc = {
+      .cfg = cfg,
+    };
+
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+         "processing config glob '%s'\n",
+         inline_path);
+
+    nret = GNUNET_DISK_glob (inline_path, inline_glob_cb, &igc);
+    if (-1 == nret)
+    {
+      GNUNET_free (inline_path);
+      return GNUNET_SYSERR;
+    }
+  }
+  else if (NULL != restrict_section)
+  {
+    struct GNUNET_CONFIGURATION_Handle *other_cfg;
+    enum GNUNET_GenericReturnValue fret;
+    struct ConfigSection *cs;
+
+    fret = GNUNET_DISK_file_test_read (inline_path);
+
+    if (GNUNET_OK != fret)
+    {
+      set_section_inaccessible (cfg, restrict_section);
+      GNUNET_free (inline_path);
+      return GNUNET_OK;
+    }
+
+    other_cfg = GNUNET_CONFIGURATION_create ();
+    if (GNUNET_OK != GNUNET_CONFIGURATION_parse (other_cfg,
+                                                 inline_path))
+    {
+      GNUNET_free (inline_path);
+      GNUNET_CONFIGURATION_destroy (other_cfg);
+      return GNUNET_SYSERR;
+    }
+
+    cs = find_section (other_cfg, restrict_section);
+    if (NULL == cs)
+    {
+      LOG (GNUNET_ERROR_TYPE_ERROR,
+           "inlined configuration '%s' does not contain section '%s'\n",
+           inline_path,
+           restrict_section);
+      GNUNET_free (inline_path);
+      GNUNET_free (other_cfg);
+      return GNUNET_SYSERR;
+    }
+    for (struct ConfigEntry *ce = cs->entries;
+         NULL != ce;
+         ce = ce->next)
+      GNUNET_CONFIGURATION_set_value_string (cfg,
+                                             restrict_section,
+                                             ce->key,
+                                             ce->val);
+    GNUNET_CONFIGURATION_destroy (other_cfg);
+  }
+  else if (GNUNET_OK !=
+           GNUNET_CONFIGURATION_parse (cfg,
+                                       inline_path))
+  {
+    GNUNET_free (inline_path);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_free (inline_path);
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+deserialize_internal (struct GNUNET_CONFIGURATION_Handle *cfg,
+                      const char *mem,
+                      size_t size,
+                      const char *source_filename)
 {
-  char *line;
-  char *line_orig;
   size_t line_size;
-  char *pos;
   unsigned int nr;
   size_t r_bytes;
   size_t to_read;
-  size_t i;
-  int emptyline;
   enum GNUNET_GenericReturnValue ret;
   char *section;
   char *eq;
   char *tag;
   char *value;
+  char *line_orig = NULL;
 
   ret = GNUNET_OK;
-  section = GNUNET_strdup ("");
+  section = NULL;
   nr = 0;
   r_bytes = 0;
-  line_orig = NULL;
   while (r_bytes < size)
   {
+    char *pos;
+    char *line;
+    bool emptyline;
+
     GNUNET_free (line_orig);
     /* fgets-like behaviour on buffer */
     to_read = size - r_bytes;
@@ -280,7 +485,7 @@ GNUNET_CONFIGURATION_deserialize (struct 
GNUNET_CONFIGURATION_Handle *cfg,
     nr++;
     /* tabs and '\r' are whitespace */
     emptyline = GNUNET_YES;
-    for (i = 0; i < line_size; i++)
+    for (size_t i = 0; i < line_size; i++)
     {
       if (line[i] == '\t')
         line[i] = ' ';
@@ -294,7 +499,7 @@ GNUNET_CONFIGURATION_deserialize (struct 
GNUNET_CONFIGURATION_Handle *cfg,
       continue;
 
     /* remove tailing whitespace */
-    for (i = line_size - 1;
+    for (size_t i = line_size - 1;
          (i >= 1) && (isspace ((unsigned char) line[i]));
          i--)
       line[i] = '\0';
@@ -308,60 +513,103 @@ GNUNET_CONFIGURATION_deserialize (struct 
GNUNET_CONFIGURATION_Handle *cfg,
          ('%' == line[0]) )
       continue;
 
-    /* handle special "@INLINE@" directive */
-    if (0 == strncasecmp (line,
-                          "@INLINE@ ",
-                          strlen ("@INLINE@ ")))
+    /* Handle special directives. */
+    if ('@' == line[0])
     {
-      char *inline_path;
+      char *end = strchr (line + 1, '@');
+      char *directive;
+      enum GNUNET_GenericReturnValue directive_ret;
 
-      if (NULL == source_filename)
+      if (NULL == end)
       {
-        LOG (GNUNET_ERROR_TYPE_DEBUG,
-             "Refusing to parse @INLINE@ configurations, "
-             "not allowed without source filename!\n");
+        LOG (GNUNET_ERROR_TYPE_WARNING,
+             _ ("Bad directive in line %u\n"),
+             nr);
         ret = GNUNET_SYSERR;
         break;
       }
-      /* FIXME: also trim space and end of line comment? */
-      value = &line[strlen ("@INLINE@ ")];
-      if ('/' == *value)
-        inline_path = GNUNET_strdup (value);
-      else
+      *end = '\0';
+      directive = line + 1;
+
+      if (0 == strcasecmp (directive, "INLINE"))
       {
-        /* We compute the canonical, absolute path first,
-           so that relative imports resolve properly with symlinked
-           config files.  */
-        char *source_realpath;
-        char *endsep;
-
-        source_realpath = realpath (source_filename,
-                                    NULL);
-        if (NULL == source_realpath)
+        const char *path = end + 1;
+
+        /* Skip space before path */
+        for (; isspace (*path); path++)
+          ;
+
+        directive_ret = handle_inline (cfg,
+                                       path,
+                                       false,
+                                       NULL,
+                                       source_filename);
+      }
+      else if (0 == strcasecmp (directive, "INLINE-MATCHING"))
+      {
+        const char *path = end + 1;
+
+        /* Skip space before path */
+        for (; isspace (*path); path++)
+          ;
+
+        directive_ret = handle_inline (cfg,
+                                       path,
+                                       true,
+                                       NULL,
+                                       source_filename);
+      }
+      else if (0 == strcasecmp (directive, "INLINE-SECRET"))
+      {
+        char *secname = end + 1;
+        char *secname_end;
+        const char *path;
+
+        /* Skip space before secname */
+        for (; isspace (*secname); secname++)
+          ;
+
+        secname_end = strchr (secname, ' ');
+
+        if (NULL == secname_end)
         {
-          /* Couldn't even resolve path of base dir. */
-          GNUNET_break (0);
-          ret = GNUNET_SYSERR;       /* failed to parse included config */
+          LOG (GNUNET_ERROR_TYPE_WARNING,
+               _ ("Bad inline-secret directive in line %u\n"),
+               nr);
+          ret = GNUNET_SYSERR;
           break;
         }
-        endsep = strrchr (source_realpath, '/');
-        GNUNET_assert (NULL != endsep);
-        *endsep = '\0';
-        GNUNET_asprintf (&inline_path,
-                         "%s/%s",
-                         source_realpath,
-                         value);
-        free (source_realpath);
+        *secname_end = '\0';
+        path = secname_end + 1;
+
+        /* Skip space before path */
+        for (; isspace (*path); path++)
+          ;
+
+        directive_ret = handle_inline (cfg,
+                                       path,
+                                       false,
+                                       secname,
+                                       source_filename);
       }
-      if (GNUNET_OK !=
-          GNUNET_CONFIGURATION_parse (cfg,
-                                      inline_path))
+      else
       {
-        GNUNET_free (inline_path);
-        ret = GNUNET_SYSERR;       /* failed to parse included config */
+        LOG (GNUNET_ERROR_TYPE_WARNING,
+             _ ("Unknown or malformed directive '%s' in line %u\n"),
+             directive,
+             nr);
+        ret = GNUNET_SYSERR;
+        break;
+      }
+      if (GNUNET_OK != directive_ret)
+      {
+        LOG (GNUNET_ERROR_TYPE_WARNING,
+             _ ("Bad directive '%s' in line %u\n"),
+             directive,
+             nr);
+        ret = GNUNET_SYSERR;
         break;
       }
-      GNUNET_free (inline_path);
       continue;
     }
     if (('[' == line[0]) && (']' == line[line_size - 1]))
@@ -375,10 +623,13 @@ GNUNET_CONFIGURATION_deserialize (struct 
GNUNET_CONFIGURATION_Handle *cfg,
     }
     if (NULL != (eq = strchr (line, '=')))
     {
+      size_t i;
+
       /* tag = value */
       tag = GNUNET_strndup (line, eq - line);
       /* remove tailing whitespace */
-      for (i = strlen (tag) - 1; (i >= 1) && (isspace ((unsigned char) 
tag[i]));
+      for (i = strlen (tag) - 1;
+           (i >= 1) && (isspace ((unsigned char) tag[i]));
            i--)
         tag[i] = '\0';
 
@@ -417,6 +668,16 @@ GNUNET_CONFIGURATION_deserialize (struct 
GNUNET_CONFIGURATION_Handle *cfg,
 }
 
 
+enum GNUNET_GenericReturnValue
+GNUNET_CONFIGURATION_deserialize (struct GNUNET_CONFIGURATION_Handle *cfg,
+                                  const char *mem,
+                                  size_t size,
+                                  const char *source_filename)
+{
+  return deserialize_internal (cfg, mem, size, source_filename);
+}
+
+
 enum GNUNET_GenericReturnValue
 GNUNET_CONFIGURATION_parse (struct GNUNET_CONFIGURATION_Handle *cfg,
                             const char *filename)
@@ -711,6 +972,14 @@ GNUNET_CONFIGURATION_iterate_section_values (
     spos = spos->next;
   if (NULL == spos)
     return;
+  if (spos->inaccessible)
+  {
+    LOG (GNUNET_ERROR_TYPE_WARNING,
+         "Section '%s' is marked as inaccessible, because the configuration "
+         " file that contains the section can't be read.\n",
+         section);
+    return;
+  }
   for (epos = spos->entries; NULL != epos; epos = epos->next)
     if (NULL != epos->val)
       iter (iter_cls, spos->name, epos->key, epos->val);
@@ -804,26 +1073,6 @@ GNUNET_CONFIGURATION_dup (const struct 
GNUNET_CONFIGURATION_Handle *cfg)
 }
 
 
-/**
- * Find a section entry from a configuration.
- *
- * @param cfg configuration to search in
- * @param section name of the section to look for
- * @return matching entry, NULL if not found
- */
-static struct ConfigSection *
-find_section (const struct GNUNET_CONFIGURATION_Handle *cfg,
-              const char *section)
-{
-  struct ConfigSection *pos;
-
-  pos = cfg->sections;
-  while ((pos != NULL) && (0 != strcasecmp (section, pos->name)))
-    pos = pos->next;
-  return pos;
-}
-
-
 /**
  * Find an entry from a configuration.
  *
@@ -842,6 +1091,16 @@ find_entry (const struct GNUNET_CONFIGURATION_Handle *cfg,
 
   if (NULL == (sec = find_section (cfg, section)))
     return NULL;
+  if (sec->inaccessible)
+  {
+    LOG (GNUNET_ERROR_TYPE_WARNING,
+         "Section '%s' is marked as inaccessible, because the configuration "
+         " file that contains the section can't be read.  Attempts to use "
+         "option '%s' will fail.\n",
+         section,
+         key);
+    return NULL;
+  }
   pos = sec->entries;
   while ((pos != NULL) && (0 != strcasecmp (key, pos->key)))
     pos = pos->next;
diff --git a/src/util/disk.c b/src/util/disk.c
index 3bafe311d..f68b32db5 100644
--- a/src/util/disk.c
+++ b/src/util/disk.c
@@ -432,9 +432,15 @@ GNUNET_DISK_directory_test (const char *fil, int 
is_readable)
   return GNUNET_YES;
 }
 
-
-enum GNUNET_GenericReturnValue
-GNUNET_DISK_file_test (const char *fil)
+/**
+ * Check if fil can be accessed using amode.
+ * 
+ * @param fil file to check for
+ * @param amode access mode
+ * @returns GNUnet error code
+ */
+static enum GNUNET_GenericReturnValue
+file_test_internal (const char *fil, int amode)
 {
   struct stat filestat;
   int ret;
@@ -461,7 +467,7 @@ GNUNET_DISK_file_test (const char *fil)
     GNUNET_free (rdir);
     return GNUNET_NO;
   }
-  if (access (rdir, F_OK) < 0)
+  if (access (rdir, amode) < 0)
   {
     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "access", rdir);
     GNUNET_free (rdir);
@@ -472,6 +478,20 @@ GNUNET_DISK_file_test (const char *fil)
 }
 
 
+enum GNUNET_GenericReturnValue
+GNUNET_DISK_file_test (const char *fil)
+{
+  return file_test_internal (fil, F_OK);
+}
+
+
+enum GNUNET_GenericReturnValue
+GNUNET_DISK_file_test_read (const char *fil)
+{
+  return file_test_internal (fil, R_OK);
+}
+
+
 enum GNUNET_GenericReturnValue
 GNUNET_DISK_directory_create (const char *dir)
 {
@@ -882,6 +902,143 @@ GNUNET_DISK_directory_scan (const char *dir_name,
   return count;
 }
 
+/**
+ * Check for a simple wildcard match.
+ * Only asterisks are allowed.
+ * Asterisks match everything, including slashes.
+ *
+ * @param pattern pattern with wildcards
+ * @param str string to match against
+ * @returns true on match, false otherwise
+ */
+static bool
+glob_match (const char *pattern, const char *str)
+{
+  /* Position in the input string */
+  const char *str_pos = str;
+  /* Position in the pattern */
+  const char *pat_pos = pattern;
+  /* Backtrack position in string */
+  const char *str_bt = NULL;
+  /* Backtrack position in pattern */
+  const char *pat_bt = NULL;
+
+  for (;;)
+  {
+    if (*pat_pos == '*')
+    {
+      str_bt = str_pos;
+      pat_bt = pat_pos++;
+    }
+    else if (*pat_pos == *str_pos)
+    {
+      if ('\0' == *pat_pos)
+        return true;
+      str_pos++;
+      pat_pos++;
+    }
+    else
+    {
+      if (NULL == str_bt)
+        return false;
+      /* Backtrack to match one more
+         character as part of the asterisk. */
+      str_pos = str_bt + 1;
+      if ('\0' == *str_pos)
+        return false;
+      pat_pos = pat_bt;
+    }
+  }
+}
+
+struct GlobClosure
+{
+  const char *glob;
+  GNUNET_FileNameCallback cb;
+  void *cls;
+};
+
+/**
+ * Function called with a filename.
+ *
+ * @param cls closure
+ * @param filename complete filename (absolute path)
+ * @return #GNUNET_OK to continue to iterate,
+ *  #GNUNET_NO to stop iteration with no error,
+ *  #GNUNET_SYSERR to abort iteration with error!
+ */
+static enum GNUNET_GenericReturnValue
+glob_cb (void *cls,
+         const char *filename)
+{
+  struct GlobClosure *gc = cls;
+  const char *fn;
+
+  fn = strrchr (filename, DIR_SEPARATOR);
+  fn = (NULL == fn) ? filename : (fn + 1);
+
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "checking glob '%s' against '%s'\n",
+       gc->glob,
+       fn);
+
+  if (glob_match (gc->glob, fn))
+  {
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+         "found glob match '%s'\n",
+         filename);
+    gc->cb (gc->cls, filename);
+  }
+  return GNUNET_OK;
+}
+
+
+int
+GNUNET_DISK_glob (const char *glob_pattern,
+                  GNUNET_FileNameCallback callback,
+                  void *callback_cls)
+{
+  char *mypat = GNUNET_strdup (glob_pattern);
+  char *sep;
+  int ret;
+
+  sep = strrchr (mypat, DIR_SEPARATOR);
+  if (NULL == sep)
+  {
+    GNUNET_free (mypat);
+    return -1;
+  }
+
+  *sep = '\0';
+
+  if (NULL != strchr (mypat, '*'))
+  {
+    GNUNET_free (mypat);
+    GNUNET_break (0);
+    LOG (GNUNET_ERROR_TYPE_ERROR,
+         "glob pattern may only contain '*' in the final path component\n");
+    return -1;
+  }
+
+  {
+    struct GlobClosure gc = {
+      .glob = sep + 1,
+      .cb = callback,
+      .cls = callback_cls,
+    };
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+         "scanning directory '%s' for glob matches on '%s'\n",
+         mypat,
+         gc.glob);
+    ret = GNUNET_DISK_directory_scan (mypat,
+                                      glob_cb,
+                                      &gc
+                                      );
+  }
+  GNUNET_free (mypat);
+  return ret;
+}
+
 
 /**
  * Function that removes the given directory by calling
@@ -997,7 +1154,7 @@ GNUNET_DISK_file_copy (const char *src,
   GNUNET_DISK_file_close (in);
   GNUNET_DISK_file_close (out);
   return GNUNET_OK;
-FAIL:
+  FAIL:
   GNUNET_free (buf);
   GNUNET_DISK_file_close (in);
   GNUNET_DISK_file_close (out);

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]