/* $Id: qfollow.pc,v 2.32 2009-11-05 22:12:02 zacheiss Exp $
 *
 * Query followup routines
 *
 * Copyright (C) 1987-1998 by the Massachusetts Institute of Technology
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 *
 */

#include <mit-copyright.h>
#include "mr_server.h"
#include "query.h"
#include "qrtn.h"

#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

EXEC SQL INCLUDE sqlca;

RCSID("$Header: /afs/athena.mit.edu/astaff/project/moiradev/repository/moira/server/qfollow.pc,v 2.32 2009-11-05 22:12:02 zacheiss Exp $");

extern char *whoami, *table_name[];
extern int dbms_errno, mr_errcode;

EXEC SQL BEGIN DECLARE SECTION;
extern char stmt_buf[];
EXEC SQL END DECLARE SECTION;

EXEC SQL WHENEVER SQLERROR DO dbmserr();


/* FOLLOWUP ROUTINES */

/* generic set_modtime routine.  This takes the table id from the query,
 * and will update the modtime, modby, and modwho fields in the entry in
 * the table whose name field matches argv[0].
 */

int set_modtime(struct query *q, char *argv[], client *cl)
{
  char *name, *entity, *table;
  int who, row = 0;

  entity = cl->entity;
  who = cl->client_id;
  table = table_name[q->rtable];

  if (q->type == MR_Q_UPDATE)
    row = 1;

  name = argv[0 + row];

  sprintf(stmt_buf, "UPDATE %s SET modtime = SYSDATE, modby = %d, "
	  "modwith = '%s' WHERE name = '%s'", table, who, entity, name);
  EXEC SQL EXECUTE IMMEDIATE :stmt_buf;

  return MR_SUCCESS;
}

/* generic set_modtime_by_id routine.  This takes the table id from
 * the query, and the id name from the validate record,
 * and will update the modtime, modby, and modwho fields in the entry in
 * the table whose id matches argv[0].
 */

int set_modtime_by_id(struct query *q, char *argv[], client *cl)
{
  char *entity, *table, *id_name;
  int who, id;

  entity = cl->entity;
  who = cl->client_id;
  table = table_name[q->rtable];
  id_name = q->validate->object_id;

  id = *(int *)argv[0];
  sprintf(stmt_buf, "UPDATE %s SET modtime = SYSDATE, modby = %d, "
	  "modwith = '%s' WHERE %s = %d", table, who, entity, id_name, id);
  EXEC SQL EXECUTE IMMEDIATE :stmt_buf;
  return MR_SUCCESS;
}


/* Sets the finger modtime on a user record.  The users_id will be in argv[0].
 */

int set_finger_modtime(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int users_id, who;
  char *entity;
  EXEC SQL END DECLARE SECTION;

  entity = cl->entity;
  who = cl->client_id;
  users_id = *(int *)argv[0];

  EXEC SQL UPDATE users SET fmodtime = SYSDATE, fmodby = :who,
    fmodwith = :entity WHERE users_id = :users_id;

  return MR_SUCCESS;
}


/* Sets the pobox modtime on a user record.  The users_id will be in argv[0].
 */

int set_pobox_modtime(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int users_id, who;
  char *entity;
  EXEC SQL END DECLARE SECTION;

  entity = cl->entity;
  who = cl->client_id;
  users_id = *(int *)argv[0];

  EXEC SQL UPDATE users SET pmodtime = SYSDATE, pmodby = :who,
    pmodwith = :entity WHERE users_id = :users_id;

  return MR_SUCCESS;
}


/* Like set_modtime, but uppercases the name first.
 */

int set_uppercase_modtime(struct query *q, char *argv[], client *cl)
{
  char *name, *entity, *table;
  int who;

  entity = cl->entity;
  who = cl->client_id;
  table = table_name[q->rtable];
  name = argv[0];

  sprintf(stmt_buf, "UPDATE %s SET modtime = SYSDATE, modby = %d, "
	  "modwith = '%s' WHERE name = UPPER('%s')", table, who, entity, name);
  EXEC SQL EXECUTE IMMEDIATE :stmt_buf;

  return MR_SUCCESS;
}


