/*++++++++++++++++++++++
 refdbdref.c: refdb application server, reference handling functions 
 markus@mhoenicka.de 2-10-00 
 $Id: refdbdref.c,v 1.65.2.24 2006/02/08 20:43:11 mhoenicka Exp $

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, see <http://www.gnu.org/licenses/>

   ++++++++++++++++++++++++*/


#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h> /* priority level definitions */
#include <time.h>
#include <expat.h> /* the xml parser header file */
#include <iconv.h>
#include <errno.h>
#include <dbi/dbi.h>

#include "strfncs.h"
#include "refdb.h"
#include "connect.h"
#include "backend.h"
#include "linklist.h"
#include "refdbd.h" /* depends on backend.h */
#include "tokenize.h"
#include "readris.h"
#include "writeris.h" /* depends on backend.h */
#include "risdb.h"
#include "dbfncs.h"
#include "xmlhandler.h"
#include "risxhandler.h"
#include "authorinfo.h"
#include "risdata.h"
#include "refdbdgetref.h"
#include "noteshandler.h"
#include "backend-html.h"
#include "refdbdcheckref.h"

/* some globals */
extern char server_ip[]; /* the IP address of the database server */
extern char refdblib[]; /* location of shareable data */
extern int n_log_level; /* numeric version of log_level */
extern int nongeek_offset; /* :-) */
extern char cs_term[];
extern char upper_citekey[];
extern char main_db[];


