/**
    send and receive files

    Copyright (c) 2020-2022 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "config.h"
#include "spth.h"

#include "logger.h"
#include "table.h"
#include "error.h"
#include "utils.h"
#include "system.h"
#include "crypto.h"
#include "file.h"
#include "socket.h"
#include "keygen.h"
#include "contact.h"
#include "param.h"
#include "proto.h"
#include "limit.h"
#include "proxy.h"
#include "nat.h"
#include "client.h"
#include "msg.h"
#include "xfer.h"
#include "audio.h"
#include "api.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>

#ifdef _WIN32
#include <io.h>

#ifdef _MSC_VER
#define lseek _lseeki64
#endif

#undef SOCKET_ADD
#undef SOCKET_DELETE
#define SOCKET_ADD(fd) nil ()
#define SOCKET_DELETE(fd)
#else
#include <unistd.h>
#endif

#define SIM_MODULE "xfer"

#define FILE_XFERS "transfers"
#define XFER_KEY_ARRAY "x"
#define XFER_KEY_RANDOM "id"

#define XFER_FLAG_MASTER 1    /* i have priority to send files. the other side has priority to receive */
#define XFER_FLAG_ACCEPTED 2  /* i have received the initialization packet from the other side */
#define XFER_FLAG_CONNECTED 4 /* i have sent the initialization packet to the other side */
#define XFER_FLAG_LOCKED 16   /* i have sent a speed lock to the file sender */

static const char *xfer_extension_names[] = { ".part", ".temp", NULL, NULL };

#define XFER_MODE_PART 0 /* move file and leave it intact */
#define XFER_MODE_TEMP 1 /* single attempt to move file back */
#define XFER_MODE_RCVD 2 /* automatically rename while moving */
#define XFER_MODE_SENT 3 /* only create a soft link */
#define XFER_MODE_WIPE 4 /* securely wipe moved file */

#define XFER_SPLIT_NAME(xfer) sim_file_name_split ((xfer)->name.ptr, (xfer)->name.len - 1)
#define XFER_CHECK_MODE(err) ((err) != SIM_FILE_NO_SPACE && (err) != SIM_XFER_NO_RESIZE && (err) != SIM_XFER_NO_SEND)
#define XFER_GET_MODE(err) \
  ((err) == SIM_XFER_NO_ERROR ? XFER_MODE_SENT : XFER_CHECK_MODE (err) ? XFER_MODE_WIPE : XFER_MODE_PART)
#define XFER_GET_KEY(xfer) pointer_new_len (&(xfer)->handle, sizeof ((xfer)->handle))

#define XFER_FORMAT_TYPE_NAME(c, x) (c) && (x) && ((simclient) (c))->xfer.cid != (x)->cid ? "-" : "", \
                                    XFER_LOOKUP_TYPE_NAME (x), (c) && ((simclient) (c))->xfer.current == (x) ? ':' : ' '
#define XFER_LOOKUP_TYPE_NAME(xfer) ((xfer) ? xfer_type_names[(xfer)->type - XFER_TYPE_DONE] : "NONE")

#define XFER_TYPE_DONE (-5) /* outgoing transfer finished (waiting for end) */
#define XFER_TYPE_INIT (-4) /* outgoing transfer created */
#define XFER_TYPE_WAIT (-3) /* outgoing transfer sent (waiting for accept) */
#define XFER_TYPE_HOLD (-2) /* outgoing transfer sent (had refused to accept) */
#define XFER_TYPE_SEND (-1) /* outgoing transfer started (accepted) */
#define XFER_TYPE_WIPE 0    /* file scheduled for removal */
#define XFER_TYPE_RECV 1    /* incoming transfer (started if current, finished if err not SIM_OK) */
#define XFER_TYPE_HASH 2    /* received file about to be accepted */
#define XFER_TYPE_DATA 3    /* received normal file (not yet accepted) */
#define XFER_TYPE_EXEC 4    /* received unix executable file (not yet accepted) */

static const char *xfer_type_names[] = {
  SIM_XFER_TYPE_DONE, SIM_XFER_TYPE_INIT, SIM_XFER_TYPE_WAIT, SIM_XFER_TYPE_HOLD, SIM_XFER_TYPE_SEND,
  SIM_XFER_TYPE_WIPE, SIM_XFER_TYPE_RECV, SIM_XFER_TYPE_HASH, SIM_XFER_TYPE_DATA, SIM_XFER_TYPE_EXEC
};
static const char *xfer_state_names[] = { "s", "s", "", "", "w", "r", "R" };

static pth_message_t xfer_item_all;  /* signal xfer thread to work */
static pth_message_t xfer_item_none; /* signal xfer thread to quit */
static simtype event_xfer_table;     /* preallocated SIM_EVENT_XFER */

static simtype xfer_file;          /* non-loaded contents of FILE_XFERS */
static simunsigned xfer_save_tick; /* time to save FILE_XFERS or zero if not dirty */
static int xfer_save_timeout;      /* cached value of contact.save */

static int xfer_factor;    /* cached value of xfer.namefactor */
static int xfer_ack_bytes; /* cached value of xfer.ack */
static int xfer_test;      /* cached value of xfer.test */

static simclient xfer_lock_current = NULL; /* currently transferring a file over proxy connection if non-NULL */
static int xfer_lock_fd = -1;              /* a disk operation is taking place if non-negative */
#define xfer_unlock() (xfer_lock_fd = -1)

#define XFER_TAG_CANCEL 1 /* sender aborted or failed the transfer */
#define XFER_TAG_RECV 2   /* transfer has finished successfully or unsuccessfully */
#define XFER_TAG_REJECT 3 /* receiver aborted or failed the transfer */
#define XFER_TAG_LOCAL 4  /* no message unless combined with one of the previous three */

#if HAVE_LIBPTH
#define XFER_UNPROTECT(id) (event_send_tick (id, SIM_EVENT_XFER), sim_system_cpu_get (SYSTEM_CPU_TIME_THREAD, NULL))
#define XFER_PROTECT_(cpu) limit_set_cpu (sim_system_cpu_get (SYSTEM_CPU_TIME_THREAD, NULL) - (cpu), 0)
#else
#define XFER_UNPROTECT(id) \
  (event_send_tick (id, SIM_EVENT_XFER), pth_unprotect (), sim_system_cpu_get (SYSTEM_CPU_TIME_THREAD, NULL))
#define XFER_PROTECT_(cpu) \
  ((cpu) = sim_system_cpu_get (SYSTEM_CPU_TIME_THREAD, NULL) - (cpu), pth_protect_ (), limit_set_cpu (cpu, 0))
#endif

static void event_send_xfer_history (simcontact contact, const struct _xfer *xfer, const char *tag,
                                     simnumber rcvtime, simnumber number, simbool local, simbool outgoing) {
  char *filename = xfer->name.ptr;
  simtype name = nil ();
#ifdef _WIN32
  char *s;
  if (outgoing)
    for (filename = s = (name = string_copy_string (xfer->name)).ptr; *s; s++)
      if (*s == *FILE_DIRECTORY_SLASH)
        *s = '/';
#endif
  event_send_history_system (contact, tag, filename, xfer->handle, rcvtime / 1000000000, number, local);
  string_free (name);
}

#define event_send_xfer_type(contact, xfer, oldtype, newtype) \
  event_send_xfer (contact, (xfer)->handle, pointer_new (oldtype), pointer_new (newtype))

static void event_send_xfer (simcontact contact, simnumber handle, simtype oldname, simtype newname) {
  table_set_number (event_xfer_table, SIM_CMD_XFER_SEND_HANDLE, handle);
  table_set (event_xfer_table, SIM_CMD_XFER_SEND_TYPE, newname);
  table_set (event_xfer_table, SIM_EVENT_XFER, oldname);
  event_send_fast (contact->id, event_xfer_table);
}

struct _xfer *xfer_find (simcontact contact, simnumber handle) {
  simtype table = contact->xfer.hash;
  if (table.typ == SIMNIL) {
    int len;
    struct _xfer *xfer;
    if (! xfer_factor) {
      for (xfer = contact->xfers; xfer && xfer->handle != handle; xfer = xfer->next) {}
      return xfer;
    }
    if ((len = contact->xfer.names / (contact->xfer.name + 1)) == 0)
      len = param_get_number ("xfer.names") / (param_get_number ("xfer.name") + 1);
    for (xfer = contact->xfers; xfer; xfer = xfer->next)
      len++;
    contact->xfer.hash = table = table_new_rand (len * xfer_factor / 100);
    for (xfer = contact->xfers; xfer; xfer = xfer->next)
      table_add_key_pointer_len (table, XFER_GET_KEY (xfer), xfer, sizeof (*xfer));
  }
  return table_get_key_pointer (table, pointer_new_len (&handle, sizeof (handle)));
}

static struct _xfer *xfer_find_type (simclient client, int type, int type2) {
  struct _xfer *xfer = client->contact->xfers;
  while (xfer && ((xfer->cid != client->xfer.cid && xfer->cid) || (xfer->type != type && xfer->type != type2)))
    xfer = xfer->next;
  return xfer;
}

static int xfer_put (simclient client, const struct _xfer *xfer) {
  LOG_XTRA_ ("put%d $%d %s%s%c#%lld '%s'\n", pth_msgport_pending (client->xfer.queue), client->sock->fd,
             XFER_FORMAT_TYPE_NAME (client, xfer), xfer ? xfer->handle : 0, client->contact->nick.str);
  return pth_msgport_pending (client->xfer.queue) > 0 ? SIM_OK : pth_queue_put (client->xfer.queue, &xfer_item_all);
}

static simnumber xfer_get_time (void) {
  static time_t xfer_time_last = 0;
  static int xfer_time_delta = 0;
  time_t now = time (NULL);
  if (now == (time_t) -1 || ! now)
    return -1000000000;
  xfer_time_delta = xfer_time_last != now ? 0 : xfer_time_delta + (xfer_time_delta < 999999000) * 1000;
  return (simnumber) (xfer_time_last = now) * 1000000000 + xfer_time_delta;
}

static int xfer_get_speed (simclient client, const char *param) {
  int speed, limit = param_get_number_min (param, param_get_max ("xfer.reserved", 0) >> 10) << 10;
  if ((speed = socket_check_server (client->sock) ? limit_get_param (LIMIT_PARAM_SEND) / 2 : 0) > 0)
    speed -= param_get_number ("xfer.reserved");
  return limit > 0 && (! speed || speed > limit) ? limit : speed;
}

const char *xfer_get_type_name (simcontact contact, const struct _xfer *xfer, int *error) {
  int err = xfer->err;
  if (error)
    *error = err;
  switch (xfer->type) {
    case XFER_TYPE_DATA:
      return SIM_XFER_TYPE_EXEC;
    case XFER_TYPE_RECV:
      if (! contact->client || contact->client->xfer.current != xfer)
        return SIM_XFER_TYPE_DATA;
      break;
    case XFER_TYPE_HOLD:
      if (err != SIM_OK)
        return SIM_XFER_TYPE_INIT;
      if (error)
        *error = xfer->status;
  }
  return xfer_type_names[xfer->type - XFER_TYPE_DONE];
}

static int xfer_get_type (const struct _xfer *xfer) {
  int type = xfer->type;
  if (type == XFER_TYPE_SEND || type == XFER_TYPE_WAIT || type == XFER_TYPE_DONE)
    return XFER_TYPE_INIT;
  return type == XFER_TYPE_INIT || type == XFER_TYPE_WIPE || type == XFER_TYPE_HOLD ? type : XFER_TYPE_RECV;
}

static struct _xfer *xfer_set_type (simcontact contact, struct _xfer *xfer, const char *name, int type) {
  xfer->type = (signed char) type;
  event_send_xfer_type (contact, xfer, name, type != XFER_TYPE_WIPE ? xfer_get_type_name (contact, xfer, NULL) : "");
  LOG_XTRA_ ("type $%d %s #%lld (error %d) '%s'\n", contact->client ? contact->client->sock->fd : -1,
             XFER_LOOKUP_TYPE_NAME (xfer), xfer->handle, xfer->err, contact->nick.str);
  return xfer;
}

static simclient xfer_set_current (simclient client, struct _xfer *xfer) {
  if (! xfer && client->xfer.current) {
    client->contact->connected -= client->contact->connected > 0;
    client->xfer.flags &= ~XFER_FLAG_LOCKED;
    if (client == xfer_lock_current) {
      LOG_XTRA_ ("current $%d\n", client->sock->fd);
      xfer_lock_current = NULL;
    }
    if (client->xfer.current->type == XFER_TYPE_RECV)
      event_send_xfer_type (client->contact, client->xfer.current, SIM_XFER_TYPE_RECV, SIM_XFER_TYPE_DATA);
  } else if (socket_check_server (client->sock) && xfer) {
    LOG_XTRA_ ("current $%d '%s'\n", client->sock->fd, client->contact->nick.str);
    xfer_lock_current = client;
  }
  client->xfer.current = xfer;
  client->xfer.tick[1] = client->xfer.tick[0] = 0;
  LOG_XTRA_ ("current $%d #%lld '%s'\n", client->sock->fd, xfer ? xfer->handle : 0, client->contact->nick.str);
  return client;
}

static void xfer_set_tick (int error) {
  simunsigned tick = system_get_tick ();
  if (error == SIM_LIMIT_NO_ERROR && (error = param_get_number_max ("xfer.save", xfer_save_timeout / 1000)) != 0) {
    xfer_save_tick = tick + error * 1000;
  } else if ((tick += xfer_save_timeout) < xfer_save_tick || ! xfer_save_tick)
    xfer_save_tick = tick;
}

static void xfer_set_error (simcontact contact, struct _xfer *xfer, int error) {
  xfer_set_tick (xfer->err = (short) error);
  event_send_xfer_type (contact, xfer, "", xfer_get_type_name (contact, xfer, NULL));
}

static void xfer_set_position (simclient client, simnumber size) {
  simnumber tick = client->xfer.tick[1] = system_get_tick ();
  if (! client->xfer.tick[0]) {
    client->xfer.tick[0] = tick;
    client->xfer.tickpos[0] = size;
  }
  client->xfer.tickpos[1] = size;
}

static void xfer_set_handle (simcontact contact, simnumber handle) {
  LOG_XTRA_ ("pause #%lld '%s'\n", handle, contact->nick.str);
  contact->xfer.pause = handle;
}