/* Sets the modtime on the machine whose mach_id is in argv[0].  This routine
 * is necessary for add_machine_to_cluster becuase the table that query
 * operates on is "mcm", not "machine".
 */

int set_mach_modtime_by_id(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char *entity;
  int who, id;
  EXEC SQL END DECLARE SECTION;

  entity = cl->entity;
  who = cl->client_id;
  id = *(int *)argv[0];
  EXEC SQL UPDATE machine SET modtime = SYSDATE, modby = :who,
    modwith = :entity WHERE mach_id = :id;

  return MR_SUCCESS;
}


/* Sets the modtime on the cluster whose mach_id is in argv[0].  This routine
 * is necessary for add_cluster_data and delete_cluster_data becuase the
 * table that query operates on is "svc", not "cluster".
 */

int set_cluster_modtime_by_id(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char *entity;
  int who, id;
  EXEC SQL END DECLARE SECTION;

  entity = cl->entity;
  who = cl->client_id;

  id = *(int *)argv[0];
  EXEC SQL UPDATE clusters SET modtime = SYSDATE, modby = :who,
    modwith = :entity WHERE clu_id = :id;
  return MR_SUCCESS;
}


/* sets the modtime on the serverhost where the service name is in argv[0]
 * and the mach_id is in argv[1].
 */

int set_serverhost_modtime(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char *entity, *serv;
  int who, id;
  EXEC SQL END DECLARE SECTION;

  entity = cl->entity;
  who = cl->client_id;

  serv = argv[0];
  id = *(int *)argv[1];
  EXEC SQL UPDATE serverhosts
    SET modtime = SYSDATE, modby = :who, modwith = :entity
    WHERE service = :serv AND mach_id = :id;
  return MR_SUCCESS;
}


/* sets the modtime on the nfsphys where the mach_id is in argv[0] and the
 * directory name is in argv[1].
 */

int set_nfsphys_modtime(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char *entity, *dir;
  int who, id;
  EXEC SQL END DECLARE SECTION;

  entity = cl->entity;
  who = cl->client_id;

  id = *(int *)argv[0];
  dir = argv[1];
  EXEC SQL UPDATE nfsphys SET modtime = SYSDATE, modby = :who,
    modwith = :entity WHERE dir = :dir AND mach_id = :id;
  return MR_SUCCESS;
}


/* sets the modtime on a filesystem, where argv[0] contains the filesys
 * label.
 */

int set_filesys_modtime(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char *label, *entity;
  int who;
  extern int _var_phys_id;
  EXEC SQL END DECLARE SECTION;

  entity = cl->entity;
  who = cl->client_id;

  label = argv[0];
  if (!strcmp(q->shortname, "ufil"))
    label = argv[1];

  EXEC SQL UPDATE filesys SET modtime = SYSDATE, modby = :who,
    modwith = :entity, phys_id = :_var_phys_id
    WHERE label = :label;
  return MR_SUCCESS;
}


/* sets the modtime on a zephyr class, where argv[0] contains the class
 * name.
 */

int set_zephyr_modtime(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char *class, *entity;
  int who;
  EXEC SQL END DECLARE SECTION;

  entity = cl->entity;
  who = cl->client_id;

  class = argv[0];

  EXEC SQL UPDATE zephyr SET modtime = SYSDATE, modby = :who,
    modwith = :entity WHERE class = :class;

  return MR_SUCCESS;
}

/* sets the modtime on an entry in services table, where argv[0] contains
 * the service name.
 */

int set_service_modtime(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char *service, *protocol, *entity;
  int who;
  EXEC SQL END DECLARE SECTION;

  entity = cl->entity;
  who = cl->client_id; 

  service = argv[0];
  protocol = argv[1];

  EXEC SQL UPDATE services SET modtime = SYSDATE, modby = :who,
    modwith = :entity WHERE name = :service AND protocol = :protocol;

  return MR_SUCCESS;
}