/* forward declaration of local functions */
static unsigned long long read_ris_data(struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult, dbi_conn conn, dbi_conn conn_refdb, int replace_ref, int n_keep_id, const char* the_user, Lilid* ptr_sentinel, iconv_t conv_descriptor, int iconv_init_status);
static int real_run_keyword_scan(struct CLIENT_REQUEST* ptr_clrequest, Lilid* ptr_sentinel, int mode, int* ptr_insert);
static int is_journal(dbi_conn conn, const char* field, const char* quoted_name);


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  update_meta(): updates the meta information in a reference database

  int update_meta returns 1 if failed, 0 if successful

  dbi_conn conn connection to a database

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int update_meta(dbi_conn conn, struct CLIENT_REQUEST* ptr_clrequest) {
  dbi_result dbires;
  char date_buffer[24];
  char sql_command[128];
  time_t the_time;

  time(&the_time);
  strftime(date_buffer, 24, "%Y-%m-%d %H:%M:%S", gmtime(&the_time));

  if (!strcmp(my_dbi_conn_get_cap(conn, "multiple_db"), "f")) {
    sprintf(sql_command, "UPDATE t_meta SET meta_modify_date='%s' WHERE meta_type='risx'", date_buffer);
  }
  else {
    sprintf(sql_command, "UPDATE %s.t_meta SET meta_modify_date='%s' WHERE meta_type='risx'", ptr_clrequest->current_db, date_buffer);
  }

  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    /* treat as non-critical */
    LOG_PRINT(LOG_WARNING, "updating t_meta failed");
    return 1;
  }

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  whichdb(): implements the client command whichdb

  int whichdb returns 1 if failed, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int whichdb(struct CLIENT_REQUEST* ptr_clrequest) {
  dbi_conn conn;
  dbi_driver driver;
  dbi_result dbires;
  char create_date[64];
  char modify_date[64];
  char* return_msg;
  const char* db_encoding;
  unsigned long long numrefs;
  unsigned long long maxid;
  unsigned long long numnotes;
  unsigned long long maxnid;
  int numbyte;
  time_t the_time;

  /* get some memory for a return message */
  return_msg = malloc((size_t)512);
  if (return_msg == NULL) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* connect to database server*/
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    free(return_msg);
    return 1;
  }

  driver = dbi_conn_get_driver(conn);

  db_encoding = dbi_conn_get_encoding(conn);

  /* the following functions return 0 in case of an error. We do not
     check as the subsequent functions will fail if there is a general
     problem with the database */
  numrefs = get_reference_count(conn, &maxid, 0 /* istemp */);

  numnotes = get_notes_count(conn, &maxnid, 0 /* istemp */);

  dbires = dbi_conn_query(conn, "SELECT meta_app,meta_type,meta_version,meta_dbversion,meta_create_date,meta_modify_date FROM t_meta WHERE meta_type='risx'");
  if (!dbires
      || !dbi_result_next_row(dbires)) {
    send_status(ptr_clrequest->fd, 207, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(207));
    if (dbires) {
      dbi_result_free(dbires);
    }
    dbi_conn_close(conn);
    free(return_msg);
    return 1;
  }

  the_time = dbi_result_get_datetime(dbires, "meta_create_date");
  if (my_dbi_conn_error_flag(conn) == 0) {
    if (the_time) {
      strftime(create_date, 64, "%Y-%m-%d %H:%M:%S", gmtime(&the_time));
    }
    else {
      strcpy(create_date, "nd");
    }
  }
  else {
    strcpy(create_date, "nd");
  }

  the_time = dbi_result_get_datetime(dbires, "meta_modify_date");
  if (my_dbi_conn_error_flag(conn) == 0) {
    if (the_time) {
      strftime(modify_date, 64, "%Y-%m-%d %H:%M:%S", gmtime(&the_time));
    }
    else {
      strcpy(modify_date, "nd");
    }
  }
  else {
    strcpy(modify_date, "nd");
  }

  /* The returned integers are quads (8byte) */
  sprintf(return_msg, "Current database: %s\nNumber of references: "ULLSPEC"\nHighest reference ID: "ULLSPEC"\nNumber of notes: "ULLSPEC"\nHighest note ID: "ULLSPEC"\nEncoding: %s\nDatabase type: %s\nDatabase version: %d\nDatabase server: %s\nCreated: %s UTC\nCreated by %s version: %s\nLast modified: %s UTC\n", ptr_clrequest->current_db, (unsigned long long)numrefs, (unsigned long long)maxid, (unsigned long long)numnotes, (unsigned long long)maxnid, db_encoding, dbi_result_get_string(dbires, "meta_type"),dbi_result_get_short(dbires, "meta_dbversion"),  dbi_driver_get_name(driver), create_date, dbi_result_get_string(dbires, "meta_app"), dbi_result_get_string(dbires, "meta_version"), modify_date);

  dbi_result_free(dbires);

  send_status(ptr_clrequest->fd, 0, TERM_NO);
  numbyte = tiwrite(ptr_clrequest->fd, return_msg, TERM_YES);
  if (numbyte == -1) {
    LOG_PRINT(LOG_WARNING, "Cannot write to client");
  }

  free(return_msg);

  dbi_conn_close(conn);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  addref(): implements the client commands addref, updateref, checkref

  int addref returns 1 if failed, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct bibinfo* ptr_biblio_info ptr to structure with bibliographic info
                  this function uses only the encoding information therein 

  char *set_owner the username of the owner of new/updated
                      references if different from *username

  struct ADDRESULT* addresult this structure will be filled in with the number
                      of (un-)successfully added/updated references

  int replace_ref if ADDREF_UPDATE, ref will be updated according to ID field
                  if ADDREF_ADD, ref will be added, ignoring an ID field
		  if ADDREF_UPDATE_PERSONAL, only the personal settings
                     (N1, AV, RP) will be updated according to the ID field
                  if ADDREF_CHECK, ref will be added to temporary tables
                     and checked

  int n_keep_id if 1, any existing reference ID will be saved in U5

  int informat input format (REFRIS|REFRISX)

  int outformat output format (REFSCREEN|REFXHTML)

  Lilid* ptr_sentinel ptr to linked list that will receive all
                      added ID values

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int addref(struct CLIENT_REQUEST* ptr_clrequest, struct bibinfo* ptr_biblio_info, char* set_owner, struct ADDRESULT* ptr_addresult, int replace_ref, int n_keep_id, int informat, int outformat, Lilid* ptr_sentinel) {
  dbi_conn conn;
  dbi_conn conn_refdb = NULL;
  XML_Parser p;
  int numbyte; /* number of bytes written */
  unsigned long long ullresult; /* result value of processing a RIS set */
  int intresult; /* result value of processing a risx dataset */
  int retval = 0; /* be optimistic */
  int iconv_init_status = 0; /* indicates iconv initialization problem */
  size_t return_msg_len = 512;
  char *the_user; /* name (either set_owner or username) */
  char *return_msg; /* string to hold a return message for the client */
  const char *db_encoding; /* encoding of database */
  struct addrisx_data ardata;
  struct lilimem sentinel;
  struct renderinfo rendinfo;

  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';

  ardata.msgpool = NULL;
  ardata.ptr_ainfo = NULL;

  /* if set_owner is set, use it, otherwise use username as owner */
  if (set_owner && *set_owner) {
    the_user = set_owner;
  }
  else {
    the_user = ptr_clrequest->username;
  }

  /* get some memory for a return message */
  return_msg = malloc(return_msg_len);
  if (return_msg == NULL || insert_lilimem(&sentinel, (void**)&return_msg, NULL)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* connect to database server*/
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  /* get the database encoding */
  db_encoding = dbi_conn_get_encoding(conn);

  /* if we need to convert, create a conversion descriptor for iconv() */
  if (db_encoding && strcmp(db_encoding, informat == RISX ? "UTF-8" : ptr_clrequest->db_encoding)) {
    char to_encoding[64];

    if (!strcmp(db_encoding, "US-ASCII")) {
      strcpy(to_encoding, "ASCII//TRANSLIT");
    }
    else {
      snprintf(to_encoding, 64, "%s//TRANSLIT", db_encoding);
    }

    ardata.conv_descriptor = iconv_open(to_encoding, informat == RISX ? "UTF-8" : ptr_clrequest->db_encoding);
    if (ardata.conv_descriptor == (iconv_t)(-1)) {
      ardata.conv_descriptor = NULL;
      LOG_PRINT(LOG_WARNING, "cannot set conversion descriptor (input/database):");
      LOG_PRINT(LOG_DEBUG, ptr_clrequest->db_encoding);
      LOG_PRINT(LOG_DEBUG, db_encoding);
      iconv_init_status = 701;
    }
    else {
      LOG_PRINT(LOG_DEBUG, "input encoding is:");
      LOG_PRINT(LOG_DEBUG, ptr_clrequest->db_encoding);
    }
  }
  else {
    ardata.conv_descriptor = NULL;
    LOG_PRINT(LOG_DEBUG, "no character encoding conversion required");
  }

  LOG_PRINT(LOG_DEBUG, "database encoding is:");
  LOG_PRINT(LOG_DEBUG, db_encoding);

  if (!strcmp(my_dbi_conn_get_cap(conn, "multiple_db"), "f")) {
    /* pgsql, sqlite, sqlite3 need another connection to retrieve values from
       the refdb database */

    conn_refdb = connect_to_db(ptr_clrequest, main_db, 0);

    if (conn_refdb == NULL) {
      send_status(ptr_clrequest->fd, 202, TERM_NO);
      LOG_PRINT(LOG_WARNING, get_status_msg(202));
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      if (ardata.conv_descriptor) {
	iconv_close(ardata.conv_descriptor);
      }
      return 1;
    }
  }
  /* else: do nothing for other drivers */

  if (replace_ref == ADDREF_CHECK) {
    /* create temporary tables */
    if (create_temporary_tables(conn, ptr_clrequest)) {
      send_status(ptr_clrequest->fd, 242, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(242));
      retval = 1;
      goto Finish;
    }
  }

  /* fill in invariant elements of structure. We'll later abuse the
     functions that create the getref output. Only a part of the info
     of the renderinfo struct will actually be used, so we have a
     couple of empty or NULL members here */
  rendinfo.ptr_biblio_info = ptr_biblio_info;
  rendinfo.ref_format = outformat;
  rendinfo.nuse_citestyle = 0;
  rendinfo.database = ptr_clrequest->current_db;
  rendinfo.dbname = NULL;
  rendinfo.username = ptr_clrequest->username;
  rendinfo.pdfroot = ptr_clrequest->pdfroot;
  rendinfo.cgi_url = ptr_clrequest->cgi_url;
  rendinfo.ptr_clrequest = ptr_clrequest;
  rendinfo.ptr_ref = &return_msg;
  rendinfo.ptr_ref_len = &return_msg_len;
  rendinfo.javascript = 0;

  if (informat == RISX) {
    while (1) { /* leave with break */
      /* create the parser instance */
      p = XML_ParserCreate(NULL);
      if (!p) {
	send_status(ptr_clrequest->fd, 801, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	retval = 1;
	goto Finish;
      }

      /* initialize "global" handler data */
      ardata.msgpool_len = 512;
      ardata.msgpool = malloc(ardata.msgpool_len);
      if (!ardata.msgpool || insert_lilimem(&sentinel, (void**)&(ardata.msgpool), NULL)) {
	send_status(ptr_clrequest->fd, 801, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	retval = 1;
	goto Finish;
      }

      ardata.ptr_ainfo = new_authorinfo();
      if (!ardata.ptr_ainfo) {
	send_status(ptr_clrequest->fd, 801, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	retval = 1;
	goto Finish;
      }

      *(ardata.msgpool) = '\0';
      *(ardata.real_citekey) = '\0';
      *(ardata.type) = '\0';
      strncpy(ardata.username, the_user, 16);
      ardata.username[15] = '\0';
      ardata.ptr_clrequest = ptr_clrequest;
      ardata.ptr_id_sentinel = ptr_sentinel;
      ardata.ptr_first = NULL;
      ardata.ptr_risdata = NULL;
      ardata.nmem_error = 0;
      ardata.ndb_error = 0;
      ardata.n_skip = 0;
      ardata.depth = 0;
      ardata.depth_adjust = 0;
      ardata.set_count = 0;
      ardata.added_count = 0;
      ardata.skipped_count = 0;
      ardata.updated_count = 0;
      ardata.failed_count = 0;
      ardata.authorpos = 0;
      ardata.replace_ref = replace_ref;
      ardata.create_new = 1;
      ardata.n_user_id = 0;
      ardata.conn = conn;
      ardata.conn_refdb = conn_refdb;
      ardata.driver = dbi_conn_get_driver(conn);
      ardata.drivername = dbi_driver_get_name(ardata.driver);


      /* register our handlers. these handlers will be called whenever expat finds a start- or endtag or character data */
      XML_SetElementHandler(p, risx_start_handler, risx_end_handler);
      XML_SetCharacterDataHandler(p, risx_char_handler);
      
      /* make pointer to "global data" available for handlers */
      XML_SetUserData(p, (void*)&ardata);

      send_status(ptr_clrequest->fd, iconv_init_status, TERM_NO);

      intresult = read_xml(ptr_clrequest->fd, p, ptr_addresult);
      XML_ParserFree(p);

      /* do something intelligent in the case of a parse or mem error */
      if (!intresult || ardata.ndb_error || ardata.nmem_error) {
	if (strcmp(my_dbi_conn_get_cap(ardata.conn, "transaction"), "t")) {
	  /* we have to delete the junk reference manually */
	  delete_ref_by_id(ardata.n_refdb_id, conn, ptr_clrequest, ptr_addresult);
	}
	else {
	  my_dbi_conn_rollback(ardata.conn);
	}
	my_dbi_conn_unlock(ardata.conn);
      }

      /* send messages to client */
/* 	send_status(ptr_clrequest->fd, 400, TERM_NO); */
      if (replace_ref != ADDREF_CHECK) {
	numbyte = tiwrite(ptr_clrequest->fd, ardata.msgpool, TERM_NO);
	if (numbyte == -1) {
	  LOG_PRINT(LOG_INFO, "timeout while writing");
	  retval = 1;
	  goto Finish;
	}
      }

      ptr_addresult->success += ardata.added_count;
      ptr_addresult->failure += ardata.failed_count;
      ptr_addresult->updated += ardata.updated_count;
      ptr_addresult->skipped += ardata.skipped_count;
      if (!intresult) { /* error */
	retval = 1;
	goto Finish;
      }
      else if (intresult == -1) { /* we're done */
	break;
      }
    } /* end while */
  }
  else { /* ris */
    ullresult = read_ris_data(ptr_clrequest, ptr_addresult, conn, conn_refdb, replace_ref, n_keep_id, the_user, ptr_sentinel, ardata.conv_descriptor, iconv_init_status);

    if (!ullresult) { /* error */
      retval = 1;
      goto Finish;
    }

    if (replace_ref != ADDREF_CHECK) {
      /* terminate whatever was sent so far */
/*       numbyte = tiwrite(ptr_clrequest->fd, "", TERM_YES); /\* end of #8 *\/ */
/*       if (numbyte == -1) { */
/* 	LOG_PRINT(LOG_INFO, "timeout while writing"); */
/* 	retval = 1; */
/* 	goto Finish; */
/*       } */
    }
  }

  /*  html output starts here */
  if (outformat == REFXHTML) {
    rendinfo.javascript = 1; /* request javascript support */
    if ((retval = prepare_render_html(&rendinfo)) != 0) {
      send_status(ptr_clrequest->fd, retval, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(retval));
      goto Finish;
    }
    else {
      numbyte = tiwrite(ptr_clrequest->fd, return_msg, TERM_NO);
      if (numbyte == -1) {
	LOG_PRINT(LOG_INFO, "timeout while writing");
	retval = 1;
	goto Finish;
      }
    }
  }
  else {
    /* no header required for screen output */
  }

  if (replace_ref == ADDREF_CHECK) {
    /* check references for duplicates */
/*     printf("start check\n"); */
    retval = check_references(ptr_clrequest, &rendinfo, conn, outformat);
/*     printf("end check, retval went to %d\n", retval); */
    if (retval) {
      LOG_PRINT(LOG_WARNING, get_status_msg(retval));
      numbyte = tiwrite(ptr_clrequest->fd, get_status_msg(retval), TERM_NO);
    }
  }

  if (outformat == REFXHTML) {
    /* reset buffer string */
    *return_msg = '\0';
    if ((retval = finish_render_html(&rendinfo)) != 0) {
      send_status(ptr_clrequest->fd, retval, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(retval));
      goto Finish;
    }
    else {
      numbyte = tiwrite(ptr_clrequest->fd, return_msg, TERM_NO);
      if (numbyte == -1) {
	LOG_PRINT(LOG_INFO, "timeout while writing");
	retval = 1;
	goto Finish;
      }
    }
  }
  else {
    /* no footer required for screen output */
  }

  /* terminate output */
  numbyte = tiwrite(ptr_clrequest->fd, "", TERM_YES);
  if (numbyte == -1) {
    LOG_PRINT(LOG_INFO, "timeout while writing");
    retval = 1;
  }

 Finish:
  if (replace_ref != ADDREF_CHECK
      && (ptr_addresult->success
	  || ptr_addresult->updated)) {
    /* database was changed, update meta info */
    update_meta(conn, ptr_clrequest);
  }
  
  if (conn_refdb) {
    dbi_conn_close(conn_refdb);
  }
  dbi_conn_close(conn);
  delete_all_lilimem(&sentinel);
  free_authorinfo(ardata.ptr_ainfo);

  if (ardata.conv_descriptor) {
    iconv_close(ardata.conv_descriptor);
  }

  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  read_ris_data(): reads RIS data from the client

  static unsigned long long read_ris_data returns 0 if failed, >0  if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct ADDRESULT* addresult this structure will be filled in with the number
                      of (un-)successfully added/updated references

  dbi_conn conn connection to reference database

  dbi_conn conn_refdb connection to shared database
  
  int replace_ref if ADDREF_UPDATE, ref will be updated according to ID field
                  if ADDREF_ADD, ref will be added, ignoring an ID field
		  if ADDREF_UPDATE_PERSONAL, only the personal settings
                     (N1, AV, RP) will be updated according to the ID field
                  if ADDREF_CHECK, ref will be added to temporary tables
                     and checked

  int n_keep_id if 1, any existing reference ID will be saved in U5

  char *the_user the username of the owner of new/updated
                      references

  Lilid* ptr_sentinel ptr to linked list that will receive all
                      added ID values

  iconv_t conv_descriptor conversion descriptor for iconv()

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static unsigned long long read_ris_data(struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult, dbi_conn conn, dbi_conn conn_refdb, int replace_ref, int n_keep_id, const char* the_user, Lilid* ptr_sentinel, iconv_t conv_descriptor, int iconv_init_status) {
  int numbyte;
  int n_requested_bufsize; /* buffer size a client requests to transfer data */
  int result; /* result value of processing a RIS set */
  int cs_status;
  unsigned long long set_count = 0; /* counter for datasets */
  char inbuffer[COMMAND_INBUF_LEN]; /* buffer for client commands */
  char return_msg[256];
  char *ris_set; /* pointer to a string containing a RIS dataset */
  char *new_ris_set; /* pointer used during reallocation of *ris_set */
  char *msg_pool; /* collects messages during RIS parsing */
  size_t msg_pool_len;

  ris_set = malloc((size_t)1);
  if (ris_set == NULL) {
    /* out of memory */
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 0;
  }
  *ris_set = '\0';

  msg_pool_len = 256;
  msg_pool = malloc(msg_pool_len);
  if (msg_pool == NULL) {
    /* out of memory */
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    free(ris_set);
    return 0;
  }

  send_status(ptr_clrequest->fd, iconv_init_status, TERM_NO);

  /* loop until the client requests a buffer size of zero indicating
     that he's done sending datasets */
  do {
    /* ------------------------------------------------------------ */
    /* PHASE 1 */
    /* see how much memory we'll need */
/*     printf("phase 1\n"); */
    
    cs_status = read_status(ptr_clrequest->fd);
    if (cs_status == 402) {
      /* client is done sending data */
      send_status(ptr_clrequest->fd, 403, TERM_NO); /* #8 */
      LOG_PRINT(LOG_INFO, get_status_msg(403));
      free(ris_set);
      free(msg_pool);
/*       printf("leaving after 402\n"); */
      break;
    }

    /* see how much memory we'll need */
    numbyte = tread(ptr_clrequest->fd, inbuffer, 10);
    if (numbyte == -1) {
      LOG_PRINT(LOG_INFO, get_status_msg(109));
      free(ris_set);
      free(msg_pool);
      return 0;
    }
    
    /* the size of the chunk the client wants to send */
    n_requested_bufsize = atoi(inbuffer);
/*     printf("inbuffer: %s\nbufsize: %d\n", inbuffer, n_requested_bufsize); */

    /* ------------------------------------------------------------ */
    /* PHASE 2 */
    /* send back reply */
/*     printf("phase 2\n"); */

    /* try to get a buffer large enough to hold the reference
       if n_requested_bufsize == 0, then this command equals free() */
    new_ris_set = (char*)realloc(ris_set, (size_t)n_requested_bufsize);
    if (new_ris_set == NULL && n_requested_bufsize) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      free(ris_set);
      free(msg_pool);
      return 0;
    }
    else {
      ris_set = new_ris_set;
    }
    *ris_set = '\0';

    /* read the data proper unless size was zero (= end of transmission) */
    if (n_requested_bufsize) {
      size_t inlength;
      size_t outlength;
      char* my_ris_set = NULL; /* this ptr will be modified by iconv() */
      char* my_ris_set_start = NULL; /* records initial state of my_elvalue */
      const char* my_instring = NULL; /* this ptr will be modified by iconv() */

      /* acknowledge that we have a buffer ready, let them data come... */
      send_status(ptr_clrequest->fd, 0, TERM_NO);

      /* ------------------------------------------------------------ */
      /* PHASE 3 */
      /* read dataset */
/*       printf("phase 3\n"); */
      numbyte = iread(ptr_clrequest->fd, ris_set, n_requested_bufsize);
      /*        printf("requested: %d\nread: %d\n", n_requested_bufsize, numbyte); */
      if (numbyte == -1) {
	LOG_PRINT(LOG_INFO, get_status_msg(109));
	free(ris_set);
	free(msg_pool);
	return 0;
      }

      /* run a character encoding conversion if required */
      if (conv_descriptor && numbyte) {
	inlength = numbyte - TERM_LEN;
	/* with the encodings supported by our database engines, the converted
	   string can't be longer than six times the input string */
	outlength = 6*inlength;

	if ((my_ris_set = malloc(outlength)) == NULL) {
	  ptr_addresult->failure++;
	  send_status(ptr_clrequest->fd, 801, TERM_NO);
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  free(msg_pool);
	  return 0;
	}

	/* keep start of the converted string */
	my_ris_set_start = my_ris_set;

	/* variable will be modified by iconv, so don't use original */
	my_instring = (const char*)ris_set;

	/* now actually do the conversion */
	if (iconv(conv_descriptor, &my_instring, &inlength, &my_ris_set, &outlength) == (size_t)(-1)) {
	  if (errno == EILSEQ) {
	    sprintf(return_msg, "iconv: invalid input character sequence\n");
	    LOG_PRINT(LOG_WARNING, "iconv: invalid input character sequence");
	  }
	  else if (errno == E2BIG) {
	    sprintf(return_msg, "iconv: output buffer too small\n");
	    LOG_PRINT(LOG_WARNING, "iconv: output buffer too small");
	  }
	  else if (errno == EINVAL) {
	    sprintf(return_msg, "iconv: incomplete input character\n");
	    LOG_PRINT(LOG_WARNING, "iconv: incomplete input character");
	  }
	  /* according to man iconv there are no other error codes */
	  
	  /* phase 4 */
	  send_status(ptr_clrequest->fd, 702, TERM_NO);
	  numbyte = tiwrite(ptr_clrequest->fd, return_msg, TERM_YES);
	  if (numbyte == -1) {
	    LOG_PRINT(LOG_INFO, get_status_msg(110));
	    free(ris_set);
	    free(msg_pool);
	    return 0;
	  }
	  LOG_PRINT(LOG_CRIT, get_status_msg(702));
	  free(ris_set);
	  free(msg_pool);
	  return 0;
	}
	/* else: conversion went ok. We free the original string and replace
	   it with the converted copy */
	if (ris_set) {
	  free(ris_set);
	}
	ris_set = my_ris_set_start;
      }
      /* else: no conversion required */

      /* ------------------------------------------------------------ */
      /* PHASE 4 */
      /* send back message */
/*       printf("phase 4\n"); */

      /* split reference into lines and squeeze contents into database */
/*       printf("entering process_ris_set()\n"); */
      
      *msg_pool = '\0'; /* reset */
      result = process_ris_set(ris_set, conn, conn_refdb, replace_ref, (char*)the_user, ptr_clrequest, n_keep_id, ptr_sentinel, set_count, &msg_pool, &msg_pool_len);
/*       printf("leaving process_ris_set()\n"); */
      if (result == 1) {
	ptr_addresult->success++;
/* 	sprintf(return_msg, "Adding input set %d successful\n", set_count + nongeek_offset); */
	send_status(ptr_clrequest->fd, 408, TERM_NO);
	if (*msg_pool) {
	  numbyte = tiwrite(ptr_clrequest->fd, msg_pool, TERM_NO);
	  if (numbyte == -1) {
	    LOG_PRINT(LOG_INFO, get_status_msg(110));
	    free(ris_set);
	    free(msg_pool);
	    return 0;
	  }
	}
	sprintf(return_msg, "408:"ULLSPEC"\n", (unsigned long long)(set_count + nongeek_offset));
	LOG_PRINT(LOG_INFO, "dataset added successfully");
	numbyte = tiwrite(ptr_clrequest->fd, return_msg, TERM_YES);
	if (numbyte == -1) {
	  LOG_PRINT(LOG_INFO, get_status_msg(110));
	  free(ris_set);
	  free(msg_pool);
	  return 0;
	}
      }
      else if (result == 2) {
	ptr_addresult->updated++;
/* 	sprintf(return_msg, "Updating input set %d successful\n", set_count + nongeek_offset); */
	send_status(ptr_clrequest->fd, 413, TERM_NO);
	if (*msg_pool) {
	  numbyte = tiwrite(ptr_clrequest->fd, msg_pool, TERM_NO);
	  if (numbyte == -1) {
	    LOG_PRINT(LOG_INFO, get_status_msg(110));
	    free(ris_set);
	    free(msg_pool);
	    return 0;
	  }
	}
	sprintf(return_msg, "413:"ULLSPEC"\n", (unsigned long long)(set_count + nongeek_offset));
	LOG_PRINT(LOG_INFO, "dataset updated successfully");
	numbyte = tiwrite(ptr_clrequest->fd, return_msg, TERM_YES);
	if (numbyte == -1) {
	  LOG_PRINT(LOG_INFO, get_status_msg(110));
	  free(ris_set);
	  free(msg_pool);
	  return 0;
	}
      }
      else if  (result == 3) {
	ptr_addresult->skipped++;
	send_status(ptr_clrequest->fd, 407, TERM_NO);
	if (*msg_pool) {
	  numbyte = tiwrite(ptr_clrequest->fd, msg_pool, TERM_NO);
	  if (numbyte == -1) {
	    LOG_PRINT(LOG_INFO, get_status_msg(110));
	    free(ris_set);
	    free(msg_pool);
	    return 0;
	  }
	}
	sprintf(return_msg, "407:"ULLSPEC"\n", (unsigned long long)(set_count + nongeek_offset));
	LOG_PRINT(LOG_INFO, "skipped processing dataset");
	numbyte = tiwrite(ptr_clrequest->fd, return_msg, TERM_YES);
	if (numbyte == -1) {
	  LOG_PRINT(LOG_INFO, get_status_msg(110));
	  free(ris_set);
	  free(msg_pool);
	  return 0;
	}
      }
      else {
	ptr_addresult->failure++;
	send_status(ptr_clrequest->fd, 414, TERM_NO);
	if (*msg_pool) {
	  numbyte = tiwrite(ptr_clrequest->fd, msg_pool, TERM_NO);
	  if (numbyte == -1) {
	    LOG_PRINT(LOG_INFO, get_status_msg(110));
	    free(ris_set);
	    free(msg_pool);
	    return 0;
	  }
	}
	sprintf(return_msg, "414:"ULLSPEC"\n", (unsigned long long)(set_count + nongeek_offset));
	LOG_PRINT(LOG_WARNING, "failed processing dataset");
	numbyte = tiwrite(ptr_clrequest->fd, return_msg, TERM_YES);
	if (numbyte == -1) {
	  LOG_PRINT(LOG_INFO, get_status_msg(110));
	  free(ris_set);
	  free(msg_pool);
	  return 0;
	}
      }
      set_count++;
    }
  } while (n_requested_bufsize);

  /* ris_set should be deallocated by now due to a realloc(0) call */
/*   printf("leaving at end\n"); */
  return set_count;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  run_keyword_scan(): scans datasets for keywords after adding
                      (dispatcher for real_run_keyword_scan())

  int run_keyword_scan returns 1 if failed, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  Lilid* ptr_sentinel ptr to linked list with ID values to check
                      or NULL if all references in the database
		      should be scanned

  int mode 0 = reference entries 1 = note entries 2 = both
  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int run_keyword_scan(struct CLIENT_REQUEST* ptr_clrequest, Lilid* ptr_sentinel, int mode) {
  int retval;
  int n_insert = 0; /* number of inserts that actually ran */
  dbi_conn conn;

  if (!mode) { /* check only references */
    retval = real_run_keyword_scan(ptr_clrequest, ptr_sentinel, 0, &n_insert);
  }
  else if (mode == 1) { /* check only notes */
    retval = real_run_keyword_scan(ptr_clrequest, ptr_sentinel, 1, &n_insert);
  }
  else { /* check both references and notes */
    retval = real_run_keyword_scan(ptr_clrequest, ptr_sentinel, 0, &n_insert);
    
    if (!retval) {
      retval = real_run_keyword_scan(ptr_clrequest, ptr_sentinel, 1, &n_insert);
    }
  }
  
  if (n_insert) {
    /* connect to database server*/
    if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
      LOG_PRINT(LOG_ERR, get_status_msg(205));
      return 1;
    }
    update_meta(conn, ptr_clrequest);

    dbi_conn_close(conn);
  }

  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  real_run_keyword_scan(): scans datasets for keywords after adding

  int real_run_keyword_scan returns 1 if failed, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  Lilid* ptr_sentinel ptr to linked list with ID values to check
                      or NULL if all references in the database
		      should be scanned

  int mode 0 = reference entries 1 = note entries

  int* ptr_insert ptr to insert counter

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int real_run_keyword_scan(struct CLIENT_REQUEST* ptr_clrequest, Lilid* ptr_sentinel, int mode, int* ptr_insert) {
  int i;
  unsigned long long keyword_id;
  unsigned long long xref_id;
  size_t sql_cmd_len = 1024;
  char *sql_command;
  char *new_sql_command;
  char id_buffer[21] = "";
  const char *item;
  const char *item_kw;
  const char scanfields[2][5][19] = {
    {"refdb_abstract", "refdb_title", "refdb_booktitle", "refdb_title_series", ""},
    {"note_title", "note_content", ""}};

  dbi_conn conn;
  dbi_result dbires;
  dbi_result dbires1;
  dbi_result dbires2;
  dbi_result dbires_kwlist;
  Lilid *ptr_curr;

  if ((sql_command = malloc(sql_cmd_len)) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return 1;
  }

  /* connect to database server*/
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    LOG_PRINT(LOG_ERR, get_status_msg(205));
    free(sql_command);
    return 1;
  }

  /* get a list of all existing keywords */
  strcpy(sql_command, "SELECT * FROM t_keyword");
  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires_kwlist = dbi_conn_query(conn, sql_command);
  if (!dbires_kwlist) {
    LOG_PRINT(LOG_WARNING, "keyword query error");
    free(sql_command);
    dbi_conn_close(conn);
    return 1;
  }

  if (dbi_result_get_numrows(dbires_kwlist) < 1) {
    /* nothing to do */
    free(sql_command);
    dbi_conn_close(conn);
    return 0;
  }

  if (ptr_sentinel) { /* we have a list of IDs to scan */
    if (!mode) {
      strcpy(sql_command, "SELECT refdb_id,refdb_abstract,refdb_title,refdb_booktitle,refdb_title_series FROM t_refdb WHERE refdb_id IN (");
    }
    else {
      strcpy(sql_command, "SELECT note_id,note_title,note_content FROM t_note WHERE note_id IN (");
    }

    ptr_curr = ptr_sentinel;

    /* loop over all references to scan */
    while ((ptr_curr = get_next_lilid(ptr_curr)) != NULL) {
      sprintf(id_buffer, ULLSPEC",", (unsigned long long)(ptr_curr->value));

      if ((new_sql_command = mstrcat(sql_command, id_buffer, &sql_cmd_len, 0)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	free(sql_command);
	dbi_result_free(dbires_kwlist);
	dbi_conn_close(conn);
	return 1;
      }
      else {
	sql_command = new_sql_command;
      }
    }
    if (*id_buffer) {
      /* overwrite trailing comma with closing bracket */
      if ((new_sql_command = mstrcat(sql_command, ")", &sql_cmd_len, 1)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	free(sql_command);
	dbi_result_free(dbires_kwlist);
	dbi_conn_close(conn);
	return 1;
      }
      else {
	sql_command = new_sql_command;
      }
    }
    else {
      /* no IDs found */
      free(sql_command);
      LOG_PRINT(LOG_INFO, "no IDs found for keyword scan");
      dbi_result_free(dbires_kwlist);
      dbi_conn_close(conn);
      return 0;
    }
  }
  else { /* scan all IDs in the database */
    if (!mode) {
      strcpy(sql_command, "SELECT refdb_id,refdb_abstract,refdb_title,refdb_booktitle,refdb_title_series FROM t_refdb WHERE refdb_id>0");
    }
    else {
      strcpy(sql_command, "SELECT note_id,note_title,note_content FROM t_note WHERE note_id>0");
    }
  }    

  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    LOG_PRINT(LOG_WARNING, "reference query error");
    free(sql_command);
    dbi_result_free(dbires_kwlist);
    dbi_conn_close(conn);
    return 1;
  }

  /* loop over all IDs in our result set */
  while (dbi_result_next_row(dbires)) { 
    xref_id = my_dbi_result_get_idval(dbires, (mode) ? "note_id":"refdb_id");

    /* rewind keyword list */
    dbi_result_first_row(dbires_kwlist);

    /* loop over all keywords */
    while (dbi_result_next_row(dbires_kwlist)) {
      item_kw = my_dbi_result_get_string(dbires_kwlist, "keyword_name");
      keyword_id = my_dbi_result_get_idval(dbires_kwlist, "keyword_id");
      if (item_kw && *item_kw) {
	/* loop over fields to scan */
	i = 0;
	while (scanfields[mode][i][0]) {
	  item = my_dbi_result_get_string(dbires, scanfields[mode][i]);
	  if (item && *item && strstr(item, item_kw)) {
	    /* check for existing entry in t_xkeyword */
	    sprintf(sql_command, "SELECT keyword_id FROM t_xkeyword WHERE keyword_id="ULLSPEC" AND xkeyword_type=\'%s\' AND xref_id="ULLSPEC, (unsigned long long)keyword_id, (mode) ? "NOTE":"REFERENCE", (unsigned long long)xref_id);
	    LOG_PRINT(LOG_DEBUG, sql_command);
	    
	    dbires1 = dbi_conn_query(conn, sql_command);
	    if (!dbires1) {
	      LOG_PRINT(LOG_WARNING, "select error");
	      free(sql_command);
	      dbi_result_free(dbires);
	      dbi_result_free(dbires_kwlist);
	      dbi_conn_close(conn);
	      return 1;
	    }
	    if (!dbi_result_next_row(dbires1)) {
	      sprintf(sql_command, "INSERT INTO t_xkeyword (keyword_id,xref_id,xkeyword_type) VALUES ("ULLSPEC","ULLSPEC",\'%s\')", (unsigned long long)keyword_id, (unsigned long long)xref_id, (mode)? "NOTE":"REFERENCE");
	      LOG_PRINT(LOG_DEBUG, sql_command);
	      
	      dbires2 = dbi_conn_query(conn, sql_command);
	      if (!dbires2) {
		LOG_PRINT(LOG_WARNING, "insert error");
		free(sql_command);
		dbi_result_free(dbires);
		dbi_result_free(dbires_kwlist);
		dbi_conn_close(conn);
		return 1;
	      }
	      *ptr_insert++;

	      dbi_result_free(dbires2);
	      
	      if (!mode) {
		sprintf(sql_command, "noticed keyword %s in reference %s:"ULLSPEC, item_kw, ptr_clrequest->current_db, (unsigned long long)xref_id);
	      }
	      else {
		sprintf(sql_command, "noticed keyword %s in note %s:"ULLSPEC, item_kw, ptr_clrequest->current_db, (unsigned long long)xref_id);
	      }

	      LOG_PRINT(LOG_INFO, sql_command);

	      /* jump out as we don't have to scan further fields - the
		 keyword has been added anyway */
	      dbi_result_free(dbires1);
	      break;
	    }
	    dbi_result_free(dbires1);
	  }
	  i++;
	} /* end loop over all fields */
      } /* end if keyword */
    } /* end loop over all keywords */
  }
  dbi_result_free(dbires);

  free(sql_command);

  dbi_result_free(dbires_kwlist);
  dbi_conn_close(conn);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  deleteref(): implements the client command deleteref

  int deleteref returns >0 if failed, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct ADDRESULT* ptr_addresult structure to hold number of successful and
                              failed deleterefs

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int deleteref(struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn;
  int numbyte; /* number of bytes written */
  int error;
  int retval = 0;
  int cs_status;
  size_t n_bufsize;
  char buffer[64];
  char* id_list;
  char* new_msg;
  const char* drivername;
  struct lilimem sentinel;
  Lilid id_sentinel;
  Lilid *ptr_curr;

  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';

  id_sentinel.ptr_next = NULL;

  n_bufsize = atoi(ptr_clrequest->argument);

  /* try to allocate the amount the client requested */
  id_list = malloc(n_bufsize);
  if (id_list == NULL || insert_lilimem(&sentinel, (void**)&id_list, NULL)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* send acknowledgement to client */
  send_status(ptr_clrequest->fd, 0, TERM_NO);

  if ((cs_status = read_status(ptr_clrequest->fd)) != 0) {
    LOG_PRINT(LOG_INFO, get_status_msg(112));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  /* read id list from client */
  numbyte = tread(ptr_clrequest->fd, id_list, n_bufsize);
  if (numbyte == -1) {
    LOG_PRINT(LOG_INFO, get_status_msg(109));
    delete_all_lilimem(&sentinel);
    return 1;
  }
  
  /* split id_list into tokens */
  error = string_tokenize_lili(&id_sentinel, id_list);
  if (error == 2) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    delete_all_lilid(&id_sentinel);
    delete_all_lilimem(&sentinel);
    return 1;
  }
  else if (error == 1) {
    send_status(ptr_clrequest->fd, 412, TERM_NO);
    delete_all_lilid(&id_sentinel);
    delete_all_lilimem(&sentinel);
    return 1;
  }

  /* connect to database server*/
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    delete_all_lilid(&id_sentinel);
    delete_all_lilimem(&sentinel);
    return 1;
  }

  drivername = dbi_driver_get_name(dbi_conn_get_driver(conn));


  ptr_curr = &id_sentinel;

  /* loop over arguments */
  while ((ptr_curr = get_next_lilid(ptr_curr)) != NULL) {
    if ((retval = delete_ref_by_id(ptr_curr->value, conn, ptr_clrequest, ptr_addresult)) != 0) {
      sprintf(buffer, "420:"ULLSPEC"\n", (unsigned long long)ptr_curr->value);
      if ((new_msg = mstrcat(ptr_addresult->msg, buffer, &(ptr_addresult->msg_len), 0)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 1;
      }
      else {
	ptr_addresult->msg = new_msg;
      }
    }
  } /* end while */

  if (conn && ptr_addresult->success) {
    update_meta(conn, ptr_clrequest);
  }

  dbi_conn_close(conn);
  delete_all_lilid(&id_sentinel);
  delete_all_lilimem(&sentinel);
  
  send_status(ptr_clrequest->fd, 0, TERM_NO);

  numbyte = tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);
  if (numbyte == -1) {
    LOG_PRINT(LOG_INFO, get_status_msg(110));
    retval = 1;
  }

  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  delete_ref_by_id(): deletes a reference by its ID value

  int delete_ref_by_id returns 0 if failed, 1 if successful

  unsigned long long idval ID value of the reference to be deleted

  dbi_conn conn database connection

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct ADDRESULT* ptr_addresult structure to hold number of successful and
                              failed deleterefs

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int delete_ref_by_id(unsigned long long idval, dbi_conn conn, struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult) {
  char sql_command[640]; /* fixed length is ok here since only IDs are appended to the query strings, and the length of an ID is limited by the database */
  char periodical_id[32] = ""; /* will hold the ID until used */
  char* new_msg;
  int result;
  unsigned long long n_id = 0;
  unsigned long long n_xid = 0;
  unsigned long long n_periodical_id = 0;
  dbi_result dbires;
  dbi_result dbires_user;
  dbi_result dbires_note;

  /* The general procedure to remove a reference is as follows: Remove
     the entry in the main table t_refdb. Query the author, keyword, user
     crosslink tables for authors, keywords, users used by this reference.
     Find out whether these authors, keywords, users are used by any other
     datasets. If not, delete them from the authors, keywords, users data
     tables. In both cases, delete the entries in the crosslink tables */
    
  /* If the db server supports it, start a transaction. We want one
     transaction per reference */
  if (my_dbi_conn_begin(conn)) {
    LOG_PRINT(LOG_WARNING, get_status_msg(227));
    return 227;
  }
  
  /* lock the tables we'll write to to prevent concurrent writes
     from different clients */
  if (my_dbi_conn_lock(conn, 0 /* lock regular tables */)) {
    LOG_PRINT(LOG_WARNING, get_status_msg(228));
    return 228;
  }

  sprintf(sql_command, "SELECT refdb_id FROM t_refdb WHERE refdb_type != \'DUMMY\' AND refdb_id="ULLSPEC, (unsigned long long)idval);
  LOG_PRINT(LOG_DEBUG, sql_command);
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    return 234;
  }
    
  if (dbi_result_get_numrows(dbires) == 0) {
    /* ID does not exist - nothing to do */
    ptr_addresult->skipped++;
    dbi_result_free(dbires);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_commit(conn);

    /* add message, reuse sql_command */
    sprintf(sql_command, "417:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    return 417;
  }
    
  dbi_result_free(dbires);

  /* find user id and xuser id entries which we might have to remove */
  sprintf(sql_command, "SELECT t_user.user_id, t_user.user_name, t_xuser.xuser_id FROM t_user INNER JOIN t_xuser ON t_user.user_id=t_xuser.user_id WHERE t_xuser.refdb_id="ULLSPEC, (unsigned long long)idval);
  LOG_PRINT(LOG_DEBUG, sql_command);
  dbires_user = dbi_conn_query(conn, sql_command);
  if (!dbires_user) {
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    return 234;
  }

  if (dbi_result_get_numrows(dbires_user) > 0) {
    if (dbi_result_next_row(dbires_user) == 0) {
      dbi_result_free(dbires_user);
      my_dbi_conn_unlock(conn);
      my_dbi_conn_rollback(conn);
      LOG_PRINT(LOG_WARNING, get_status_msg(234));
      return 234;
    }

    /* save for later use */
    n_id = my_dbi_result_get_idval(dbires_user, "user_id");
    n_xid = my_dbi_result_get_idval(dbires_user, "xuser_id");
  }

  /* check whether the reference has been picked by other users */
  sprintf(sql_command, "SELECT DISTINCT t_user.user_id,t_user.user_name FROM t_user INNER JOIN t_note ON t_user.user_id=t_note.note_user_id INNER JOIN t_xnote ON t_xnote.note_id=t_note.note_id WHERE t_xnote.xref_id="ULLSPEC" AND t_xnote.xnote_type='REFERENCE'", (unsigned long long)idval);
  LOG_PRINT(LOG_DEBUG, sql_command);
  dbires_note = dbi_conn_query(conn, sql_command);
  if (!dbires_note) {
    dbi_result_free(dbires_user);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    return 234;
  }

  if (dbi_result_get_numrows(dbires_note) > 0) {
    int used_by_other = 1;

    if (dbi_result_next_row(dbires_note) == 0) {
      dbi_result_free(dbires_user);
      dbi_result_free(dbires_note);
      my_dbi_conn_unlock(conn);
      my_dbi_conn_rollback(conn);
      LOG_PRINT(LOG_WARNING, get_status_msg(234));
      return 234;
    }

    if (dbi_result_get_numrows(dbires_note) == 1) {

      if (!strcmp(ptr_clrequest->username, my_dbi_result_get_string(dbires_user, "user_name"))) {
	used_by_other--;
      }
    }

    if (used_by_other) {
      /* dataset is used by at least one other user, refuse to delete it */
      ptr_addresult->failure++;


      dbi_result_free(dbires_user);
      dbi_result_free(dbires_note);
      my_dbi_conn_unlock(conn);
      my_dbi_conn_rollback(conn);

      /* send message to client, reuse sql_command */
      sprintf(sql_command, "423:"ULLSPEC"\n", (unsigned long long)idval);
      if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	ptr_addresult->msg = new_msg;
      }

      return 423;
    }
    /* else: reference is only in lists owned by the reference owner */
  }

  /* retrieve periodical_id for later use */
  sprintf(sql_command, "SELECT refdb_periodical_id FROM t_refdb WHERE refdb_id="ULLSPEC, (unsigned long long)idval);
  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    dbi_result_free(dbires_user);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    return 234;
  }

  if (dbi_result_next_row(dbires) != 0) {
    if ((n_periodical_id = my_dbi_result_get_idval(dbires, "refdb_periodical_id")) != 0) {
      sprintf(periodical_id, ULLSPEC, (unsigned long long)n_periodical_id); /* save ID */
    }
  }

  dbi_result_free(dbires);

  /* search orphans in t_keyword */
  result = remove_keyword_entries(idval, conn, 0);

  if (result) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "229:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    dbi_result_free(dbires_user);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    return 229;
  }

  /* search orphans in t_author */
  result = remove_author_entries(idval, conn);

  if (result) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "230:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    dbi_result_free(dbires_user);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    return 230;
  }


  /* search orphans in t_link */
  result = remove_ulink_entries(idval, NULL, conn, 0 /* reference */);

  if (result) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "259:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    dbi_result_free(dbires_user);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    return 259;
  }

  /* remove orphans in t_xnote */
  result = remove_xnote_entries(0 /* any note */, idval, conn, 0 /*ref*/);

  if (result != 0 && result != 4) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "245:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    dbi_result_free(dbires_user);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    return 245;
  }


  /* search orphans in t_periodical */
  if (n_periodical_id) {
    result = remove_periodical_entries(n_periodical_id, conn);

    if (result) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "231:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    dbi_result_free(dbires_user);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    return 231;
    }
  }

  /* delete entry in main table */
  sprintf(sql_command, "DELETE FROM t_refdb WHERE refdb_id="ULLSPEC, (unsigned long long)idval);
  LOG_PRINT(LOG_DEBUG, sql_command);
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "248:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    dbi_result_free(dbires_user);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    LOG_PRINT(LOG_WARNING, get_status_msg(248));
    return 248;
  }

  dbi_result_free(dbires);

  /* search orphans in t_user. The query returning dbires_user ran
     above. The loop should run only once */
  /* delete entry in xuser table */
  sprintf(sql_command, "DELETE FROM t_xuser WHERE xuser_id="ULLSPEC, (unsigned long long)n_xid);
  LOG_PRINT(LOG_DEBUG, sql_command);
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "234:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    dbi_result_free(dbires_user);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    return 234;
  }

  dbi_result_free(dbires);

  /* delete entry in user table */
  result = remove_user_entries(n_id, conn);
  
  if (result != 0 && result != 4) {
    /* send message to client, reuse sql_command */
    sprintf(sql_command, "234:"ULLSPEC"\n", (unsigned long long)idval);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      dbi_result_free(dbires_user);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    dbi_result_free(dbires_user);
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    return 234;
  }

  dbi_result_free(dbires_user);
  ptr_addresult->success++;

  /* send message to client, reuse sql_command */
  sprintf(sql_command, "419:"ULLSPEC"\n", (unsigned long long)idval);
  if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 801;
  }
  else {
    ptr_addresult->msg = new_msg;
  }

  my_dbi_conn_unlock(conn);
  my_dbi_conn_commit(conn);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  pickref(): implements the client command pickref

  int pickref returns >0 if failed, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  int n_remove if 1, remove from personal interest list. if 0, add to list

  struct ADDRESULT* ptr_addresult structure to hold number of successful and
                              failed pickrefs

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int pickref(struct CLIENT_REQUEST* ptr_clrequest, int n_remove, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn;
  dbi_result dbires;
  dbi_result dbires1;
  int error = 0;
  int numrows;
  int numbyte; /* number of bytes written */
  int n_bufsize;
  int cs_status;
  int retval = 0;
  int result;
  unsigned long long n_user_id = 0;
  size_t sql_cmd_len; /* length of the sql_command buffer */
  char *sql_command; /* a buffer for the sql commands we got to piece together */
  char *new_msg;
  char *id_list; /* the list of IDs that the client sends in */
  char *quoted_listname;
  char *quoted_user;
  const char *drivername; /* name of the libdbi driver */
  struct lilimem sentinel;
  Lilid id_sentinel;
  Lilid *ptr_curr;

  /* initialize linked lists */
  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';

  id_sentinel.ptr_next = NULL;

  /* allocate memory */
  sql_cmd_len = 256;
  sql_command = malloc(sql_cmd_len);
  if (sql_command == NULL || insert_lilimem(&sentinel, (void**)&sql_command, NULL)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  n_bufsize = atoi(ptr_clrequest->argument);

  /* try to allocate the amount the client requested */
  id_list = malloc(n_bufsize);
  if (id_list == NULL || insert_lilimem(&sentinel, (void**)&id_list, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* send acknowledgement to client */
  send_status(ptr_clrequest->fd, 0, TERM_NO);

  /* read id list from client */
  if ((cs_status = read_status(ptr_clrequest->fd)) != 0) {
    LOG_PRINT(LOG_INFO, get_status_msg(112));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  numbyte = tread(ptr_clrequest->fd, id_list, n_bufsize);
  if (numbyte == -1) {
    delete_all_lilimem(&sentinel);
    LOG_PRINT(LOG_ERR, get_status_msg(109));
    return 1;
  }
  
  /* split id_list into tokens */
  error = string_tokenize_lili(&id_sentinel, id_list);
  if (error == 2) {
    delete_all_lilimem(&sentinel);
    delete_all_lilid(&id_sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  else if (error == 1) {
    delete_all_lilimem(&sentinel);
    delete_all_lilid(&id_sentinel);
    send_status(ptr_clrequest->fd, 412, TERM_NO);
    LOG_PRINT(LOG_INFO, get_status_msg(412));
    return 2;
  }

  /* connect to database server*/
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    delete_all_lilid(&id_sentinel);
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    return 1;
  }

  /* The general procedure to pick a reference is:
     check whether there is an entry in t_xuser for the current
     user pointing to this refdb_id. If yes and we shall add,
     we're done. If yes and we shall remove, do so. If no entry is
     found: If we shall remove, we're done, if we shall add, do so.
     Personal reference lists are implemented as notes.
     We also have to add the user to t_user if he's not already
     there. And upon removing entries from t_xuser we have to check
     whether we remove the last reference to the user in t_user. If
     so, we have to kick him out */

  /* If the db server supports it, start a transaction. We want one
     transaction per reference */
  if (my_dbi_conn_begin(conn)) {
    send_status(ptr_clrequest->fd, 227, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(227));
    retval = 1;
    goto Finish;
  }
  
  /* lock the tables we'll write to to prevent concurrent writes
     from different clients */
  if (my_dbi_conn_lock(conn, 0 /* lock regular tables */)) {
    send_status(ptr_clrequest->fd, 228, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(228));
    retval = 1;
    goto Finish;
  }

  /* get the user_id for the current user */
  quoted_user = strdup(ptr_clrequest->username);

  if (!quoted_user || !dbi_conn_quote_string(conn, &quoted_user)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    retval = 1;
    goto Finish;
  }
	
  sprintf(sql_command, "SELECT user_id FROM t_user WHERE user_name=%s", quoted_user);
  
  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    send_status(ptr_clrequest->fd, 234, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(234));
    my_dbi_conn_unlock(conn);
    my_dbi_conn_rollback(conn);
    retval = 1;
    goto Finish;
  }

  if (dbi_result_get_numrows(dbires) == 0) {
    if (!n_remove) {

      /* create an entry for the current user in t_user */
      sprintf(sql_command, "INSERT INTO t_user (user_name) VALUES (%s)", quoted_user);

      LOG_PRINT(LOG_DEBUG, sql_command);

      dbires1 = dbi_conn_query(conn, sql_command);
      if (!dbires1) {
	send_status(ptr_clrequest->fd, 233, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(233));
	dbi_result_free(dbires);
	my_dbi_conn_unlock(conn);
	my_dbi_conn_rollback(conn);
	retval = 1;
	goto Finish;
      }

      dbi_result_free(dbires1);

      /* retrieve the generated user_id in t_user */
      drivername = dbi_driver_get_name(dbi_conn_get_driver(conn));

      if (!strcmp(my_dbi_conn_get_cap(conn, "named_seq"), "f")) {
	n_user_id = dbi_conn_sequence_last(conn, NULL);
      }
      else {
	n_user_id = dbi_conn_sequence_last(conn, "t_user_user_id_seq");
      }
    }
  }
  else { /* numrows > 0 */

    /* retrieve the user_id from t_user */
    if (dbi_result_next_row(dbires) != 0) {
      n_user_id = (unsigned long long)my_dbi_result_get_idval(dbires, "user_id");
    }
    else {
      send_status(ptr_clrequest->fd, 234, TERM_NO);
      LOG_PRINT(LOG_WARNING, get_status_msg(234));
      dbi_result_free(dbires);
      my_dbi_conn_unlock(conn);
      my_dbi_conn_rollback(conn);
      retval = 1;
      goto Finish;
    }
  }

  free(quoted_user);

  dbi_result_free(dbires);

  /*----------------------------------------------------------------*/
  /* remove entries */
  if (n_remove) {
    /* remove entries one by one to get exact status reports */
    ptr_curr = &id_sentinel;

    /* loop over arguments */
    while ((ptr_curr = get_next_lilid(ptr_curr)) != NULL) {
      result = dump_one_reference(conn, ptr_curr->value, n_user_id, ptr_clrequest->listname, ptr_clrequest->username, ptr_addresult);
      
      if (result == 234 || result == 262 || result == 267) {
	send_status(ptr_clrequest->fd, result, TERM_NO);
	LOG_PRINT(LOG_WARNING, get_status_msg(result));
	retval = 1;
	goto Finish;
      }
      else if (result == 801) {
	send_status(ptr_clrequest->fd, 801, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	my_dbi_conn_unlock(conn);
	my_dbi_conn_rollback(conn);
	retval = 1;
	goto Finish;
      }
      /* else: ok */
    }

    my_dbi_conn_unlock(conn);
    my_dbi_conn_commit(conn);

    /* see whether personal reference list is empty */
    if (*(ptr_clrequest->listname)) {
      quoted_listname = strdup(ptr_clrequest->listname);
  
      if (!quoted_listname || !dbi_conn_quote_string(conn, &quoted_listname)) {
	send_status(ptr_clrequest->fd, 801, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	my_dbi_conn_unlock(conn);
	my_dbi_conn_rollback(conn);
	retval = 1;
	goto Finish;
      }

      if (*upper_citekey == 't') {
	strup(quoted_listname);
      }

      sprintf(sql_command, "SELECT xnote_id FROM t_xnote INNER JOIN t_note ON t_note.note_id=t_xnote.note_id WHERE t_note.note_key=%s", quoted_listname);

      LOG_PRINT(LOG_DEBUG, sql_command);

      dbires = dbi_conn_query(conn, sql_command);
      if (!dbires) {
	send_status(ptr_clrequest->fd, 234, TERM_NO);
	LOG_PRINT(LOG_WARNING, get_status_msg(234));
	my_dbi_conn_unlock(conn);
	my_dbi_conn_rollback(conn);
	retval = 1;
	goto Finish;
      }

      numrows = dbi_result_get_numrows(dbires);

      if (!numrows) { /* list contains no references */
	struct ADDRESULT* p_dummy_addresult;
	if ((p_dummy_addresult = new_addresult(512)) == NULL) {
	    LOG_PRINT(LOG_CRIT, get_status_msg(801));
	    return 801;
	}

	if (remove_personal_list(conn, quoted_listname, ptr_clrequest, p_dummy_addresult)) {
	  /* error */
	  sprintf(sql_command, "264:%s\n", quoted_listname);
	  if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	    LOG_PRINT(LOG_CRIT, get_status_msg(801));
	    return 801;
	  }
	  else {
	    ptr_addresult->msg = new_msg;
	  }
	}
	else {
	  sprintf(sql_command, "265:%s\n", quoted_listname);
	  if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	    LOG_PRINT(LOG_CRIT, get_status_msg(801));
	    return 801;
	  }
	  else {
	    ptr_addresult->msg = new_msg;
	  }
	}
	free_addresult(p_dummy_addresult);
      }
      dbi_result_free(dbires);
    }
  }

  /*----------------------------------------------------------------*/
  /* add entries */
  else { /* if (!n_remove) */
    /* add entries one by one to get exact status reports */
    ptr_curr = &id_sentinel;

    /* loop over arguments */
    while ((ptr_curr = get_next_lilid(ptr_curr)) != NULL) {
      result = pick_one_reference(conn, ptr_curr->value, n_user_id, ptr_clrequest->listname, ptr_clrequest->username, ptr_addresult);

      if (result == 234 || result == 262 || result == 266) {
	send_status(ptr_clrequest->fd, result, TERM_NO);
	LOG_PRINT(LOG_WARNING, get_status_msg(result));
	retval = 1;
	goto Finish;
      }
      else if (result == 801) {
	send_status(ptr_clrequest->fd, 801, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	my_dbi_conn_unlock(conn);
	my_dbi_conn_rollback(conn);
	retval = 1;
	goto Finish;
      }
      /* else: ok */
    }
    my_dbi_conn_unlock(conn);
    my_dbi_conn_commit(conn);
  }


  /* send report to client */
  send_status(ptr_clrequest->fd, 0, TERM_NO);

  numbyte = tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);
  if (numbyte == -1) {
    LOG_PRINT(LOG_INFO, get_status_msg(110));
    retval = 1;
  }


 Finish:   
  if (conn && ptr_addresult->success) {
    update_meta(conn, ptr_clrequest);
  }

  dbi_conn_close(conn);
  delete_all_lilid(&id_sentinel);
  delete_all_lilimem(&sentinel);
  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  pick_one_reference(): adds one reference to a personal reference list
                        when calling this function, the tables which are
			going to be modified should be locked by the
			calling function

  int pick_one_reference returns >0 if failed, 0 if successful

  dbi_conn conn connection structure

  unsigned long long n_ref_id ID of the reference to be picked

  unsigned long long n_user_id ID of the current user

  const char *listname name of the personal reference list

  const char *username name of the current user

  struct ADDRESULT* ptr_addresult structure to hold number of successful and
                              failed pickrefs

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int pick_one_reference(dbi_conn conn, unsigned long long n_ref_id, unsigned long long n_user_id, const char *listname, const char *username, struct ADDRESULT* ptr_addresult) {
  int result;
  unsigned long long n_list_id;
  unsigned long long n_list_owner_id;
  char refdb_citekey[256] = "";
  char sql_command[1024];
  char full_listname[PREFS_BUF_LEN+USERNAME_LENGTH+2] = "";
  char *quoted_listname;
  char *new_msg;
  dbi_result dbires;
  
  /* make sure the target exists */
  sprintf(sql_command, "SELECT refdb_id, refdb_citekey FROM t_refdb WHERE refdb_id="ULLSPEC, (unsigned long long)n_ref_id);
  LOG_PRINT(LOG_DEBUG, sql_command);
  
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    return 234;
  }

  if (!dbi_result_next_row(dbires)) {
    /* target ID missing */
    dbi_result_free(dbires);
    /* reuse sql_command */
    sprintf(sql_command, "412:"ULLSPEC"\n", (unsigned long long)n_ref_id);
    ptr_addresult->skipped++;
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    return 0;
  }
  else {
    strcpy(refdb_citekey, dbi_result_get_string(dbires, "refdb_citekey"));
  }
  
  dbi_result_free(dbires);

  /* check for existing note representing the personal list */
  if (!*listname) {
    quoted_listname = malloc(strlen(username)*2+2);
    if (quoted_listname) {
      sprintf(quoted_listname, "%s-%s", username, username);
      strcpy(full_listname, quoted_listname); /* preserve for later use */
    }
  }
  else {
    quoted_listname = strdup(listname);
  }
  
  if (!quoted_listname || !dbi_conn_quote_string(conn, &quoted_listname)) {
    return 801;
  }

  /* uppercase string if citation keys are expected in uppercase */
  if (*upper_citekey == 't') {
    strup(quoted_listname);
    strup(full_listname); /* initialized to empty string if not used */
  }

  sprintf(sql_command, "SELECT note_id, note_user_id FROM t_note WHERE note_key=%s", quoted_listname);

  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    free(quoted_listname);
    LOG_PRINT(LOG_DEBUG, "dbires went to NULL");
    return 234;
  }

  if (dbi_result_next_row(dbires)) {
    /* note already exists, check user id */
    n_list_owner_id = my_dbi_result_get_idval(dbires, "note_user_id");
/*     sprintf(sql_command, "n_list_owner_id went to %lld", n_list_owner_id); */
    LOG_PRINT(LOG_DEBUG, sql_command);

    if (n_list_owner_id != n_user_id) { /* we're not owner of this list */
      free(quoted_listname);
      LOG_PRINT(LOG_DEBUG, "not owner of this list");

      sprintf(sql_command, "266:%s:%s\n", (*listname)?listname:full_listname, username);
      if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	ptr_addresult->msg = new_msg;
      }

      return 266;
    }

    /* we own this list. retrieve ID */
    n_list_id = my_dbi_result_get_idval(dbires, "note_id");
/*     sprintf(sql_command, "n_list_id went to %lld", n_list_id); */
    LOG_PRINT(LOG_DEBUG, sql_command);
  }
  else {
    /* note does not yet exist. Create a new one */
    n_list_id = create_personal_list(conn, (*listname)?(char*)listname:full_listname, n_user_id, username);

/*     sprintf(sql_command, "new n_list_id went to %lld", n_list_id); */
    LOG_PRINT(LOG_DEBUG, sql_command);

    if (!n_list_id) {
      free(quoted_listname);
      sprintf(sql_command, "262:%s\n", (*listname)?listname:full_listname);
      if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	ptr_addresult->msg = new_msg;
      }

      return 262;
    }

    /* else: was created ok */
    sprintf(sql_command, "263:%s\n", (*listname)?listname:full_listname);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
  }
  
  dbi_result_free(dbires);
  
  /* insert new link */
  /* function will quote ptr_curr->value */
  result = insert_link("REFERENCE", refdb_citekey, conn, n_list_id);
  /*       printf("my_type: %s<< ptr_curr->value: %s<<\n", my_type, ptr_curr->value); */
  
  /* reuse sql_command */
  if (!result) { /* ok */
    sprintf(sql_command, "421:%s -> %s:%s\n", quoted_listname, "REFERENCE", refdb_citekey);
    ptr_addresult->success++;
  }
  else if (result == 7) { /* link already exists */
    sprintf(sql_command, "418:%s -> %s:%s\n", quoted_listname, "REFERENCE", refdb_citekey);
    ptr_addresult->skipped++;
  }
  else { /* other errors */
    sprintf(sql_command, "414:%s -> %s:%s\n", quoted_listname, "REFERENCE", refdb_citekey);
    ptr_addresult->failure++;
  }
  
  if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
    free(quoted_listname);
    return 801;
  }
  else {
    ptr_addresult->msg = new_msg;
  }

  free(quoted_listname);

  /* success */
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  dump_one_reference(): removes one reference from a personal reference list
                        when calling this function, the tables which are
			going to be modified should be locked by the
			calling function

  int dump_one_reference returns >0 if failed, 0 if successful

  dbi_conn conn connection structure

  unsigned long long n_ref_id ID of the reference to be picked

  unsigned long long n_user_id ID of the current user

  const char *listname name of the personal reference list

  const char *username name of the current user

  struct ADDRESULT* ptr_addresult structure to hold number of successful and
                              failed pickrefs

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int dump_one_reference(dbi_conn conn, unsigned long long n_ref_id, unsigned long long n_user_id, const char *listname, const char *username, struct ADDRESULT* ptr_addresult) {
  int retval = 0;
  int retcode = 0;
  unsigned long long n_list_id;
  unsigned long long n_list_owner_id;
  unsigned long long n_xnote_id;
  char sql_command[1024];
  char *quoted_listname;
  char *new_msg;
  dbi_result dbires;

  /* strategy
     check existence and ownership of list
     find link to reference in list
     if not found, return
     if found, remove link
   */
  if (!*listname) {
    /* if no listname is specified, it defaults to the current users main list */
    quoted_listname = malloc(strlen(username)*2+2);
    if (quoted_listname) {
      sprintf(quoted_listname, "%s-%s", username, username);
    }
  }
  else {
    quoted_listname = strdup(listname);
  }

  if (!quoted_listname || !dbi_conn_quote_string(conn, &quoted_listname)) {
    return 801;
  }

  if (*upper_citekey == 't') {
    strup(quoted_listname);
  }

  /* check whether we own the list at all */
  sprintf(sql_command, "SELECT note_id, note_user_id FROM t_note WHERE note_key=%s", quoted_listname);

  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    free(quoted_listname);
    return 234;
  }

  if (dbi_result_next_row(dbires)) {
    /* note exists, check user id */
    n_list_owner_id = my_dbi_result_get_idval(dbires, "note_user_id");
    
    if (n_list_owner_id != n_user_id) { /* we're not owner of this list */
      sprintf(sql_command, "266:%s:%s\n", quoted_listname, username);
      free(quoted_listname);
      if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	ptr_addresult->msg = new_msg;
      }

      return 266;
    }

    /* we own this list. retrieve ID */
    n_list_id = my_dbi_result_get_idval(dbires, "note_id");
  }
  else {
    /* list does not exist */
    sprintf(sql_command, "267:%s:%s\n", quoted_listname, username);
    free(quoted_listname);
    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
    
    return 267;
  }

  dbi_result_free(dbires);

  /* find note crosslink entry for this reference */
  sprintf(sql_command, "SELECT t_xnote.xnote_id FROM t_xnote INNER JOIN t_note ON t_note.note_id=t_xnote.note_id WHERE t_note.note_key=%s AND t_xnote.xref_id="ULLSPEC" AND t_note.note_user_id="ULLSPEC, quoted_listname, (unsigned long long)n_ref_id, (unsigned long long)n_user_id);
  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    return 234;
  }

  if (!dbi_result_next_row(dbires)) {
    ptr_addresult->skipped++;
    dbi_result_free(dbires);
    return 0;
  }

  n_xnote_id = my_dbi_result_get_idval(dbires, "xnote_id");

  if (!n_xnote_id) {
    ptr_addresult->failure++;
    dbi_result_free(dbires);
    return 234;
  }

  /* remove link to reference from the list */
  sprintf(sql_command, "DELETE FROM t_xnote WHERE xnote_id="ULLSPEC, (unsigned long long)n_xnote_id);
  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    return 234;
  }

  if (!dbi_result_get_numrows_affected(dbires)) {
    retval = 417;
  }
  else {
    /* successfully deleted */
    retval = 419;
  }
  dbi_result_free(dbires);

  /* todo: now if the user has personal data but has no longer picked the ref, find orphans in t_xuser and t_xlink and t_link */

  /* see if ref is attached to any other list of the same user */
  sprintf(sql_command, "SELECT t_note.note_key FROM t_note INNER JOIN t_xnote ON t_note.note_id=t_xnote.note_id WHERE t_xnote.xref_id="ULLSPEC" AND t_note.note_user_id="ULLSPEC, (unsigned long long)n_ref_id, (unsigned long long)n_user_id);
  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    return 234;
  }

  if (!dbi_result_next_row(dbires)) {
    /* ref is not picked in another list. Remove personal data. Start
       with ulinks as that code relies on xuser entries */
    retcode = remove_ulink_entries(n_ref_id, username, conn, 0 /* reference */);

    retcode = remove_xuser_entries(n_ref_id, (char*)username, conn);
  }

  dbi_result_free(dbires);

  /* todo: check retcode */

  if (retval == 417) {
    sprintf(sql_command, "417:"ULLSPEC":%s\n", (unsigned long long)n_ref_id, username);
    ptr_addresult->skipped++;
  }
  else {
    /* successfully deleted */
    sprintf(sql_command, "419:%s->"ULLSPEC":%s\n", quoted_listname, (unsigned long long)n_ref_id, username);
    ptr_addresult->success++;
  }

  free(quoted_listname);

  if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
    return 801;
  }
  else {
    ptr_addresult->msg = new_msg;
  }

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  updatejo(): implements the client command updatejo

  int updatejo returns 1 if failed, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct ADDRESULT* addresult this structure will be filled in with the number
                      of (un-)successfully added/updated references

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int updatejo(struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn;
  dbi_result dbires;
  dbi_driver driver;
  char **inargv; /* tokens of the client request */
  int inargc; /* number of tokens of the client request */
  int inargcmax; /* maximum number of tokens of the client request */
  size_t len;
  int retval = 0;
  int havefirst = 0;
  int error = 0; /* codes: 0 = no error, 1 = out of memory, 2 = subselect failed */
  int syn_count = 0; /* counter for synonyms changed */
  size_t buffer_len; /* these are the allocated lengths of the buffers */
  size_t sql_command_len;
  size_t return_msg_len;
  char *return_msg;
  char *sql_command; /* these are ptrs to the buffers and temporary ptrs */
  char *buffer;
  char *new_buffer;
  char *token;
  char *newtoken;
  char *stripped_token;
  char *quoted_token;
  char *eostring;
  char journal_val[256] = ""; /* name of journal*/
  char journal_field[23] = ""; /* t_periodical field name of synonym */
  char field_buffer[10];
  char jo[256] = "";
  char jf[256] = "";
  char j1[256] = "";
  char j2[256] = "";
  struct SQLTOKEN sqltoken;
  struct lilimem sentinel;
  Liliform *ptr_curr;
  Liliform syn_sentinel;

  ptr_addresult->success = 0;
  ptr_addresult->failure = 0;
  ptr_addresult->updated = 0;
  ptr_addresult->skipped = 0;

  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';

  syn_sentinel.ptr_next = NULL;
  syn_sentinel.name[0] = '\0';
  syn_sentinel.value = NULL;

  /* get buffer to analyze the request */
  inargc = 0;
  inargcmax = 10;
  inargv = malloc((size_t)inargcmax*sizeof(char*));
  if (inargv == NULL || insert_lilimem(&sentinel, (void**)&inargv, NULL)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* get buffer to assemble the SQL queries */
  sql_command_len = 4096; 
  sql_command = malloc(sql_command_len); 

  if (sql_command == NULL || insert_lilimem(&sentinel, (void**)&sql_command, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  *sql_command = '\0'; /* start with an empty string */

  /* get a buffer to hold tokens */
  buffer_len = 4096;
  buffer = malloc(buffer_len); 

  if (buffer == NULL || insert_lilimem(&sentinel, (void**)&buffer, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  *buffer = '\0'; /* start with an empty string */

  /* yet another buffer */
  return_msg_len = 4096;
  return_msg = malloc(return_msg_len); 

  if (return_msg == NULL || insert_lilimem(&sentinel, (void**)&return_msg, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }
  *return_msg = '\0'; /* start with an empty string */

  eostring = ptr_clrequest->argument + strlen(ptr_clrequest->argument);

  /* connect to the database */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  driver = dbi_conn_get_driver(conn);

  token = ptr_clrequest->argument;
  newtoken = token;

/*    printf("%s\n", token); */

  /* general strategy: scan the command string for items and add all of
     them except the first to a linked list. Then walk through
     the linked list to assemble the update query */


  /* loop as long as we find more tokens */
  while (newtoken != NULL) {
    token = link_tokenize(newtoken, &sqltoken);
    newtoken = sqltoken.next_token;
/*      printf("token:%s<<newtoken:%s<<\n", token, newtoken); */

    if (token != NULL) {
      /* extract the token and save to a temporary string */
      if (sqltoken.length > buffer_len) {
	new_buffer = (char*)realloc(buffer, (size_t)sqltoken.length);
	if (new_buffer == NULL) { /* out of memory */
	  error = 1;
	  break;
	}
	else {
	  buffer = new_buffer;
	  buffer_len = sqltoken.length;
	}
      }
      strncpy(buffer, token, sqltoken.length);
      buffer[sqltoken.length] ='\0';
      if (sqltoken.type == 4) { 
	/*----------------------------------------------------------------*/
	/* target fields */
	if (strncmp(token, ":JF:", 4) == 0 || /* journal full */
	    strncmp(token, ":JO:", 4) == 0 || /* journal abbrev */
	    strncmp(token, ":JA:", 4) == 0 || /* journal abbrev */
	    strncmp(token, ":J1:", 4) == 0 || /* journal custabbrev1 */
	    strncmp(token, ":J2:", 4) == 0) { /* journal custabbrev2 */

	  strcpy(field_buffer, buffer);
	  field_buffer[4] = '\0'; /* terminate string */

	  /* obtain the token after the '=' */
	  token = link_tokenize(newtoken, &sqltoken);
	  newtoken = sqltoken.next_token;
	  if (token != NULL) {
	    strncpy(buffer, token, sqltoken.length);
	    buffer[sqltoken.length] ='\0'; /* terminate string */
	    len = 0;

	    token = nstrtok(buffer, &len, " ");
	    if (token != NULL) {
/* 	      printf("token went to: %s<<\n", token); */
	      memmove(buffer, token, sqltoken.length);
	      buffer[sqltoken.length] ='\0'; /* terminate string */
	      len = 0;
	      token = nstrtok(buffer, &len, " ");
	      if (token != NULL) {
		quoted_token = malloc(strlen(token)+1);
		if (!quoted_token) {
		  error = 1;
		  break;
		}

		stripped_token = strip_quote(token);

		unescape_chars(quoted_token, stripped_token, strlen(stripped_token));


/* 		printf("quoted_token went to: %s<<\n", quoted_token); */

		if (!havefirst) {
		  if (!strcmp(field_buffer, ":JF:")) {
		    strcpy(journal_field, "periodical_name");
		  }
		  else if (!strcmp(field_buffer, ":JO:")
			   || !strcmp(field_buffer, ":JA:")) {
		    strcpy(journal_field, "periodical_abbrev");
		  }
		  else if (!strcmp(field_buffer, ":J1:")) {
		    strcpy(journal_field, "periodical_custabbrev1");
		  }
		  else {
		    strcpy(journal_field, "periodical_custabbrev2");
		  }

		  if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
		    error = 1;
		    break;
		  }

		  strncpy(journal_val, quoted_token, 255);
		  journal_val[255] = '\0';
/* 		  	      printf("key:%s<<value:%s<<\n", journal_field, journal_val); */
		  if (is_journal(conn, journal_field, journal_val) != 1) {
		    error = 2;
		    break;
		  }
		  havefirst++;
		}
		else {
		  if (insert_liliform(&syn_sentinel, field_buffer, quoted_token)) {
		    error = 1;
		    break;
		  }
		}
		free(quoted_token);
	      }
	    }
	  }
	}
	else {
	  /* ignore unknown specifier */
	  ptr_addresult->skipped++;
	}
      } /* if (sqltoken.type != 4) / else */
    } /* if (token != NULL) */
  } /* while (newtoken != NULL) */

  if (error || !*journal_field || !*journal_val) {
    if (error == 1) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
    }
    else {
      send_status(ptr_clrequest->fd, 417, TERM_NO);
      LOG_PRINT(LOG_INFO, get_status_msg(417));
    }
    retval = 1;
    goto Finish;
  }

  /* loop over all synonyms. If the same synonym is specified more
     than once, the last value prevails */
  ptr_curr = &syn_sentinel;

  while ((ptr_curr = get_next_liliform(ptr_curr)) != NULL) {
    /* strcpy is sufficient as ptr_curr->value is already checked for length */
    quoted_token = mstrdup(ptr_curr->value);
    if (!quoted_token) {
      error = 1;
      break;
    }
    if (dbi_conn_quote_string(conn, &quoted_token) == 0) {
      error = 1;
      break;
    }

    if (!strcmp(ptr_curr->name, ":JO:")) {
      strcpy(jo, quoted_token);
    }
    else if (!strcmp(ptr_curr->name, ":JF:")) {
      strcpy(jf, quoted_token);
    }
    else if (!strcmp(ptr_curr->name, ":J1:")) {
      strcpy(j1, quoted_token);
    }
    else if (!strcmp(ptr_curr->name, ":J2:")) {
      strcpy(j2, quoted_token);
    }
    free(quoted_token);
  }

  if (error) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    retval = 1;
    goto Finish;
  }

  /* assemble query string */
  strcpy(sql_command, "UPDATE t_periodical SET ");

  if (*jf) {
    sprintf(sql_command + strlen(sql_command), "periodical_name=%s, ", jf);
    syn_count++;
    sprintf(return_msg, "425:%s:%s\n", journal_val, jf);
  }

  if (*jo) {
    sprintf(sql_command + strlen(sql_command), "periodical_abbrev=%s, ", jo);
    syn_count++;
    sprintf(return_msg+strlen(return_msg), "425:%s:%s\n", journal_val, jo);
  }

  if (*j1) {
    sprintf(sql_command + strlen(sql_command), "periodical_custabbrev1=%s, ", j1);
    syn_count++;
    sprintf(return_msg+strlen(return_msg), "425:%s:%s\n", journal_val, j1);
  }

  if (*j2) {
    sprintf(sql_command + strlen(sql_command), "periodical_custabbrev2=%s, ", j2);
    syn_count++;
    sprintf(return_msg+strlen(return_msg), "425:%s:%s\n", journal_val, j2);
  }

  /* remove trailing comma */
  sprintf(sql_command  + strlen(sql_command) - 2, " WHERE %s=%s", journal_field, journal_val);

  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);

  if (!dbires) {
    error = 1;
    goto Finish;
  }
    
  ptr_addresult->success += syn_count;

 Finish:
  if (ptr_addresult->success) {
    /* database was changed, update meta info */
    update_meta(conn, ptr_clrequest);
  }

  /* send back result message */
  if (!error) {
    send_status(ptr_clrequest->fd, 0, TERM_NO);
  }
  else {
    send_status(ptr_clrequest->fd, 260, TERM_NO);
  }

  tiwrite(ptr_clrequest->fd, return_msg, TERM_YES); 

  dbi_conn_close(conn);
  delete_all_lilimem(&sentinel);
  delete_all_liliform(&syn_sentinel);
  
  return retval;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  is_journal(): checks whether journal exists

  int is_journal returns 0 = not found, 1 = journal exists, -1: error

  dbi_conn conn connection to database

  const char* field field to check

  const char* quoted_name periodical name (properly escaped and quoted)

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int is_journal(dbi_conn conn, const char* field, const char* quoted_name) {
  unsigned long long result;
  char sql_command[512];
  dbi_result dbires;

  snprintf(sql_command, 512, "SELECT periodical_id FROM t_periodical WHERE %s=%s", field, quoted_name);

  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);

  if (!dbires) {
    LOG_PRINT(LOG_ERR, get_status_msg(234));
    return -1;
  }

  result = dbi_result_get_numrows(dbires);

  if (!result) {
    return 0;
  }
  else if (result == DBI_ROW_ERROR) {
    LOG_PRINT(LOG_ERR, get_status_msg(234));
    return -1;
  }
  else {
    return 1;
  }
}