static int xfer_set_pause (simclient client, simcontact contact, simnumber handle) {
  int err = SIM_OK;
  struct _xfer *xfer, **last;
  simbool ok, server = ! contact;
  if (server)
    contact = client->contact;
  last = handle > 1 && (xfer = xfer_find (contact, handle)) != NULL ? xfer->last : contact->xfer.last;
  ok = *last || handle == SIM_CMD_XFER_INIT_PAUSE_ALL || handle == SIM_CMD_XFER_INIT_PAUSE_NONE;
  if (*last) {
    event_send_xfer_type (contact, *last, xfer_get_type_name (contact, *last, NULL), "");
    if (last != &contact->xfers) {
      struct _xfer *next = *(contact->xfers->last = &(*last)->next);
      (*last)->next = *((*last)->last = &contact->xfers);
      contact->xfers = *last;
      if ((*last = next) != NULL) {
        next->last = last;
      } else
        contact->xfer.last = last;
      last = &contact->xfers;
    }
  }
  LOG_ANY_ (ok ? SIM_LOG_INFO : SIM_LOG_NOTE, "pause $%d %s%s%c#%lld (error %d) '%s'\n", client ? client->sock->fd : -1,
            XFER_FORMAT_TYPE_NAME (client, *last), handle, *last ? (*last)->err : SIM_OK, contact->nick.str);
  if (ok || (server && handle > 0)) {
    xfer = NULL;
    if (handle != SIM_CMD_XFER_INIT_PAUSE_ALL && contact->xfer.pause == SIM_CMD_XFER_INIT_PAUSE_ALL) {
      for (xfer = contact->xfers; xfer; xfer = xfer->next)
        if (xfer->type == XFER_TYPE_WAIT || (xfer->err == SIM_OK ? XFER_TYPE_HOLD : XFER_TYPE_DONE) == xfer->type) {
          if (client && (client->xfer.namesent -= XFER_SPLIT_NAME (xfer).len + client->xfer.name) < 0)
            LOG_ERROR_ ("zombie unpause%d $%d %s%s%c#%lld %s '%s'\n", client->xfer.namesent, client->sock->fd,
                        XFER_FORMAT_TYPE_NAME (client, xfer), xfer->handle, xfer->name.str, contact->nick.str);
          xfer_set_type (contact, xfer, xfer_get_type_name (contact, xfer, NULL), XFER_TYPE_INIT);
        }
    } else if (*last && (*last)->type == XFER_TYPE_HOLD)
      xfer_set_type (contact, xfer = *last, xfer_get_type_name (contact, *last, NULL), XFER_TYPE_INIT);
#ifdef DONOT_DEFINE
    if (! server && client && *last && (*last)->cid != client->xfer.cid && (*last)->cid)
      handle = SIM_CMD_XFER_INIT_PAUSE_NONE;
#endif
    if (handle != SIM_CMD_XFER_INIT_PAUSE_NONE && client && client->xfer.current && client->xfer.current != *last)
      if (client->xfer.current->type == XFER_TYPE_SEND && client->xfer.current->err == SIM_OK) {
        xfer_set_type (contact, xfer = client->xfer.current, SIM_XFER_TYPE_SEND, XFER_TYPE_INIT);
        string_free (xfer->hash), xfer->hash = nil ();
      }
    if (xfer && client && (client->xfer.namesent -= XFER_SPLIT_NAME (xfer).len + client->xfer.name) < 0)
      LOG_ERROR_ ("zombie pause%d $%d %s%s%c#%lld %s '%s'\n", client->xfer.namesent, client->sock->fd,
                  XFER_FORMAT_TYPE_NAME (client, xfer), xfer->handle, xfer->name.str, contact->nick.str);
    xfer_set_handle (contact, server && handle != SIM_CMD_XFER_INIT_PAUSE_ALL ? (handle > 1) * -handle : handle);
    xfer_set_tick (SIM_OK);
    if (client && client->xfer.tid)
      err = xfer_put (client, *last);
  }
  return ok ? err : SIM_API_BAD_PARAM;
}

void xfer_free_name (simtype filename) {
  if (filename.str[filename.len]) {
    filename.str[filename.len]--;
  } else
    string_free (filename);
}

static struct _xfer *xfer_free (struct _xfer *xfer) {
  struct _xfer *next = xfer->next;
  LOG_DEBUG_ ("free %s #%lld (error %d) %s\n", XFER_LOOKUP_TYPE_NAME (xfer), xfer->handle, xfer->err, xfer->name.str);
  xfer_free_name (xfer->name);
  string_free (xfer->hash);
  string_free (sim_crypt_md_free (xfer->md));
  sim_free (xfer, sizeof (*xfer));
  return next;
}

simnumber xfer_new_handle (simcontact contact, int fd) {
  simnumber handle;
  simtype id = nil (), key;
  simbyte seed[RANDOM_SIZE_HASH * 2];
  unsigned len = 0, keylen = contact ? contact->key.len : 0;
  if (fd < 0) {
    simtype name = sim_file_name_new ("", FILE_DIRECTORY_USER);
#ifdef _WIN32
    name = (id = name).typ != SIMNIL ? sim_convert_utf_to_ucs (name.ptr, &name.len) : nil ();
    string_free (id);
#endif
    if (name.typ != SIMNIL && ! (id = sim_system_dir_get_id (name.ptr)).len)
      LOG_ERROR_ ("zombie user directory error %d\n", errno);
    string_free (name);
  } else if (! (id = sim_file_get_id (fd)).len)
    return 0;
  if ((key = table_get_string (xfer_file, XFER_KEY_RANDOM)).typ == SIMNIL) {
    random_get (random_public, key = string_new (RANDOM_SIZE_HASH));
    table_set (xfer_file, XFER_KEY_RANDOM, key);
    xfer_set_tick (SIM_LIMIT_NO_ERROR);
  }
  memcpy (memset (seed, 0, sizeof (seed)), key.str, key.len > RANDOM_SIZE_HASH ? RANDOM_SIZE_HASH : key.len);
  len = id.len > RANDOM_SIZE_HASH - keylen ? RANDOM_SIZE_HASH - keylen : id.len;
  memcpy (seed + RANDOM_SIZE_HASH, id.str, len);
  if (contact)
    memcpy (seed + RANDOM_SIZE_HASH + len, contact->key.str, keylen);
  string_buffer_free (id);
  random_open_seed (random_seeded, pointer_new_len (seed, RANDOM_SIZE_HASH + len + keylen), pointer_new (""), 1, 1);
  handle = random_get_number (random_seeded, ((simnumber) 1 << (fd < 0 ? 60 : param_get_number ("xfer.testbits"))) - 1);
  random_close (random_seeded);
  memset (seed, 0, sizeof (seed));
  return handle ? handle << 1 : 2;
}

static struct _xfer *xfer_new (simcontact contact, simtype name, simnumber filesize, simnumber handle, simnumber now) {
  struct _xfer *xfer = sim_new (sizeof (*xfer));
  memset (xfer, 0, sizeof (*xfer));
  xfer->last = contact->xfer.last;
  xfer->name = name;
  xfer->hash = nil ();
  xfer->filesize = filesize;
  xfer->handle = handle;
  xfer->sndtime = now;
  xfer->mdsize = xfer->rcvdpos = -1;
  xfer->type = XFER_TYPE_INIT;
  if (contact->xfer.hash.typ != SIMNIL)
    table_add_key_pointer_len (contact->xfer.hash, XFER_GET_KEY (xfer), xfer, sizeof (*xfer));
  *contact->xfer.last = xfer;
  contact->xfer.last = &xfer->next;
  return xfer;
}

#define xfer_new_cmd(cmd, key, value, key2, value2) \
  client_new_cmd (cmd, key, number_new (value), key2, number_new (value2))
#define xfer_new_cmd_speed(size, speed) \
  xfer_new_cmd (SIM_CMD_XFER_OFFSET, SIM_CMD_XFER_OFFSET_SIZE, size, SIM_CMD_XFER_OFFSET_SPEED, speed)
#define xfer_new_cmd_close(handle, error) \
  xfer_new_cmd (SIM_CMD_XFER_CLOSE, SIM_CMD_XFER_CLOSE_HANDLE, handle, SIM_CMD_XFER_CLOSE_ERROR, error)
#define xfer_new_cmd_end(handle, error) \
  xfer_new_cmd (SIM_CMD_XFER_END, SIM_CMD_XFER_END_HANDLE, handle, SIM_CMD_XFER_END_ERROR, error)

static simtype xfer_new_cmd_recv (simnumber handle, simnumber error, simtype hash) {
  simtype cmd = xfer_new_cmd (SIM_CMD_XFER_RECV, SIM_CMD_XFER_RECV_HANDLE, handle, SIM_CMD_XFER_RECV_SIZE, error);
  table_add (cmd, SIM_CMD_XFER_RECV_HASH, hash);
  return cmd;
}

static simtype xfer_new_name (simcontact contact, simnumber handle, const char *filename, int mode) {
  simtype name = param_get_string (mode != XFER_MODE_SENT ? "xfer.received" : "xfer.sent"), tmp;
  if (! name.len)
    return nil ();
  tmp = name = pointer_new_len (name.str, name.len);
  if (filename) {
    if (contact)
      tmp = sim_convert_simunsigned_to_string (name.ptr, contact->id, FILE_DIRECTORY_SLASH);
    name = string_cat_len (tmp.ptr, tmp.len, filename, strlen (filename) + 1);
    string_free (tmp);
    if (handle) {
      char *s = strrchr ((tmp = name).ptr, '.');
      if (s) {
        simtype str = sim_convert_simunsigned_to_string ("", handle, s);
        *s = 0;
        name = string_concat (name.ptr, "_", str.ptr, NULL);
        *s = '.';
        string_free (str);
      } else {
        name.str[strlen (name.ptr)] = '_';
        name = sim_convert_simunsigned_to_string (name.ptr, handle, "");
      }
      string_free (tmp);
    }
    tmp = string_concat (name.ptr, xfer_extension_names[mode], NULL);
  } else if (handle) {
    name = sim_convert_simunsigned_to_string (name.ptr, contact->id, NULL);
    tmp = sim_convert_simunsigned_to_string (name.ptr, handle, xfer_extension_names[mode]);
    tmp.str[strlen (name.ptr)] = *FILE_DIRECTORY_SLASH;
  }
  string_free (name);
  name = sim_file_name_new (tmp.ptr, FILE_DIRECTORY_USER);
  string_free (tmp);
  return name;
}

simtype xfer_new_dir_name (simcontact contact, simbool sent) {
  simtype name = xfer_new_name (contact, 0, "", sent ? XFER_MODE_SENT : XFER_MODE_RCVD);
  if (name.typ != SIMNIL)
    name.str[name.len - 1] = 0;
  return name;
}

static simtype xfer_create_name (simcontact contact, simnumber handle, const char *filename, int mode) {
  int i = (mode & XFER_MODE_RCVD << 8) != 0;
  simtype name = filename ? pointer_new (filename) : nil (), pathname;
  if (handle) {
    name = sim_convert_simunsigned_to_string (NULL, handle, filename);
    memmove (name.str, name.str + 1, name.len);
  }
  while (! sim_file_lstat ((pathname = xfer_new_name (contact, i++, name.ptr, mode & XFER_MODE_SENT)).ptr, NULL)) {
    string_free (pathname), pathname = nil ();
    if (mode & XFER_MODE_TEMP << 8)
      break;
    i += i == 1;
  }
  string_free (name);
  return pathname;
}

static void xfer_create_dir (simcontact contact, char *pathname) {
  char *s, *slash = strrchr (pathname, *FILE_DIRECTORY_SLASH);
  if (slash) {
    *slash = 0;
    if ((s = strrchr (pathname, *FILE_DIRECTORY_SLASH)) != NULL) {
      *s = 0;
      sim_file_mkdir (pathname);
      *s = *FILE_DIRECTORY_SLASH;
      if (! sim_file_mkdir (pathname) && param_get_number ("contact.links"))
        contact_new_link (NULL, pathname, nil (), contact->nick);
    }
    *slash = *FILE_DIRECTORY_SLASH;
  }
}

static void xfer_create_link (simcontact contact, const struct _xfer *xfer) {
  int err = SIM_XFER_NO_SAVE;
#ifdef _WIN32
  simtype newname = string_cat (XFER_SPLIT_NAME (xfer).ptr, ".lnk");
#else
  simtype newname = XFER_SPLIT_NAME (xfer);
#endif
  simtype name = xfer_create_name (contact, 0, newname.ptr, XFER_MODE_SENT);
  if (name.typ != SIMNIL && (err = ! sim_file_create_link (xfer->name.ptr, name.ptr)) != 0)
    if (xfer_create_dir (contact, name.ptr), (err = ! sim_file_create_link (xfer->name.ptr, name.ptr)) != 0) {
      string_free (name);
      name = xfer_create_name (contact, xfer->handle, strrchr (newname.ptr, '.'), XFER_MODE_SENT);
      err = sim_file_create_link (xfer->name.ptr, name.ptr) ? SIM_OK : errno ? errno : ENOENT;
    }
  if (err == SIM_OK)
    event_send_xfer (contact, SIM_XFER_GET_SENT, pointer_new (""), sim_file_name_split (name.ptr, name.len));
  LOG_ANY_ (err ? SIM_LOG_WARN : SIM_LOG_DEBUG, "link #%lld (error %d) %s\n", xfer->handle, err, name.str);
  string_free (name);
  string_free (newname);
}

static int xfer_stat (simcontact contact, simnumber handle, simnumber *filesize, simnumber *filetimes) {
  simtype name = xfer_new_name (contact, handle, NULL, XFER_MODE_TEMP);
  int err = sim_file_size (name.ptr, filesize, filetimes);
  if (err && filesize) {
    string_free (name);
    name = xfer_new_name (contact, handle, NULL, XFER_MODE_PART);
    err = sim_file_size (name.ptr, filesize, filetimes);
  }
  string_free (name);
  return err;
}

static int xfer_move_file (simclient client, const simtype oldname, const simtype newname) {
  simtype name = pointer_new ("");
  int err = errno = 0;
  if (newname.typ != SIMNIL) {
    if (newname.typ != SIMPOINTER || sim_file_link (oldname.ptr, newname.ptr))
      if (sim_file_rename (oldname.ptr, newname.ptr))
        return -1;
    name = sim_file_name_split (newname.ptr, newname.len);
  } else if (sim_file_remove (oldname.ptr) && (err = errno ? errno : ENOENT, ! sim_file_lstat (oldname.ptr, NULL))) {
    sim_error_set_text (" [", oldname.ptr, "]", err);
    event_send_name (client->contact, SIM_EVENT_ERROR, SIM_EVENT_ERROR_FILE_DELETE, number_new (err));
    return -1;
  }
  client->xfer.files -= newname.typ == SIMNIL;
  event_send_xfer (client->contact, SIM_XFER_GET_RECEIVED, sim_file_name_split (oldname.ptr, oldname.len), name);
  return 0;
}

static int xfer_move (simclient client, const struct _xfer *xfer, int mode) {
  int err = SIM_OK, newmode = mode & XFER_MODE_TEMP ? XFER_MODE_PART : XFER_MODE_TEMP;
  simtype oldname = xfer_new_name (client->contact, xfer->handle, NULL, newmode), newname;
  if (! (newname = xfer_create_name (client->contact, xfer->handle, NULL, (mode & XFER_MODE_TEMP) | mode << 8)).ptr) {
    err = SIM_FILE_BAD_NAME;
  } else if (! oldname.ptr || xfer_move_file (client, oldname, newname)) {
    err = ! oldname.ptr ? SIM_FILE_BAD_NAME : errno ? errno : SIM_XFER_NO_RENAME;
  } else
    LOG_DEBUG_ ("move $%d #%lld %s\n", client->sock->fd, xfer->handle, newname.str);
  if (err == SIM_OK && mode & XFER_MODE_WIPE && param_get_number ("xfer.wipe")) {
    if (param_get_number ("file.wipe") > 0) {
      simtype ptr = sim_file_name_split (newname.ptr, newname.len), name = FILE_NAME_COPY (ptr);
      struct _xfer *wipe = xfer_new (client->contact, name, xfer->filesize, -xfer->handle, xfer->sndtime);
      wipe->rcvtime = xfer->rcvtime;
      client->contact->xfers->last = &wipe->next;
      *(client->contact->xfer.last = wipe->last) = NULL;
      wipe->next = *(wipe->last = &client->contact->xfers);
      xfer_set_type (client->contact, client->contact->xfers = wipe, SIM_XFER_TYPE_WIPE, XFER_TYPE_WIPE);
      xfer_set_tick (xfer_put (client, wipe));
    } else if (! xfer_move_file (client, newname, nil ()))
      LOG_DEBUG_ ("delete $%d #%lld '%s'\n", client->sock->fd, xfer->handle, client->contact->nick.str);
  }
  string_free (newname);
  string_free (oldname);
  return err;
}

static void xfer_remove (simclient client, const struct _xfer *xfer, int mode) {
  int err = SIM_XFER_NO_SAVE;
  simtype name = xfer_new_name (client->contact, xfer->handle, NULL, XFER_MODE_TEMP);
  simnumber size = 0;
  if (mode != XFER_MODE_SENT)
    sim_file_size (name.ptr, &size, NULL);
  if (name.typ != SIMNIL) {
    if (size <= 0) {
      err = xfer_move_file (client, name, nil ());
    } else if ((err = mode != XFER_MODE_TEMP ? xfer_move (client, xfer, mode) : SIM_OK) != SIM_OK) {
      sim_error_set_text (" [", name.ptr, "]", err);
      event_send_name (client->contact, SIM_EVENT_ERROR, SIM_EVENT_ERROR_FILE_RENAME, number_new (err));
    }
  }
  if (size <= 0 || err != SIM_OK)
    LOG_ANY_ (err == SIM_OK ? SIM_LOG_DEBUG : SIM_LOG_WARN, "remove $%d #%lld (error %d) '%s'\n",
              client->sock->fd, xfer->handle, size > 0 ? err : errno, client->contact->nick.str);
  string_free (name);
}