/* fixes the modby field.  This will be the second to last thing in the
 * argv, the argv length is determined from the query structure.  It is
 * passed as a pointer to an integer.  This will either turn it into a
 * username, or # + the users_id.
 */
int followup_fix_modby(struct query *q, struct save_queue *sq,
		       struct validate *v, int (*action)(int, char *[], void *),
		       void *actarg, client *cl)
{
  int i, j;
  char **argv;
  int id, status;

  i = q->vcnt - 2;
  while (sq_get_data(sq, &argv))
    {
      id = atoi(argv[i]);
      if (id > 0)
	status = id_to_name(id, USERS_TABLE, &argv[i]);
      else
	status = id_to_name(-id, STRINGS_TABLE, &argv[i]);
      if (status && status != MR_NO_MATCH)
	return status;
      (*action)(q->vcnt, argv, actarg);
      for (j = 0; j < q->vcnt; j++)
	free(argv[j]);
      free(argv);
    }
  sq_destroy(sq);
  return MR_SUCCESS;
}

/**
 ** followup_ausr - add finger and pobox entries, set_user_modtime
 **
 ** Inputs:
 **	argv[0] - login (add_user)
 **	argv[U_LAST] - last name
 **	argv[U_FIRST] - first name
 **	argv[U_MIDDLE] - middle name
 **
 **/

int followup_ausr(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int who, status;
  char *login, *entity, *name;
  char fullname[USERS_FIRST_SIZE + USERS_MIDDLE_SIZE + USERS_LAST_SIZE];
  EXEC SQL END DECLARE SECTION;

  /* build fullname */
  if (strlen(argv[U_FIRST]) && strlen(argv[U_MIDDLE]))
    sprintf(fullname, "%s %s %s", argv[U_FIRST], argv[U_MIDDLE], 
	    argv[U_LAST]);
  else if (strlen(argv[U_FIRST]))
    sprintf(fullname, "%s %s", argv[U_FIRST], argv[U_LAST]);
  else
    sprintf(fullname, "%s", argv[U_LAST]);

  login = argv[0];
  who = cl->client_id;
  entity = cl->entity;

  /* create finger entry, pobox & set modtime on user */
  EXEC SQL UPDATE users
    SET modtime = SYSDATE, modby = :who, modwith = :entity,
    fullname = NVL(:fullname, CHR(0)), affiliation = type,
    fmodtime = SYSDATE, fmodby = :who, fmodwith = :entity,
    potype = 'NONE', pmodtime = SYSDATE, pmodby = :who, pmodwith = :entity
    WHERE login = :login;

  return MR_SUCCESS;
}

/* followup_gpob: fixes argv[2] and argv[3] based on the pobox type.
 * Then completes the upcall to the user.
 *
 * argv[2] is the users_id on input and should be converted to the
 * pobox name on output. argv[3] is empty on input and should be
 * converted to an email address on output.
 */

