URI:
       Added configuration file parser (using the SAX API in libxml2). Added command line parsing. - susmb - mounting of SMB/CIFS shares via FUSE
  HTML git clone git://git.codemadness.org/susmb
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 0e75a1aa0cdd9993a0141293a190b7f16758371d
   DIR parent c43a2e4e311601dbbee65ff680b8ec388ecb543d
  HTML Author: geoff <devnull@localhost>
       Date:   Sun,  7 May 2006 21:25:33 +0000
       
       Added configuration file parser (using the SAX API in libxml2).
       Added command line parsing.
       
       Diffstat:
         M Makefile                            |      18 +++++++++++++-----
         A auth.c                              |      10 ++++++++++
         A conffile.c                          |     188 +++++++++++++++++++++++++++++++
         A conffile.h                          |      51 +++++++++++++++++++++++++++++++
         A doc/sax_model.mdzip                 |       0 
         A doc/sax_state_machine.png           |       0 
         A options.c                           |     150 +++++++++++++++++++++++++++++++
         A options.h                           |       9 +++++++++
         A sax.c                               |     706 +++++++++++++++++++++++++++++++
         A sax.h                               |       9 +++++++++
         M usmb.c                              |     266 ++++++++++++++++++++-----------
         A usmb.conf                           |      16 ++++++++++++++++
         M usmb.h                              |       7 -------
         M usmb_dir.c                          |      14 ++++++++------
         M usmb_file.c                         |      61 +++++++++++++++++++++++++++----
         M usmb_file.h                         |       2 ++
         A utils.c                             |      92 +++++++++++++++++++++++++++++++
         A utils.h                             |      15 +++++++++++++++
         A version.h                           |      21 +++++++++++++++++++++
       
       19 files changed, 1520 insertions(+), 115 deletions(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       @@ -1,12 +1,20 @@
        SAMBA = /opt/samba
        CC = gcc
       -CFLAGS = -Wall -W -std=c99 -pedantic -O $(shell pkg-config --cflags fuse) \
       +
       +CFLAGS = -Wall -W -std=c99 -pedantic -O \
                 -I$(SAMBA)/include -D_BSD_SOURCE -DFUSE_USE_VERSION=25 \
       -                                 -DHAVE_UTIME_H # -DDEBUG
       -LDLIBS = $(shell pkg-config --libs-only-l fuse) -lsmbclient
       -LDFLAGS = $(shell pkg-config --libs-only-L fuse) -L$(SAMBA)/lib
       +                                 -DHAVE_UTIME_H -ggdb -DDEBUG
       +
       +LDLIBS = -lsmbclient
       +LDFLAGS = -L$(SAMBA)/lib
       +
       +PACKAGES = glib-2.0 libxml-2.0 fuse
       +
       +CFLAGS += $(shell pkg-config --cflags $(PACKAGES))
       +LDLIBS += $(shell pkg-config --libs-only-l $(PACKAGES))
       +LDFLAGS += $(shell pkg-config --libs-only-L $(PACKAGES))
        
       -SOURCES = usmb.c usmb_dir.c usmb_file.c
       +SOURCES = conffile.c options.c sax.c usmb.c usmb_dir.c usmb_file.c utils.c
        OBJECTS = $(SOURCES:.c=.o)
        
        PROGRAM = usmb
   DIR diff --git a/auth.c b/auth.c
       @@ -0,0 +1,10 @@
       +struct auth {
       +        const char *server;
       +        const char *share;
       +        const char *domain;
       +        const char *username;
       +        const char *password;
       +};
       +
       +
       +
   DIR diff --git a/conffile.c b/conffile.c
       @@ -0,0 +1,188 @@
       +#include <assert.h>
       +#include <glib.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include "conffile.h"
       +#include "utils.h"
       +
       +
       +struct conffile {
       +  GHashTable *credentials;
       +  GHashTable *mounts;
       +};
       +
       +
       +struct credentials * credentials_new (const char *domain,
       +                                      const char *username,
       +                                      const char *password)
       +{
       +  struct credentials *creds = malloc (sizeof (struct credentials));
       +
       +  if (NULL == creds)
       +    return NULL;
       +  
       +  creds->domain = xstrdup (domain);
       +  creds->username = xstrdup (username);
       +  creds->password = xstrdup (password);
       +
       +  if ((NULL == creds->domain) ||
       +      (NULL == creds->username) ||
       +      (NULL == creds->password))
       +  {
       +    credentials_destroy (creds);
       +    creds = NULL;
       +  }
       +
       +  return creds;
       +}
       +
       +
       +void credentials_destroy (struct credentials *creds)
       +{
       +  assert (creds != NULL);
       +
       +  xfree (creds->password);
       +  xfree (creds->username);
       +  xfree (creds->domain);
       +
       +  free (creds);
       +}
       +
       +
       +struct mount * mount_new (const char *server, const char *share,
       +                          const char *mountpoint, const char *credentials,
       +                                                                                                        const char *options)
       +{
       +  struct mount *mount = malloc (sizeof (struct mount));
       +
       +  if (NULL == mount)
       +    return NULL;
       +
       +  mount->server = xstrdup (server);
       +  mount->share = xstrdup (share);
       +        mount->mountpoint = xstrdup (mountpoint);
       +  mount->options = xstrdup (options);
       +  mount->credentials = xstrdup (credentials);
       +
       +  if ((NULL == mount->server) ||
       +      (NULL == mount->share) ||
       +      (NULL == mount->mountpoint) ||
       +      (NULL == mount->credentials) ||
       +      (NULL == mount->options))
       +  {
       +    mount_destroy (mount);
       +    mount = NULL;
       +  }
       +
       +  return mount;
       +}
       +
       +
       +void mount_destroy (struct mount *mount)
       +{
       +  assert (NULL != mount);
       +
       +  xfree (mount->credentials);
       +  xfree (mount->options);
       +  xfree (mount->mountpoint);
       +  xfree (mount->share);
       +  xfree (mount->server);
       +
       +  free (mount);
       +}
       +
       +
       +struct conffile * conffile_new (void)
       +{
       +  struct conffile *cf = malloc (sizeof (struct conffile));
       +
       +  if (NULL == cf)
       +    return NULL;
       +
       +
       +  cf->credentials = g_hash_table_new_full (g_str_hash, g_str_equal, free,
       +                                           (GDestroyNotify)credentials_destroy);
       +  cf->mounts = g_hash_table_new_full (g_str_hash, g_str_equal,
       +                                      free, (GDestroyNotify)mount_destroy);
       +
       +  if ((NULL == cf->credentials) || (NULL == cf->mounts))
       +  {
       +    conffile_destroy (cf);
       +    cf = NULL;
       +  }
       +
       +  return cf;
       +}
       +
       +
       +void conffile_destroy (struct conffile *cf)
       +{
       +  assert (NULL != cf);
       +
       +  if (NULL != cf->mounts)
       +    g_hash_table_destroy (cf->mounts);
       +
       +  if (NULL != cf->credentials)
       +    g_hash_table_destroy (cf->credentials);
       +
       +  free (cf);
       +}
       +
       +
       +bool conffile_add_mount (struct conffile *cf, char *key, struct mount *mount)
       +{
       +  assert (NULL != cf);
       +  assert (NULL != key);
       +  assert (NULL != mount);
       +
       +        DEBUG (fprintf (stderr, "add_mount (%s)\n", key));
       +
       +  if (NULL != g_hash_table_lookup (cf->mounts, key))
       +  {
       +    fprintf (stderr, "Duplicate mount name: %s\n", key);
       +    return false;
       +  }
       +
       +  g_hash_table_insert (cf->mounts, key, mount);
       +  return true;
       +}
       +
       +
       +bool conffile_add_credentials (struct conffile *cf, char *key,
       +                               struct credentials *credentials)
       +{
       +  assert (NULL != cf);
       +  assert (NULL != key);
       +  assert (NULL != credentials);
       +
       +        DEBUG (fprintf (stderr, "add_credentials (%s)\n", key));
       +
       +  if (NULL != g_hash_table_lookup (cf->credentials, key))
       +  {
       +    fprintf (stderr, "Duplicate credentials name: %s\n", key);
       +    return false;
       +  }
       +
       +  g_hash_table_insert (cf->credentials, key, credentials);
       +  return true;
       +}
       +
       +
       +struct mount * conffile_get_mount (struct conffile *cf, const char *key)
       +{
       +  assert (NULL != cf);
       +  assert (NULL != key);
       +
       +  return g_hash_table_lookup (cf->mounts, key);
       +}
       +
       +
       +struct credentials * conffile_get_credentials (struct conffile *cf,
       +                                               const char *key)
       +{
       +  assert (NULL != cf);
       +  assert (NULL != key);
       +
       +  return g_hash_table_lookup (cf->credentials, key);
       +}
   DIR diff --git a/conffile.h b/conffile.h
       @@ -0,0 +1,51 @@
       +#ifndef CONFFILE_H
       +  #define CONFFILE_H
       +
       +  #include <stdbool.h>
       +
       +  struct credentials {
       +    char *domain;
       +    char *username;
       +    char *password;
       +  };
       +
       +
       +  struct mount {
       +    char *server;
       +    char *share;
       +    char *mountpoint;
       +    char *credentials;
       +    char *options;
       +  };
       +
       +
       +  struct conffile;
       +
       +
       +  struct credentials * credentials_new (const char *domain,
       +                                        const char *username,
       +                                        const char *password);
       +
       +  void credentials_destroy (struct credentials *creds);
       +
       +  struct mount * mount_new (const char *server, const char *share,
       +                                  const char *mountpoint, const char *credentials,
       +                                                                                                                const char *options);
       +
       +  void mount_destroy (struct mount *mount);
       +
       +  struct conffile * conffile_new (void);
       +
       +  void conffile_destroy (struct conffile *cf);
       +
       +  bool conffile_add_mount (struct conffile *cf, char *key, struct mount *mount);
       +
       +  bool conffile_add_credentials (struct conffile *cf, char *key,
       +                                 struct credentials *credentials);
       +
       +  struct mount * conffile_get_mount (struct conffile *cf, const char *key);
       +
       +  struct credentials * conffile_get_credentials (struct conffile *cf,
       +                                                 const char *key);
       +
       +#endif
   DIR diff --git a/doc/sax_model.mdzip b/doc/sax_model.mdzip
       Binary files differ.
   DIR diff --git a/doc/sax_state_machine.png b/doc/sax_state_machine.png
       Binary files differ.
   DIR diff --git a/options.c b/options.c
       @@ -0,0 +1,150 @@
       +#include <assert.h>
       +#include <glib.h>
       +#include <stdbool.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <sys/time.h>
       +#include <libsmbclient.h>
       +#include <fuse.h>
       +#include "options.h"
       +#include "utils.h"
       +#include "version.h"
       +
       +
       +static gboolean version = FALSE;
       +static gchar *conffile = NULL;
       +static gboolean debug = FALSE;
       +static gboolean nofork = FALSE;
       +static gchar **remaining = NULL;
       +
       +
       +static GOptionEntry entries[] = {
       +  { .long_name = "version",
       +    .short_name = 'v',
       +    .flags = 0,
       +    .arg = G_OPTION_ARG_NONE,
       +    .arg_data = &version,
       +    .description = "Show usmb, FUSE and Samba versions" },
       +
       +  { .long_name = "version",
       +    .short_name = 'V',
       +    .flags = G_OPTION_FLAG_HIDDEN,
       +    .arg = G_OPTION_ARG_NONE,
       +    .arg_data = &version,
       +    .description = NULL },
       +
       +  { .long_name = "config",
       +    .short_name = 'c',
       +    .flags = 0,
       +    .arg = G_OPTION_ARG_FILENAME,
       +    .arg_data = &conffile,
       +    .description = "usmb configuration file" },
       +
       +  { .long_name = "debug",
       +    .short_name = 'd',
       +    .arg = G_OPTION_ARG_NONE,
       +    .arg_data = &debug,
       +    .description = "Debug mode" },
       +
       +  { .long_name = "nofork",
       +    .short_name = 'f',
       +    .arg = G_OPTION_ARG_NONE,
       +    .arg_data = &nofork,
       +    .description = "Foreground operation" },
       +
       +  { .long_name = G_OPTION_REMAINING,
       +    .short_name = 0,
       +    .arg = G_OPTION_ARG_STRING_ARRAY,
       +    .arg_data = &remaining,
       +    .description = NULL },
       +
       +  { .long_name = NULL }
       +};
       +
       +
       +bool parse_args (int *argc, char ***argv,
       +                 const char **mountid, const char **out_conffile)
       +{
       +  GError *error = NULL;
       +
       +  GOptionContext *context =
       +    g_option_context_new ("- mount SMB shares via FUSE and Samba");
       +  g_option_context_add_main_entries (context, entries, NULL);
       +  bool ret = g_option_context_parse (context, argc, argv, &error);
       +  g_option_context_free (context);
       +
       +  if ((NULL == remaining) || (NULL == remaining[0]))
       +  {
       +    fputs ("No share ID given.\n", stderr);
       +    return false;
       +  }
       +
       +  if (NULL != remaining[1])
       +  {
       +    fputs ("Too many arguments.\n", stderr);
       +    return false;
       +  }
       +
       +  *mountid = remaining[0];
       +  g_free (remaining);
       +  DEBUG (fprintf (stderr, "Mount ID: %s\n", *mountid));
       +
       +  if (version)
       +  {
       +    show_version (stdout);
       +    exit (EXIT_SUCCESS);
       +  }
       +
       +        if (NULL != conffile)
       +                *out_conffile = conffile;
       +
       +  return ret;
       +}
       +
       +
       +/* FUSE args are:
       + *
       + * argv[0]
       + * -s
       + * -d          -- if debug mode requested
       + * -f          -- if foreground mode requested
       + * -o ...      -- if any mount options in the config file
       + * mount point
       + */
       +#define MAXARGS 10
       +void build_fuse_args (char *options, char *mountpoint,
       +                      int *out_argc, char ***out_argv)
       +{
       +  assert (NULL != mountpoint);
       +  static char *argv[MAXARGS];
       +
       +  int argc = 0;
       +
       +  argv[argc++] = "usmb";
       +        argv[argc++] = "-s";
       +
       +  if (debug)
       +    argv[argc++] = "-d";
       +  
       +  if (nofork)
       +    argv[argc++] = "-f";
       +  
       +  if ((NULL != options) && ('\0' != options[0]))
       +  {
       +    argv[argc++] = "-o";
       +    argv[argc++] = options;
       +  }
       +
       +  argv[argc++] = mountpoint;
       +  argv[argc] = NULL;          // for good measure...
       +
       +  assert (argc < MAXARGS);
       +  *out_argc = argc;
       +  *out_argv = argv;
       +
       +#if 0
       +        for (int i = 0; i < argc; ++i)
       +                fprintf (stderr, "%d: %s\n", i, argv[i]);
       +#endif
       +}
       +
   DIR diff --git a/options.h b/options.h
       @@ -0,0 +1,9 @@
       +#ifndef OPTIONS_H
       +  #define OPTIONS_H
       +
       +  bool parse_args (int *argc, char ***argv,
       +                   const char **mountid, const char **out_conffile);
       +  void build_fuse_args (char *options, char *mountpoint,
       +                        int *out_argc, char ***out_argv);
       +
       +#endif
   DIR diff --git a/sax.c b/sax.c
       @@ -0,0 +1,706 @@
       +#include <assert.h>
       +#include <ctype.h>
       +#include <stdarg.h>
       +#include <stdio.h>
       +#include <string.h>
       +#include <libxml/xmlreader.h>
       +#include <glib.h>
       +#include "conffile.h"
       +#include "sax.h"
       +#include "utils.h"
       +
       +
       +/* Configuration file format
       + * -------------------------
       + *
       + * usmbconfig section
       + *   Any number of credentials and mount sections
       + *
       + *   credentials section
       + *     domain
       + *     username
       + *     password
       + *
       + *   mount section
       + *     server
       + *     share
       + *     mountpoint
       + *     options
       + *
       + * Example:
       + *   <usmbconfig>
       + *   
       + *     <credentials id="workgroup">
       + *       <domain>WORKGROUP</domain>
       + *       <username>user</username>
       + *       <password>password</password>
       + *     </credentials>
       + *   
       + *     <mount id="home" credentials="workgroup">
       + *       <server>127.0.0.1</server>
       + *       <share>home</share>
       + *       <mountpoint>/tmp/smb</mountpoint>
       + *       <options>readonly</options>
       + *     </mount>
       + *   
       + *   </usmbconfig>
       + */    
       +
       +
       +/* SAX state machine
       + * -----------------
       + *
       + * See doc/sax_state_machine.png or the MagicDraw model doc/sax_model.mdzip.
       + */
       +
       +
       +enum sax_state {
       +  START,
       +  INITIAL,
       +  USMBCFG,
       +  DONE,
       +  FINISH,
       +  CREDS,
       +  MOUNT,
       +  SERVER,
       +  SHARE,
       +  MOUNTPT,
       +  OPTIONS,
       +  DOMAIN,
       +  USER,
       +  PASS,
       +  ERROR
       +};
       +
       +
       +enum element {
       +  ELT_USMBCFG,
       +  ELT_CREDS,
       +  ELT_DOMAIN,
       +  ELT_USER,
       +  ELT_PASS,
       +  ELT_MOUNT,
       +  ELT_SERVER,
       +  ELT_SHARE,
       +  ELT_MOUNTPT,
       +  ELT_OPTIONS,
       +  ELT_UNKNOWN
       +};
       +
       +
       +// passed as the ctx pointer into SAX callbacks
       +struct sax_parser {
       +  enum sax_state state;
       +
       +  union {
       +    struct {
       +      char *id;
       +      GString *domain;
       +      GString *username;
       +      GString *password;
       +    } credentials;
       +    struct {
       +      char *id;
       +      char *creds;
       +      GString *server;
       +      GString *share;
       +      GString *mountpoint;
       +      GString *options;
       +    } mount;
       +  } strs;
       +
       +  struct conffile *cf;
       +};
       +
       +
       +static enum element get_element (const char *elt)
       +{
       +  const char *elts[] = {
       +    [ ELT_USMBCFG ] = "usmbconfig",
       +    [ ELT_CREDS ]   = "credentials",
       +    [ ELT_DOMAIN ]  = "domain",
       +    [ ELT_USER ]    = "username",
       +    [ ELT_PASS ]    = "password",
       +    [ ELT_MOUNT ]   = "mount",
       +    [ ELT_SERVER ]  = "server",
       +    [ ELT_SHARE ]   = "share",
       +    [ ELT_MOUNTPT ] = "mountpoint",
       +    [ ELT_OPTIONS ] = "options"
       +  };
       +
       +  for (size_t i = 0; i < sizeof (elts) / sizeof (elts[0]); ++i)
       +    if (!strcmp (elt, elts[i]))
       +      return i;
       +  
       +  return ELT_UNKNOWN;
       +}
       +
       +
       +static void start_document (void *ctx)
       +{
       +  struct sax_parser *sp = ctx;
       +  assert (sp->state == START);
       +  sp->state = INITIAL;
       +
       +  // these four aren't strictly necessary as we're using a union
       +  sp->strs.credentials.id = NULL;
       +  sp->strs.credentials.domain = NULL;
       +  sp->strs.credentials.username = NULL;
       +  sp->strs.credentials.password = NULL;
       +
       +  sp->strs.mount.id = NULL;
       +  sp->strs.mount.creds = NULL;
       +  sp->strs.mount.server = NULL;
       +  sp->strs.mount.share = NULL;
       +  sp->strs.mount.mountpoint = NULL;
       +  sp->strs.mount.options = NULL;
       +}
       +
       +
       +static void end_document (void *ctx)
       +{
       +  struct sax_parser *sp = ctx;
       +
       +  if (ERROR == sp->state)
       +    return;
       +
       +  if (sp->state != DONE)
       +  {
       +    fputs ("Unexpected end of configuration file\n", stderr);
       +    sp->state = ERROR;
       +  }
       +
       +  sp->state = FINISH;
       +}
       +
       +
       +static void error (void *ctx, const char *msg, ...)
       +{
       +  struct sax_parser *sp = ctx;
       +
       +  if (ERROR == sp->state)
       +    return;
       +
       +  va_list ap;
       +
       +  fputs ("Parse error: ", stderr);
       +  va_start (ap, msg);
       +  vfprintf (stderr, msg ,ap);
       +  va_end (ap);
       +  sp->state = ERROR;
       +}
       +
       +
       +static bool set_string (char **out, const char *in, const char *dup_err)
       +{
       +  assert (NULL != out);
       +  assert (NULL != in);
       +
       +  if (NULL != *out)
       +  {
       +    if (NULL != dup_err)
       +    {
       +      fputs (dup_err, stderr);
       +      return false;
       +    }
       +  }
       +
       +  *out = xstrdup (in);
       +
       +  if (NULL == *out)
       +  {
       +    fputs ("Out of memory\n", stderr);
       +    return false;
       +  }
       +
       +  return true;
       +}
       +
       +
       +static void start_mount (struct sax_parser *sp, const xmlChar **attrs)
       +{
       +  assert (NULL == sp->strs.mount.server);
       +  assert (NULL == sp->strs.mount.share);
       +  assert (NULL == sp->strs.mount.mountpoint);
       +  assert (NULL == sp->strs.mount.options);
       +
       +  // must have id and credentials attributes
       +  for (int i = 0; NULL != attrs[i]; i += 2)
       +  {
       +    assert (NULL != attrs[i+1]);
       +
       +    if (!strcmp (attrs[i], "id"))
       +    {
       +      if (!set_string (&sp->strs.mount.id, (const char *)attrs[i+1],
       +                       "Duplicate id attribute in <mount>\n"))
       +      {
       +        sp->state = ERROR;
       +        return;
       +      }
       +    }
       +
       +    else if (!strcmp (attrs[i], "credentials"))
       +    {
       +      if (!set_string (&sp->strs.mount.creds, (const char *)attrs[i+1],
       +                       "Duplicate credentials attribute in <mount>\n"))
       +      {
       +        sp->state = ERROR;
       +        return;
       +      }
       +    }
       +
       +    else
       +    {
       +      fprintf (stderr, "Unrecognised <mount> attribute %s\n", attrs[i]);
       +      sp->state = ERROR;
       +      return;
       +    }
       +  }
       +
       +  if ((NULL == sp->strs.mount.id) ||
       +      (NULL == sp->strs.mount.creds))
       +  {
       +    fputs ("<mount> must have id and credentials attributes\n", stderr);
       +    sp->state = ERROR;
       +    return;
       +  }
       +
       +  sp->state = MOUNT;
       +}
       +
       +
       +static void start_creds (struct sax_parser *sp, const xmlChar **attrs)
       +{
       +  assert (NULL == sp->strs.credentials.domain);
       +  assert (NULL == sp->strs.credentials.username);
       +  assert (NULL == sp->strs.credentials.password);
       +
       +  // must have an id attribute
       +  for (int i = 0; NULL != attrs[i]; i += 2)
       +  {
       +    assert (NULL != attrs[i+1]);
       +
       +    if (!strcmp (attrs[i], "id"))
       +    {
       +      if (!set_string (&sp->strs.credentials.id, (const char *)attrs[i+1],
       +                       "Duplicate id attribute in <credentials>\n"))
       +      {
       +        sp->state = ERROR;
       +        return;
       +      }
       +    }
       +
       +    else
       +    {
       +      fprintf (stderr, "Unrecognised <credentials> attribute %s\n", attrs[i]);
       +      sp->state = ERROR;
       +      return;
       +    }
       +  }
       +
       +  if (NULL == sp->strs.credentials.id)
       +  {
       +    fputs ("<credentials> must have an id attribute\n", stderr);
       +    sp->state = ERROR;
       +    return;
       +  }
       +
       +  sp->state = CREDS;
       +}
       +
       +
       +static void start_char_state (struct sax_parser *sp,
       +                              GString **target,
       +                              const char *name,
       +                              const xmlChar **attrs)
       +{
       +  if (ERROR == sp->state)
       +    return;
       +
       +  if ((NULL != attrs) && (NULL != attrs[0]))
       +  {
       +    fprintf (stderr, "Spurious attributes on <%s>\n", name);
       +    sp->state = ERROR;
       +    return;
       +  }
       +
       +  if (NULL != *target)
       +  {
       +    fprintf (stderr, "Duplicate <%s>\n", name);
       +    sp->state = ERROR;
       +    return;
       +  }
       +
       +  *target = g_string_new (NULL);
       +  if (NULL == *target)
       +  {
       +    fputs ("Out of memory\n", stderr);
       +    sp->state = ERROR;
       +    return;
       +  }
       +}
       +
       +
       +static void start_server (struct sax_parser *sp, const xmlChar **attrs)
       +{
       +  start_char_state (sp, &sp->strs.mount.server, "server", attrs);
       +}
       +
       +
       +static void start_share (struct sax_parser *sp, const xmlChar **attrs)
       +{
       +  start_char_state (sp, &sp->strs.mount.share, "share", attrs);
       +}
       +
       +
       +static void start_mtpt (struct sax_parser *sp, const xmlChar **attrs)
       +{
       +  start_char_state (sp, &sp->strs.mount.mountpoint, "mountpoint", attrs);
       +}
       +
       +
       +static void start_options (struct sax_parser *sp, const xmlChar **attrs)
       +{
       +  start_char_state (sp, &sp->strs.mount.options, "options", attrs);
       +}
       +
       +
       +static void start_domain (struct sax_parser *sp, const xmlChar **attrs)
       +{
       +  start_char_state (sp, &sp->strs.credentials.domain, "domain", attrs);
       +}
       +
       +
       +static void start_username (struct sax_parser *sp, const xmlChar **attrs)
       +{
       +  start_char_state (sp, &sp->strs.credentials.username, "username", attrs);
       +}
       +
       +
       +static void start_password (struct sax_parser *sp, const xmlChar **attrs)
       +{
       +  start_char_state (sp, &sp->strs.credentials.password, "password", attrs);
       +}
       +
       +
       +static void start_element (void *ctx,
       +                           const xmlChar *name, const xmlChar **attrs)
       +{
       +  struct sax_parser *sp = ctx;
       +
       +  if (ERROR == sp->state)
       +    return;
       +
       +  /* all valid transitions on <element> */
       +  const struct {
       +    enum sax_state to, from;
       +    enum element on;
       +    void (*fn) (struct sax_parser *, const xmlChar **attrs);
       +  } transitions[] = {
       +    { .from = INITIAL, .on = ELT_USMBCFG, .to = USMBCFG, .fn = NULL           },
       +    { .from = USMBCFG, .on = ELT_MOUNT,   .to = MOUNT,   .fn = start_mount    },
       +    { .from = USMBCFG, .on = ELT_CREDS,   .to = CREDS,   .fn = start_creds    },
       +    { .from = MOUNT,   .on = ELT_SERVER,  .to = SERVER,  .fn = start_server   },
       +    { .from = MOUNT,   .on = ELT_SHARE,   .to = SHARE,   .fn = start_share    },
       +    { .from = MOUNT,   .on = ELT_MOUNTPT, .to = MOUNTPT, .fn = start_mtpt     },
       +    { .from = MOUNT,   .on = ELT_OPTIONS, .to = OPTIONS, .fn = start_options  },
       +    { .from = CREDS,   .on = ELT_DOMAIN,  .to = DOMAIN,  .fn = start_domain   },
       +    { .from = CREDS,   .on = ELT_USER,    .to = USER,    .fn = start_username },
       +    { .from = CREDS,   .on = ELT_PASS,    .to = PASS,    .fn = start_password }
       +  };
       +
       +
       +  enum element elt = get_element ((const char *)name);
       +
       +  for (size_t i = 0; i < sizeof (transitions) / sizeof (transitions[0]); ++i)
       +  {
       +    if ((transitions[i].from == sp->state) &&
       +        (transitions[i].on == elt))
       +    {
       +      if (NULL != transitions[i].fn)
       +        transitions[i].fn (sp, attrs);
       +
       +      if (ERROR != sp->state)
       +        sp->state = transitions[i].to;
       +      return;
       +    }
       +  }
       +
       +  fprintf (stderr, "Unexpected <%s>\n", name);
       +  sp->state = ERROR;
       +}
       +
       +
       +static void end_mount (struct sax_parser *sp)
       +{
       +  // must have id, creds, server, share and mountpoint; options are optional
       +  assert (NULL != sp->strs.mount.id);
       +  assert (NULL != sp->strs.mount.creds);
       +
       +  if (NULL == conffile_get_credentials (sp->cf, sp->strs.mount.creds))
       +  {
       +    fprintf (stderr, "Mount %s: credentials %s not found\n",
       +             sp->strs.mount.id, sp->strs.mount.creds);
       +    sp->state = ERROR;
       +  }
       +
       +  else if (NULL != conffile_get_mount (sp->cf, sp->strs.mount.id))
       +  {
       +    fprintf (stderr, "Duplicate mount ID: %s\n", sp->strs.mount.id);
       +    sp->state = ERROR;
       +  }
       +
       +  else if ((NULL == sp->strs.mount.server) || (0 == sp->strs.mount.server->len))
       +  {
       +    fprintf (stderr, "Mount %s: no server given\n", sp->strs.mount.id);
       +    sp->state = ERROR;
       +  }
       +
       +  else if ((NULL == sp->strs.mount.share) || (0 == sp->strs.mount.share->len))
       +  {
       +    fprintf (stderr, "Mount %s: no share given\n", sp->strs.mount.id);
       +    sp->state = ERROR;
       +  }
       +
       +  else if ((NULL == sp->strs.mount.mountpoint) ||
       +           (0 == sp->strs.mount.mountpoint->len))
       +  {
       +    fprintf (stderr, "Mount %s: no mount point given\n", sp->strs.mount.id);
       +    sp->state = ERROR;
       +  }
       +
       +  else
       +  {
       +    struct mount *mount =
       +      mount_new (sp->strs.mount.server ? sp->strs.mount.server->str : "",
       +                 sp->strs.mount.share ? sp->strs.mount.share->str : "",
       +                 sp->strs.mount.mountpoint ?
       +                   sp->strs.mount.mountpoint->str : "",
       +                 sp->strs.mount.creds,
       +                 sp->strs.mount.options ?
       +                   sp->strs.mount.options->str : "");
       +
       +    if ((NULL == mount) ||
       +        !conffile_add_mount (sp->cf, sp->strs.mount.id, mount))
       +      sp->state = ERROR;
       +  }
       +
       +  if (sp->strs.mount.server)
       +    g_string_free (sp->strs.mount.server, TRUE);
       +  
       +  if (sp->strs.mount.share)
       +    g_string_free (sp->strs.mount.share, TRUE);
       +  
       +  if (sp->strs.mount.mountpoint)
       +    g_string_free (sp->strs.mount.mountpoint, TRUE);
       +  
       +  if (sp->strs.mount.options)
       +    g_string_free (sp->strs.mount.options, TRUE);
       +
       +  sp->strs.mount.server = NULL;
       +  sp->strs.mount.share = NULL;
       +  sp->strs.mount.mountpoint = NULL;
       +  sp->strs.mount.options = NULL;
       +
       +  if (ERROR == sp->state)
       +    xfree (sp->strs.mount.id);
       +
       +  xfree (sp->strs.mount.creds);
       +
       +  sp->strs.mount.id = NULL;
       +  sp->strs.mount.creds = NULL;
       +}
       +
       +
       +static void end_creds (struct sax_parser *sp)
       +{
       +  // must have username; domain and password are optional
       +  assert (NULL != sp->strs.credentials.id);
       +
       +  if (NULL != conffile_get_credentials (sp->cf, sp->strs.credentials.id))
       +  {
       +    fprintf (stderr, "Duplicate credentials ID: %s\n", sp->strs.credentials.id);
       +    sp->state = ERROR;
       +  }
       +
       +  else if ((NULL == sp->strs.credentials.username) ||
       +           (0 == sp->strs.credentials.username->len))
       +  {
       +    fprintf (stderr, "Credentials %s: no username given\n",
       +    sp->strs.credentials.id);
       +    sp->state = ERROR;
       +  }
       +
       +  else
       +  {
       +    struct credentials *creds =
       +      credentials_new (sp->strs.credentials.domain ?
       +                         sp->strs.credentials.domain->str : "",
       +                       sp->strs.credentials.username ?
       +                         sp->strs.credentials.username->str : "",
       +                       sp->strs.credentials.password ?
       +                         sp->strs.credentials.password->str : "");
       +
       +    if ((NULL == creds) ||
       +        !conffile_add_credentials (sp->cf, sp->strs.credentials.id, creds))
       +      sp->state = ERROR;
       +  }
       +
       +  g_string_free (sp->strs.credentials.domain, TRUE);
       +  g_string_free (sp->strs.credentials.username, TRUE);
       +  g_string_free (sp->strs.credentials.password, TRUE);
       +
       +  sp->strs.credentials.domain = NULL;
       +  sp->strs.credentials.username = NULL;
       +  sp->strs.credentials.password = NULL;
       +
       +  if (ERROR == sp->state)
       +    xfree (sp->strs.credentials.id);
       +
       +  sp->strs.credentials.id = NULL;
       +}
       +
       +
       +static void end_element (void *ctx, const xmlChar *name)
       +{
       +  struct sax_parser *sp = ctx;
       +
       +  if (ERROR == sp->state)
       +    return;
       +
       +  /* all valid transitions on </element> */
       +  const struct {
       +    enum sax_state to, from;
       +    enum element on;
       +    void (*fn) (struct sax_parser *);
       +  } transitions[] = {
       +    { .from = USMBCFG, .on = ELT_USMBCFG, .to = DONE,    .fn = NULL      },
       +    { .from = MOUNT,   .on = ELT_MOUNT,   .to = USMBCFG, .fn = end_mount },
       +    { .from = CREDS,   .on = ELT_CREDS,   .to = USMBCFG, .fn = end_creds },
       +    { .from = SERVER,  .on = ELT_SERVER,  .to = MOUNT,   .fn = NULL      },
       +    { .from = SHARE,   .on = ELT_SHARE,   .to = MOUNT,   .fn = NULL      },
       +    { .from = MOUNTPT, .on = ELT_MOUNTPT, .to = MOUNT,   .fn = NULL      },
       +    { .from = OPTIONS, .on = ELT_OPTIONS, .to = OPTIONS, .fn = NULL      },
       +    { .from = DOMAIN,  .on = ELT_DOMAIN,  .to = CREDS,   .fn = NULL      },
       +    { .from = USER,    .on = ELT_USER,    .to = CREDS,   .fn = NULL      },
       +    { .from = PASS,    .on = ELT_PASS,    .to = CREDS,   .fn = NULL      }
       +  };
       +
       +
       +  enum element elt = get_element ((const char *)name);
       +
       +  for (size_t i = 0; i < sizeof (transitions) / sizeof (transitions[0]); ++i)
       +  {
       +    if ((transitions[i].from == sp->state) &&
       +        (transitions[i].on == elt))
       +    {
       +      if (NULL != transitions[i].fn)
       +        transitions[i].fn (sp);
       +
       +      if (ERROR != sp->state)
       +        sp->state = transitions[i].to;
       +      return;
       +    }
       +  }
       +
       +  fprintf (stderr, "Unexpected </%s>\n", name);
       +  sp->state = ERROR;
       +}
       +
       +
       +// strips leading and trailing whitespace; returns true iff characters remain
       +static bool strip_whitespace (const char **str, int *len)
       +{
       +  assert (NULL != str);
       +  assert (NULL != len);
       +
       +  /* leading whitespace */
       +  while ((*len > 0) && isspace (**str))
       +  {
       +    ++(*str);
       +    --(*len);
       +  }
       +
       +  /* trailing whitespace */
       +  while ((*len > 0) && (isspace ((*str)[*len - 1])))
       +    --(*len);
       +  
       +  return (0 != *len);
       +}
       +
       +
       +static inline void append_chars (GString *target, const xmlChar *str, int len)
       +{
       +  assert (NULL != target);
       +  assert (NULL != str);
       +
       +  g_string_append_len (target, (const char *)str, len);
       +}
       +
       +
       +static void chars (void *ctx, const xmlChar *str, int len)
       +{
       +  struct sax_parser *sp = ctx;
       +
       +  if (ERROR == sp->state)
       +    return;
       +
       +  // all states admit whitespace characters
       +  if (!strip_whitespace ((const char **)&str, &len))
       +    return;
       +
       +  // these states admit non-whitespace characters
       +  switch (sp->state)
       +  {
       +    case SERVER:  append_chars (sp->strs.mount.server,         str, len); break;
       +    case SHARE:   append_chars (sp->strs.mount.share,          str, len); break;
       +    case MOUNTPT: append_chars (sp->strs.mount.mountpoint,     str, len); break;
       +    case OPTIONS: append_chars (sp->strs.mount.options,        str, len); break;
       +    case DOMAIN:  append_chars (sp->strs.credentials.domain,   str, len); break;
       +    case USER:    append_chars (sp->strs.credentials.username, str, len); break;
       +    case PASS:    append_chars (sp->strs.credentials.password, str, len); break;
       +
       +    default:
       +      fputs ("Unexpected characters: ", stderr);
       +      while (len--)
       +        fputc (*str++, stderr);
       +      fputc ('\n', stderr);
       +      sp->state = ERROR;
       +      break;
       +  }
       +}
       +
       +
       +struct conffile * parse_xml_conf (const char *file)
       +{
       +  LIBXML_TEST_VERSION;
       +
       +  struct _xmlSAXHandler sax = {
       +    .startDocument = start_document,
       +    .endDocument = end_document,
       +    .startElement = start_element,
       +    .endElement = end_element,
       +    .error = error,
       +    .characters = chars,
       +  };
       +
       +
       +  struct sax_parser user_data = {
       +    .state = START
       +  };
       +
       +  user_data.cf = conffile_new();
       +
       +  if (NULL != user_data.cf)
       +  {
       +    if ((0 != xmlSAXUserParseFile (&sax, &user_data, file)) ||
       +        (user_data.state != FINISH))
       +    {
       +      fputs ("Parse error\n", stderr);
       +      conffile_destroy (user_data.cf);
       +      user_data.cf = NULL;
       +    }
       +  }
       +
       +  return user_data.cf;
       +}
   DIR diff --git a/sax.h b/sax.h
       @@ -0,0 +1,9 @@
       +#ifndef SAX_H
       +  #define SAX_H
       +
       +        #include "conffile.h"
       +
       +  struct conffile * parse_xml_conf (const char *file);
       +
       +#endif
       +
   DIR diff --git a/usmb.c b/usmb.c
       @@ -1,86 +1,37 @@
        #include <sys/time.h>        // struct timeval needed by libsmbclient.h
       +#include <sys/types.h>
       +#include <sys/stat.h>
       +#include <unistd.h>
        #include <libsmbclient.h>
        #include <fuse.h>
        #include <assert.h>
       +#include <errno.h>
        #include <stdarg.h>
       +#include <stdbool.h>
        #include <stddef.h>
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
       +#include "conffile.h"
       +#include "options.h"
       +#include "sax.h"
        #include "usmb.h"
        #include "usmb_dir.h"
        #include "usmb_file.h"
       +#include "utils.h"
        
        
       +static const char *share = NULL;
        
       -#define INIT_LEN 256
       -static char * concat_strings (int num, ...)
       -{
       -  va_list ap;
       -
       -  char *base, *out;
       -
       -  base = out = malloc (INIT_LEN);
       -  if (NULL == base)
       -    return NULL;
       -
       -  size_t buff_size = INIT_LEN;
       -
       -  va_start (ap, num);
       -  for (int i = 0; i < num; ++i)
       -  {
       -    const char *next = va_arg (ap, const char *);
       -    assert (NULL != next);
       -
       -    size_t next_len = strlen (next);
       -    size_t required = (out - base) + next_len + 1;
       -
       -    if (buff_size < required)
       -    {
       -      while (buff_size < required)
       -      {
       -        size_t dbl_len = buff_size * 2;
       -        if (dbl_len < buff_size)
       -        {
       -          free (base);
       -          return NULL;
       -        }
       -
       -        buff_size = dbl_len;
       -      }
       -
       -      ptrdiff_t diff = out - base;
       -
       -      char *newbase = realloc (base, buff_size);
       -      if (NULL == newbase)
       -      {
       -        free (base);
       -        return NULL;
       -      }
       -
       -      base = newbase;
       -      out = base + diff;
       -    }
       -
       -    memcpy (out, next, next_len);
       -    out += next_len;
       -  }
       -  va_end (ap);
       -
       -  *out = '\0';
       -  return base;
       -}
       -
       -
       -static const char *share = "//127.0.0.1/geoff";
        
        char * make_url (const char *path)
        {
       -  DEBUG (fprintf (stderr, "make_url (%s)\n", path));
       +  assert (NULL != share);
       +
          if ((NULL == path) || ('\0' == path[0]))
       -    return concat_strings (2, "smb:", share);
       +    return xstrdup (share);
          else
       -    return concat_strings (3, "smb:", share, path);
       +    return concat_strings (2, share, path);
        }
        
        
       @@ -91,6 +42,10 @@ static inline void do_strncpy (char *to, const char *from, int tolen)
        }
        
        
       +static const char *domain = NULL;
       +static const char *username = NULL;
       +static const char *password = NULL;
       +
        static void auth_fn (const char *srv, const char *shr, char *wg, int wglen,
                             char *un, int unlen, char *pw, int pwlen)
        {
       @@ -98,14 +53,19 @@ static void auth_fn (const char *srv, const char *shr, char *wg, int wglen,
          (void)shr;
          DEBUG (fprintf (stderr, "Authenticating for \\\\%s\\%s\n", srv, shr));
        
       -  do_strncpy (wg, "WORKGROUP", wglen);
       -  do_strncpy (un, "geoff", unlen);
       -  do_strncpy (pw, "ze]T72%5V", pwlen);
       +  assert (NULL != domain);
       +  assert (NULL != username);
       +  assert (NULL != password);
       +
       +  do_strncpy (wg, domain, wglen);
       +  do_strncpy (un, username, unlen);
       +  do_strncpy (pw, password, pwlen);
        }
        
        
        static void * usmb_init (void)
        {
       +  DEBUG (fputs ("usmb_init()\n", stderr));
          return NULL;
        }
        
       @@ -113,49 +73,41 @@ static void * usmb_init (void)
        static void usmb_destroy (void *unused)
        {
          (void)unused;
       +  DEBUG (fputs ("usmb_destroy()\n", stderr));
        }
        
        
       -// probably won't (can't ?) implement these.
       -#define usmb_readlink NULL
       -#define usmb_mknod NULL
       -#define usmb_symlink NULL
       -#define usmb_chmod NULL
       -#define usmb_chown NULL
       -#define usmb_flush NULL
       -#define usmb_fsync NULL
       +// probably won't (can't ?) implement these:
       +// readlink mknod symlink flush fsync
        
        // fuse.h says "Just a placeholder, don't set".
       -#define usmb_statfs NULL
       -
       -#define usmb_access NULL
       +// statfs
        
        // no easy way of implementing these.
       -#define usmb_ftruncate NULL
       -
       +// access ftruncate
        
        static struct fuse_operations fuse_ops = {
          .getattr = usmb_getattr,
       -  .readlink = usmb_readlink,
       +  .readlink = NULL,
          .getdir = NULL,
       -  .mknod = usmb_mknod,
       +  .mknod = NULL,
          .mkdir = usmb_mkdir,
          .unlink = usmb_unlink,
          .rmdir = usmb_rmdir,
       -  .symlink = usmb_symlink,
       +  .symlink = NULL,
          .rename = usmb_rename,
          .link = NULL,
          .chmod = usmb_chmod,
       -  .chown = usmb_chown,
       +  .chown = NULL, // usmb_chown, --not implemented in libsmbclient
          .truncate = usmb_truncate,
          .utime = usmb_utime,
          .open = usmb_open,
          .read = usmb_read,
          .write = usmb_write,
       -  .statfs = usmb_statfs,
       -  .flush = usmb_flush,
       +  .statfs = NULL,
       +  .flush = NULL,
          .release = usmb_release,
       -  .fsync = usmb_fsync,
       +  .fsync = NULL,
        #ifdef SETXATTR
          .setxattr = usmb_setxattr,
          .getxattr = usmb_getxattr,
       @@ -168,22 +120,156 @@ static struct fuse_operations fuse_ops = {
          .fsyncdir = NULL,
          .init = usmb_init,
          .destroy = usmb_destroy,
       -  .access = usmb_access,
       +  .access = NULL,
          .create = usmb_create,
       -  .ftruncate = usmb_ftruncate,
       +  .ftruncate = NULL,
          .fgetattr = usmb_fgetattr
        };
        
       -  
       +
       +// this should really open() the file and check the fd, but the XML parser
       +// takes a filename, not a file descriptor
       +static bool check_conf_perms (const char *conffile)
       +{
       +  struct stat buf;
       +  if (0 == stat (conffile, &buf))
       +  {
       +    if (getuid() != buf.st_uid)
       +    {
       +      fprintf (stderr, "You do not own the configuration file %s\n",
       +               conffile);
       +      return false;
       +    }
       +
       +    if (buf.st_mode & (S_IRWXG | S_IRWXO))
       +    {
       +      fprintf (stderr, "Configuration file %s is accessible to non-owner\n",
       +               conffile);
       +      return false;
       +    }
       +  }
       +  else
       +  {
       +    fprintf (stderr, "Cannot configuration file %s: %s\n",
       +             conffile, strerror (errno));
       +    return false;
       +  }
       +
       +  return true;
       +}
       +
       +
       +static bool fix_up_conf_details (struct conffile *cf, struct mount *mount)
       +{
       +  struct credentials *creds = conffile_get_credentials (cf, mount->credentials);
       +  assert (NULL != creds);
       +
       +  size_t len = strlen ("smb:///") +
       +               strlen (mount->server) +
       +               strlen (mount->share) + 1;
       +  char *str;
       +  if (NULL == (str = malloc (len)))
       +  {
       +    perror ("Cannot allocate share name");
       +    return false;
       +  }
       +
       +  strcpy (str, "smb://");
       +  strcat (str, mount->server);
       +  strcat (str, "/");
       +  strcat (str, mount->share);
       +
       +  domain = xstrdup (creds->domain);
       +  username = xstrdup (creds->username);
       +  password = xstrdup (creds->password);
       +
       +  if ((NULL == domain) ||
       +      (NULL == username) ||
       +      (NULL == password))
       +  {
       +    fputs ("Out of memory\n", stderr);
       +    xfree (password);
       +    xfree (username);
       +    xfree (domain);
       +    free (str);
       +    return false;
       +  }
       +
       +  share = str;
       +
       +  DEBUG (fprintf (stderr, "Share URL: %s\n", share));
       +  DEBUG (fprintf (stderr, "Username: %s\\%s\n", domain, username));
       +  return true;
       +}
       +
        
        int main (int argc, char **argv)
        {
       +  const char *conffile;
       +  const char *mountid;
       +
       +        {
       +                static char conf[256];
       +                snprintf (conf, sizeof (conf), "%s/.usmb.conf", getenv ("HOME"));
       +                conffile = conf;
       +        }
       +
       +  if (!parse_args (&argc, &argv, &mountid, &conffile))
       +    return EXIT_FAILURE;
       +
       +  if (!check_conf_perms (conffile))
       +    return EXIT_FAILURE;
       +
       +  struct conffile *cf = parse_xml_conf (conffile);
       +  if (NULL == cf)
       +    return EXIT_FAILURE;
       +
       +  struct mount *mount = conffile_get_mount (cf, mountid);
       +
       +  if (NULL == mount)
       +  {
       +    fprintf (stderr, "Mount %s not defined in configuration file %s\n",
       +             mountid, conffile);
       +    conffile_destroy (cf);
       +    return EXIT_FAILURE;
       +  }
       +
       +  char *mountpoint = xstrdup (mount->mountpoint);
       +  char *options = xstrdup (mount->options);
       +
       +  if ((NULL == mountpoint) ||
       +      (mount->options && (NULL == options)))
       +  {
       +    perror ("Cannot allocate mountpoint");
       +    return EXIT_FAILURE;
       +  }
       +
       +  {
       +    bool ret = fix_up_conf_details (cf, mount);
       +    conffile_destroy (cf);
       +
       +    if (false == ret)
       +      return EXIT_FAILURE;
       +  }
       +
          if (0 != smbc_init (auth_fn, 0))
          {
            perror ("Cannot initialise SMB library");
            return EXIT_FAILURE;
          }
        
       -  return fuse_main (argc, argv, &fuse_ops);
       +  int fuse_argc;
       +  char **fuse_argv;
       +   build_fuse_args (options, mountpoint, &fuse_argc, &fuse_argv);
       +  int ret = fuse_main (fuse_argc, fuse_argv, &fuse_ops);
       +
       +  xfree (mountpoint);
       +  xfree (options);
       +  xfree (domain);
       +  xfree (username);
       +  xfree (password);
       +  xfree (share);
       +
       +  return ret;
        }
        
   DIR diff --git a/usmb.conf b/usmb.conf
       @@ -0,0 +1,16 @@
       +<usmbconfig>
       +
       +  <credentials id="workgroup">
       +    <domain>WORKGROUP</domain>
       +    <username>user</username>
       +    <password>pass</password>
       +  </credentials>
       +
       +  <mount id="home" credentials="workgroup">
       +    <server>127.0.0.1</server>
       +    <share>myshare</share>
       +    <mountpoint>/tmp/smb</mountpoint>
       +  </mount>
       +
       +</usmbconfig>
       +  
   DIR diff --git a/usmb.h b/usmb.h
       @@ -1,13 +1,6 @@
        #ifndef USMB_H
          #define USMB_H
        
       -  #ifdef DEBUG
       -    #undef DEBUG
       -    #define DEBUG(x) (x)
       -  #else
       -    #define DEBUG(x) ((void)0)
       -  #endif
       -
          char * make_url (const char *path);
        
        #endif
   DIR diff --git a/usmb_dir.c b/usmb_dir.c
       @@ -10,6 +10,7 @@
        #include <string.h>
        #include "usmb_dir.h"
        #include "usmb.h"
       +#include "utils.h"
        
        
        int usmb_mkdir (const char *dirname, mode_t mode)
       @@ -19,7 +20,7 @@ int usmb_mkdir (const char *dirname, mode_t mode)
            return -ENOMEM;
        
          DEBUG (fprintf (stderr, "mkdir (%s)\n", url));
       -  int ret = smbc_mkdir (dirname, mode) ? -errno : 0;
       +  int ret = smbc_mkdir (url, mode) ? -errno : 0;
          free (url);
          return ret;
        }
       @@ -31,8 +32,8 @@ int usmb_rmdir (const char *dirname)
          if (NULL == url)
            return -ENOMEM;
        
       -  DEBUG (fprintf (stderr, "mkdir (%s)\n", url));
       -  int ret = smbc_rmdir (dirname) ? -errno : 0;
       +  DEBUG (fprintf (stderr, "rmdir (%s)\n", url));
       +  int ret = smbc_rmdir (url) ? -errno : 0;
          free (url);
          return ret;
        }
       @@ -44,9 +45,10 @@ int usmb_opendir (const char *dirname, struct fuse_file_info *fi)
          if (NULL == url)
            return -ENOMEM;
        
       -  DEBUG (fprintf (stderr, "opendir (%s)\n", url));
       +  DEBUG (fprintf (stderr, "opendir (%s)", url));
          int fd = smbc_opendir (url);
          fi->fh = fd;
       +  DEBUG (fprintf (stderr, " = %d\n", fd));
        
          int ret = (fd < 0) ? -errno : 0;
          free (url);
       @@ -60,7 +62,7 @@ int usmb_readdir (const char *path, void *h, fuse_fill_dir_t filler,
          (void)path;
          (void)offset;
        
       -  DEBUG (fprintf (stderr, "readdir (%s)\n", path));
       +  DEBUG (fprintf (stderr, "readdir (%s, %llu)\n", path, fi->fh));
        
          struct smbc_dirent *dirent;
          errno = 0;
       @@ -98,7 +100,7 @@ int usmb_releasedir (const char *path, struct fuse_file_info *fi)
        {
          (void)path;
        
       -  DEBUG (fprintf (stderr, "releasedir (%s)\n", path));
       +  DEBUG (fprintf (stderr, "releasedir (%s, %llu)\n", path, fi->fh));
          return (smbc_closedir (fi->fh) < 0) ? -errno : 0;
        }
        
   DIR diff --git a/usmb_file.c b/usmb_file.c
       @@ -8,6 +8,7 @@
        #include <string.h>
        #include "usmb_file.h"
        #include "usmb.h"
       +#include "utils.h"
        
        
        int usmb_getattr (const char *filename, struct stat *st)
       @@ -25,6 +26,7 @@ int usmb_fgetattr (const char *filename, struct stat *st,
                           struct fuse_file_info *fi)
        {
          (void)filename;
       +  DEBUG (fprintf (stderr, "fgetattr (%s, %llu)\n", filename, fi->fh));
          return (smbc_fstat (fi->fh, st) < 0) ? -errno : 0;
        }
        
       @@ -35,6 +37,7 @@ int usmb_unlink (const char *filename)
          if (NULL == url)
            return -ENOMEM;
        
       +  DEBUG (fprintf (stderr, "unlink (%s)\n", url));
          int ret = (smbc_unlink (url) < 0) ? -errno : 0;
          free (url);
          return ret;
       @@ -47,11 +50,12 @@ int usmb_open (const char *filename, struct fuse_file_info *fi)
          if (NULL == url)
            return -ENOMEM;
        
       -  DEBUG (fprintf (stderr, "open (%s)\n", url));
       +  DEBUG (fprintf (stderr, "open (%s)", url));
        
          int fd = smbc_open (url, fi->flags, 0);
          int ret = (fd < 0) ? -errno : 0;
          fi->fh = fd;
       +  DEBUG (fprintf (stderr, " = %d\n", fd));
        
          free (url);
          return ret;
       @@ -61,6 +65,7 @@ int usmb_open (const char *filename, struct fuse_file_info *fi)
        int usmb_release (const char *filename, struct fuse_file_info *fi)
        {
          (void)filename;
       +  DEBUG (fprintf (stderr, "release (%s, %llu)\n", filename, fi->fh));
          return (smbc_close (fi->fh) < 0) ? -errno : 0;
        }
        
       @@ -71,8 +76,11 @@ int usmb_read (const char *filename, char *buff, size_t len, off_t off,
          (void)filename;
          (void)off;
        
       -  /* TODO: this may be WRONG: do we need to consider the offset */
          DEBUG (fprintf (stderr, "read (%p, %u)\n", buff, len));
       +
       +  if (smbc_lseek (fi->fh, off, SEEK_SET) < 0)
       +    return -errno;
       +
          int bytes = smbc_read (fi->fh, buff, len);
          return (bytes < 0) ? -errno : bytes;
        }
       @@ -84,8 +92,11 @@ int usmb_write (const char *filename, const char *buff, size_t len, off_t off,
          (void)filename;
          (void)off;
        
       -  /* TODO: this may be WRONG: do we need to consider the offset */
       -  DEBUG (fprintf (stderr, "write (%p, %u)\n", buff, len));
       +  DEBUG (fprintf (stderr, "write (%p, len=%u, off=%lld)\n", buff, len, off));
       +
       +  if (smbc_lseek (fi->fh, off, SEEK_SET) < 0)
       +    return -errno;
       +
          int bytes = smbc_write (fi->fh, (char *)buff, len);
          return (bytes < 0) ? -errno : bytes;
        }
       @@ -97,9 +108,12 @@ int usmb_create (const char *filename, mode_t mode, struct fuse_file_info *fi)
          if (NULL == url)
            return -ENOMEM;
        
       +  DEBUG (fprintf (stderr, "creat (%s)", url));
       +
          int fd = smbc_creat (url, mode);
          int ret = (fd < 0) ? -errno : 0;
          fi->fh = fd;
       +  DEBUG (fprintf (stderr, " = %d\n", fd));
          
          free (url);
          return ret;
       @@ -119,6 +133,7 @@ int usmb_rename (const char *from, const char *to)
            return -ENOMEM;
          }
        
       +  DEBUG (fprintf (stderr, "rename (%s, %s)\n", fromurl, tourl));
          int ret = (smbc_rename (fromurl, tourl) < 0) ? -errno : 0;
          free (tourl);
          free (fromurl);
       @@ -131,7 +146,8 @@ int usmb_utime (const char *filename, struct utimbuf *utb)
          char *url = make_url (filename);
          if (NULL == url)
            return -ENOMEM;
       -  
       +
       +  DEBUG (fprintf (stderr, "utime (%s)\n", url));
          int ret = (smbc_utime (url, utb) < 0) ? -errno : 0;
          free (url);
          return ret;
       @@ -140,13 +156,15 @@ int usmb_utime (const char *filename, struct utimbuf *utb)
        
        int usmb_truncate (const char *filename, off_t newsize)
        {
       -  // FIXME: TODO: currently ignores newsize
       -  (void)newsize;
       +  // FIXME: TODO: handle newsize != 0
       +  if (0 != newsize)
       +    return -ENOSYS;
        
          char *url = make_url (filename);
          if (NULL == url)
            return -ENOMEM;
          
       +  DEBUG (fprintf (stderr, "truncate (%s)\n", url));
          int fd = smbc_open (url, O_WRONLY | O_TRUNC, 0);
          if (fd < 0)
            return -errno;
       @@ -154,3 +172,32 @@ int usmb_truncate (const char *filename, off_t newsize)
          smbc_close (fd);
          return 0;
        }
       +
       +
       +int usmb_chmod (const char *filename, mode_t mode)
       +{
       +  char *url = make_url (filename);
       +  if (NULL == url)
       +    return -ENOMEM;
       +  
       +  DEBUG (fprintf (stderr, "chmod (%s, %u)\n", url, mode));
       +  int ret = (smbc_chmod (url, mode) < 0) ? -errno : 0;
       +  free (url);
       +  return ret;
       +}
       +
       +
       +#if 0
       +// smbc_chown isn't implemented in libsmbclient
       +int usmb_chown (const char *filename, uid_t owner, uid_t group)
       +{
       +  char *url = make_url (filename);
       +  if (NULL == url)
       +    return -ENOMEM;
       +  
       +  DEBUG (fprintf (stderr, "chown (%s, %d, %d)\n", url, owner, group));
       +  int ret = (smbc_chown (url, owner, group) < 0) ? -errno : 0;
       +  free (url);
       +  return ret;
       +}
       +#endif
   DIR diff --git a/usmb_file.h b/usmb_file.h
       @@ -20,5 +20,7 @@
          int usmb_rename (const char *from, const char *to);
          int usmb_utime (const char *filename, struct utimbuf *utb);
          int usmb_truncate (const char *filename, off_t newsize);
       +  int usmb_chmod (const char *filename, mode_t mode);
       +//  int usmb_chown (const char *filename, uid_t owner, uid_t group);
        
        #endif
   DIR diff --git a/utils.c b/utils.c
       @@ -0,0 +1,92 @@
       +#include <assert.h>
       +#include <stdarg.h>
       +#include <stddef.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include "utils.h"
       +
       +
       +
       +#define INIT_LEN 256
       +char * concat_strings (int num, ...)
       +{
       +  va_list ap;
       +
       +  char *base, *out;
       +
       +  base = out = malloc (INIT_LEN);
       +  if (NULL == base)
       +    return NULL;
       +
       +  size_t buff_size = INIT_LEN;
       +
       +  va_start (ap, num);
       +  for (int i = 0; i < num; ++i)
       +  {
       +    const char *next = va_arg (ap, const char *);
       +    assert (NULL != next);
       +
       +    size_t next_len = strlen (next);
       +    size_t required = (out - base) + next_len + 1;
       +
       +    if (buff_size < required)
       +    {
       +      while (buff_size < required)
       +      {
       +        size_t dbl_len = buff_size * 2;
       +        if (dbl_len < buff_size)
       +        {
       +          free (base);
       +          return NULL;
       +        }
       +
       +        buff_size = dbl_len;
       +      }
       +
       +      ptrdiff_t diff = out - base;
       +
       +      char *newbase = realloc (base, buff_size);
       +      if (NULL == newbase)
       +      {
       +        free (base);
       +        return NULL;
       +      }
       +
       +      base = newbase;
       +      out = base + diff;
       +    }
       +
       +    memcpy (out, next, next_len);
       +    out += next_len;
       +  }
       +  va_end (ap);
       +
       +  *out = '\0';
       +  return base;
       +}
       +
       +
       +char * xstrdup (const char *in)
       +{
       +  char *out = NULL;
       +
       +  if (in)
       +  {
       +    size_t len = strlen (in) + 1;
       +
       +    if ((out = malloc (len)))
       +      memcpy (out, in, len);
       +  }
       +
       +  return out;
       +}
       +
       +
       +// the const here lets us pass a pointer to const
       +void xfree (const void *ptr)
       +{
       +  if (NULL != ptr)
       +    free ((void *)ptr);
       +}
       +
   DIR diff --git a/utils.h b/utils.h
       @@ -0,0 +1,15 @@
       +#ifndef UTILS_H
       +  #define UTILS_H
       +
       +  #ifdef DEBUG
       +    #undef DEBUG
       +    #define DEBUG(x) (x)
       +  #else
       +    #define DEBUG(x) ((void)0)
       +  #endif
       +
       +        char * concat_strings (int num, ...);
       +        char * xstrdup (const char *in);
       +        void xfree (const void *ptr);
       +
       +#endif
   DIR diff --git a/version.h b/version.h
       @@ -0,0 +1,21 @@
       +#ifndef VERSION_H
       +  #define VERSION_H
       +
       +        #include <stdio.h>
       +
       +        // suffix: a = alpha, b = beta, p = pre-release, r = release
       +
       +        #define USMB_VERSION_MAJOR 0
       +        #define USMB_VERSION_MINOR 1
       +        #define USMB_VERSION_SUFFIX 'a'
       +
       +        static inline void show_version (FILE *fp)
       +        {
       +          fprintf (fp, "usmb version: %d.%d%c\n",
       +                   USMB_VERSION_MAJOR, USMB_VERSION_MINOR, USMB_VERSION_SUFFIX);
       +          fprintf (fp, "FUSE version: %d.%d\n",
       +                         FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION);
       +          fprintf (fp, "Samba version: %s\n", smbc_version());
       +        }
       +
       +#endif