static int xfer_rename (simclient client, struct _xfer *xfer) {
  int err = SIM_XFER_NO_RENAME, i = -1;
  simtype newname, name = sim_file_name_parse (xfer->name, xfer_extension_names);
  simtype oldname = xfer_new_name (client->contact, xfer->handle, NULL, XFER_MODE_TEMP);
  if ((newname = xfer_create_name (client->contact, 0, name.ptr, XFER_MODE_RCVD)).typ != SIMNIL) {
    while (xfer_move_file (client, oldname, pointer_new_len (newname.str, newname.len)) && ++i <= 1) {
      string_free (newname);
      newname = xfer_create_name (client->contact, xfer->handle, i ? NULL : strrchr (name.ptr, '.'), XFER_MODE_RCVD);
    }
    if (i <= 1) {
      simtype ptr = sim_file_name_split (newname.ptr, newname.len);
      if ((client->xfer.namercvd += ptr.len - xfer->name.len + 1) < 0) {
        LOG_ERROR_ ("zombie rename%d $%d %s%s%c#%lld %s '%s'\n", client->xfer.namercvd, client->sock->fd,
                    XFER_FORMAT_TYPE_NAME (client, xfer), xfer->handle, xfer->name.str, client->contact->nick.str);
      } else
        LOG_DEBUG_ ("rename $%d #%lld %s\n", client->sock->fd, xfer->handle, newname.str);
      xfer_free_name (xfer->name);
      xfer->name = FILE_NAME_COPY (ptr);
      event_send_xfer_type (client->contact, xfer, "", SIM_XFER_TYPE_RECV);
      err = SIM_OK;
    } else
      LOG_WARN_ ("rename $%d #%lld error %d '%s'\n", client->sock->fd, xfer->handle, errno, client->contact->nick.str);
  } else
    err = SIM_XFER_NO_SAVE;
  string_free (newname);
  string_free (oldname);
  string_free (name);
  return err;
}

static int xfer_close_file (simclient client, simnumber filetime) {
  int err = SIM_OK;
#if ! HAVE_FUTIMES && ! defined(_WIN32)
  if (client->xfer.fd >= 0 && sim_file_close (client->xfer.fd))
    err = errno ? errno : SIM_FILE_NO_SPACE;
  client->xfer.fd = -1;
#endif
  if (filetime > 0) {
    simtype name = nil ();
    struct timeval tv[2];
    tv[1].tv_sec = tv[0].tv_sec = (unsigned long) (filetime / 1000000000);
    tv[1].tv_usec = tv[0].tv_usec = (unsigned long) (filetime % 1000000000 / 1000);
    if (client->xfer.fd < 0)
      name = xfer_new_name (client->contact, client->xfer.current->handle, NULL, XFER_MODE_TEMP);
    if (client->xfer.fd < 0 ? sim_file_utimes (name.ptr, tv) : sim_file_set_times (client->xfer.fd, tv))
      LOG_WARN_ ("close:%d $%d #%lld error %d '%s'\n", client->xfer.fd, client->sock->fd,
                 client->xfer.current->handle, errno, client->contact->nick.str);
    string_free (name);
  }
#if HAVE_FUTIMES || defined(_WIN32)
  if (client->xfer.fd >= 0 && sim_file_close (client->xfer.fd))
    err = errno ? errno : SIM_FILE_NO_SPACE;
  client->xfer.fd = -1;
#endif
  return err;
}

static simbool xfer_close_current (simclient client, simnumber filetime) {
  int err = SIM_OK;
  LOG_XTRA_ ("current:%d $%d #%lld '%s'\n", client->xfer.fd, client->sock->fd,
             client->xfer.current ? client->xfer.current->handle : 0, client->contact->nick.str);
  if (client->xfer.fd >= 0) {
    struct _xfer *xfer = filetime == -1 ? client->xfer.current : NULL;
    if (client->xfer.current->type == XFER_TYPE_RECV) {
      limit_send_request (client, LIMIT_SPEED_STOP);
      if (filetime < 0)
        filetime = client->xfer.current->sndtime;
    }
    SOCKET_DELETE (client->xfer.fd);
    if ((err = xfer_close_file (client, filetime)) != SIM_OK)
      LOG_DEBUG_ ("close $%d #%lld error %d '%s'\n", client->sock->fd,
                  client->xfer.current->handle, err, client->contact->nick.str);
    client->xfer.size = 0;
    if (xfer && xfer->type == XFER_TYPE_RECV)
      xfer_remove (client, xfer, XFER_MODE_TEMP);
  }
  return err == SIM_OK;
}

static struct _xfer *xfer_close_contact (simcontact contact, struct _xfer *xfer) {
  struct _xfer **last = xfer->last;
  if ((*last = xfer->next) != NULL) {
    (*last)->last = last;
  } else
    contact->xfer.last = last;
  xfer_set_tick (SIM_OK);
  if (xfer->handle == contact->xfer.pause || -xfer->handle == contact->xfer.pause)
    xfer_set_handle (contact, xfer->handle == contact->xfer.pause ? SIM_CMD_XFER_INIT_PAUSE_NONE : 0);
  if (contact->xfer.hash.typ != SIMNIL)
    table_delete_key (contact->xfer.hash, XFER_GET_KEY (xfer));
  event_send_xfer_type (contact, xfer, "", xfer_get_type_name (contact, xfer, NULL));
  return xfer_free (xfer);
}

static struct _xfer *xfer_close_client (simclient client, struct _xfer *xfer) {
  int len = client->xfer.namesent;
  if (! xfer) {
    for (xfer = client->contact->xfers; xfer; xfer = xfer->next) {
      int type = xfer_get_type (xfer);
      if (xfer->type == XFER_TYPE_SEND)
        string_free (xfer->hash), xfer->hash = nil ();
      if (type == XFER_TYPE_RECV) {
        client->xfer.namercvd -= xfer->name.len - 1 + client->contact->xfer.name;
      } else if (xfer->type != XFER_TYPE_INIT && (type == XFER_TYPE_INIT || type == XFER_TYPE_HOLD))
        len -= XFER_SPLIT_NAME (xfer).len + client->xfer.name;
      if (xfer->type != type)
        xfer_set_type (client->contact, xfer, xfer_get_type_name (client->contact, xfer, NULL), type);
    }
    if (len || client->xfer.namercvd)
      LOG_ERROR_ ("zombie:%d:%d $%d '%s'\n", len, client->xfer.namercvd, client->sock->fd, client->contact->nick.str);
    return NULL;
  }
  if (xfer->type == XFER_TYPE_RECV || xfer->type == XFER_TYPE_DATA || xfer->type == XFER_TYPE_EXEC) {
    len = client->xfer.namercvd -= xfer->name.len - 1 + client->contact->xfer.name;
  } else if (xfer->type != XFER_TYPE_INIT)
    len = client->xfer.namesent -= XFER_SPLIT_NAME (xfer).len + client->xfer.name;
  if (len < 0)
    LOG_ERROR_ ("zombie%d $%d %s%s%c#%lld %s '%s'\n", len, client->sock->fd,
                XFER_FORMAT_TYPE_NAME (client, xfer), xfer->handle, xfer->name.str, client->contact->nick.str);
  return xfer_close_contact (client->contact, xfer);
}

static int xfer_close (simclient client, struct _xfer *xfer, int error, int tag) {
  static const char *xfer_tag_names[] = { NULL, "FILE CANCEL", "FILE RECV", "FILE REJECT" };
  int err, local = tag / XFER_TAG_LOCAL;
  const char *text = xfer_tag_names[tag &= ~XFER_TAG_LOCAL];
  simbool incoming = xfer->type == XFER_TYPE_RECV || xfer->type == XFER_TYPE_DATA || xfer->type == XFER_TYPE_EXEC;
  if (tag) {
    simnumber sendtime = xfer->sndtime;
    if (error == SIM_XFER_CANCELLED && (tag == XFER_TAG_CANCEL || tag == XFER_TAG_REJECT))
      error = SIM_OK;
    event_send_xfer_history (client->contact, xfer, text, sendtime, error, local, (tag != XFER_TAG_CANCEL) ^ incoming);
  }
  err = client->xfer.current == xfer ? xfer_put (xfer_set_current (client, NULL), xfer) : SIM_OK;
  if (tag || error != SIM_OK) {
    simnumber handle = xfer->handle;
    xfer_close_client (client, xfer);
    if (incoming) {
      event_send_xfer (client->contact, -handle, pointer_new (""), pointer_new (""));
    } else
      msg_ack (client->contact, handle, 0);
  }
  return err;
}

static simbool xfer_check_close (simcontact contact, struct _xfer **xfer, int error) {
  if ((*xfer)->err == SIM_OK)
    xfer_set_error (contact, *xfer, error == SIM_XFER_CANCELLED ? error : SIM_XFER_NO_FILE);
  if ((*xfer)->mdsize >= 0)
    return false;
  if (error == SIM_XFER_CANCELLED)
    error = SIM_OK;
  msg_ack (contact, (*xfer)->handle, 0);
  event_send_xfer_history (contact, *xfer, "FILE CANCEL", (*xfer)->sndtime, error, true, true);
  *xfer = xfer_close_contact (contact, *xfer);
  return true;
}

static simbool xfer_check_size (simcontact contact, const struct _xfer *xfer) {
  int err, minmb = param_get_number ("xfer.minmb");
  if (minmb >= 0) {
    simunsigned byte, bytes, node, nodes, minbytes = (simunsigned) minmb << 20;
    simtype name = xfer_new_name (NULL, 0, NULL, XFER_MODE_TEMP);
    if ((err = sim_file_get_stat (name, &byte, &bytes, &node, &nodes)) != SIM_OK) {
      char *s;
      name.str[strlen (name.ptr) - 1] = 0;
      if ((s = strrchr (name.ptr, *FILE_DIRECTORY_SLASH)) != NULL)
        *s = 0;
      err = sim_file_get_stat (name, &byte, &bytes, &node, &nodes);
    }
    string_free (name);
    if (err != SIM_OK) {
      LOG_WARN_ ("statfs #%lld error %d\n", xfer->handle, err);
    } else if (node != nodes && nodes && node <= minbytes / (bytes / nodes)) {
      LOG_NOTE_ ("statfs #%lld nodes %lld <= %lld\n", xfer->handle, node, minbytes / (bytes / nodes));
      return false;
    } else if (byte < (minbytes += client_get_param (CLIENT_PARAM_XFER_SIZE) + xfer->filesize)) {
      err = xfer_stat (contact, xfer->handle, (simnumber *) &bytes, NULL);
      if (err || (bytes <= (simunsigned) xfer->filesize && byte < minbytes - bytes)) {
        LOG_NOTE_ ("statfs #%lld size %lld > %lld\n", xfer->handle, xfer->filesize, byte);
        return false;
      }
    }
  }
  return true;
}

static int xfer_check_files (simclient client) {
  simtype name = xfer_new_dir_name (client->contact, false);
  int maxfiles = param_get_number ("xfer.files");
  if (name.typ == SIMNIL)
    return SIM_XFER_NO_SAVE;
  if (maxfiles > 0 && client->xfer.files <= 0) {
    void *dir = sim_system_dir_open (name.ptr);
    client->xfer.files = 1;
    if (dir) {
      for (client->xfer.files -= 2; sim_system_dir_read (dir); client->xfer.files++) {}
      sim_system_dir_close (dir);
    }
    LOG_ANY_ (dir ? SIM_LOG_DEBUG : SIM_LOG_WARN, "files = %lld (error %d) '%s'\n",
              client->xfer.files, errno, client->contact->nick.str);
    if (client->xfer.files <= 0)
      client->xfer.files = 1;
  }
  string_free (name);
  return maxfiles <= 0 || client->xfer.files <= maxfiles ? SIM_OK : SIM_XFER_NO_NAME;
}

simbool xfer_check_pending (simcontact contact, simnumber cid) {
  struct _xfer *xfer;
  simbool paused = contact->xfer.pause == SIM_CMD_XFER_INIT_PAUSE_ALL || contact->msgs.flags & CONTACT_MSG_OVERFLOW;
  if ((contact->flags & CONTACT_FLAG_AUDIO && MSG_CHECK_CALL (contact)) || contact->msgs.pinging != MSG_NUMBER_INVALID)
    return true;
  if (! paused && contact->flags & CONTACT_FLAG_XFER && CONTACT_CHECK_RIGHT_XFER (contact) && contact->xfers) {
    if (contact->xfer.pause == SIM_CMD_XFER_INIT_PAUSE_NONE || contact->xfer.pause > 1)
      return true;
    for (xfer = contact->xfers; xfer; xfer = xfer->next)
      if (xfer->cid == cid || ! xfer->cid || ! cid || xfer->type == XFER_TYPE_WIPE) {
        if (xfer->err != SIM_OK || xfer->type == XFER_TYPE_INIT || xfer->type == XFER_TYPE_DONE)
          return true;
        if (xfer->type == XFER_TYPE_WIPE || xfer->type == XFER_TYPE_SEND || xfer->type == XFER_TYPE_WAIT)
          return true;
      }
  } else
    for (xfer = contact->xfers; xfer; xfer = xfer->next)
      if (((xfer->cid == cid || ! xfer->cid || ! cid) && xfer->err != SIM_OK) || xfer->type == XFER_TYPE_WIPE)
        return true;
  return msg_check_pending (contact);
}

static simbool xfer_check_locked (simclient client, simclient lock, int flags) {
  simbool nok = true;
  if (! (client->flags & (flags | CLIENT_FLAG_TRAVERSING)))
    if (! socket_check_server (client->sock) || (! lock && ! client_get_param (CLIENT_PARAM_XFER_NAT))) {
      simclient tmp = audio_status.client;
      if (! tmp || tmp == AUDIO_CLIENT_TEST || tmp->flags & CLIENT_FLAG_UDP || ! SOCKET_CHECK_PROXY (tmp->sock)) {
        nok = false;
      } else if (tmp != client && (! socket_check_server (client->sock) || ! socket_check_server (tmp->sock)))
        nok = false;
    }
  return nok;
}

void xfer_check_unlock (simclient client) {
  if (client == xfer_lock_current && ! socket_check_server (client->sock)) {
    LOG_XTRA_ ("current $%d\n", client->sock->fd);
    xfer_lock_current = NULL;
  }
  if (client->xfer.flags & XFER_FLAG_LOCKED && ! xfer_check_locked (client, NULL, 0))
    client_send (client, xfer_new_cmd_speed (client->xfer.current->mdsize, 0));
}

static void xfer_lock_ (int fd) {
  if (xfer_lock_fd >= 0) {
    LOG_DEBUG_ ("LOCKED $%d\n", xfer_lock_fd);
    while (xfer_lock_fd >= 0)
      pth_usleep_ (100000);
    LOG_DEBUG_ ("LOCK $%d\n", fd);
  }
  xfer_lock_fd = fd;
}