int followup_gpob(struct query *q, struct save_queue *sq, struct validate *v,
		  int (*action)(int, char *[], void *), void *actarg,
		  client *cl)
{
  char **argv;
  char *ptype, *p;
  int mid, sid, status, i;
  EXEC SQL BEGIN DECLARE SECTION;
  int users_id, pid, iid, bid, eid;
  char mach[MACHINE_NAME_SIZE], fs[FILESYS_LABEL_SIZE];
  char str[STRINGS_STRING_SIZE];
  EXEC SQL END DECLARE SECTION;

  /* for each row */
  while (sq_get_data(sq, &argv))
    {
      mr_trim_args(4, argv);
      ptype = argv[1];
      users_id = atoi(argv[2]);

      EXEC SQL SELECT pop_id, imap_id, box_id, exchange_id INTO :pid, :iid, :bid, :eid
	FROM users WHERE users_id = :users_id;
      if (sqlca.sqlcode)
	return MR_USER;

      if (ptype[0] == 'S')
	{
	  /* SMTP or SPLIT */
	  EXEC SQL SELECT string INTO :str FROM strings
	    WHERE string_id = :bid;
	  if (sqlca.sqlcode)
	    return MR_STRING;

	  /* If SMTP, don't bother fetching IMAP and POP boxes. */
	  if (ptype[1] == 'M')
	    pid = iid = eid = 0;
	}
      if (iid)
	{
	  /* IMAP, or SPLIT with IMAP */
	  EXEC SQL SELECT f.label, m.name INTO :fs, :mach
	    FROM filesys f, machine m
	    WHERE f.filsys_id = :iid AND f.mach_id = m.mach_id;
	  if (sqlca.sqlcode)
	    return MR_FILESYS;
	}
      if (pid)
	{
	  /* POP, or SPLIT with POP */
	  EXEC SQL SELECT m.name INTO :mach FROM machine m, users u
	    WHERE u.users_id = :users_id AND u.pop_id = m.mach_id;
	  if (sqlca.sqlcode)
	    return MR_MACHINE;
	}
      if (eid)
	{
	  /* EXCHANGE, or SPLIT with EXCHANGE */
	  EXEC SQL SELECT m.name INTO :mach FROM machine m, users u
	    WHERE u.users_id = :users_id AND u.exchange_id = m.mach_id;
	  if (sqlca.sqlcode)
	    return MR_MACHINE;
	}

      free(argv[2]);
      free(argv[3]);

      /* Now assemble the right answer. */
      if (!strcmp(ptype, "POP") || !strcmp(ptype, "EXCHANGE"))
	{
	  argv[2] = xstrdup(strtrim(mach));
	  argv[3] = xmalloc(strlen(argv[0]) + strlen(argv[2]) + 2);
	  sprintf(argv[3], "%s@%s", argv[0], argv[2]);
	}
      else if (!strcmp(ptype, "SMTP"))
	{
	  argv[2] = xstrdup(strtrim(str));
	  argv[3] = xstrdup(str);
	}
      else if (!strcmp(ptype, "IMAP"))
	{
	  argv[2] = xstrdup(strtrim(fs));
	  argv[3] = xmalloc(strlen(argv[0]) + strlen(strtrim(mach)) + 2);
	  sprintf(argv[3], "%s@%s", argv[0], mach);
	}
      else if (!strcmp(ptype, "SPLIT"))
	{
	  argv[2] = xstrdup(strtrim(str));
	  argv[3] = xmalloc(strlen(argv[0]) + strlen(strtrim(mach)) +
			    strlen(str) + 4);
	  sprintf(argv[3], "%s@%s, %s", argv[0], mach, str);
	}
      else /* ptype == "NONE" */
	goto skip;

      if (!strcmp(q->shortname, "gpob"))
	{
	  sid = atoi(argv[5]);
	  if (sid > 0)
	    status = id_to_name(sid, USERS_TABLE, &argv[5]);
	  else
	    status = id_to_name(-sid, STRINGS_TABLE, &argv[5]);
	  if (status && status != MR_NO_MATCH)
	    return status;
	}

      (*action)(q->vcnt, argv, actarg);
    skip:
      /* free saved data */
      for (i = 0; i < q->vcnt; i++)
	free(argv[i]);
      free(argv);
    }

  sq_destroy(sq);
  return MR_SUCCESS;
}

/* Fix an ace_name, based on its type. */

static int fix_ace(char *type, char **name)
{
  int id = atoi(*name);

  if (!strcmp(type, "LIST"))
    return id_to_name(id, LIST_TABLE, name);
  else if (!strcmp(type, "USER"))
    return id_to_name(id, USERS_TABLE, name);
  else if (!strcmp(type, "KERBEROS"))
    return id_to_name(id, STRINGS_TABLE, name);
  else
    {
      free(*name);
      if (!strcmp(type, "NONE"))
	*name = xstrdup("NONE");
      else
	*name = xstrdup("???");
      return MR_SUCCESS;
    }
}


