Merge branch 'maint'
authorAndre Noll <maan@tuebingen.mpg.de>
Thu, 22 Jan 2015 17:45:38 +0000 (18:45 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Thu, 22 Jan 2015 17:45:38 +0000 (18:45 +0100)
Was cooking for a week and seems to be regression-free.

aft: Generate a remove event when adding duplicate files.
command.c: Add missing items to EMPTY_STATUS_ITEMS.
aft.c: Don't call osl_close_disk_object() on failure.
Add missing osl() wrapper calls.
fd.c: Add missing va_end().

The conflict in aft.c was trivial to fix up.

1  2 
aft.c
blob.c
command.c
fd.c

diff --combined aft.c
--- 1/aft.c
--- 2/aft.c
+++ b/aft.c
@@@ -1,5 -1,5 +1,5 @@@
  /*
 - * Copyright (C) 2007-2013 Andre Noll <maan@systemlinux.org>
 + * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
   *
   * Licensed under the GPL v2. For licencing details see COPYING.
   */
@@@ -87,9 -87,10 +87,9 @@@ enum ls_flags 
  /**
   * The size of the individual output fields of the ls command.
   *
 - * These depend on the actual content being listed. If, for instance only files
 - * with duration less than an hour are being listed, then the duration with is
 - * made smaller because then the duration is listed as mm:ss rather than
 - * hh:mm:ss.
 + * These depend on the content being listed. For example, if each listed file
 + * is shorter than an hour, the duration format is set to mm:ss. Otherwise it
 + * is set to hh:mm:ss.
   */
  struct ls_widths {
        /** size of the score field. */
@@@ -162,7 -163,7 +162,7 @@@ enum afsi_offsets 
        AFSI_SIZE = 32
  };
  
 -/**
 +/*
   * Convert a struct afs_info to an osl object.
   *
   * \param afsi Pointer to the audio file info to be converted.
   *
   * \sa load_afsi().
   */
 -void save_afsi(struct afs_info *afsi, struct osl_object *obj)
 +static void save_afsi(struct afs_info *afsi, struct osl_object *obj)
  {
        char *buf = obj->data;
  
        memset(buf + AFSI_AUDIO_FORMAT_UNUSED_OFFSET, 0, 2);
  }
  
 -/**
 - *  Get the audio file selector info struct stored in an osl object.
 +/*
 + * Get the audio file selector info struct stored in an osl object.
   *
   * \param afsi Points to the audio_file info structure to be filled in.
   * \param obj The osl object holding the data.
   *
 - * \return Positive on success, negative on errors. Possible errors: \p E_BAD_AFS.
 + * \return Standard.
   *
   * \sa save_afsi().
   */
 -int load_afsi(struct afs_info *afsi, struct osl_object *obj)
 +static int load_afsi(struct afs_info *afsi, struct osl_object *obj)
  {
        char *buf = obj->data;
        if (obj->size < AFSI_SIZE)
@@@ -492,16 -493,15 +492,16 @@@ static int aft_get_row_of_hash(unsigne
        return osl(osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row));
  }
  
 -/**
 - * Get the osl object holding the audio file selector info of a row.
 +/*
 + * Get the audio file selector info object of a row.
   *
   * \param row Pointer to a row in the audio file table.
   * \param obj Result pointer.
   *
   * \return Standard.
   */
 -int get_afsi_object_of_row(const struct osl_row *row, struct osl_object *obj)
 +static int get_afsi_object_of_row(const struct osl_row *row,
 +              struct osl_object *obj)
  {
        return osl(osl_get_object(audio_file_table, row, AFTCOL_AFSI, obj));
  }
@@@ -541,15 -541,15 +541,15 @@@ int get_afsi_of_row(const struct osl_ro
        return load_afsi(afsi, &obj);
  }
  
 -/**
 +/*
   * Get the audio file selector info, given the path of an audio table.
   *
   * \param path The full path of the audio file.
   * \param afsi Result pointer.
   *
 - * \return Positive on success, negative on errors.
 + * \return Standard.
   */
 -int get_afsi_of_path(const char *path, struct afs_info *afsi)
 +static int get_afsi_of_path(const char *path, struct afs_info *afsi)
  {
        struct osl_object obj;
        int ret = get_afsi_object_of_path(path, &obj);
@@@ -755,6 -755,7 +755,6 @@@ static void get_duration_buf(int second
        }
  }
  
 -
  static int write_attribute_items(struct para_buffer *b,
                const char *att_bitmap, struct afs_info *afsi)
  {
@@@ -829,8 -830,8 +829,8 @@@ static int print_chunk_table(struct ls_
        ret = aft_get_row_of_hash(d->hash, &aft_row);
        if (ret < 0)
                return ret;
-       ret = osl_open_disk_object(audio_file_table, aft_row,
-               AFTCOL_CHUNKS, &chunk_table_obj);
+       ret = osl(osl_open_disk_object(audio_file_table, aft_row,
+               AFTCOL_CHUNKS, &chunk_table_obj));
        if (ret < 0)
                return ret;
        ret = para_printf(b, "%s\n"
@@@ -1105,11 -1106,12 +1105,11 @@@ int open_and_update_audio_file(struct o
        if (ret < 0)
                return ret;
        afd->afhi.chunk_table = NULL;
-       ret = osl_open_disk_object(audio_file_table, aft_row,
-               AFTCOL_CHUNKS, &chunk_table_obj);
+       ret = osl(osl_open_disk_object(audio_file_table, aft_row,
+               AFTCOL_CHUNKS, &chunk_table_obj));
        if (ret < 0)
-               goto err;
+               return ret;
 -      ret = mmap_full_file(path, O_RDONLY, &map.data,
 -              &map.size, &afd->fd);
 +      ret = mmap_full_file(path, O_RDONLY, &map.data, &map.size, &afd->fd);
        if (ret < 0)
                goto err;
        hash_function(map.data, map.size, file_hash);
@@@ -1716,10 -1718,11 +1716,11 @@@ static void com_add_callback(int fd, co
                struct osl_object obj;
                if (pb) { /* hs trumps pb, remove pb */
                        if (flags & ADD_FLAG_VERBOSE) {
-                               ret = para_printf(&msg, "removing path brother\n");
+                               ret = para_printf(&msg, "removing %s\n", path);
                                if (ret < 0)
                                        goto out;
                        }
+                       afs_event(AUDIO_FILE_REMOVE, &msg, pb);
                        ret = osl(osl_del_row(audio_file_table, pb));
                        if (ret < 0)
                                goto out;
@@@ -1874,8 -1877,12 +1875,8 @@@ static int add_one_audio_file(const cha
        ret = 1;
        if (pb && (pad->flags & ADD_FLAG_LAZY)) { /* lazy is really cheap */
                if (pad->flags & ADD_FLAG_VERBOSE)
 -                      send_ret = pad->cc->use_sideband?
 -                              send_sb_va(&pad->cc->scc, SBD_OUTPUT,
 -                                      "lazy-ignore: %s\n", path)
 -                      :
 -                              sc_send_va_buffer(&pad->cc->scc,
 -                                      "lazy-ignore: %s\n", path);
 +                      send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT,
 +                              "lazy-ignore: %s\n", path);
                goto out_free;
        }
        /* We still want to add this file. Compute its hash. */
        ret = 1;
        if (pb && hs && hs == pb && !(pad->flags & ADD_FLAG_FORCE)) {
                if (pad->flags & ADD_FLAG_VERBOSE)
 -                      send_ret = pad->cc->use_sideband?
 -                              send_sb_va(&pad->cc->scc, SBD_OUTPUT,
 -                                      "%s exists, not forcing update\n", path)
 -                      :
 -                              sc_send_va_buffer(&pad->cc->scc,
 -                                      "%s exists, not forcing update\n", path);
 +                      send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT,
 +                              "%s exists, not forcing update\n", path);
                goto out_unmap;
        }
        /*
        munmap(map.data, map.size);
        close(fd);
        if (pad->flags & ADD_FLAG_VERBOSE) {
 -              send_ret = pad->cc->use_sideband?
 -                      send_sb_va(&pad->cc->scc, SBD_OUTPUT,
 -                              "adding %s\n", path)
 -              :
 -                      sc_send_va_buffer(&pad->cc->scc,
 -                              "adding %s\n", path);
 +              send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT,
 +                      "adding %s\n", path);
                if (send_ret < 0)
                        goto out_free;
        }
@@@ -1929,8 -1944,14 +1930,8 @@@ out_unmap
        munmap(map.data, map.size);
  out_free:
        if (ret < 0 && send_ret >= 0)
 -              send_ret = pad->cc->use_sideband?
 -                      send_sb_va(&pad->cc->scc, SBD_ERROR_LOG,
 -                              "failed to add %s (%s)\n", path,
 -                              para_strerror(-ret))
 -              :
 -                      sc_send_va_buffer(&pad->cc->scc,
 -                              "failed to add %s (%s)\n", path,
 -                              para_strerror(-ret));
 +              send_ret = send_sb_va(&pad->cc->scc, SBD_ERROR_LOG,
 +                      "failed to add %s (%s)\n", path, para_strerror(-ret));
        free(obj.data);
        clear_afhi(afhi_ptr);
        /* Stop adding files only on send errors. */
@@@ -1974,7 -1995,11 +1975,7 @@@ int com_add(struct command_context *cc
                char *path;
                ret = verify_path(cc->argv[i], &path);
                if (ret < 0) {
 -                      ret = cc->use_sideband?
 -                              send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s: %s\n",
 -                              cc->argv[i], para_strerror(-ret))
 -                      :
 -                              sc_send_va_buffer(&cc->scc, "%s: %s\n",
 +                      ret = send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s: %s\n",
                                cc->argv[i], para_strerror(-ret));
                        if (ret < 0)
                                return ret;
                }
                ret = stat(path, &statbuf);
                if (ret < 0) {
 -                      ret = cc->use_sideband?
 -                              send_sb_va(&cc->scc, SBD_ERROR_LOG,
 -                                      "failed to stat %s (%s)\n", path,
 -                                      strerror(errno))
 -                              :
 -                              sc_send_va_buffer(&cc->scc,
 -                                      "failed to stat %s (%s)\n", path,
 -                                      strerror(errno));
 +                      ret = send_sb_va(&cc->scc, SBD_ERROR_LOG,
 +                              "failed to stat %s (%s)\n", path,
 +                              strerror(errno));
                        free(path);
                        if (ret < 0)
                                return ret;
                else
                        ret = add_one_audio_file(path, &pad);
                if (ret < 0) {
 -                      if (cc->use_sideband)
 -                              send_sb_va(&cc->scc, SBD_OUTPUT, "%s: %s\n", path,
 -                                      para_strerror(-ret));
 -                      else
 -                              sc_send_va_buffer(&cc->scc, "%s: %s\n", path,
 -                                      para_strerror(-ret));
 +                      send_sb_va(&cc->scc, SBD_OUTPUT, "%s: %s\n", path,
 +                              para_strerror(-ret));
                        free(path);
                        return ret;
                }
                free(path);
        }
        return 1;
 -
  }
  
  /**
@@@ -2405,12 -2440,13 +2406,12 @@@ static void com_cpsi_callback(int fd, c
  out:
        if (ret < 0)
                para_printf(&cad.pb, "%s\n", para_strerror(-ret));
 -      else if (cad.flags & CPSI_FLAG_VERBOSE) {
 -              if (pmd.num_matches > 0)
 +      else if (pmd.num_matches > 0) {
 +              if (cad.flags & CPSI_FLAG_VERBOSE)
                        para_printf(&cad.pb, "copied requested afsi from %s "
                                "to %u files\n", source_path, pmd.num_matches);
 -              else
 -                      para_printf(&cad.pb, "nothing copied\n");
 -      }
 +      } else
 +              para_printf(&cad.pb, "no matches - nothing copied\n");
        if (cad.pb.offset)
                pass_buffer_as_shm(fd, SBD_OUTPUT, cad.pb.buf, cad.pb.offset);
        free(cad.pb.buf);
@@@ -2467,108 -2503,6 +2468,108 @@@ int com_cpsi(struct command_context *cc
        return ret;
  }
  
 +struct change_atts_data {
 +      uint64_t add_mask, del_mask;
 +      struct para_buffer pb;
 +};
 +
 +static int change_atts(__a_unused struct osl_table *table,
 +              struct osl_row *row, __a_unused const char *name, void *data)
 +{
 +      int ret;
 +      struct osl_object obj;
 +      struct afs_info old_afsi, new_afsi;
 +      struct afsi_change_event_data aced = {
 +              .aft_row = row,
 +              .old_afsi = &old_afsi
 +      };
 +      struct change_atts_data *cad = data;
 +
 +      ret = get_afsi_object_of_row(row, &obj);
 +      if (ret < 0)
 +              return ret;
 +      ret = load_afsi(&old_afsi, &obj);
 +      if (ret < 0)
 +              return ret;
 +      new_afsi = old_afsi;
 +      new_afsi.attributes |= cad->add_mask;
 +      new_afsi.attributes &= ~cad->del_mask;
 +      save_afsi(&new_afsi, &obj); /* in-place update */
 +      afs_event(AFSI_CHANGE, &cad->pb, &aced);
 +      return 1;
 +}
 +
 +static void com_setatt_callback(int fd, const struct osl_object *query)
 +{
 +      char *p;
 +      int ret;
 +      size_t len;
 +      struct change_atts_data cad = {
 +              .pb = {
 +                      .max_size = shm_get_shmmax(),
 +                      .max_size_handler = afs_max_size_handler,
 +                      .private_data = &(struct afs_max_size_handler_data) {
 +                              .fd = fd,
 +                              .band = SBD_OUTPUT
 +                      }
 +              }
 +      };
 +      struct pattern_match_data pmd = {
 +              .table = audio_file_table,
 +              .loop_col_num = AFTCOL_HASH,
 +              .match_col_num = AFTCOL_PATH,
 +              .pm_flags = PM_SKIP_EMPTY_NAME,
 +              .data = &cad,
 +              .action = change_atts
 +      };
 +
 +      for (p = query->data; p < (char *)query->data + query->size; p += len + 1) {
 +              char c;
 +              unsigned char bitnum;
 +
 +              len = strlen(p);
 +              ret = -E_ATTR_SYNTAX;
 +              if (len == 0)
 +                      goto out;
 +              c = p[len - 1];
 +              if (c != '+' && c != '-')
 +                      break;
 +              p[len - 1] = '\0';
 +              ret = get_attribute_bitnum_by_name(p, &bitnum);
 +              if (ret < 0)
 +                      goto out;
 +              if (c == '+')
 +                      cad.add_mask |= (1UL << bitnum);
 +              else
 +                      cad.del_mask |= (1UL << bitnum);
 +      }
 +      ret = -E_ATTR_SYNTAX;
 +      if (!cad.add_mask && !cad.del_mask)
 +              goto out;
 +      pmd.patterns.data = p;
 +      assert(p < (char *)query->data + query->size);
 +      pmd.patterns.size = (char *)query->data + query->size - p;
 +      ret = for_each_matching_row(&pmd);
 +      if (ret < 0)
 +              goto out;
 +      if (pmd.num_matches == 0)
 +              para_printf(&cad.pb, "no matches\n");
 +out:
 +      if (ret < 0)
 +              para_printf(&cad.pb, "%s\n", para_strerror(-ret));
 +      if (cad.pb.offset)
 +              pass_buffer_as_shm(fd, SBD_OUTPUT, cad.pb.buf, cad.pb.offset);
 +      free(cad.pb.buf);
 +}
 +
 +int com_setatt(struct command_context *cc)
 +{
 +      if (cc->argc < 3)
 +              return -E_ATTR_SYNTAX;
 +      return send_standard_callback_request(cc->argc - 1, cc->argv + 1,
 +              com_setatt_callback, afs_cb_result_handler, cc);
 +}
 +
  static void afs_stat_callback(int fd, const struct osl_object *query)
  {
        int *parser_friendly = query->data;
   * is used to pass the status items from the afs process to the command handler
   * via a shared memory area and a pipe.
   *
 - * \return The return value of the underyling call to \ref send_callback_request().
 + * \return The return value of the underlying call to \ref send_callback_request().
   */
  int send_afs_status(struct command_context *cc, int parser_friendly)
  {
@@@ -2743,7 -2677,7 +2744,7 @@@ static int aft_event_handler(enum afs_e
  {
        int ret;
  
 -      switch(event) {
 +      switch (event) {
        case ATTRIBUTE_REMOVE: {
                const struct rmatt_event_data *red = data;
                ret = para_printf(pb, "clearing attribute %s (bit %u) from all "
diff --combined blob.c
--- 1/blob.c
--- 2/blob.c
+++ b/blob.c
@@@ -1,5 -1,5 +1,5 @@@
  /*
 - * Copyright (C) 2007-2013 Andre Noll <maan@systemlinux.org>
 + * Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>
   *
   * Licensed under the GPL v2. For licencing details see COPYING.
   */
@@@ -311,12 -311,12 +311,12 @@@ static int com_rmblob(callback_functio
                afs_cb_result_handler, cc);
  }
  
 -static void com_addblob_callback(struct osl_table *table, __a_unused int fd,
 +static void com_addblob_callback(struct osl_table *table, int fd,
                const struct osl_object *query)
  {
        struct osl_object objs[NUM_BLOB_COLUMNS];
 -      char *name = query->data;
 -      size_t name_len = strlen(name) + 1;
 +      char *name = query->data, *msg;
 +      size_t name_len = strlen(name) + 1, msg_len;
        uint32_t id;
        unsigned num_rows;
        int ret;
                if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
                        goto out;
                if (ret >= 0) { /* we already have a blob with this name */
 +                      ret = osl(osl_get_object(table, row, BLOBCOL_ID, &obj));
 +                      if (ret < 0)
 +                              goto out;
 +                      id = *(uint32_t *)obj.data;
                        obj.data = name + name_len;
                        obj.size = query->size - name_len;
                        ret = osl(osl_update_object(table, row, BLOBCOL_DEF, &obj));
        afs_event(BLOB_ADD, NULL, table);
  out:
        if (ret < 0)
 -              PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
 +              msg_len = xasprintf(&msg, "could not add %s: %s\n", name,
 +                      para_strerror(-ret));
 +      else
 +              msg_len = xasprintf(&msg, "added %s as id %u\n", name, id);
 +      pass_buffer_as_shm(fd, SBD_OUTPUT, msg, msg_len);
 +      free(msg);
  }
  
 -/*
 - * write input from fd to dynamically allocated buffer,
 - * but maximal max_size byte.
 - */
 -static int fd2buf(struct stream_cipher_context *scc, unsigned max_size, struct osl_object *obj)
 +/* Write input from fd to dynamically allocated buffer, but maximal 10M. */
 +static int fd2buf(struct stream_cipher_context *scc, struct osl_object *obj)
  {
 -      const size_t chunk_size = 1024;
 -      size_t size = 2048, received = 0;
 +      size_t max_size = 10 * 1024 * 1024;
        int ret;
 -      char *buf = para_malloc(size);
 +      struct iovec iov;
  
 -      for (;;) {
 -              ret = sc_recv_bin_buffer(scc, buf + received, chunk_size);
 -              if (ret <= 0)
 -                      break;
 -              received += ret;
 -              if (received + chunk_size >= size) {
 -                      size *= 2;
 -                      ret = -E_INPUT_TOO_LARGE;
 -                      if (size > max_size)
 -                              break;
 -                      buf = para_realloc(buf, size);
 -              }
 +      obj->data = NULL;
 +      obj->size = 0;
 +again:
 +      do {
 +              ret = recv_sb(scc, SBD_BLOB_DATA, max_size, &iov);
 +      } while (ret == 0);
 +
 +      if (ret < 0) {
 +              free(obj->data);
 +              obj->data = NULL;
 +              obj->size = 0;
 +              return ret;
        }
 -      obj->data = buf;
 -      obj->size = received;
 -      if (ret < 0)
 -              free(buf);
 -      return ret;
 +      if (iov.iov_len == 0) /* end of blob */
 +              return 1;
 +      if (!obj->data) {
 +              obj->data = iov.iov_base;
 +              obj->size = iov.iov_len;
 +      } else {
 +              obj->data = para_realloc(obj->data, obj->size + iov.iov_len);
 +              memcpy(obj->data + obj->size, iov.iov_base, iov.iov_len);
 +              obj->size += iov.iov_len;
 +              free(iov.iov_base);
 +              max_size -= iov.iov_len;
 +      }
 +      goto again;
 +      return 1;
  }
  
  /*
   * Read data from a file descriptor, and send it to the afs process.
   *
 - * \param scc crypt context containing the file descriptor to read data from.
 + * \param cc Contains the file descriptor to read data from.
   * \param arg_obj Pointer to the arguments to \a f.
   * \param f The callback function.
 - * \param max_len Don't read more than that many bytes from stdin.
   * \param result_handler See \ref send_callback_request.
   * \param private_result_data See \ref send_callback_request.
   *
 - * This function is used by commands that wish to let para_server store
 - * arbitrary data specified by the user (for instance the add_blob family of
 - * commands). First, at most \a max_len bytes are read and decrypted from the
 - * file descriptor given by \a scc. The result is concatenated with the buffer
 - * given by \a arg_obj, and the combined buffer is made available to the afs
 - * process via the callback method. See \ref send_callback_request for details.
 + * This function is used by the addblob commands that instruct para_server to
 + * store arbitrary data in a blob table. Input data is read and decrypted from
 + * the file descriptor given by \a cc. This data is concatenated with the
 + * buffer given by \a arg_obj, and the combined buffer is made available to the
 + * afs process via the callback method. See \ref send_callback_request for
 + * details.
   *
   * \return Negative on errors, the return value of the underlying call to
   * send_callback_request() otherwise.
   */
  static int stdin_command(struct command_context *cc, struct osl_object *arg_obj,
 -              callback_function *f, unsigned max_len,
 -              callback_result_handler *result_handler,
 +              callback_function *f, callback_result_handler *result_handler,
                void *private_result_data)
  {
        struct osl_object query, stdin_obj;
        int ret;
  
 -      if (cc->use_sideband)
 -              ret = send_sb(&cc->scc, NULL, 0, SBD_AWAITING_DATA, false);
 -      else
 -              ret = sc_send_buffer(&cc->scc, AWAITING_DATA_MSG);
 +      ret = send_sb(&cc->scc, NULL, 0, SBD_AWAITING_DATA, false);
        if (ret < 0)
                return ret;
 -      ret = fd2buf(&cc->scc, max_len, &stdin_obj);
 +      ret = fd2buf(&cc->scc, &stdin_obj);
        if (ret < 0)
                return ret;
        query.size = arg_obj->size + stdin_obj.size;
        query.data = para_malloc(query.size);
        memcpy(query.data, arg_obj->data, arg_obj->size);
 -      memcpy((char *)query.data + arg_obj->size, stdin_obj.data, stdin_obj.size);
 +      if (stdin_obj.size > 0)
 +              memcpy((char *)query.data + arg_obj->size, stdin_obj.data,
 +                      stdin_obj.size);
        free(stdin_obj.data);
        ret = send_callback_request(f, &query, result_handler, private_result_data);
        free(query.data);
@@@ -479,54 -468,38 +479,54 @@@ static int com_addblob(callback_functio
                return -E_BLOB_SYNTAX;
        arg_obj.size = strlen(cc->argv[1]) + 1;
        arg_obj.data = (char *)cc->argv[1];
 -      return stdin_command(cc, &arg_obj, f, 10 * 1024 * 1024, NULL, NULL);
 +      return stdin_command(cc, &arg_obj, f, afs_cb_result_handler, cc);
  }
  
 -/* FIXME: Print output to client, not to log file */
 -static void com_mvblob_callback(struct osl_table *table, __a_unused int fd,
 +static void com_mvblob_callback(struct osl_table *table, int fd,
                const struct osl_object *query)
  {
        char *src = (char *) query->data;
        struct osl_object obj = {.data = src, .size = strlen(src) + 1};
        char *dest = src + obj.size;
        struct osl_row *row;
 +      struct para_buffer pb = {
 +              .max_size = shm_get_shmmax(),
 +              .private_data = &fd,
 +              .max_size_handler = afs_max_size_handler
 +      };
        int ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row));
  
 -      if (ret < 0)
 +      if (ret < 0) {
 +              para_printf(&pb, "could not locate %s: %s\n", src,
 +                      para_strerror(-ret));
                goto out;
 +      }
        obj.data = dest;
        obj.size = strlen(dest) + 1;
        ret = osl(osl_update_object(table, row, BLOBCOL_NAME, &obj));
 -      if (ret < 0)
 +      if (ret < 0) {
 +              para_printf(&pb, "failed to update object %s: %s\n", dest,
 +                      para_strerror(-ret));
                goto out;
 +      }
        afs_event(BLOB_RENAME, NULL, table);
  out:
 -      if (ret < 0)
 -              PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
 +      if (pb.offset)
 +              pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset);
 +      free(pb.buf);
  }
  
  static int com_mvblob(callback_function *f, struct command_context *cc)
  {
 +      int ret;
 +
        if (cc->argc != 3)
                return -E_MOOD_SYNTAX;
 -      return send_option_arg_callback_request(NULL, cc->argc - 1,
 -              cc->argv + 1, f, NULL, NULL);
 +      ret = send_option_arg_callback_request(NULL, cc->argc - 1,
 +              cc->argv + 1, f, afs_cb_result_handler, cc);
 +      if (ret < 0)
 +              send_strerror(cc, -ret);
 +      return ret;
  }
  
  #define DEFINE_BLOB_COMMAND(cmd_name, table_name, cmd_prefix) \
@@@ -645,7 -618,7 +645,7 @@@ static int blob_get_name_and_def_by_row
        static int table_name ## _create(const char *dir) \
        { \
                table_name ## _table_desc.dir = dir; \
-               return osl_create_table(&table_name ## _table_desc); \
+               return osl(osl_create_table(&table_name ## _table_desc)); \
        }
  
  static int blob_open(struct osl_table **table,
diff --combined command.c
+++ b/command.c
@@@ -1,20 -1,15 +1,20 @@@
  /*
 - * Copyright (C) 1997-2013 Andre Noll <maan@systemlinux.org>
 + * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
   *
   * Licensed under the GPL v2. For licencing details see COPYING.
   */
  
  /** \file command.c Client authentication and server commands. */
  
 +#include <netinet/in.h>
 +#include <sys/socket.h>
  #include <regex.h>
  #include <signal.h>
  #include <sys/types.h>
  #include <osl.h>
 +#include <arpa/inet.h>
 +#include <sys/un.h>
 +#include <netdb.h>
  
  #include "para.h"
  #include "error.h"
  #include "fd.h"
  #include "ipc.h"
  #include "user_list.h"
 -#include "server_command_list.h"
 -#include "afs_command_list.h"
 +#include "server.command_list.h"
 +#include "afs.command_list.h"
  #include "signal.h"
  #include "version.h"
  
 -struct server_command afs_cmds[] = {DEFINE_AFS_CMD_ARRAY};
 -struct server_command server_cmds[] = {DEFINE_SERVER_CMD_ARRAY};
 +static struct server_command afs_cmds[] = {DEFINE_AFS_CMD_ARRAY};
 +static struct server_command server_cmds[] = {DEFINE_SERVER_CMD_ARRAY};
  
  /** Commands including options must be shorter than this. */
  #define MAX_COMMAND_LEN 32768
@@@ -115,6 -110,7 +115,6 @@@ static unsigned get_status(struct misc_
        char mtime[30] = "";
        char *status, *flags; /* vss status info */
        /* nobody updates our version of "now" */
 -      char *ut = get_server_uptime_str(NULL);
        long offset = (nmmd->offset + 500) / 1000;
        struct timeval current_time;
        struct tm mtime_tm;
                (long unsigned)current_time.tv_usec);
        free(flags);
        free(status);
 -      free(ut);
        *result = b.buf;
        return b.offset;
  }
@@@ -210,7 -207,7 +210,7 @@@ static int check_sender_args(int argc, 
   *
   * The nonblock flag must be disabled for the file descriptor given by \a scc.
   *
 - * Stream cipher encryption is automatically activated if neccessary via the
 + * Stream cipher encryption is automatically activated if necessary via the
   * sideband transformation, depending on the value of \a band.
   *
   * \return Standard.
@@@ -223,8 -220,8 +223,8 @@@ int send_sb(struct stream_cipher_contex
        int ret;
        struct sb_context *sbc;
        struct iovec iov[2];
 -      struct sb_buffer sbb = SBB_INIT(band, buf, numbytes);
        sb_transformation trafo = band < SBD_PROCEED? NULL : sc_trafo;
 +      struct sb_buffer sbb = SBB_INIT(band, buf, numbytes);
  
        sbc = sb_new_send(&sbb, dont_free, trafo, scc->send);
        do {
@@@ -271,7 -268,10 +271,7 @@@ __printf_3_4 int send_sb_va(struct stre
   */
  int send_strerror(struct command_context *cc, int err)
  {
 -      return cc->use_sideband?
 -              send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s\n", para_strerror(err))
 -      :
 -              sc_send_va_buffer(&cc->scc, "%s\n", para_strerror(err));
 +      return send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s\n", para_strerror(err));
  }
  
  /**
@@@ -338,17 -338,23 +338,17 @@@ static int com_sender(struct command_co
                        free(msg);
                        msg = tmp;
                }
 -              if (cc->use_sideband)
 -                      return send_sb(&cc->scc, msg, ret, SBD_OUTPUT, false);
 -              ret = sc_send_buffer(&cc->scc, msg);
 -              free(msg);
 -              return ret;
 +              return send_sb(&cc->scc, msg, ret, SBD_OUTPUT, false);
        }
        ret = check_sender_args(cc->argc, cc->argv, &scd);
        if (ret < 0) {
                if (scd.sender_num < 0)
                        return ret;
 -              msg = senders[scd.sender_num].help();
 -              if (cc->use_sideband)
 -                      return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT,
 -                              false);
 -              ret = sc_send_buffer(&cc->scc, msg);
 -              free(msg);
 -              return ret;
 +              if (strcmp(cc->argv[2], "status") == 0)
 +                      msg = senders[scd.sender_num].status();
 +              else
 +                      msg = senders[scd.sender_num].help();
 +              return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
        }
  
        switch (scd.cmd_num) {
        for (i = 0; i < 10; i++) {
                mutex_lock(mmd_mutex);
                if (mmd->sender_cmd_data.cmd_num >= 0) {
 +                      /* another sender command is active, retry in 100ms */
 +                      struct timespec ts = {.tv_nsec = 100 * 1000 * 1000};
                        mutex_unlock(mmd_mutex);
 -                      usleep(100 * 1000);
 +                      nanosleep(&ts, NULL);
                        continue;
                }
 -              memcpy(&mmd->sender_cmd_data, &scd, sizeof(scd));
 +              mmd->sender_cmd_data = scd;
                mutex_unlock(mmd_mutex);
                break;
        }
  /* server info */
  static int com_si(struct command_context *cc)
  {
 -      int i, ret;
 -      char *msg, *ut, *sender_info = NULL;
 +      int ret;
 +      char *msg, *ut;
  
        if (cc->argc != 1)
                return -E_COMMAND_SYNTAX;
        mutex_lock(mmd_mutex);
 -      for (i = 0; senders[i].name; i++) {
 -              char *info = senders[i].info();
 -              sender_info = para_strcat(sender_info, info);
 -              free(info);
 -      }
 -      ut = get_server_uptime_str(now);
 +      ut = daemon_get_uptime_str(now);
        ret = xasprintf(&msg,
 -              "version: %s\n"
                "up: %s\nplayed: %u\n"
                "server_pid: %d\n"
                "afs_pid: %d\n"
                "connections (active/accepted/total): %u/%u/%u\n"
                "current loglevel: %s\n"
 -              "supported audio formats: %s\n"
 -              "%s",
 -              version_git(),
 +              "supported audio formats: %s\n",
                ut, mmd->num_played,
                (int)getppid(),
                (int)mmd->afs_pid,
                mmd->num_commands,
                mmd->num_connects,
                conf.loglevel_arg,
 -              AUDIO_FORMAT_HANDLERS,
 -              sender_info
 +              AUDIO_FORMAT_HANDLERS
        );
        mutex_unlock(mmd_mutex);
        free(ut);
 -      free(sender_info);
 -      if (cc->use_sideband)
 -              return send_sb(&cc->scc, msg, ret, SBD_OUTPUT, false);
 -      ret = sc_send_bin_buffer(&cc->scc, msg, ret);
 -      free(msg);
 -      return ret;
 +      return send_sb(&cc->scc, msg, ret, SBD_OUTPUT, false);
  }
  
  /* version */
@@@ -416,9 -434,12 +416,10 @@@ static int com_version(struct command_c
        if (cc->argc != 1)
                return -E_COMMAND_SYNTAX;
        len = xasprintf(&msg, "%s", version_text("server"));
 -      if (cc->use_sideband)
 -              return send_sb(&cc->scc, msg, len, SBD_OUTPUT, false);
 -      return sc_send_bin_buffer(&cc->scc, msg, len);
 +      return send_sb(&cc->scc, msg, len, SBD_OUTPUT, false);
  }
  
+ /** These status items are cleared if no audio file is currently open. */
  #define EMPTY_STATUS_ITEMS \
        ITEM(PATH) \
        ITEM(DIRECTORY) \
        ITEM(YEAR) \
        ITEM(ALBUM) \
        ITEM(COMMENT) \
-       ITEM(AMPLIFICATION)
+       ITEM(MTIME) \
+       ITEM(FILE_SIZE) \
+       ITEM(CHUNK_TIME) \
+       ITEM(NUM_CHUNKS) \
+       ITEM(AMPLIFICATION) \
  
  /**
   * Write a list of audio-file related status items with empty values.
@@@ -516,13 -541,24 +521,13 @@@ static int com_stat(struct command_cont
        for (;;) {
                mmd_dup(nmmd);
                ret = get_status(nmmd, parser_friendly, &s);
 -              if (cc->use_sideband)
 -                      ret = send_sb(&cc->scc, s, ret, SBD_OUTPUT, false);
 -              else {
 -                      ret = sc_send_bin_buffer(&cc->scc, s, ret);
 -                      free(s);
 -              }
 +              ret = send_sb(&cc->scc, s, ret, SBD_OUTPUT, false);
                if (ret < 0)
                        goto out;
                if (nmmd->vss_status_flags & VSS_NEXT) {
                        char *esi;
                        ret = empty_status_items(parser_friendly, &esi);
 -                      if (cc->use_sideband)
 -                              ret = send_sb(&cc->scc, esi, ret, SBD_OUTPUT,
 -                                      false);
 -                      else {
 -                              ret = sc_send_bin_buffer(&cc->scc, esi, ret);
 -                              free(esi);
 -                      }
 +                      ret = send_sb(&cc->scc, esi, ret, SBD_OUTPUT, false);
                        if (ret < 0)
                                goto out;
                } else
@@@ -542,6 -578,7 +547,6 @@@ out
  static int send_list_of_commands(struct command_context *cc, struct server_command *cmd,
                const char *handler)
  {
 -      int ret;
        char *msg = NULL;
  
        for (; cmd->name; cmd++) {
                msg = para_strcat(msg, tmp);
                free(tmp);
        }
 -      if (cc->use_sideband)
 -              return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
 -      ret = sc_send_buffer(&cc->scc, msg);
 -      free(msg);
 -      return ret;
 +      assert(msg);
 +      return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
  }
  
  /* returns string that must be freed by the caller */
@@@ -609,7 -649,11 +614,7 @@@ static int com_help(struct command_cont
        );
        free(perms);
        free(handler);
 -      if (cc->use_sideband)
 -              return send_sb(&cc->scc, buf, ret, SBD_OUTPUT, false);
 -      ret = sc_send_buffer(&cc->scc, buf);
 -      free(buf);
 -      return ret;
 +      return send_sb(&cc->scc, buf, ret, SBD_OUTPUT, false);
  }
  
  /* hup */
@@@ -718,7 -762,7 +723,7 @@@ static int com_ff(struct command_contex
                promille += 1000 * i / mmd->afd.afhi.seconds_total;
        if (promille < 0)
                promille = 0;
 -      if (promille >  1000) {
 +      if (promille > 1000) {
                mmd->new_vss_status_flags |= VSS_NEXT;
                goto out;
        }
@@@ -749,8 -793,8 +754,8 @@@ static int com_jmp(struct command_conte
        if (i > 100)
                i = 100;
        PARA_INFO_LOG("jumping to %lu%%\n", i);
 -      mmd->repos_request = (mmd->afd.afhi.chunks_total * i + 50)/ 100;
 -      PARA_INFO_LOG("sent: %lu,  offset before jmp: %lu\n",
 +      mmd->repos_request = (mmd->afd.afhi.chunks_total * i + 50) / 100;
 +      PARA_INFO_LOG("sent: %lu, offset before jmp: %lu\n",
                mmd->chunks_sent, mmd->offset);
        mmd->new_vss_status_flags |= VSS_REPOS;
        mmd->new_vss_status_flags &= ~VSS_NEXT;
@@@ -761,16 -805,6 +766,16 @@@ out
        return ret;
  }
  
 +static int com_tasks(struct command_context *cc)
 +{
 +      char *tl = server_get_tasks();
 +      int ret = 1;
 +
 +      if (tl)
 +              ret = send_sb(&cc->scc, tl, strlen(tl), SBD_OUTPUT, false);
 +      return ret;
 +}
 +
  /*
   * check if perms are sufficient to exec a command having perms cmd_perms.
   * Returns 0 if perms are sufficient, -E_PERM otherwise.
@@@ -781,6 -815,58 +786,6 @@@ static int check_perms(unsigned int per
        return (cmd_ptr->perms & perms) < cmd_ptr->perms ? -E_PERM : 0;
  }
  
 -/*
 - * Parse first string from *cmd and lookup in table of valid commands.
 - * On error, NULL is returned.
 - */
 -static struct server_command *parse_cmd(const char *cmdstr)
 -{
 -      char buf[255];
 -      int n = 0;
 -
 -      sscanf(cmdstr, "%200s%n", buf, &n);
 -      if (!n)
 -              return NULL;
 -      buf[n] = '\0';
 -      return get_cmd_ptr(buf, NULL);
 -}
 -
 -static int read_command(struct stream_cipher_context *scc, char **result)
 -{
 -      int ret;
 -      char buf[4096];
 -      char *command = NULL;
 -
 -      for (;;) {
 -              size_t numbytes;
 -              char *p;
 -
 -              ret = sc_recv_buffer(scc, buf, sizeof(buf));
 -              if (ret < 0)
 -                      goto out;
 -              if (!ret)
 -                      break;
 -              numbytes = ret;
 -              ret = -E_COMMAND_SYNTAX;
 -              if (command && numbytes + strlen(command) > MAX_COMMAND_LEN) /* DOS */
 -                      goto out;
 -              command = para_strcat(command, buf);
 -              p = strstr(command, EOC_MSG);
 -              if (p) {
 -                      *p = '\0';
 -                      break;
 -              }
 -      }
 -      ret = command? 1 : -E_COMMAND_SYNTAX;
 -out:
 -      if (ret < 0)
 -              free(command);
 -      else
 -              *result = command;
 -      return ret;
 -
 -}
 -
  static void reset_signals(void)
  {
        para_sigaction(SIGCHLD, SIG_IGN);
        para_sigaction(SIGHUP, SIG_DFL);
  }
  
 +struct connection_features {
 +      bool sideband_requested;
 +      bool aes_ctr128_requested;
 +};
 +
  static int parse_auth_request(char *buf, int len, struct user **u,
 -              bool *use_sideband)
 +              struct connection_features *cf)
  {
        int ret;
        char *p, *username, **features = NULL;
        size_t auth_rq_len = strlen(AUTH_REQUEST_MSG);
  
        *u = NULL;
 -      *use_sideband = false;
 +      memset(cf, 0, sizeof(*cf));
        if (len < auth_rq_len + 2)
                return -E_AUTH_REQUEST;
        if (strncmp(buf, AUTH_REQUEST_MSG, auth_rq_len) != 0)
                create_argv(p, ",", &features);
                for (i = 0; features[i]; i++) {
                        if (strcmp(features[i], "sideband") == 0)
 -                              *use_sideband = true;
 +                              cf->sideband_requested = true;
 +                      else if (strcmp(features[i], "aes_ctr128") == 0)
 +                              cf->aes_ctr128_requested = true;
                        else {
                                ret = -E_BAD_FEATURE;
                                goto out;
                        }
                }
        }
 -      PARA_DEBUG_LOG("received auth request for user %s (sideband = %s)\n",
 -              username, *use_sideband? "true" : "false");
 +      PARA_DEBUG_LOG("received auth request for user %s\n", username);
        *u = lookup_user(username);
        ret = 1;
  out:
@@@ -854,7 -934,7 +859,7 @@@ static int parse_sb_command(struct comm
        if (ret < 0)
                goto out;
        end = iov->iov_base + iov->iov_len;
 -      for (i = 0, p = iov->iov_base; p < end; i++)
 +      for (i = 0; p < end; i++)
                p += strlen(p) + 1;
        cc->argc = i;
        cc->argv = para_malloc((cc->argc + 1) * sizeof(char *));
@@@ -875,22 -955,24 +880,22 @@@ out
   * \param fd The file descriptor to send output to.
   * \param peername Identifies the connecting peer.
   *
 - * Whenever para_server accepts an incoming tcp connection on
 - * the port it listens on, it forks and the resulting child
 - * calls this function.
 + * Whenever para_server accepts an incoming tcp connection on the port it
 + * listens on, it forks and the resulting child calls this function.
   *
 - * An RSA-based challenge/response is used to authenticate
 - * the peer. It that authentication succeeds, a random
 - * session key is generated and sent back to the peer,
 - * encrypted with its RSA public key.  From this point on,
 - * all transfers are crypted with this session key.
 + * An RSA-based challenge/response is used to authenticate the peer. It that
 + * authentication succeeds, a random session key is generated and sent back to
 + * the peer, encrypted with its RSA public key. From this point on, all
 + * transfers are crypted with this session key.
   *
 - * Next it is checked if the peer supplied  a valid server command or a command
 - * for the audio file selector.  If yes, and if the user has sufficient
 + * Next it is checked if the peer supplied a valid server command or a command
 + * for the audio file selector. If yes, and if the user has sufficient
   * permissions to execute that command, the function calls the corresponding
   * command handler which does argument checking and further processing.
   *
 - * In order to cope with a DOS attacks, a timeout is set up
 - * which terminates the function if the connection was not
 - * authenticated when the timeout expires.
 + * In order to cope with a DOS attacks, a timeout is set up which terminates
 + * the function if the connection was not authenticated when the timeout
 + * expires.
   *
   * \sa alarm(2), crypt.c, crypt.h
   */
@@@ -899,11 -981,9 +904,11 @@@ __noreturn void handle_connect(int fd, 
        int ret;
        unsigned char rand_buf[CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
        unsigned char challenge_hash[HASH_SIZE];
 -      char *p, *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */;
 +      char *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */;
        size_t numbytes;
        struct command_context cc_struct = {.peer = peername}, *cc = &cc_struct;
 +      struct iovec iov;
 +      struct connection_features cf;
  
        cc->scc.fd = fd;
        reset_signals();
                goto net_err;
        /* send Welcome message */
        ret = write_va_buffer(fd, "This is para_server, version "
 -              PACKAGE_VERSION  ".\n"
 -              "Features: sideband\n"
 +              PACKAGE_VERSION ".\n"
 +              "Features: sideband,aes_ctr128\n"
        );
        if (ret < 0)
                goto net_err;
        ret = recv_buffer(fd, buf, HANDSHAKE_BUFSIZE);
        if (ret < 0)
                goto net_err;
 -      ret = parse_auth_request(buf, ret, &cc->u, &cc->use_sideband);
 +      ret = parse_auth_request(buf, ret, &cc->u, &cf);
        if (ret < 0)
                goto net_err;
 -      p = buf + strlen(AUTH_REQUEST_MSG);
 -      PARA_DEBUG_LOG("received auth request for user %s\n", p);
 -      cc->u = lookup_user(p);
 +      if (!cf.sideband_requested) { /* sideband is mandatory */
 +              PARA_ERROR_LOG("client did not request sideband\n");
 +              ret = -E_BAD_FEATURE;
 +              goto net_err;
 +      }
        if (cc->u) {
                get_random_bytes_or_die(rand_buf, sizeof(rand_buf));
                ret = pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf),
                numbytes = 256;
                get_random_bytes_or_die((unsigned char *)buf, numbytes);
        }
 -      PARA_DEBUG_LOG("sending %u byte challenge + rc4 keys (%zu bytes)\n",
 +      PARA_DEBUG_LOG("sending %u byte challenge + session key (%zu bytes)\n",
                CHALLENGE_SIZE, numbytes);
 -      if (cc->use_sideband) {
 -              struct iovec iov;
 -              ret = send_sb(&cc->scc, buf, numbytes, SBD_CHALLENGE, false);
 -              buf = NULL;
 -              if (ret < 0)
 -                      goto net_err;
 -              ret = recv_sb(&cc->scc, SBD_CHALLENGE_RESPONSE,
 -                      HANDSHAKE_BUFSIZE, &iov);
 -              if (ret < 0)
 -                      goto net_err;
 -              buf = iov.iov_base;
 -              numbytes = iov.iov_len;
 -      } else {
 -              ret = write_all(fd, buf, numbytes);
 -              if (ret < 0)
 -                      goto net_err;
 -              /* recv challenge response */
 -              ret = recv_bin_buffer(fd, buf, HASH_SIZE);
 -              if (ret < 0)
 -                      goto net_err;
 -              numbytes = ret;
 -      }
 +      ret = send_sb(&cc->scc, buf, numbytes, SBD_CHALLENGE, false);
 +      buf = NULL;
 +      if (ret < 0)
 +              goto net_err;
 +      ret = recv_sb(&cc->scc, SBD_CHALLENGE_RESPONSE,
 +              HANDSHAKE_BUFSIZE, &iov);
 +      if (ret < 0)
 +              goto net_err;
 +      buf = iov.iov_base;
 +      numbytes = iov.iov_len;
        PARA_DEBUG_LOG("received %zu bytes challenge response\n", numbytes);
        ret = -E_BAD_USER;
        if (!cc->u)
        alarm(0);
        PARA_INFO_LOG("good auth for %s\n", cc->u->name);
        /* init stream cipher keys with the second part of the random buffer */
 -      cc->scc.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN);
 -      cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, SESSION_KEY_LEN);
 -      if (cc->use_sideband)
 -              ret = send_sb(&cc->scc, NULL, 0, SBD_PROCEED, false);
 -      else
 -              ret = sc_send_buffer(&cc->scc, PROCEED_MSG);
 +      cc->scc.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN,
 +              cf.aes_ctr128_requested);
 +      cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN,
 +              SESSION_KEY_LEN, cf.aes_ctr128_requested);
 +      ret = send_sb(&cc->scc, NULL, 0, SBD_PROCEED, false);
        if (ret < 0)
                goto net_err;
 -      if (cc->use_sideband) {
 -              struct iovec iov;
 -              ret = recv_sb(&cc->scc, SBD_COMMAND, MAX_COMMAND_LEN, &iov);
 -              if (ret < 0)
 -                      goto net_err;
 -              ret = parse_sb_command(cc, &iov);
 -              if (ret < 0)
 -                      goto err_out;
 -              cc->argc = ret;
 -      } else {
 -              ret = read_command(&cc->scc, &command);
 -              if (ret == -E_COMMAND_SYNTAX)
 -                      goto err_out;
 -              if (ret < 0)
 -                      goto net_err;
 -              ret = -E_BAD_CMD;
 -              cc->cmd = parse_cmd(command);
 -              if (!cc->cmd)
 -                      goto err_out;
 -              /* valid command, check permissions */
 -              ret = check_perms(cc->u->perms, cc->cmd);
 -              if (ret < 0)
 -                      goto err_out;
 -              /* valid command and sufficient perms */
 -              ret = create_argv(command, "\n", &cc->argv);
 -              if (ret < 0)
 -                      goto err_out;
 -              cc->argc = ret;
 -      }
 +      ret = recv_sb(&cc->scc, SBD_COMMAND, MAX_COMMAND_LEN, &iov);
 +      if (ret < 0)
 +              goto net_err;
 +      ret = parse_sb_command(cc, &iov);
 +      if (ret < 0)
 +              goto err_out;
 +      cc->argc = ret;
        PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", cc->cmd->name,
                cc->u->name, peername);
        ret = cc->cmd->handler(cc);
        if (ret >= 0)
                goto out;
  err_out:
 -      if (send_strerror(cc, -ret) >= 0 && cc->use_sideband)
 +      if (send_strerror(cc, -ret) >= 0)
                send_sb(&cc->scc, NULL, 0, SBD_EXIT__FAILURE, true);
  net_err:
        PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
@@@ -1012,7 -1125,7 +1017,7 @@@ out
                mmd->events++;
        mmd->active_connections--;
        mutex_unlock(mmd_mutex);
 -      if (ret >= 0 && cc->use_sideband) {
 +      if (ret >= 0) {
                ret = send_sb(&cc->scc, NULL, 0, SBD_EXIT__SUCCESS, true);
                if (ret < 0)
                        PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
diff --combined fd.c
--- 1/fd.c
--- 2/fd.c
+++ b/fd.c
@@@ -1,5 -1,5 +1,5 @@@
  /*
 - * Copyright (C) 2006-2013 Andre Noll <maan@systemlinux.org>
 + * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
   *
   * Licensed under the GPL v2. For licencing details see COPYING.
   */
@@@ -10,6 -10,8 +10,6 @@@
  #include <sys/types.h>
  #include <dirent.h>
  #include <sys/mman.h>
 -#include <fcntl.h>
 -#include <sys/uio.h>
  
  #include "para.h"
  #include "error.h"
@@@ -144,6 -146,7 +144,7 @@@ __printf_2_3 int write_va_buffer(int fd
  
        va_start(ap, fmt);
        ret = xvasprintf(&msg, fmt, ap);
+       va_end(ap);
        ret = write_all(fd, msg, ret);
        free(msg);
        return ret;
   *
   * \return Zero or a negative error code. If the underlying call to readv(2)
   * returned zero (indicating an end of file condition) or failed for some
 - * reason other than \p EAGAIN, a negative return value is returned.
 + * reason other than \p EAGAIN, a negative error code is returned.
   *
   * In any case, \a num_bytes contains the number of bytes that have been
   * successfully read from \a fd (zero if the first readv() call failed with
@@@ -524,7 -527,6 +525,7 @@@ static int para_opendir(const char *dir
  {
        int ret;
  
 +      *dir = NULL;
        if (cwd) {
                ret = para_open(".", O_RDONLY, 0);
                if (ret < 0)