static int xfer_hash_ (void *md, simclient client, simnumber *size, int fd, const struct _xfer *xfer) {
  int err = SIM_OK, len = -1, type = xfer->type, buflen = abs (param_get_number ("file.wipe"));
  simtype buf = string_buffer_new (buflen > SIM_FILE_BUFFER_SIZE ? buflen : SIM_FILE_BUFFER_SIZE);
  simunsigned count = *size, cpu;
#ifdef _WIN32
  simbool locked;
  HANDLE handle = (HANDLE) _get_osfhandle (fd);
  OVERLAPPED ov;
#endif
  xfer_lock_ (client->sock->fd);
#ifdef _WIN32
  memset (&ov, 0, sizeof (ov));
  if ((locked = LockFileEx (handle, LOCKFILE_FAIL_IMMEDIATELY, 0, (DWORD) count, (DWORD) (count >> 32), &ov)) == false)
    LOG_WARN_ ("lock:%d $%d error %d '%s'\n", fd, client->sock->fd, GetLastError (), client->contact->nick.str);
#endif
  while (err == SIM_OK && xfer->err == SIM_OK && xfer->type == type && count && len) {
    if (client->sock->err != SIM_OK && (*size != (simnumber) 1 << 62 || simself.state != CLIENT_STATE_RUNNING))
      break;
    cpu = XFER_UNPROTECT (client->contact->id);
    if ((len = sim_file_read (fd, buf.str, buf.len > count ? (unsigned) count : buf.len)) > 0) {
      count -= len;
      sim_crypt_md_update (md, buf.str, len);
    } else if (len < 0) {
      LOG_WARN_ ("hash:%d $%d #%lld error %d %s '%s'\n", fd, client->sock->fd, xfer->handle,
                 errno, xfer->name.str, client->contact->nick.str);
      err = SIM_FILE_END;
    }
    XFER_PROTECT_ (cpu);
  }
#ifdef _WIN32
  memset (&ov, 0, sizeof (ov));
  if (locked && ! UnlockFileEx (handle, 0, (DWORD) *size, (DWORD) ((simunsigned) *size >> 32), &ov))
    LOG_WARN_ ("unlock:%d $%d error %d '%s'\n", fd, client->sock->fd, GetLastError (), client->contact->nick.str);
#endif
  *size -= count;
  xfer_unlock ();
  string_buffer_free (buf);
  return xfer->err == SIM_OK ? err : xfer->err;
}

static void xfer_append_hash (simcontact contact, const simtype hash, const char *filename, int mode) {
  simtype name;
  if (param_get_number ("xfer.md5") && (name = xfer_new_name (contact, 0, FILE_EXTENSION_MD5 + 1, mode)).ptr) {
    int err = SIM_OK;
    const char *tag = SIM_EVENT_ERROR_FILE_CREATE;
    FILE *f;
    name.str[strlen (name.ptr) - SIM_STRING_GET_LENGTH_CONST (FILE_EXTENSION_MD5)] = '.';
    if ((f = sim_file_fopen (name.ptr, "at")) != NULL) {
      unsigned i;
      char buf[3];
      for (i = 0; i < hash.len && err == SIM_OK; i++)
        if (sprintf (buf, "%02x", hash.str[i]), fputs (buf, f) < 0)
          err = errno ? errno : ENOSPC;
      if (err == SIM_OK && (fputs ("  ", f) < 0 || fputs (filename, f) < 0 || fputc ('\n', f) < 0))
        err = errno ? errno : ENOSPC;
      if (fclose (f) && err == SIM_OK) {
        err = errno ? errno : ENOSPC;
        tag = SIM_EVENT_ERROR_FILE_CLOSE;
      } else
        tag = SIM_EVENT_ERROR_FILE_WRITE;
    } else
      err = errno ? errno : ENOSPC;
    if (err != SIM_OK)
      sim_error_set_text (" [", name.ptr, "]", err);
    event_test_error (contact, tag, err);
    string_free (name);
  }
}

static int xfer_send_names (simclient client) {
  int err = SIM_OK, flag, minlen = param_get_min ("xfer.names", 0);
  struct _xfer *xfer;
  for (flag = -2; (client->contact->xfer.pause == SIM_CMD_XFER_INIT_PAUSE_ALL ? -1 : 1) >= ++flag;)
    for (xfer = client->contact->xfers; xfer && err == SIM_OK;) {
      if (xfer->type == XFER_TYPE_INIT && (xfer->err != SIM_OK ? -1 : xfer->status != SIM_SOCKET_NO_ERROR) == flag)
        if (xfer->cid == client->xfer.cid || ! xfer->cid) {
          simnumber size = xfer->filesize, dt;
          simtype cmd, name = XFER_SPLIT_NAME (xfer);
          int fd, type = 0, len = client->xfer.namesent + client->xfer.name + name.len;
          if (xfer->err != SIM_OK) {
            LOG_DEBUG_ ("init $%d close #%lld:#%lld error %d '%s'\n", client->sock->fd,
                        client->xfer.cid, xfer->handle, xfer->err, client->contact->nick.str);
            err = client_send (client, xfer_new_cmd_close (xfer->handle, xfer->err));
            client->xfer.namesent = len;
            xfer = xfer_set_type (client->contact, xfer, SIM_XFER_TYPE_INIT, XFER_TYPE_DONE)->next;
            continue;
          }
          if (client->xfer.names == minlen ? client->xfer.namesent : len > client->xfer.names) {
            flag = 1;
            break;
          }
          if ((fd = sim_file_open (xfer->name.ptr, FILE_BIT_READ, &size)) >= 0) {
            type = sim_file_get_type (fd, NULL);
            sim_file_close (fd);
          } else if (xfer_check_close (client->contact, &xfer, errno))
            continue;
          client->xfer.namesent = len;
          LOG_DEBUG_ ("init $%d %lld:#%lld %lld %s '%s'\n", client->sock->fd, client->xfer.cid, xfer->handle,
                      size, xfer->status == SIM_OK ? name.str : (simbyte *) "", client->contact->nick.str);
          cmd = xfer_new_cmd (SIM_CMD_XFER_SEND, SIM_CMD_XFER_SEND_HANDLE, xfer->handle, SIM_CMD_XFER_SEND_SIZE, size);
          if (type > 0)
            table_add_pointer (cmd, SIM_CMD_XFER_SEND_TYPE, "");
          if (xfer->status == SIM_OK)
            table_add (cmd, SIM_CMD_XFER_SEND_NAME, string_copy_string (name));
          table_add_number (cmd, SIM_CMD_XFER_SEND_TIME, (dt = time (NULL) - xfer->sndtime / 1000000000) > 0 ? dt : 0);
          if (xfer->filesize != size) {
            xfer->filesize = size;
            xfer_set_error (client->contact, xfer, SIM_OK);
          } else if (xfer->hash.typ != SIMNIL || ! xfer->cid || xfer->mdsize < 0)
            xfer_set_tick (SIM_OK);
          string_free (xfer->hash), xfer->hash = nil ();
          xfer->cid = client->xfer.cid;
          xfer_set_type (client->contact, xfer, SIM_XFER_TYPE_INIT, XFER_TYPE_WAIT);
          if ((err = client_send (client, cmd)) == SIM_OK && xfer->mdsize < 0)
            msg_ack (client->contact, xfer->handle, 1);
          xfer->mdsize = 0;
        }
      xfer = xfer->next;
    }
  return err;
}

static int xfer_send_ (simclient client, struct _xfer *xfer) {
  simtype hash = nil ();
  simtype tmp = client_new_cmd (SIM_CMD_XFER_DATA, SIM_CMD_XFER_DATA_BYTES, string_new (256), NULL, nil ());
  simnumber size = 0, hashsize = xfer->mdsize, filetime = 0;
  int err = SIM_OK, err2 = SIM_OK, fd, retries, addsize = table_size (tmp, 0) - 256;
  table_free (tmp);
  client->contact->connected++;
  event_send_xfer_type (xfer_set_current (client, xfer)->contact, xfer, SIM_XFER_TYPE_SEND, SIM_XFER_TYPE_SEND);
  fd = xfer->err == SIM_OK ? sim_file_open (xfer->name.ptr, FILE_BIT_READ, &size) : (errno = xfer->err, -1);
  LOG_DEBUG_SIMTYPE_ (xfer->hash, LOG_BIT_BIN, "send:%d $%d #%lld (error %d) %lld/%lld '%s' ",
                      fd, client->sock->fd, xfer->handle, errno, hashsize, size, client->contact->nick.str);
  if ((client->xfer.fd = fd) >= 0) {
    void *md = xfer->md ? sim_crypt_md_copy (xfer->md) : NULL;
    simnumber ftimes[3], chtime = sim_file_get_type (fd, ftimes) < 0 ? 0 : ftimes[1];
    if (SOCKET_ADD (fd).typ != SIMNIL)
      LOG_ERROR_ ("zombie send:%d '%s'\n", fd, client->contact->nick.str);
    if ((retries = param_get_number ("xfer.retries")) != 0 && xfer->retries >= (unsigned short) retries) {
      err2 = SIM_XFER_NO_SEND;
    } else if (hashsize > size || hashsize < xfer->rcvdsize) {
      LOG_WARN_ ("send:%d $%d #%lld %lld/%lld size %lld:%lld '%s'\n", fd, client->sock->fd, xfer->handle,
                 hashsize, size, xfer->rcvdsize, xfer->rcvdpos, client->contact->nick.str);
      err2 = SIM_XFER_NO_RESUME;
    } else if (xfer->filesize != size) {
      err2 = SIM_XFER_NO_RESIZE;
    } else if (! md || xfer->rcvtime != chtime) {
      if (md)
        LOG_WARN_ ("send:%d $%d #%lld %lld/%lld '%s'\n", fd, client->sock->fd,
                   xfer->handle, size, xfer->filesize, client->contact->nick.str);
      string_free (sim_crypt_md_free (md));
      md = sim_crypt_md_new (CRYPT_MD_MD5);
      xfer->rcvdsize = 0;
    } else if (lseek (fd, xfer->rcvdsize, SEEK_SET) != xfer->rcvdsize) {
      err2 = SIM_FILE_NO_SEEK;
      LOG_ERROR_ ("lseek:%d #%lld error %d\n", fd, xfer->handle, errno);
    } else
      hashsize -= xfer->rcvdsize;
    xfer->retries += err2 != SIM_XFER_NO_SEND;
    client->xfer.speed = 0;
    if (err2 == SIM_OK && (err = limit_send_request (client, LIMIT_SPEED_SEND)) == SIM_OK)
      if ((err2 = xfer_hash_ (md, client, &hashsize, fd, xfer)) == SIM_OK) {
        if (client->sock->err == SIM_OK) {
          hash = sim_crypt_md_free (sim_crypt_md_copy (md));
          if (xfer->type == XFER_TYPE_SEND && string_check_diff_len (xfer->hash, hash.str, hash.len))
            err2 = SIM_XFER_NO_RESEND;
        } else
          err = client->sock->err;
        size -= hashsize = xfer->rcvdsize += hashsize;
        if (xfer->type == XFER_TYPE_SEND)
          xfer->rcvtime = chtime;
      }
    string_free (sim_crypt_md_free (xfer->md));
    xfer->md = md;
    md = NULL;
    if (err2 == SIM_OK) {
      simcustomer proxy;
      simunsigned cpu;
      simnumber bytes, oldbytes = client->xfer.tick[1] = -1, tick = system_get_tick ();
      simtype buf = string_buffer_new (SIM_MAX_PACKET_SIZE / 2);
      int len = 0, reserved = param_get_max ("xfer.reserved", 0), speed, oldspeed = 0, expand = -1, pad, bufsize;
      md = sim_crypt_md_copy (xfer->md);
      while (err == SIM_OK && xfer->err == SIM_OK && xfer->type == XFER_TYPE_SEND && xfer->filesize) {
        int buflen = socket_size_client (client->sock, pad = param_get_number ("xfer.padding"), &bufsize) - addsize;
#ifndef DONOT_DEFINE
        if (client->contact->xfer.pause == SIM_CMD_XFER_INIT_PAUSE_ALL) {
          xfer_set_type (client->contact, xfer, SIM_XFER_TYPE_SEND, XFER_TYPE_INIT);
          string_free (xfer->hash), xfer->hash = nil ();
          if ((client->xfer.namesent -= XFER_SPLIT_NAME (xfer).len + client->xfer.name) < 0)
            LOG_ERROR_ ("zombie accept%d $%d %s%s%c#%lld %s '%s'\n", client->xfer.namesent, client->sock->fd,
                        XFER_FORMAT_TYPE_NAME (client, xfer), xfer->handle, xfer->name.str, client->contact->nick.str);
          break;
        }
#endif
        if (expand < 0 && (expand = param_get_number ("xfer.expand")) > 0) {
          int padding = client->sock->crypt.padding;
          pad = pad >= padding ? pad / padding : 1;
          addsize += (int) (((xfer_new_handle (NULL, fd) >> 1) % (buflen * expand / 100) % (pad * padding)));
        }
        if (((speed = xfer_get_speed (client, "xfer.upload")) == 0 || speed > client->xfer.speed) && client->xfer.speed)
          speed = client->xfer.speed;
        if (socket_check_server (client->sock) && (proxy = proxy_get_proxy (NULL, NULL, NULL)) != NULL) {
          simnumber rcvd = SOCKET_GET_STAT_RECEIVED (proxy->sock) + SOCKET_GET_STAT_RECEIVED (&proxy->sock->old);
          simnumber sent = SOCKET_GET_STAT_SENT (proxy->sock) + SOCKET_GET_STAT_SENT (&proxy->sock->old);
          bytes = rcvd + sent;
        } else
          bytes = SOCKET_GET_STAT_RECEIVED (client->sock) + SOCKET_GET_STAT_SENT (client->sock);
        if (speed >= reserved || oldbytes < 0) {
          simnumber now = system_get_tick (), msec = now - tick;
          simnumber min, delay = speed > 0 ? (SOCKET_CALC_OVERHEAD_TCP (bufsize) * 2 + bufsize) * 1000 / speed : 0;
          if (oldbytes < 0 || bytes - oldbytes < speed * msec / 1000 || (oldspeed > 0 && speed != oldspeed)) {
            tick = now;
            oldbytes = bytes;
          } else if ((min = (bytes - oldbytes) * 1000 / speed - msec) > delay)
            delay = min;
          LOG_XTRA_ ("speed $%d %d bytes/sec (%lld ms)\n", client->sock->fd, speed, delay);
          while ((msec = system_get_tick () - now) < delay && xfer->err == SIM_OK && xfer->type == XFER_TYPE_SEND)
            if (delay - msec >= 1000) {
              if ((err = xfer_send_names (client)) != SIM_OK || (err = client->sock->err) != SIM_OK)
                break;
              pth_sleep_ (1);
            } else
              pth_usleep_ (((unsigned) (delay - msec) + 1) * 1000 - 1);
        }
        oldspeed = speed;
        cpu = XFER_UNPROTECT (client->contact->id);
        if ((len = sim_file_read (fd, buf.str, buflen)) > 0)
          sim_crypt_md_update (md, buf.str, len);
        XFER_PROTECT_ (cpu);
        if (len <= 0 || (size -= len) < 0)
          break;
        while (err == SIM_OK && xfer->type == XFER_TYPE_SEND) {
          if ((err = xfer_send_names (client)) != SIM_OK || xfer->err != SIM_OK || xfer->type != XFER_TYPE_SEND)
            break;
          if (! client->xfer.speed || client->xfer.speed >= reserved)
            if (! xfer_check_locked (client, NULL, CLIENT_FLAG_BUFFERING)) {
              simtype ptr = pointer_new_len (buf.str, len);
              hashsize += len;
              if (len != buflen) {
                ptr = string_copy_len (buf.str, len);
                err = client_send_cmd (client, SIM_CMD_XFER_DATA, SIM_CMD_XFER_DATA_BYTES, ptr, NULL, nil ());
              } else
                err = client_send_cmd_ (client, SIM_CMD_XFER_DATA, SIM_CMD_XFER_DATA_BYTES, ptr, NULL, nil ());
              break;
            }
          pth_sleep_ (1);
          err = client->sock->err;
        }
      }
      string_buffer_free (buf);
      if (err == SIM_OK && xfer->err == SIM_OK && xfer->type == XFER_TYPE_SEND) {
        if (len || size) {
          LOG_WARN_ ("read:%d $%d #%lld (error %d) %lld %s '%s'\n", fd, client->sock->fd,
                     xfer->handle, errno, size, xfer->name.str, client->contact->nick.str);
          err2 = len < 0 ? SIM_FILE_END : SIM_XFER_NO_RESIZE;
        } else {
          int check = param_get_number ("xfer.recheck");
          filetime = sim_file_get_type (fd, ftimes) >= 0 ? ftimes[0] : 0;
          if (check && lseek (fd, 0, SEEK_SET)) {
            err2 = SIM_FILE_NO_SEEK;
            LOG_ERROR_ ("lseek:%d #%lld error %d\n", fd, xfer->handle, errno);
          } else if (check > 0 || (check && ftimes[1] != chtime)) {
            LOG_WARN_ ("send:%d $%d #%lld '%s'\n", fd, client->sock->fd, xfer->handle, client->contact->nick.str);
            string_free (hash);
            hash = sim_crypt_md_free (md);
            err2 = xfer_hash_ (md = sim_crypt_md_new (CRYPT_MD_MD5), client, &hashsize, fd, xfer);
            if (err2 == SIM_OK && xfer->type == XFER_TYPE_SEND && client->sock->err == SIM_OK) {
              sim_file_get_type (fd, NULL);
              if (string_check_diff_len (tmp = sim_crypt_md_free (sim_crypt_md_copy (md)), hash.str, hash.len)) {
                err2 = SIM_XFER_NO_RESEND;
              } else if ((err = sim_file_get_size (fd, &hashsize)) == SIM_OK && xfer->filesize != hashsize)
                err2 = SIM_XFER_NO_RESIZE;
              string_free (tmp);
            }
          }
        }
      }
    }
    string_free (hash);
    hash = sim_crypt_md_free (md);
    SOCKET_DELETE (fd);
    sim_file_close (fd), client->xfer.fd = -1;
    limit_send_request (client, LIMIT_SPEED_STOP);
  } else if ((err2 = errno) != xfer->err)
    err2 = SIM_XFER_NO_FILE;
  if (xfer->type != XFER_TYPE_SEND) {
    simnumber handle = xfer->handle;
    xfer_set_current (client, NULL);
    LOG_DEBUG_ ("send $%d end #%lld (error %d) '%s'\n", client->sock->fd, handle, err2, client->contact->nick.str);
    err2 = SIM_XFER_PAUSED;
    if (xfer->type == XFER_TYPE_DONE) {
      xfer_close_client (client, xfer);
      err2 = SIM_OK;
    } else if (xfer->type != XFER_TYPE_INIT && xfer->type != XFER_TYPE_WAIT)
      LOG_FATAL_ (SIM_OK, "send %s%s%c#%lld (error %d) '%s'\n",
                  XFER_FORMAT_TYPE_NAME (client, xfer), handle, xfer->err, client->contact->nick.str);
    if (err == SIM_OK)
      err = client_send (client, xfer_new_cmd_end (handle, err2));
  } else if (err == SIM_OK) {
    if (xfer->err == SIM_OK)
      xfer_set_error (client->contact, xfer, err2);
    LOG_DEBUG_ ("send $%d close #%lld (error %d) '%s'\n", client->sock->fd,
                xfer->handle, xfer->err, client->contact->nick.str);
    xfer_set_type (xfer_set_current (client, NULL)->contact, xfer, SIM_XFER_TYPE_SEND, XFER_TYPE_DONE);
    string_free (xfer->hash), xfer->hash = nil ();
    tmp = xfer_new_cmd_close (xfer->handle, xfer->err);
    if (xfer->err == SIM_OK && hash.typ != SIMNIL) {
      void *md = sim_crypt_md_new (CRYPT_MD_MD5);
      table_add (tmp, SIM_CMD_XFER_CLOSE_HASH, sim_crypt_md_hash (md, xfer->hash = hash, nil (), nil ()));
      hash = nil ();
    } else
      table_add_pointer (tmp, SIM_CMD_XFER_CLOSE_HASH, "");
    if (xfer->err == SIM_OK && filetime > 0 && SIM_NUMBER_CHECK (filetime)) {
      static const int xfer_time_divisors[] = { 1000000000, 1000000, 1000, 1 };
      int i = param_get_number ("xfer.sendtime");
      table_add_number (tmp, SIM_CMD_XFER_CLOSE_TIME, i ? filetime - filetime % xfer_time_divisors[i - 1] : 0);
    }
    err = client_send (client, tmp);
  } else
    xfer_set_current (client, NULL);
  string_free (hash);
  return err;
}