/* followup_gsnt: fix the ace_name and modby */

int followup_gsnt(struct query *q, struct save_queue *sq, struct validate *v,
		  int (*action)(int, char *[], void *), void *actarg,
		  client *cl)
{
  char **argv;
  int status, idx;

  if (q->version < 8)
    idx = 0;
  else
    idx = 3;

  while (sq_get_data(sq, &argv))
    {
      mr_trim_args(q->vcnt, argv);

      status = fix_ace(argv[7 + idx], &argv[8 + idx]);
      if (status && status != MR_NO_MATCH)
	return status;
    }

  return followup_fix_modby(q, sq, v, action, actarg, cl);
}


/* followup_ghst: fix the ace_name, strings and modby */

int followup_ghst(struct query *q, struct save_queue *sq, struct validate *v,
		  int (*action)(int, char *[], void *), void *actarg,
		  client *cl)
{
  char **argv;
  int id, status, idx;

  if (q->version < 6)
    idx = 0;
  else if (q->version >= 6 && q->version < 8)
    idx = 1;
  else
    idx = 2;

  while (sq_get_data(sq, &argv))
    {
      mr_trim_args(q->vcnt, argv);

      id = atoi(argv[13 + idx]);
      status = id_to_name(id, STRINGS_TABLE, &argv[13 + idx]);
      if (status)
	return status;
      id = atoi(argv[14 + idx]);
      status = id_to_name(id, STRINGS_TABLE, &argv[14 + idx]);
      if (status)
	return status;
      id = atoi(argv[16 + idx]);
      if (id < 0)
	status = id_to_name(-id, STRINGS_TABLE, &argv[16 + idx]);
      else
	status = id_to_name(id, USERS_TABLE, &argv[16 + idx]);
      if (status && status != MR_NO_MATCH)
	return status;

      status = fix_ace(argv[11 + idx], &argv[12 + idx]);
      if (status && status != MR_NO_MATCH)
	return status;
    }

  return followup_fix_modby(q, sq, v, action, actarg, cl);
}


/* followup_glin: fix the ace_name, modace_name, expiration, and modby */

int followup_glin(struct query *q, struct save_queue *sq, struct validate *v,
		  int (*action)(int, char *[], void *), void *actarg,
		  client *cl)
{
  char **argv;
  int status;

  while (sq_get_data(sq, &argv))
    {
      mr_trim_args(q->vcnt, argv);

      if (q->version == 2)
	status = fix_ace(argv[7], &argv[8]);
      else if (q->version > 2 && q->version < 10)
	status = fix_ace(argv[8], &argv[9]);
      else
	status = fix_ace(argv[10], &argv[11]);

      if (status && status != MR_NO_MATCH)
	return status;

      if (q->version > 3)
	{
	  if (q->version < 10)
	    status = fix_ace(argv[10], &argv[11]);
	  else if (q->version >= 10)
	    status = fix_ace(argv[12], &argv[13]); 
      
	  if (status && status != MR_NO_MATCH)
	    return status;
	}

      if (atoi(argv[6]) == -1)
	{
	  argv[6] = xrealloc(argv[6], strlen(UNIQUE_GID) + 1);
	  strcpy(argv[6], UNIQUE_GID);
	}
    }

  return followup_fix_modby(q, sq, v, action, actarg, cl);
}

/* followup_gsin: fix the ace_name and modby. */
int followup_gsin(struct query *q, struct save_queue *sq, struct validate *v,
		  int (*action)(int, char *[], void *), void *actarg,
		  client *cl)
{
  char **argv;
  int status;

  while (sq_get_data(sq, &argv))
    {
      mr_trim_args(q->vcnt, argv);

      status = fix_ace(argv[11], &argv[12]);
      if (status && status != MR_NO_MATCH)
	return status;
    }

  return followup_fix_modby(q, sq, v, action, actarg, cl);
}