int xfer_send_close (simcontact contact, simnumber handle, int error) {
  int err = SIM_OK, err2;
  simclient client = contact->client;
  struct _xfer *xfer = handle > 1 ? xfer_find (contact, handle) : NULL, *next;
  if (! xfer) {
    if (handle)
      return SIM_API_BAD_PARAM;
    for (xfer = contact->xfers; xfer; xfer = next) {
      next = xfer->next;
      if (xfer->type != XFER_TYPE_WIPE && (err2 = xfer_send_close (contact, xfer->handle, error)) != SIM_OK)
        err = err2;
    }
    return err;
  }
  if (error == SIM_LIMIT_NO_ERROR) {
    simbool ok = client && client->xfer.current != xfer && xfer->type != XFER_TYPE_HASH;
    return ok ? xfer_close (client, xfer, error, 0) : client ? err : (xfer_close_contact (contact, xfer), err);
  }
  if (error != SIM_XFER_CANCELLED) {
    if (client && (error == SIM_NO_ERROR || error == SIM_SOCKET_NO_ERROR))
      return client_send (client, xfer_new_cmd_end (handle, error == SIM_NO_ERROR ? SIM_OK : SIM_XFER_NO_ERROR));
    if (client && client->xfer.current == xfer && xfer->type == XFER_TYPE_RECV && error <= 0)
      xfer_close_current (client, -1);
    return error > 0 ? (xfer->err = -error) : client ? client_send (client, xfer_new_cmd_close (handle, error)) : err;
  }
  if (xfer->type != XFER_TYPE_INIT && (xfer->type != XFER_TYPE_HOLD || client)) {
    if (xfer->type != XFER_TYPE_RECV && ! client)
      return SIM_CLIENT_NOT_FOUND;
    if (xfer->err != SIM_OK)
      return SIM_OK;
    xfer_set_error (contact, xfer, SIM_XFER_CANCELLED);
    if (xfer->type == XFER_TYPE_SEND || xfer->type == XFER_TYPE_HASH || xfer->type == XFER_TYPE_DONE)
      return SIM_OK;
    if (xfer->type != XFER_TYPE_RECV)
      return client_send (client, xfer_new_cmd_close (handle, xfer->err));
  } else if (xfer_check_close (contact, &xfer, SIM_XFER_CANCELLED))
    return SIM_OK;
  if (client) {
    if (client->xfer.current == xfer)
      xfer_close_current (client, -1);
    err = xfer->cid == client->xfer.cid ? client_send (client, xfer_new_cmd_close (handle, xfer->err)) : SIM_OK;
  } else
    msg_connect (contact, true);
  return err;
}

int xfer_send_pause (simcontact contact, simnumber handle) {
  int err = SIM_OK;
  simclient client = contact->client;
  if (handle) {
    simnumber unpause = contact->xfer.pause;
    err = xfer_set_pause (client && client->xfer.flags & XFER_FLAG_CONNECTED ? client : NULL, contact, handle);
    if (err == SIM_OK && client && client->xfer.flags & XFER_FLAG_CONNECTED) {
      client->xfer.unpaused += handle > 1 || handle == SIM_CMD_XFER_INIT_PAUSE_NONE;
      err = client_send_cmd (client, SIM_CMD_XFER_INIT, SIM_CMD_XFER_INIT_PAUSE, number_new (handle), NULL, nil ());
    } else if (err == SIM_OK && contact->xfer.pause != unpause && contact->xfers)
      msg_connect (contact, true);
  } else if (client && client->xfer.tid && contact->flags & CONTACT_FLAG_XFER && CONTACT_CHECK_RIGHT_XFER (contact))
    err = xfer_put (client, NULL);
  return err;
}

int xfer_send_file (simcontact contact, const simtype pathname, simnumber *handle) {
  int err = SIM_FILE_BAD_NAME, fd = -1;
  simnumber size;
  simtype name = nil ();
  if (! contact) {
    if (pathname.ptr)
      err = sim_file_name_check (pathname.ptr, xfer_extension_names) ? SIM_OK : SIM_NO_ERROR;
  } else if ((name = sim_file_name_copy (pathname)).typ == SIMNIL) {
    err = errno ? errno : ENOMEM;
  } else if ((fd = sim_file_open (name.ptr, FILE_BIT_READ, &size)) < 0) {
    err = errno;
  } else if ((*handle = xfer_new_handle (contact, fd) + (strcmp (contact_list.me->addr, contact->addr) < 0)) > 1) {
    simclient client = contact->client;
    struct _xfer *xfer = xfer_find (contact, *handle);
    LOG_DEBUG_ ("new %lld:#%lld %s '%s'\n", client ? client->xfer.cid : 0, *handle, name.str, contact->nick.str);
    if (! xfer) {
      xfer = xfer_new (contact, name, size, *handle, xfer_get_time ());
      event_send_xfer_history (contact, xfer, "FILE SEND", 0, size, true, true);
      if (contact->xfer.pause != SIM_CMD_XFER_INIT_PAUSE_ALL) {
        err = client && client->xfer.tid ? xfer_put (client, xfer) : msg_connect (contact, true);
      } else
        err = xfer_send_pause (contact, *handle);
      xfer_set_tick (SIM_LIMIT_NO_ERROR);
      name = nil ();
    } else if (! strcmp (xfer->name.ptr, name.ptr) || param_get_number ("xfer.testbits") >= 60) {
      xfer->cid = client ? client->xfer.cid : 0;
      xfer->rcvdpos = -1;
      xfer->rcvdsize = 0;
      string_free (sim_crypt_md_free (xfer->md)), xfer->md = NULL;
      err = xfer->err != SIM_OK ? xfer->err : xfer_send_pause (contact, *handle);
    }
    contact->seen[CONTACT_SEEN_ACTIVITY] = time (NULL);
  } else
    err = errno ? errno : EBADF;
  string_free (name);
  if (fd >= 0)
    sim_file_close (fd);
  return err;
}

int xfer_accept_file (simclient client, simnumber handle, const simtype hash, simnumber hashsize) {
  int err = SIM_OK;
  struct _xfer *xfer = handle > 1 ? xfer_find (client->contact, handle) : NULL;
  LOG_ANY_ (xfer ? hashsize < 0 ? SIM_LOG_WARN : SIM_LOG_INFO : SIM_LOG_NOTE, "accept $%d %s%s%c#%lld %lld '%s'\n",
            client->sock->fd, XFER_FORMAT_TYPE_NAME (client, xfer), handle, hashsize, client->contact->nick.str);
  if (xfer && xfer->cid == client->xfer.cid) {
    if (xfer->type == XFER_TYPE_WAIT || xfer->type == XFER_TYPE_INIT) {
      const char *name = SIM_XFER_TYPE_WAIT;
      if (xfer->type == XFER_TYPE_INIT) {
        client->xfer.namesent += XFER_SPLIT_NAME (xfer).len + client->xfer.name;
        name = SIM_XFER_TYPE_INIT;
      }
      string_free (xfer->hash), xfer->hash = nil ();
      if (hashsize >= 0) {
        if (xfer->mdsize >= 0) {
          xfer->mdsize = hashsize;
          xfer->status = SIM_SOCKET_NO_ERROR;
        }
        if (hash.len)
          xfer->hash = string_copy_string (hash);
      } else if ((xfer->status = hashsize == (short) hashsize ? (short) hashsize : SIM_NO_ERROR) == SIM_XFER_NO_RECV)
        if (xfer_test & 16)
          LOG_ERROR_ ("accept %s%s%c#%lld '%s'\n", XFER_FORMAT_TYPE_NAME (client, xfer), handle,
                      client->contact->nick.str);
      xfer_set_type (client->contact, xfer, name, hashsize >= 0 ? XFER_TYPE_SEND : XFER_TYPE_HOLD);
      xfer_set_tick (xfer_put (client, xfer));
    } else if (xfer->type == XFER_TYPE_DONE && xfer->err != SIM_OK)
      err = client_send (client, xfer_new_cmd_close (handle, xfer->err));
  }
  return err;
}

int xfer_recv_file (simclient client, simnumber handle, const simtype filename,
                    simnumber filesize, const simtype filetype, simnumber sendtime) {
  int err = SIM_OK, len, maxlen = client->contact->xfer.names, retries = param_get_number ("xfer.retries");
  int addlen = param_get_min ("xfer.names", 0) == maxlen ? maxlen : (int) filename.len + client->contact->xfer.name;
  int type = filetype.typ == SIMNIL ? XFER_TYPE_DATA : filetype.len ? XFER_TYPE_RECV : XFER_TYPE_EXEC, flag, tag;
  struct _xfer *xfer = NULL, *next;
  if ((strcmp (contact_list.me->addr, client->contact->addr) < 0) != (handle & 1) && ! (handle >> 61))
    if (handle > 1 && type != XFER_TYPE_RECV && filesize >= 0) {
      if (filename.len && (strchr (filename.ptr, '/') || strlen (filename.ptr) != filename.len)) {
        err = client_send (client, xfer_new_cmd_close (handle, SIM_FILE_BAD_NAME));
      } else if ((xfer = xfer_find (client->contact, handle)) == NULL && filename.len) {
        for (flag = false; flag <= true; flag++)
          for (xfer = client->contact->xfers; (len = client->xfer.namercvd + addlen) > maxlen && xfer; xfer = next) {
            next = xfer->next;
            if (xfer->cid != client->xfer.cid || (xfer->status != SIM_OK && xfer->status != SIM_SOCKET_NO_ERROR))
              if (xfer->type == XFER_TYPE_RECV && (xfer->err != SIM_OK) == flag && client->xfer.current != xfer) {
                if (xfer->cid == client->xfer.cid || (xfer->err != SIM_OK && xfer->err != SIM_XFER_NO_ERROR)) {
                  err = xfer->cid == client->xfer.cid || xfer->err != SIM_XFER_CANCELLED ? xfer->err : SIM_XFER_NO_SEND;
                  xfer_remove (client, xfer, XFER_GET_MODE (err));
                }
                err = xfer->cid == client->xfer.cid ? xfer->status : SIM_XFER_BAD_ID;
                if (xfer->err != SIM_OK) {
                  tag = XFER_TAG_REJECT | XFER_TAG_LOCAL;
                  if ((err = xfer->err) == SIM_XFER_NO_ERROR)
                    tag = xfer->cid == client->xfer.cid ? XFER_TAG_RECV | XFER_TAG_LOCAL : XFER_TAG_LOCAL;
                } else if (xfer->cid == client->xfer.cid || xfer_stat (client->contact, xfer->handle, NULL, NULL)) {
                  tag = XFER_TAG_CANCEL;
                } else
                  tag = XFER_TAG_LOCAL;
                xfer_close (client, xfer, tag == (XFER_TAG_RECV | XFER_TAG_LOCAL) ? SIM_OK : err, tag);
              }
          }
        if (len <= maxlen) {
          simnumber ftimes[3], t = client->contact->seen[CONTACT_SEEN_ACTIVITY] = time (NULL);
          sendtime = ((time_t) t == (time_t) -1 ? -1 : sendtime < 0 || sendtime >= t ? t : t - sendtime) * 1000000000;
          client->xfer.namercvd += filename.len + client->contact->xfer.name;
          xfer = xfer_new (client->contact, FILE_NAME_COPY (filename), filesize, handle, sendtime);
          xfer->cid = client->xfer.cid;
          xfer->rcvtime = xfer_get_time ();
          if (xfer_stat (client->contact, handle, NULL, ftimes)) {
            event_send_xfer_history (client->contact, xfer, "FILE SEND", sendtime, filesize, false, false);
          } else if (ftimes[0] < sendtime || ftimes[1] < sendtime || ftimes[2] < sendtime) {
            sendtime = ftimes[ftimes[0] < ftimes[2] ? ftimes[0] > ftimes[1] : ftimes[1] < ftimes[2] ? 1 : 2];
            xfer->sndtime = sendtime > 1000000000 ? sendtime : 1000000000;
          }
          event_send_xfer (client->contact, handle, pointer_new (""), pointer_new (""));
          xfer_set_tick (err = xfer_put (client, xfer_set_type (client->contact, xfer, "", type)));
        } else
          err = client_send (client, xfer_new_cmd_recv (handle, SIM_XFER_NO_RECV, pointer_new ("")));
      } else if (! xfer || (filename.len && string_check_diff_len (filename, xfer->name.str, xfer->name.len - 1))) {
        xfer = NULL;
      } else if (xfer->type != XFER_TYPE_RECV || client->xfer.current == xfer) {
        xfer = NULL;
      } else if (xfer->filesize == filesize && (! retries || xfer->retries < (unsigned short) (retries - 1))) {
        xfer->cid = client->xfer.cid;
        xfer_set_type (client->contact, xfer, xfer_get_type_name (client->contact, xfer, NULL), type);
        xfer->retries++;
        err = xfer_put (client, xfer);
      } else if (xfer->err == SIM_OK) {
        xfer_set_error (client->contact, xfer, xfer->filesize == filesize ? SIM_XFER_NO_SEND : SIM_XFER_NO_RESIZE);
        err = client_send (client, xfer_new_cmd_close (handle, xfer->err));
      }
      if (xfer && xfer->err == SIM_OK && SOCKET_CHECK_PROXY (client->sock) && param_get_number ("nat.attempt") & 2)
        nat_traverse_attempt (client);
    }
  LOG_ANY_ (xfer ? SIM_LOG_INFO : SIM_LOG_NOTE, "new $%d %lld:#%lld %s '%s'\n", client->sock->fd,
            client->xfer.cid, handle, filename.str, client->contact->nick.str);
  return err;
}