int followup_gpsv(struct query *q, struct save_queue *sq, struct validate *v,
		  int (*action)(int, char *[], void *), void *actarg,
		  client *cl)
{
  char **argv;
  int status;

  while (sq_get_data(sq, &argv))
    {
      mr_trim_args(q->vcnt, argv);

      status = fix_ace(argv[PRINTSERVER_OWNER_TYPE],
		       &argv[PRINTSERVER_OWNER_NAME]);
      if (status && status != MR_NO_MATCH)
	return status;
    }

  return followup_fix_modby(q, sq, v, action, actarg, cl);
}
  

/* followup_gqot: Fix the entity name, directory name & modby fields
 *   argv[0] = filsys_id
 *   argv[1] = type
 *   argv[2] = entity_id
 *   argv[3] = ascii(quota)
 */

int followup_gqot(struct query *q, struct save_queue *sq, struct validate *v,
		  int (*action)(int, char *[], void *), void *actarg,
		  client *cl)
{
  int j;
  char **argv;
  EXEC SQL BEGIN DECLARE SECTION;
  int id;
  char *name, *label;
  EXEC SQL END DECLARE SECTION;
  int status, idx;

  if (!strcmp(q->name, "get_quota") ||
      !strcmp(q->name, "get_quota_by_filesys"))
    idx = 4;
  else
    idx = 3;

  while (sq_get_data(sq, &argv))
    {
      if (idx == 4)
	{
	  switch (argv[1][0])
	    {
	    case 'U':
	      status = id_to_name(atoi(argv[2]), USERS_TABLE, &argv[2]);
	      break;
	    case 'G':
	    case 'L':
	      status = id_to_name(atoi(argv[2]), LIST_TABLE, &argv[2]);
	      break;
	    case 'A':
	      free(argv[2]);
	      argv[2] = xstrdup("system:anyuser");
	      break;
	    default:
	      id = atoi(argv[2]);
	      argv[2] = xmalloc(8);
	      sprintf(argv[2], "%d", id);
	    }
	}
      id = atoi(argv[idx]);
      free(argv[idx]);
      argv[idx] = xmalloc(id ? NFSPHYS_DIR_SIZE : FILESYS_NAME_SIZE);
      name = argv[idx];
      name[0] = '\0';
      if (id == 0)
	{
	  label = argv[0];
	  EXEC SQL SELECT name INTO :name FROM filesys
	    WHERE label = :label;
	}
      else
	{
	  EXEC SQL SELECT dir INTO :name FROM nfsphys
	    WHERE nfsphys_id = :id;
	}
      if (sqlca.sqlerrd[2] != 1)
	sprintf(argv[idx], "#%d", id);

      id = atoi(argv[idx + 3]);
      if (id > 0)
	status = id_to_name(id, USERS_TABLE, &argv[idx + 3]);
      else
	status = id_to_name(-id, STRINGS_TABLE, &argv[idx + 3]);
      if (status && status != MR_NO_MATCH)
	return status;
      (*action)(q->vcnt, argv, actarg);
      for (j = 0; j < q->vcnt; j++)
	free(argv[j]);
      free(argv);
    }
  sq_destroy(sq);
  return MR_SUCCESS;
}


/* followup_aqot: Add allocation to nfsphys after creating quota.
 *   argv[0] = filsys_id
 *   argv[1] = type if "add_quota" or "update_quota"
 *   argv[2 or 1] = id
 *   argv[3 or 2] = ascii(quota)
 */

int followup_aqot(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int quota, id, fs, who, physid, table;
  char *entity, *qtype, *tname;
  EXEC SQL END DECLARE SECTION;
  char incr_qual[60];
  char *incr_argv[2];
  int status;

  table = q->rtable;
  tname = table_name[table];
  fs = *(int *)argv[0];
  EXEC SQL SELECT phys_id INTO :physid FROM filesys
    WHERE filsys_id = :fs;
  if (dbms_errno)
    return mr_errcode;

  if (!strcmp(q->shortname, "aqot") || !strcmp(q->shortname, "uqot"))
    {
      qtype = argv[1];
      id = *(int *)argv[2];
      quota = atoi(argv[3]);
      sprintf(incr_qual, "q.filsys_id = %d", fs);
    }
  else
    {
      qtype = "USER";
      id = *(int *)argv[1];
      quota = atoi(argv[2]);
      sprintf(incr_qual, "q.filsys_id = %d AND q.type = '%s' AND "
	      "q.entity_id = %d", fs, qtype, id);
    }

  /* quota case of incremental_{before|after} only looks at slot 1 */
  incr_argv[1] = qtype;

  /* Follows one of many possible gross hacks to fix these particular
   * conflicts between what is possible in the query table and what
   * is possible in SQL.
   */
  if (q->type == MR_Q_APPEND)
    {
      incremental_clear_before();
      EXEC SQL INSERT INTO quota
	(filsys_id, type, entity_id, quota, phys_id)
	VALUES (:fs, NVL(:qtype, CHR(0)), :id, :quota, :physid);
      incremental_after(table, incr_qual, incr_argv);
    }
  else
    {
      incremental_before(table, incr_qual, incr_argv);
      EXEC SQL UPDATE quota SET quota = :quota
	WHERE filsys_id = :fs AND type = :qtype AND entity_id = :id;
      status = mr_errcode;
      incremental_after(table, incr_qual, incr_argv);
    }

  if (dbms_errno)
    return mr_errcode;
  if (q->type == MR_Q_APPEND)
    {
      EXEC SQL UPDATE tblstats SET appends = appends + 1, modtime = SYSDATE
	WHERE table_name = :tname;
    }
  else
    {
      EXEC SQL UPDATE tblstats SET updates = updates + 1, modtime = SYSDATE
	WHERE table_name = :tname;
    }

  /* Proceed with original followup */
  who = cl->client_id;
  entity = cl->entity;

  EXEC SQL UPDATE quota
    SET modtime = SYSDATE, modby = :who, modwith = :entity
    WHERE filsys_id = :fs and type = :qtype and entity_id = :id;
  EXEC SQL UPDATE nfsphys SET allocated = allocated + :quota
    WHERE nfsphys_id = :physid;
  if (dbms_errno)
    return mr_errcode;
  return MR_SUCCESS;
}


/* Necessitated by the requirement of a correlation name by the incremental
 * routines, since query table deletes don't provide one.
 */
int followup_dqot(struct query *q, char **argv, client *cl)
{
  char *qtype;
  int id, fs, table;
  char *incr_argv[2];
  EXEC SQL BEGIN DECLARE SECTION;
  char incr_qual[80], *tname;
  EXEC SQL END DECLARE SECTION;

  table = q->rtable;
  tname = table_name[table];
  fs = *(int *)argv[0];
  if (!strcmp(q->shortname, "dqot"))
    {
      qtype = argv[1];
      id = *(int *)argv[2];
    }
  else
    {
      qtype = "USER";
      id = *(int *)argv[1];
    }
  sprintf(incr_qual, "q.filsys_id = %d AND q.type = '%s' AND q.entity_id = %d",
	  fs, qtype, id);

  /* quota case of incremental_{before|after} only looks at slot 1 */
  incr_argv[1] = qtype;

  incremental_before(table, incr_qual, incr_argv);
  EXEC SQL DELETE FROM quota q WHERE q.filsys_id = :fs AND q.type = :qtype
    AND q.entity_id = :id;
  incremental_clear_after();

  if (dbms_errno)
    return mr_errcode;

  EXEC SQL UPDATE tblstats SET deletes = deletes + 1, modtime = SYSDATE
    WHERE table_name = :tname;
  return MR_SUCCESS;
}

/* followup_gzcl:
 */