int xfer_recv_data_ (simclient client, const simtype data) {
  int err = SIM_OK, fd = client->xfer.fd, len = 0;
  struct _xfer *xfer = client->xfer.current;
  if (fd >= 0 && xfer->type == XFER_TYPE_RECV && data.len) {
    simnumber size = xfer->mdsize + data.len;
    int speed = xfer_check_locked (client, NULL, 0) ? 1 : xfer_get_speed (client, "xfer.download");
    if ((speed == 1) == ! (client->xfer.flags & XFER_FLAG_LOCKED)) {
      client->xfer.bytes = client->sock->rcvbytes - xfer_ack_bytes;
      client->xfer.flags ^= XFER_FLAG_LOCKED;
    }
    if (xfer_ack_bytes && client->sock->rcvbytes >= client->xfer.bytes + xfer_ack_bytes) {
      client->xfer.bytes = client->sock->rcvbytes;
      xfer_set_position (client, size);
      if ((err = client_send (client, xfer_new_cmd_speed (size, speed))) != SIM_OK)
        return err;
      if (LOG_CHECK_ANY_LEVEL_ (SIM_MODULE_API, SIM_LOG_XTRA) && xfer->md) {
        simtype hash = sim_crypt_md_free (sim_crypt_md_copy (xfer->md));
        LOG_SIMTYPE_ (SIM_MODULE_API, SIM_LOG_XTRA, hash, LOG_BIT_BIN,
                      "data:%d $%d #%lld (error %d) %lld-%u/%lld '%s' ", fd, client->sock->fd,
                      xfer->handle, xfer->err, size, data.len, xfer->filesize, client->contact->nick.str);
        string_free (hash);
      }
    } else if (! client->xfer.tick[0])
      client->xfer.tick[1] = -1;
    if ((client->xfer.size -= data.len) >= 0) {
      simunsigned cpu = XFER_UNPROTECT (client->contact->id);
      if ((unsigned) (len = sim_file_write (fd, data.str, data.len)) == data.len && xfer->md) {
        sim_crypt_md_update (xfer->md, data.str, data.len);
        xfer->mdsize = xfer->rcvdpos = size;
      }
      XFER_PROTECT_ (cpu);
      if (client->xfer.current != xfer || ! xfer->md)
        LOG_ERROR_ ("data:%d $%d #%lld (error %d) %lld-%u/%lld '%s'\n", fd, client->sock->fd,
                    xfer->handle, xfer->err, size, data.len, xfer->filesize, client->contact->nick.str);
    }
    if ((unsigned) len != data.len) {
      if ((err = xfer->err) == SIM_OK) {
        if (client->xfer.size >= 0) {
          err = FILE_CASE_ERROR (len, SIM_FILE_NO_SPACE);
          xfer_set_error (client->contact, xfer, SIM_FILE_NO_SPACE);
        } else
          xfer_set_error (client->contact, xfer, err = SIM_FILE_BAD_SIZE);
      }
      string_free (sim_crypt_md_free (xfer->md)), xfer->md = NULL;
      xfer_close_current (client, -1);
      LOG_DEBUG_ ("data $%d #%lld error %d '%s'\n", client->sock->fd, xfer->handle, err, client->contact->nick.str);
      err = client_send (client, xfer_new_cmd_close (xfer->handle, xfer->err));
    }
  } else
    LOG_NOTE_ ("data $%d %s%s%c#%lld '%s'\n", client->sock->fd, XFER_FORMAT_TYPE_NAME (client, xfer),
               xfer ? xfer->handle : 0, client->contact->nick.str);
  return err;
}

void xfer_recv_speed (simclient client, simnumber filesize, simnumber speed) {
  struct _xfer *xfer = client->xfer.current;
  if (xfer && xfer->type == XFER_TYPE_SEND) {
    if (speed >= 0 && speed == (int) speed)
      client->xfer.speed = (int) speed;
    if (xfer->rcvdpos <= filesize) {
      xfer_set_position (client, xfer->rcvdpos = filesize);
      return;
    }
  }
  LOG_NOTE_ ("speed $%d %s%s%c%lld < %lld '%s'\n", client->sock->fd, XFER_FORMAT_TYPE_NAME (client, xfer),
             filesize, xfer && xfer->type == XFER_TYPE_SEND ? xfer->rcvdpos : -2, client->contact->nick.str);
}

int xfer_recv_close (simclient client, simnumber handle, const simtype hash, simnumber filetime, int error) {
  int err = SIM_OK, mode = XFER_MODE_TEMP, tag = error == SIM_OK ? XFER_TAG_RECV : XFER_TAG_CANCEL;
  struct _xfer *xfer = handle > 1 ? xfer_find (client->contact, handle) : NULL;
  LOG_ANY_ (xfer && xfer->cid == client->xfer.cid && xfer->type != XFER_TYPE_HASH ? SIM_LOG_INFO : SIM_LOG_NOTE,
            "close $%d %s%s%c#%lld (error %d) '%s'\n", client->sock->fd,
            XFER_FORMAT_TYPE_NAME (client, xfer), handle, error, client->contact->nick.str);
  if ((xfer && xfer->type == XFER_TYPE_HASH) || xfer_test & 3) {
    err = (xfer_test & 3) == 1 ? SIM_SOCKET_EXCEEDED : SIM_OK;
  } else if (! xfer || (xfer->cid != client->xfer.cid && xfer->cid)) {
    err = client_send (client, xfer_new_cmd_end (handle, xfer ? SIM_XFER_BAD_ID : SIM_XFER_BAD_HANDLE));
  } else if (xfer->type == XFER_TYPE_RECV || xfer->type == XFER_TYPE_DATA || xfer->type == XFER_TYPE_EXEC) {
    simtype hash1 = nil (), hash2 = nil ();
    if (client->xfer.current == xfer && client->xfer.fd >= 0) {
      simnumber nsec, size = client->xfer.size;
      if (error == SIM_OK) {
        hash1 = sim_crypt_md_free (xfer->md), xfer->md = NULL;
        hash2 = sim_crypt_md_hash (sim_crypt_md_new (CRYPT_MD_MD5), hash1, nil (), nil ());
      }
      nsec = error != SIM_OK ? -2 : filetime > 0 && param_get_number ("xfer.recvtime") ? filetime : 0;
      if ((err = xfer_close_current (client, nsec) ? SIM_OK : SIM_FILE_NO_SPACE) != SIM_OK || error != SIM_OK) {
        mode = error != SIM_XFER_NO_RESEND && error != SIM_XFER_NO_RESUME ? XFER_MODE_PART : XFER_MODE_RCVD;
        mode |= error == SIM_OK || error == SIM_XFER_NO_SEND || error == SIM_XFER_NO_RESIZE ? 0 : XFER_MODE_WIPE;
      } else if (string_check_diff_len (hash2, hash.str, hash.len)) {
        mode = XFER_MODE_RCVD | XFER_MODE_WIPE;
        err = SIM_FILE_ERROR;
      } else if (size) {
        mode = XFER_MODE_PART;
        err = SIM_XFER_NO_RESIZE;
      } else if ((err = xfer_rename (client, xfer)) != SIM_OK) {
        mode = XFER_MODE_PART;
      } else if (hash1.typ != SIMNIL)
        xfer_append_hash (client->contact, hash1, xfer->name.ptr, XFER_MODE_RCVD);
      if (err != SIM_OK)
        xfer_close_file (client, xfer->sndtime);
      if (mode != XFER_MODE_TEMP && ! xfer->removed) {
        xfer->removed = true;
        xfer_remove (client, xfer, mode);
      }
    } else {
      err = xfer->err;
      if (! xfer->removed)
        xfer_remove (client, xfer, XFER_GET_MODE (err));
      error = err != SIM_OK && err != SIM_XFER_NO_ERROR ? SIM_OK : error == SIM_OK ? SIM_XFER_CANCELLED : error;
      tag = err == SIM_OK || err == SIM_XFER_NO_ERROR ? XFER_TAG_CANCEL : XFER_TAG_REJECT;
    }
    if ((tag |= error == SIM_OK ? XFER_TAG_LOCAL : 0) == (XFER_TAG_RECV | XFER_TAG_LOCAL)) {
      simtype cmd = xfer_new_cmd_close (handle, xfer->err = err == SIM_OK ? SIM_XFER_NO_ERROR : (short) err);
      table_add (cmd, SIM_CMD_XFER_CLOSE_HASH, hash1.typ != SIMNIL ? hash1 : pointer_new (""));
      if (err != SIM_OK) {
        event_send_xfer_type (client->contact, xfer, "", SIM_XFER_TYPE_RECV);
      } else if (hash1.typ != SIMNIL) {
        string_free (xfer->hash);
        xfer->hash = string_copy_string (hash1);
      }
      xfer_set_tick (err = client_send (client, cmd));
      string_free (hash2);
    } else if ((err = xfer_close (client, xfer, error == SIM_OK ? err : error, tag)) == SIM_OK)
      err = client_send (client, xfer_new_cmd_end (handle, SIM_OK));
  } else if (client->xfer.current != xfer) {
    simbool done = xfer->type == XFER_TYPE_DONE || xfer->type == XFER_TYPE_INIT;
    if (done && error == SIM_XFER_NO_ERROR && xfer->hash.typ != SIMNIL) {
      xfer_create_link (client->contact, xfer);
      xfer_append_hash (client->contact, xfer->hash, xfer->name.ptr, XFER_MODE_SENT);
      error = string_check_diff_len (xfer->hash, hash.str, hash.len) ? EBADF : SIM_OK;
    } else if (error == SIM_OK)
      error = SIM_XFER_NO_ERROR;
    xfer_put (client, xfer);
    err = xfer_close (client, xfer, error, done && hash.typ != SIMNIL ? XFER_TAG_RECV : XFER_TAG_REJECT);
    if (err == SIM_OK)
      err = client_send (client, xfer_new_cmd_end (handle, done ? SIM_XFER_NO_ERROR : SIM_OK));
  } else if (xfer->type != XFER_TYPE_DONE) {
    const char *name = SIM_XFER_TYPE_SEND;
    int err2 = error == SIM_XFER_CANCELLED ? SIM_OK : error == SIM_OK ? SIM_XFER_CANCELLED : error;
    event_send_xfer_history (client->contact, xfer, "FILE REJECT", xfer->sndtime, err2, false, true);
    if (xfer->type == XFER_TYPE_INIT) {
      client->xfer.namesent += XFER_SPLIT_NAME (xfer).len + client->xfer.name;
      name = SIM_XFER_TYPE_INIT;
    }
    string_free (xfer->hash), xfer->hash = nil ();
    xfer_set_type (client->contact, xfer, name, XFER_TYPE_DONE);
  }
  return err;
}

int xfer_recv_end (simclient client, simnumber handle, int error) {
  struct _xfer *xfer = handle > 1 ? xfer_find (client->contact, handle) : NULL;
  int err = SIM_OK, tag = XFER_TAG_RECV | XFER_TAG_LOCAL;
  simbool ok = false;
  if (xfer && (xfer->cid == client->xfer.cid || ! xfer->cid)) {
    err = xfer->err;
    if (xfer->type == XFER_TYPE_DONE || (err != SIM_OK && xfer->type != XFER_TYPE_SEND && xfer->type != XFER_TYPE_HASH))
      ok = client->xfer.current != xfer || (xfer->type != XFER_TYPE_DONE && xfer->type != XFER_TYPE_INIT);
    if (xfer->type == XFER_TYPE_RECV && error == SIM_XFER_PAUSED)
      if ((ok = err == SIM_OK && client->xfer.current == xfer) == true)
        xfer_close_current (client, -1);
  } else if (! handle) {
    simnumber paused = client->contact->xfer.pause;
    if (paused == SIM_CMD_XFER_INIT_PAUSE_NONE || paused > 1) {
      xfer_set_handle (client->contact, paused == SIM_CMD_XFER_INIT_PAUSE_NONE ? 0 : -paused);
      xfer_set_tick (ok = true);
    }
    if (error == SIM_XFER_PAUSED || error == SIM_XFER_NO_ERROR) {
      client->xfer.flags = (client->xfer.flags & ~XFER_FLAG_MASTER) | (error == SIM_XFER_PAUSED) * XFER_FLAG_MASTER;
      xfer_put (client, NULL);
    }
    client->xfer.unpaused -= client->xfer.unpaused != 0;
  }
  LOG_ANY_ (ok ? SIM_LOG_INFO : SIM_LOG_NOTE, "end $%d %s%s%c#%lld (error %d) '%s'\n", client->sock->fd,
            XFER_FORMAT_TYPE_NAME (client, xfer), handle, error, client->contact->nick.str);
  if (xfer_test & 12) {
    err = (xfer_test & 12) == 4 ? SIM_SOCKET_EXCEEDED : SIM_OK;
  } else if (ok && handle) {
    if (xfer->type != XFER_TYPE_RECV && xfer->type != XFER_TYPE_DATA && xfer->type != XFER_TYPE_EXEC) {
      xfer_put (client, xfer);
      tag = err == SIM_OK ? XFER_TAG_RECV : XFER_TAG_CANCEL | XFER_TAG_LOCAL;
    } else if (err != SIM_OK) {
      if (! xfer->removed)
        xfer_remove (client, xfer, XFER_GET_MODE (err));
      if (err == SIM_XFER_NO_ERROR) {
        err = SIM_OK;
      } else if (error != SIM_XFER_NO_ERROR || err == SIM_XFER_CANCELLED)
        tag = XFER_TAG_REJECT | XFER_TAG_LOCAL;
    } else
      tag = XFER_TAG_LOCAL;
    err = xfer_close (client, xfer, err, tag);
  } else
    err = SIM_OK;
  return err;
}