int followup_gzcl(struct query *q, struct save_queue *sq, struct validate *v,
		  int (*action)(int, char *[], void *), void *actarg,
		  client *cl)
{
  int i, n, status;
  char **argv;

  if (q->version < 5)
    n = 8;
  else
    n = 10;

  while (sq_get_data(sq, &argv))
    {
      mr_trim_args(q->vcnt, argv);

      for (i = 1; i < n; i += 2)
	{
	  status = fix_ace(argv[i], &argv[i + 1]);
	  if (status && status != MR_NO_MATCH)
	    return status;
	}
    }

  return followup_fix_modby(q, sq, v, action, actarg, cl);
}


/* followup_gsha:
 */

int followup_gsha(struct query *q, struct save_queue *sq, struct validate *v,
		  int (*action)(int, char *[], void *), void *actarg,
		  client *cl)
{
  char **argv;
  int status;

  while (sq_get_data(sq, &argv))
    {
      mr_trim_args(q->vcnt, argv);

      status = fix_ace(argv[1], &argv[2]);
      if (status && status != MR_NO_MATCH)
	return status;
    }

  return followup_fix_modby(q, sq, v, action, actarg, cl);
}


int _sdl_followup(struct query *q, char *argv[], client *cl)
{
  if (atoi(argv[0]))
    EXEC SQL ALTER SESSION SET SQL_TRACE TRUE;
  else
    EXEC SQL ALTER SESSION SET SQL_TRACE FALSE;

  return MR_SUCCESS;
}


int trigger_dcm(struct query *q, char *argv[], client *cl)
{
  pid_t pid;
  char prog[MAXPATHLEN];

  sprintf(prog, "%s/startdcm", BIN_DIR);
  pid = vfork();
  switch (pid)
    {
    case 0:
      execl(prog, "startdcm", 0);
      exit(1);

    case -1:
      return errno;

    default:
      return MR_SUCCESS;
    }
}

/* followup_gcon: fix the ace_name, memace_name, and modby */

int followup_gcon(struct query *q, struct save_queue *sq, struct validate *v,
		  int (*action)(int, char *[], void *), void *actarg,
		  client *cl)
{
  char **argv;
  int status, idx = 0;

  if (q->version >= 9)
    idx = 1;

  while (sq_get_data(sq, &argv))
  {
    mr_trim_args(q->vcnt, argv);

    status = fix_ace(argv[4 + idx], &argv[5 + idx]);
    if (status && status != MR_NO_MATCH)
	    return status;
      
	  status = fix_ace(argv[6 + idx], &argv[7 + idx]);
	  if (status && status != MR_NO_MATCH)
	    return status;
	}

  return followup_fix_modby(q, sq, v, action, actarg, cl);
}

/* followup_get_user:  fix the modby and creator.
 * This assumes that the modby and creator fields are always 
 * in the same relative position in the argv.
 */

int followup_get_user(struct query *q, struct save_queue *sq, struct 
		      validate *v, int (*action)(int, char *[], void *),
		      void *actarg, client *cl)
{
  char **argv;
  int i, j, k, status, id;

  i = q->vcnt - 4;
  j = q->vcnt - 1;
  while (sq_get_data(sq, &argv))
    {
      mr_trim_args(q->vcnt, argv);

      id = atoi(argv[i]);
      if (id > 0)
	status = id_to_name(id, USERS_TABLE, &argv[i]);
      else
	status = id_to_name(-id, STRINGS_TABLE, &argv[i]);
      if (status && status != MR_NO_MATCH)
	return status;

      id = atoi(argv[j]);
      if (id > 0)
	status = id_to_name(id, USERS_TABLE, &argv[j]);
      else
	status = id_to_name(-id, STRINGS_TABLE, &argv[j]);
      if (status && status != MR_NO_MATCH)
	return status;

      if (q->version > 11)
	{
	  status = fix_ace(argv[15], &argv[16]);
	  if (status && status != MR_NO_MATCH)
	    return status;
	}

      (*action)(q->vcnt, argv, actarg);
      for (k = 0; k < q->vcnt; k++)
	free(argv[k]);
      free(argv);
    }
  sq_destroy(sq);
  return MR_SUCCESS;
}