int xfer_recv_pause (simclient client, simnumber handle, simnumber name, simnumber names) {
  int err = SIM_OK;
  simcontact contact = client->contact;
  simbool paused = contact->xfer.pause == SIM_CMD_XFER_INIT_PAUSE_ALL;
  if (! (client->xfer.flags & XFER_FLAG_ACCEPTED) && names > 0) {
    int i = handle < -1 ? (handle = -handle, 0) : handle > 1 ? 4 : (int) handle + 2;
    int j = contact->xfer.pause < -1 ? 0 : contact->xfer.pause > 1 ? 4 : (int) contact->xfer.pause + 2;
    simnumber m = names - SIM_MAX_MSG_SIZE;
    client->xfer.name = name == (unsigned short) name ? (unsigned short) name : 0;
    client->xfer.names = (m == (int) m && m >= client->xfer.name ? (int) m : client->xfer.name) + SIM_MAX_MSG_SIZE;
    client->xfer.flags |= XFER_FLAG_ACCEPTED;
    if ((err = client_send_cmd (client, SIM_CMD_XFER_SEND, NULL, nil (), NULL, nil ())) != SIM_OK)
      return err;
    if ((! i || i == 4) && i == j ? client->flags & CLIENT_FLAG_ACCEPTED : (0xF310EC >> (j * 5 + i)) & 1) {
      LOG_XTRA_ ("pause $%d #%lld '%s'\n", client->sock->fd, contact->xfer.pause, contact->nick.str);
      return xfer_put (client, NULL);
    }
  } else if (names < 0 || ! (client->xfer.flags & XFER_FLAG_ACCEPTED) != ! ! names) {
    LOG_NOTE_ ("pause $%d #%lld names = %lld '%s'\n", client->sock->fd, handle, names, contact->nick.str);
    handle = 0;
  }
  xfer_set_pause (client, NULL, handle);
  if ((contact->xfer.pause == SIM_CMD_XFER_INIT_PAUSE_ALL) != paused)
    event_send_name (contact, SIM_EVENT_NET, SIM_EVENT_NET_XFER, number_new (paused ? SIM_OK : SIM_XFER_PAUSED));
  if (handle != SIM_CMD_XFER_INIT_PAUSE_NONE && handle <= 0)
    return SIM_OK;
  if (handle != SIM_CMD_XFER_INIT_PAUSE_NONE && ! client->xfer.unpaused) {
    err = (strcmp (contact_list.me->addr, contact->addr) < 0) == (handle & 1) ? SIM_XFER_NO_ERROR : SIM_XFER_PAUSED;
    client->xfer.flags = (client->xfer.flags & ~XFER_FLAG_MASTER) | (err == SIM_XFER_NO_ERROR) * XFER_FLAG_MASTER;
  }
  return client_send (client, xfer_new_cmd_end (0, err));
}

int xfer_recv_handle (simclient client, simnumber handle, simnumber error) {
  struct _xfer *xfer = handle > 1 ? xfer_find (client->contact, handle) : NULL;
  LOG_ANY_ (xfer ? SIM_LOG_INFO : SIM_LOG_NOTE, "handle $%d %s%s%c#%lld (error %lld) '%s'\n", client->sock->fd,
            XFER_FORMAT_TYPE_NAME (client, xfer), handle, error, client->contact->nick.str);
  if (xfer && ((xfer->type == XFER_TYPE_RECV && xfer->cid == client->xfer.cid) || error == SIM_SOCKET_NO_ERROR))
    xfer->status = error == SIM_OK ? SIM_SOCKET_NO_ERROR : error == (short) error ? (short) error : SIM_NO_ERROR;
  if (error == SIM_SOCKET_NO_ERROR) {
    simtype cmd = client_new_cmd (SIM_CMD_XFER_RECV, SIM_CMD_XFER_RECV_HANDLE, number_new (handle), NULL, nil ());
    if (! xfer || (xfer->cid != client->xfer.cid && xfer->cid))
      table_add_number (cmd, SIM_CMD_XFER_RECV_SIZE, xfer ? SIM_XFER_BAD_ID : SIM_XFER_BAD_HANDLE);
    return client_send (client, cmd);
  }
  return SIM_OK;
}

static int xfer_recv_ (simclient client, struct _xfer *xfer) {
  int err = xfer->err, fd;
  simtype cmd = nil (), name;
  simnumber hashsize = (simnumber) 1 << 62, size = 0;
  if (err == SIM_OK && client->contact->xfer.pause == SIM_CMD_XFER_INIT_PAUSE_ALL)
    err = SIM_XFER_PAUSED;
  if (err == SIM_OK && (err = xfer_check_files (client)) == SIM_OK && xfer_check_size (client->contact, xfer)) {
    void *md = xfer->md;
    int type = xfer->type, bits = (type == XFER_TYPE_EXEC ? FILE_BIT_EXEC : 0) | FILE_BIT_APPEND | FILE_BIT_CREATE;
    if (client->contact->msgs.flags & CONTACT_MSG_NO_SPACE && ! xfer_find_type (client, XFER_TYPE_RECV, XFER_TYPE_RECV))
      client->contact->msgs.flags &= ~CONTACT_MSG_NO_SPACE;
    xfer_move (client, xfer, XFER_MODE_TEMP);
    name = xfer_new_name (client->contact, xfer->handle, NULL, XFER_MODE_TEMP);
    if ((fd = sim_file_open (name.ptr, bits |= FILE_BIT_READ | FILE_BIT_WRITE, &size)) < 0)
      if ((xfer_create_dir (client->contact, name.ptr), fd = sim_file_open (name.ptr, bits, &size)) < 0)
        err = errno;
    if ((xfer->rcvdpos = size) == 0 && fd >= 0) {
      simcontact contact = client->contact;
      client->xfer.files++;
      event_send_xfer (contact, SIM_XFER_GET_RECEIVED, pointer_new (""), sim_file_name_split (name.ptr, name.len));
    }
    string_free (name);
    client->contact->connected++;
    xfer_set_type (client->contact, xfer, SIM_XFER_TYPE_EXEC, XFER_TYPE_HASH);
    if (fd >= 0 && md && xfer->mdsize != size)
      string_free (sim_crypt_md_free (md)), md = NULL;
    if (fd < 0) {
      xfer_set_error (client->contact, xfer, SIM_XFER_NO_CREATE);
    } else if (md) {
      hashsize = size;
    } else if ((err = xfer_hash_ (md = sim_crypt_md_new (CRYPT_MD_MD5), client, &hashsize, fd, xfer)) != SIM_OK) {
      xfer_set_error (client->contact, xfer, err);
    } else if (! SIM_NUMBER_CHECK (hashsize)) {
      xfer_set_error (client->contact, xfer, err = SIM_FILE_BAD_SIZE);
    } else if (xfer->type != XFER_TYPE_HASH)
      LOG_FATAL_ (SIM_OK, "recv %s%s%c#%lld (error %d) '%s'\n",
                  XFER_FORMAT_TYPE_NAME (client, xfer), xfer->handle, xfer->err, client->contact->nick.str);
    LOG_DEBUG_ ("recv:%d $%d #%lld (error %d) %lld '%s'\n", fd, client->sock->fd,
                xfer->handle, err, hashsize, client->contact->nick.str);
    xfer->mdsize = xfer->rcvdpos = hashsize;
    xfer->type = (signed char) type;
    if (xfer->err != SIM_OK) {
      xfer_set_type (xfer_set_current (client, xfer)->contact, xfer, SIM_XFER_TYPE_HASH, XFER_TYPE_RECV);
      cmd = xfer_new_cmd_close (xfer->handle, xfer->err);
    } else if (client->contact->xfer.pause != SIM_CMD_XFER_INIT_PAUSE_ALL && simself.state == CLIENT_STATE_RUNNING)
      if (xfer_find_type (client, XFER_TYPE_DATA, XFER_TYPE_EXEC) == xfer)
        if (! xfer_find_type (client, XFER_TYPE_SEND, XFER_TYPE_SEND)) {
          client->xfer.size = xfer->filesize - hashsize;
          client->xfer.fd = fd;
          xfer_set_type (xfer_set_current (client, xfer)->contact, xfer, SIM_XFER_TYPE_HASH, XFER_TYPE_RECV);
          if (SOCKET_ADD (fd).typ != SIMNIL)
            LOG_ERROR_ ("zombie recv:%d '%s'\n", fd, client->contact->nick.str);
          limit_send_request (client, LIMIT_SPEED_START);
          cmd = xfer_new_cmd_recv (xfer->handle, hashsize, sim_crypt_md_free (sim_crypt_md_copy (md)));
          fd = -1;
        }
    if (fd >= 0) {
      sim_file_close (fd);
      if (xfer->err == SIM_OK) {
        client->contact->connected -= client->contact->connected > 0;
        xfer_set_type (client->contact, xfer, SIM_XFER_TYPE_HASH, type);
      }
      if (! size)
        xfer_remove (client, xfer, XFER_MODE_SENT);
    }
    if (xfer->err != SIM_OK || simself.state != CLIENT_STATE_RUNNING)
      string_free (sim_crypt_md_free (md)), md = NULL;
    xfer->md = md;
    err = SIM_FILE_NO_SPACE;
  } else {
    xfer_set_type (client->contact, xfer, SIM_XFER_TYPE_EXEC, XFER_TYPE_RECV);
    if (xfer->err != SIM_OK) {
      xfer_set_current (client, xfer);
      cmd = xfer_new_cmd_close (xfer->handle, xfer->err);
    } else if (err != SIM_XFER_PAUSED) {
      int flag = err == SIM_OK ? (err = SIM_FILE_NO_SPACE, CONTACT_MSG_NO_SPACE) : CONTACT_MSG_NO_FILES;
      if (err != SIM_XFER_NO_SAVE && ! (client->contact->msgs.flags & flag)) {
        client->contact->msgs.flags |= flag;
        event_send_name (client->contact, SIM_EVENT_ERROR, SIM_EVENT_ERROR_XFER, number_new (err));
      }
      cmd = xfer_new_cmd_recv (xfer->handle, err, pointer_new (""));
    }
  }
  if (err == SIM_FILE_NO_SPACE)
    client->contact->msgs.flags &= ~CONTACT_MSG_NO_FILES;
  if (simself.state == CLIENT_STATE_RUNNING && cmd.typ != SIMNIL) {
    client_send_cmd (client, SIM_CMD_ACK, SIM_CMD_ACK_HANDLE, number_new (client->contact->msgs.msgnum), NULL, nil ());
    return client_send (client, cmd);
  }
  table_free (cmd);
  return simself.state == CLIENT_STATE_RUNNING ? SIM_OK : SIM_CLIENT_OFFLINE;
}

static int xfer_callback_write_ (int fd, const void *buffer, unsigned length) {
  simunsigned cpu = XFER_UNPROTECT (0);
  int len = sim_file_write (fd, buffer, length);
  XFER_PROTECT_ (cpu);
  if (simself.state == CLIENT_STATE_RUNNING)
    return len;
  errno = SIM_CLIENT_OFFLINE;
  return -1;
}

static void *thread_xfer_ (void *arg) {
  simclient client = arg;
  struct _xfer *xfer;
  int err = msg_put (client, 0), fd = client->sock->fd;
  LOG_API_DEBUG_ ("$%d #%lld '%s'\n", fd, client->xfer.cid, client->contact->nick.str);
  while (err == SIM_OK && pth_queue_wait_ (client->xfer.event, fd) > 0) {
    simbool done = false;
    pth_message_t *item = pth_queue_get (client->xfer.queue);
    LOG_XTRA_ ("get $%d '%s'\n", client->sock->fd, client->contact->nick.str);
    if (item == &xfer_item_none)
      break;
    do {
      done = true;
      while (! client->xfer.current && err != SIM_CLIENT_OFFLINE) {
        simtype name;
        xfer = xfer_find_type (client, XFER_TYPE_WIPE, XFER_TYPE_WIPE);
        if (! xfer || (name = xfer_new_name (client->contact, 0, xfer->name.ptr, XFER_MODE_RCVD)).typ == SIMNIL)
          break;
        LOG_DEBUG_ ("wipe $%d %s\n", client->sock->fd, name.str);
        xfer->rcvdpos = -2;
        xfer_lock_ (client->sock->fd);
        if ((err = file_wipe (client->contact, name.ptr, xfer_callback_write_)) == SIM_OK) {
          client->xfer.files--;
          event_send_xfer (client->contact, SIM_XFER_GET_RECEIVED, pointer_new (xfer->name.ptr), pointer_new (""));
        } else {
          sim_error_set_text (" [", name.ptr, "]", err);
          event_send_name (client->contact, SIM_EVENT_ERROR, SIM_EVENT_ERROR_FILE_DELETE, number_new (err));
        }
        if (err != SIM_CLIENT_OFFLINE)
          xfer_close_contact (client->contact, xfer);
        xfer_unlock ();
        string_free (name);
      }
      if (! (client->xfer.flags & XFER_FLAG_ACCEPTED))
        break;
      if (! (client->contact->flags & CONTACT_FLAG_XFER) || ! CONTACT_CHECK_RIGHT_XFER (client->contact))
        break;
      if (err != SIM_CLIENT_OFFLINE && (err = xfer_send_names (client)) == SIM_OK && ! client->xfer.current)
        while ((xfer = xfer_find_type (client, XFER_TYPE_SEND, XFER_TYPE_SEND)) != NULL) {
          simbool nok = xfer_check_locked (client, xfer_lock_current, 0);
          if (nok)
            pth_sleep_ (1);
          if ((err = client->sock->err) != SIM_OK)
            break;
          done = false;
          if (nok || (err = xfer_send_ (client, xfer)) != SIM_OK || client->xfer.current)
            break;
        }
      if (err == SIM_OK && ! client->xfer.current && ! client->xfer.unpaused)
        if (! (client->xfer.flags & XFER_FLAG_MASTER) || ! xfer_find_type (client, XFER_TYPE_WAIT, XFER_TYPE_INIT))
          if ((xfer = xfer_find_type (client, XFER_TYPE_DATA, XFER_TYPE_EXEC)) != NULL) {
            if (xfer_check_locked (client, xfer_lock_current, 0)) {
              pth_sleep_ (1);
              err = client->sock->err;
            } else if ((err = client->sock->err) == SIM_OK)
              if (done || ! xfer_find_type (client, XFER_TYPE_WIPE, XFER_TYPE_WIPE))
                if ((err = xfer_recv_ (client, xfer)) == SIM_OK)
                  err = client->sock->err;
            done = false;
          }
    } while (! done && err == SIM_OK);
    LOG_XTRA_ ("get $%d (error %d) '%s'\n", client->sock->fd, err, client->contact->nick.str);
    err = SIM_OK;
  }
  LOG_API_DEBUG_ ("$%d:$%d\n", fd, client->sock->fd);
  return pth_thread_exit_ (false);
}

int xfer_start_thread (simclient client) {
  int err = SIM_OK, n = param_get_number ("xfer.name"), m = param_get_number ("xfer.names");
  simbool ok = true;
  struct _xfer *xfer;
  if (client->contact->xfer.names && (n > client->contact->xfer.name || m < client->contact->xfer.names))
    for (xfer = client->contact->xfers; xfer && ok; xfer = xfer->next)
      ok = xfer->type != XFER_TYPE_RECV;
  if (ok && (client->contact->xfer.name != n || client->contact->xfer.names != m))
    xfer_set_tick ((client->contact->xfer.name = n, client->contact->xfer.names = m));
  for (xfer = client->contact->xfers; xfer; xfer = xfer->next) {
    xfer->status = SIM_OK;
    if (xfer->type == XFER_TYPE_HOLD)
      xfer_set_type (client->contact, xfer, xfer_get_type_name (client->contact, xfer, NULL), XFER_TYPE_INIT);
  }
  client->xfer.fd = -1;
  client->xfer.flags = ((client->flags & CLIENT_FLAG_ACCEPTED) != 0) * XFER_FLAG_MASTER;
  for (xfer = client->contact->xfers; xfer && err == SIM_OK; xfer = xfer->next)
    if (xfer->type == XFER_TYPE_RECV && xfer->err != SIM_OK && xfer->cid == client->xfer.cid) {
      simtype cmd = xfer_new_cmd_close (xfer->handle, xfer->err);
      if (xfer->hash.typ != SIMNIL)
        table_add (cmd, SIM_CMD_XFER_CLOSE_HASH, string_copy_string (xfer->hash));
      err = client_send (client, cmd);
    }
  for (xfer = client->contact->xfers; xfer && err == SIM_OK; xfer = xfer->next)
    if (xfer->type == XFER_TYPE_RECV && xfer->err == SIM_OK && xfer->cid == client->xfer.cid)
      err = client_send (client, xfer_new_cmd (SIM_CMD_XFER_CLOSE, SIM_CMD_XFER_CLOSE_HANDLE, xfer->handle, NULL, 0));
  if (err == SIM_OK) {
    simtype cmd = xfer_new_cmd (SIM_CMD_XFER_INIT, SIM_CMD_XFER_INIT_PAUSE, client->contact->xfer.pause, NULL, 0);
    table_add_number (cmd, SIM_CMD_XFER_INIT_NAME, client->contact->xfer.name);
    table_add_number (cmd, SIM_CMD_XFER_INIT_NAMES, client->contact->xfer.names);
    client->xfer.flags |= XFER_FLAG_CONNECTED;
    err = client_send (client, cmd);
  }
  if (err == SIM_OK && (err = pth_queue_new (&client->xfer.queue, &client->xfer.event, client->sock->fd)) == SIM_OK) {
    if ((err = pth_queue_put (client->xfer.queue, &xfer_item_all)) == SIM_OK)
      if ((err = pth_thread_spawn (thread_xfer_, client, &client->xfer.tid, client->sock->fd)) == SIM_OK)
        for (xfer = client->contact->xfers; xfer; xfer = xfer->next)
          client->xfer.namercvd += xfer->type == XFER_TYPE_RECV ? xfer->name.len - 1 + client->contact->xfer.name : 0;
    if (err != SIM_OK)
      pth_queue_free (&client->xfer.queue, &client->xfer.event, 0);
    xfer_factor = param_get_number ("xfer.namefactor");
    xfer_ack_bytes = param_get_number ("xfer.ack");
    xfer_test = param_get_number ("xfer.test");
  }
  return err;
}

void xfer_stop_thread_ (simclient client) {
  if (client->xfer.tid) {
    pth_queue_put (client->xfer.queue, &xfer_item_none);
    if (pth_thread_join_ (&client->xfer.tid, NULL, thread_xfer_, client->sock->fd) == SIM_OK)
      pth_queue_free (&client->xfer.queue, &client->xfer.event, 0);
    xfer_close_current (client, -1);
    xfer_close_client (client, NULL);
    table_free (client->contact->xfer.hash), client->contact->xfer.hash = nil ();
    if (client->xfer.current)
      xfer_set_current (client, NULL);
    xfer_factor = param_get_number ("xfer.namefactor");
    if (xfer_test) {
      int test = xfer_test;
      xfer_test &= ~(((test & 3) != 3) * 3 | ((test & 12) != 12) * 12);
      if (xfer_test != test && param_set_number ("xfer.test", xfer_test, SIM_PARAM_PERMANENT) == SIM_OK)
        event_test_error (client->contact, SIM_EVENT_ERROR_FILE_SAVE, param_save ());
    }
  }
}

void xfer_periodic (simbool save) {
  if (! save || (xfer_save_tick && system_get_tick () >= xfer_save_tick && simself.state == CLIENT_STATE_RUNNING)) {
    unsigned i;
    for (i = 1; xfer_factor && i <= contact_list.array.len; i++) {
      simcontact contact = contact_list.array.arr[i].ptr;
      if (contact->xfer.hash.typ != SIMNIL && ! contact->client)
        table_free (contact->xfer.hash), contact->xfer.hash = nil ();
    }
    if (save) {
      xfer_save_tick = 0;
      xfer_save ();
    }
  }
}

int xfer_save (void) {
  int err, count = 0;
  unsigned i;
  simtype file = table_copy (xfer_file, contact_list.array.len), array, table;
  for (i = 1; i <= contact_list.array.len; i++) {
    int type, n = 0;
    simcontact contact = contact_list.array.arr[i].ptr;
    struct _xfer *xfer;
    for (xfer = contact->xfers; xfer; xfer = xfer->next)
      n++;
    array = array_new_tables (n);
    array.len = 0;
    if (contact->auth >= CONTACT_AUTH_DELETED) {
      for (xfer = contact->xfers; xfer; xfer = xfer->next) {
        simnumber size = xfer->rcvdsize > xfer->rcvdpos && xfer->rcvdpos >= 0 ? xfer->rcvdsize : xfer->rcvdpos;
        array.arr[++array.len] = table = table_new (2);
        if ((type = xfer_get_type (xfer)) == XFER_TYPE_INIT && xfer->mdsize >= 0) {
          type = XFER_TYPE_SEND;
        } else if (type == XFER_TYPE_RECV && xfer->removed)
          type = XFER_TYPE_HASH;
        if (*xfer_state_names[type - XFER_TYPE_INIT])
          table_add_pointer (table, SIM_CMD_XFER_SEND_TYPE, xfer_state_names[type - XFER_TYPE_INIT]);
        table_add_pointer_len (table, SIM_CMD_XFER_SEND_NAME, xfer->name.str, xfer->name.len - 1);
        if (xfer->hash.typ != SIMNIL && xfer->type != XFER_TYPE_SEND)
          table_add_pointer_len (table, SIM_CMD_XFER_CLOSE_HASH, xfer->hash.str, xfer->hash.len);
        table_add_number (table, SIM_CMD_XFER_SEND_SIZE, xfer->filesize);
        table_add_number (table, SIM_CMD_XFER_SEND_HANDLE, xfer->handle);
        if (xfer->cid)
          table_add_number (table, XFER_KEY_RANDOM, xfer->cid);
        table_add_number (table, SIM_CMD_XFER_SEND_TIME, xfer->sndtime);
        if (type == XFER_TYPE_RECV || type == XFER_TYPE_HASH)
          table_add_number (table, SIM_CMD_XFER_CLOSE_TIME, xfer->rcvtime);
        if (size)
          table_add_number (table, SIM_CMD_XFER_OFFSET, size);
        if (xfer->err != SIM_OK)
          table_add_number (table, SIM_CMD_XFER_CLOSE_ERROR, xfer->err);
        count++;
      }
      table = table_get_table (file, contact->addr);
      if (array.len || contact->xfer.pause || contact->xfer.name || contact->xfer.names) {
        if (table.typ == SIMNIL)
          table_add (file, contact->addr, table = table_new (1));
        if (contact->xfer.pause) {
          table_set_number (table, SIM_CMD_XFER_INIT, contact->xfer.pause);
        } else
          table_delete (table, SIM_CMD_XFER_INIT);
        table_set_number (table, SIM_CMD_XFER_INIT_NAME, contact->xfer.name);
        table_set_number (table, SIM_CMD_XFER_INIT_NAMES, contact->xfer.names);
        if (array.len) {
          table_set (table, XFER_KEY_ARRAY, array);
          array = nil ();
        } else
          table_delete (table, XFER_KEY_ARRAY);
      } else if (table.typ != SIMNIL && ! table_count (table))
        table_delete (file, contact->addr);
    } else
      table_delete (file, contact->addr);
    array_free (array);
  }
  if (! table_count (file)) {
    table_free (file), file = nil ();
  } else
    LOG_DEBUG_ ("saving %d/%d transfers\n", count, table_count (file));
  err = file_save (file, FILE_XFERS, FILE_TYPE_ENCRYPTED);
  LOG_XTRA_SIMTYPE_ (file, 0, "save ");
  table_free (file);
  return event_test_error (NULL, SIM_EVENT_ERROR_FILE_SAVE, err);
}

static simbool xfer_load (simcontact contact) {
  int i;
  struct _xfer *xfer;
  simtype table = table_get_table (xfer_file, contact->addr), array;
  if (table.typ == SIMNIL)
    return false;
  xfer_set_handle (contact, table_detach_number (table, SIM_CMD_XFER_INIT));
  contact->xfer.name = (int) table_detach_number (table, SIM_CMD_XFER_INIT_NAME);
  contact->xfer.names = (int) table_detach_number (table, SIM_CMD_XFER_INIT_NAMES);
  array = table_detach_array_table (table, XFER_KEY_ARRAY);
  for (i = 1; i <= (int) array.len; i++) {
    simnumber handle = table_get_number (table = array.arr[i], SIM_CMD_XFER_SEND_HANDLE);
    simtype name = table_get_string (table, SIM_CMD_XFER_SEND_NAME), str = table_get (table, SIM_CMD_XFER_SEND_TYPE);
    const char *s = str.typ == SIMNIL ? "" : str.typ != SIMSTRING && str.typ != SIMPOINTER ? NULL : str.ptr;
    int type = sim_convert_string_to_enum (s, XFER_TYPE_INIT, XFER_TYPE_DONE, SIM_ARRAY (xfer_state_names));
    if (type != XFER_TYPE_DONE && (handle > 1 || type == XFER_TYPE_WIPE) && name.len) {
      simnumber size = table_get_number (table, SIM_CMD_XFER_SEND_SIZE);
      xfer = xfer_new (contact, FILE_NAME_COPY (name), size, handle, table_get_number (table, SIM_CMD_XFER_SEND_TIME));
      xfer->hash = table_detach_string (table, SIM_CMD_XFER_CLOSE_HASH);
      xfer->cid = table_get_number (table, XFER_KEY_RANDOM);
      if (type != XFER_TYPE_INIT) {
        if (type == XFER_TYPE_HASH) {
          xfer->type = XFER_TYPE_RECV;
          xfer->removed = true;
        } else if (type != XFER_TYPE_RECV && type != XFER_TYPE_WIPE) {
          xfer->mdsize = 0;
        } else
          xfer->type = (signed char) type;
      }
      xfer->rcvtime = table_get_number (table, SIM_CMD_XFER_CLOSE_TIME);
      if (type != XFER_TYPE_WIPE)
        xfer->rcvdpos = table_get_number (table, SIM_CMD_XFER_OFFSET);
      xfer->err = (short) table_get_number (table, SIM_CMD_XFER_CLOSE_ERROR);
      LOG_DEBUG_ ("new:%s %lld:#%lld (error %d) %s '%s'\n", s,
                  xfer->cid, handle, xfer->err, name.str, contact->nick.str);
    } else {
      LOG_WARN_ ("new:%s #%lld %s '%s'\n", s, handle, name.str, contact->nick.str);
      string_free (name);
    }
  }
  array_free (array);
  return array.len || contact->xfer.pause;
}

int xfer_init (void) {
  simcontact contact;
  simtype addr, name;
  simwalker ctx;
  int err = file_load (FILE_XFERS, FILE_TYPE_ENCRYPTED, &xfer_file), i;
  if (err == SIM_OK) {
    for (table_walk_first (&ctx, xfer_file); table_walk_next (&ctx, &addr).typ != SIMNIL;)
      if ((contact = contact_list_find_address (addr.ptr)) == NULL || ! xfer_load (contact))
        if (string_check_diff (addr, XFER_KEY_RANDOM))
          LOG_WARN_ ("load @%s\n", addr.str);
#ifndef DONOT_DEFINE
    for (table_walk_first (&ctx, contact_list.table); (addr = table_walk_next_string (&ctx, NULL)).typ != SIMNIL;)
      if ((addr = table_detach_table (xfer_file, (contact = addr.ptr)->addr)).typ != SIMNIL) {
        table_set (xfer_file, contact->addr, addr);
        if (! table_count (addr))
          table_delete (xfer_file, contact->addr);
      }
#endif
    LOG_XTRA_SIMTYPE_ (xfer_file, 0, "load ");
  }
  event_xfer_table = table_new_const_name (4, SIM_EVENT_XFER, NULL);
  xfer_save_tick = 0;
  xfer_save_timeout = param_get_number ("contact.save") * 1000;
  xfer_factor = param_get_number ("xfer.namefactor");
  if (err == SIM_OK && ! (param_get_number ("xfer.test") & 32)) {
    event_send_tick_type (SIM_EVENT_XFER);
    for (i = 1; i <= (int) contact_list.array.len; i++) {
      struct _xfer *xfer;
      struct _client tmp;
      memset (&tmp, 0, sizeof (tmp));
      tmp.contact = contact_list.array.arr[i].ptr;
      while ((xfer = xfer_find_type (&tmp, XFER_TYPE_WIPE, XFER_TYPE_WIPE)) != NULL) {
        if ((name = xfer_new_name (tmp.contact, 0, xfer->name.ptr, XFER_MODE_RCVD)).typ == SIMNIL)
          break;
        LOG_DEBUG_ ("wipe %s\n", name.str);
        if ((err = file_wipe (tmp.contact, name.ptr, sim_file_write)) != SIM_OK) {
          sim_error_set_text (" [", name.ptr, "]", err);
          event_send_name (tmp.contact, SIM_EVENT_ERROR, SIM_EVENT_ERROR_FILE_DELETE, number_new (err));
          err = SIM_OK;
        }
        xfer_close_contact (tmp.contact, xfer);
        string_free (name);
      }
    }
  }
  return err;
}

void xfer_uninit (void) {
  unsigned i;
  struct _xfer *xfer;
  for (i = 1; i <= contact_list.array.len; i++)
    for (xfer = ((simcontact) contact_list.array.arr[i].ptr)->xfers; xfer; xfer = xfer_free (xfer)) {}
  if (xfer_file.ptr)
    table_free (xfer_file), xfer_file = nil ();
  if (event_xfer_table.ptr)
    table_free (event_xfer_table), event_xfer_table = nil ();
}

int xfer_log_queue (const char *module, int level, simnumber id, simnumber handle) {
  simcontact contact;
  unsigned i;
  simnumber n;
  if (id) {
    struct _xfer *xfer;
    if ((contact = contact_list_find_id (id)) == NULL || *contact->xfer.last)
      return SIM_CONTACT_UNKNOWN;
    for (xfer = contact->xfers; xfer; xfer = xfer->next)
      if ((! handle || xfer->handle == handle) && *xfer->last == xfer) {
        char ch = contact->xfer.pause == SIM_CMD_XFER_INIT_PAUSE_ALL ? '-' : contact->xfer.pause ? '*' : '#';
        if (ch == '*' && contact->xfer.pause != SIM_CMD_XFER_INIT_PAUSE_NONE && xfer->handle != contact->xfer.pause)
          ch = -xfer->handle == contact->xfer.pause ? '+' : '#';
        if (param_get_number ("xfer.testbits") >= 60) {
          log_any_ (module, level, "%c%019lld", xfer->err == SIM_OK ? ch : '!', xfer->handle);
        } else
          log_any_ (module, level, "%c%lld", xfer->err == SIM_OK ? ch : '!', xfer->handle);
        if (xfer->mdsize >= 0)
          log_any_ (module, level, ":%lld", xfer->mdsize);
        log_any_ (module, level, " %s ", contact->nick.str);
        if (contact->client && contact->client->xfer.current == xfer)
          if (contact->client == xfer_lock_current || contact->client->sock->fd == xfer_lock_fd)
            log_any_ (module, level, "+");
        log_any_ (module, level, "%s%s%c", XFER_FORMAT_TYPE_NAME (contact->client, xfer));
        if (contact->client) {
          static const char *xfer_flag_names[] = { "-", "+", "*", "**", "***", "****", "*****" };
          const unsigned m = SIM_ARRAY_SIZE (xfer_flag_names) - 2;
          i = contact->client->xfer.unpaused;
          log_any_ (module, level, "%s", xfer_flag_names[i ? (i < m ? i : m) + 1 : contact->client->xfer.flags & 1]);
          if (contact->client->xfer.current == xfer && contact->client->xfer.fd >= 0)
            log_any_ (module, level, "%d", contact->client->xfer.fd);
          log_any_ (module, level, " ");
        }
        if ((n = xfer->sndtime / 1000000000) > 0 && (n = time (NULL) - n) >= 0)
          log_any_ (module, level, "<%lld:%02d:%02d> ", n / 3600, (int) (n / 60 % 60), (int) (n % 60));
        log_any_ (module, level, "%s\n", xfer->name.str);
      }
  } else {
    if ((n = client_get_param (CLIENT_PARAM_XFER_SIZE)) != 0)
      log_any_ (module, level, "DISK = %lld bytes\n", n);
    for (i = 1; i <= contact_list.array.len; i++)
      if (xfer_log_queue (module, level, ((simcontact) contact_list.array.arr[i].ptr)->id, handle) != SIM_OK)
        return SIM_NO_ERROR;
  }
  return SIM_OK;
}
