/* rbtjcgi.c -- remote CGI job operations

   Copyright 2009 Free Software Foundation, Inc.

   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 3 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 <ctype.h>
#include <sys/types.h>
#include "gbatch.h"
#ifdef  TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#elif   defined(HAVE_SYS_TIME_H)
#include <sys/time.h>
#else
#include <time.h>
#endif
#include "incl_unix.h"
#include "incl_ugid.h"
#include "incl_net.h"
#include "network.h"
#include "statenums.h"
#include "errnums.h"
#include "cfile.h"
#include "ecodes.h"
#include "helpalt.h"
#include "formats.h"
#include "optflags.h"
#include "cgiuser.h"
#include "xihtmllib.h"
#include "listperms.h"
#include "files.h"
#include "rcgilib.h"

static  char    Filename[] = __FILE__;

#define BTJLIST_INLINE

#define MAXMODE 16

HelpaltRef      progresslist;

char            *localrun,
                *realuname;

unsigned  char  exportflag = BJ_EXPORT|BJ_REMRUNNABLE;

char            sortflag, hadhdrarg;

unsigned        Jaccess_flags = 0;

extern const char * const condname[];
extern const char * const assname[];

char    *formatstring;
char    sdefaultfmt[] = "LN LU LH LI Lp LL Lt Lc LP";

char    bigbuff[JOBSPACE];

int             Njobs, Nvars;
apiBtjob        *job_list;
struct  var_with_slot  *var_sl_list;

/* For when we run out of memory.....  */

void  nomem(const char *fl, const int ln)
{
        fprintf(stderr, "%s:Mem alloc fault: %s line %d\n", progname, fl, ln);
        exit(E_NOMEM);
}

void  fmt_setup()
{
        if  (!formatstring)
                formatstring = sdefaultfmt;
}

static void  setup_main_files()
{
        slotno_t        *slots;
        int             ret;

        api_open(realuname);

        if  (jobqueue)
                gbatch_setqueue(xbapi_fd, jobqueue);

        disp_str = dest_hostname;

        if  ((ret = gbatch_joblist(xbapi_fd, Jaccess_flags, &Njobs, &slots)) < 0)  {
                html_disperror($E{Base for API errors} + ret);
                exit(0);
        }

        if  (Njobs > 0)  {
                int     cnt, actual, ret;

                if  (!(job_list = (apiBtjob *) malloc((unsigned) (sizeof(apiBtjob) * Njobs))))
                        ABORT_HTML_NOMEM;

                for  (cnt = actual = 0;  cnt < Njobs;  cnt++)  {
                        if  ((ret = gbatch_jobread(xbapi_fd, GBATCH_FLAG_IGNORESEQ, slots[cnt], &job_list[actual])) < 0)  {
                                if  (ret == GBATCH_UNKNOWN_JOB)
                                        continue;
                                html_disperror($E{Base for API errors} + ret);
                                exit(E_NETERR);
                        }
                        actual++;
                }
                Njobs = actual;
        }

        if  (sortflag && Njobs > 1)
                qsort(QSORTP1 job_list, Njobs, sizeof(apiBtjob), QSORTP4 sort_j);
}

static void  api_error(const int code)
{
        html_disperror(code < 0? $E{Base for API errors} + code: code);
        exit(E_NETERR);
}

int  jobextract(char *arg, apiBtjob *jp)
{
        int     ret;
        struct  jobswanted      jw;

        if  ((ret = decode_jnum(arg, &jw)) != 0)
                return  ret;

        api_open(realuname);
        if  ((ret = gbatch_jobfind(xbapi_fd, GBATCH_FLAG_IGNORESEQ, jw.jno, jw.host, &jw.slot, jp)) < 0)
                return  ret;

        return  0;
}

typedef unsigned        fmt_t;

#define Btjobh          apiBtjobh
#define Btjob           apiBtjob
#define CBtjobRef       const apiBtjob *
#define CBtvarRef       const apiBtvar *

JFORMAT(fmt_args)
{
        fmt_t   lng = 0;
        unsigned        ac;
#ifdef  CHARSPRINTF
        int     cnt;
#endif
        for  (ac = 0;  ac < jp->h.bj_nargs;  ac++)  {
                const   char    *arg = gbatch_getarg(jp, ac);
                if  (ac != 0)
                        bigbuff[lng++] = ',';
#ifdef  CHARSPRINTF
                if  (strchr(arg, ' '))
                        sprintf(&bigbuff[lng], "\"%s\"", arg);
                else
                        strcpy(&bigbuff[lng], arg);
                cnt = strlen(&bigbuff[lng]);
                lng += cnt;
#else
                lng += sprintf(&bigbuff[lng], strchr(arg, ' ')? "\"%s\"": "%s", arg);
#endif
        }

        return  lng;
}

#include "inline/jfmt_avoid.c"

JFORMAT(fmt_condfull)
{
        fmt_t  lng = 0;
        int     uc;
        const  apiBtvar *vp;
        const  apiJcond *cp;

#ifdef  CHARSPRINTF
        int     cnt;
#endif
        for  (uc = 0;  uc < MAXCVARS;  uc++)  {
                cp = &jp->h.bj_conds[uc];
                if  (cp->bjc_compar == C_UNUSED)
                        break;
                if  (!(vp = find_var(cp->bjc_var.slotno)))
                        continue;
                if  (uc != 0)
                        bigbuff[lng++] = ',';
#ifdef  CHARSPRINTF
                if  (vp->var_id.hostid != dest_hostid)  {
                        sprintf(&bigbuff[lng], "%s:", look_host(vp->var_id.hostid));
                        cnt = strlen(&bigbuff[lng]);
                        lng += cnt;
                }
                sprintf(&bigbuff[lng], "%s%s", vp->var_name, condname[cp->bjc_compar-1]);
                cnt = strlen(&bigbuff[lng]);
                lng += cnt;
                if  (cp->bjc_value.const_type == CON_STRING)
                        sprintf(&bigbuff[lng],
                                strchr(cp->bjc_value.con_un.con_string, ' ') ||
                                isdigit(cp->bjc_value.con_un.con_string[0])?
                                "\"%s\"": "%s", cp->bjc_value.con_un.con_string);
                else
                        sprintf(&bigbuff[lng], "%ld", (long) cp->bjc_value.con_un.con_long);
                cnt = strlen(&bigbuff[lng]);
                lng += cnt;
#else
                if  (vp->var_id.hostid != dest_hostid)
                        lng += sprintf(&bigbuff[lng], "%s:", look_host(vp->var_id.hostid));
                lng += sprintf(&bigbuff[lng], "%s%s", vp->var_name, condname[cp->bjc_compar-1]);
                if  (cp->bjc_value.const_type == CON_STRING)
                        lng += sprintf(&bigbuff[lng],
                                       strchr(cp->bjc_value.con_un.con_string, ' ') ||
                                       isdigit(cp->bjc_value.con_un.con_string[0])?
                                       "\"%s\"": "%s", cp->bjc_value.con_un.con_string);
                else
                        lng += sprintf(&bigbuff[lng], "%ld", (long) cp->bjc_value.con_un.con_long);
#endif
        }
        return  lng;
}

JFORMAT(fmt_cond)
{
        fmt_t  lng = 0;
        int     uc;
        const   apiBtvar        *vp;
        const   apiJcond        *cp;

#ifdef  CHARSPRINTF
        int     cnt;
#endif
        for  (uc = 0;  uc < MAXCVARS;  uc++)  {
                cp = &jp->h.bj_conds[uc];
                if  (cp->bjc_compar == C_UNUSED)
                        break;
                if  (!(vp = find_var(cp->bjc_var.slotno)))
                        continue;
                if  (uc != 0)
                        bigbuff[lng++] = ',';
#ifdef  CHARSPRINTF
                if  (vp->var_id.hostid != dest_hostid)  {
                        sprintf(&bigbuff[lng], "%s:", look_host(vp->var_id.hostid));
                        cnt = strlen(&bigbuff[lng]);
                        lng += cnt;
                }
                sprintf(&bigbuff[lng], "%s", vp->var_name);
                cnt = strlen(&bigbuff[lng]);
                lng += cnt;
#else
                if  (vp->var_id.hostid != dest_hostid)
                        lng += sprintf(&bigbuff[lng], "%s:", look_host(vp->var_id.hostid));
                lng += sprintf(&bigbuff[lng], "%s", vp->var_name);
#endif
        }
#ifndef BTJLIST_INLINE
        if  (lng > fwidth)
                return  (int) strlen(strcpy(bigbuff, defcondstr));
#endif
        return  lng;
}

JFORMAT(fmt_dir)
{
        return  (fmt_t) strlen(strcpy(bigbuff, gbatch_getdirect(jp)));
}

JFORMAT(fmt_env)
{
        fmt_t  lng = 0;
        unsigned        ec = 0;
        const  char     **elist;

#ifdef  CHARSPRINTF
        int     cnt;
#endif
        for  (elist = gbatch_getenvlist(jp);  *elist;  elist++)  {
                if  (ec != 0)
                        bigbuff[lng++] = ',';
                ec++;
                lng += strlen(strcpy(&bigbuff[lng], *elist));
        }
        return  lng;
}

#include "inline/jfmt_group.c"

JFORMAT(fmt_title)
{
        return  strlen(strcpy(bigbuff, gbatch_gettitle(xbapi_fd, jp)));
}

#include "inline/jfmt_interp.c"
#include "inline/jfmt_loadlev.c"
#include "inline/fmtmode.c"

JFORMAT(fmt_mode)
{
        return  fmtmode(fmtmode(fmtmode(0, "U", jp->h.bj_mode.u_flags),
                                           ",G", jp->h.bj_mode.g_flags),
                                           ",O", jp->h.bj_mode.o_flags);
}

#include "inline/jfmt_umask.c"

JFORMAT(fmt_jobno)
{
#ifdef  CHARSPRINTF
        if  (jp->h.bj_hostid != dest_hostid)
                sprintf(bigbuff, "%s:%ld", look_host(jp->h.bj_hostid), (long) jp->h.bj_job);
        else
                sprintf(bigbuff, "%ld", jp->h.bj_job);
        return  (fmt_t) strlen(bigbuff);
#else
        if  (jp->h.bj_hostid != dest_hostid)
                return  (fmt_t) sprintf(bigbuff, "%s:%ld", look_host(jp->h.bj_hostid), (long) jp->h.bj_job);
        else
                return  (fmt_t) sprintf(bigbuff, "%ld", (long) jp->h.bj_job);
#endif
}

#include "inline/jfmt_progress.c"
#include "inline/jfmt_prio.c"
#include "inline/jfmt_pid.c"

JFORMAT(fmt_queue)
{
        const   char    *colp, *title = gbatch_gettitle(-1, jp);

        if  ((colp = strchr(title, ':')))  {
                strncpy(bigbuff, title, colp - title);
                bigbuff[colp - title] = '\0';
                return  (fmt_t) (colp - title);
        }
        return  0;
}

JFORMAT(fmt_qtit)
{
        const   char    *colp, *title = gbatch_gettitle(-1, jp);
        return  (colp = strchr(title, ':'))?
                strlen(strcpy(bigbuff, colp+1)):
                strlen(strcpy(bigbuff, title));
}

JFORMAT(fmt_redirs)
{
        fmt_t  lng = 0;
        unsigned        rc;
#ifdef  CHARSPRINTF
        int     cnt;
#endif
        for  (rc = 0;  rc < jp->h.bj_nredirs;  rc++)  {
                const  apiMredir  *rp = gbatch_getredir(jp, rc);
                if  (rc != 0)
                        bigbuff[lng++] = ',';
                switch  (rp->action)  {
                case  RD_ACT_RD:
#ifdef  CHARSPRINTF
                        if  (rp->fd != 0)  {
                                sprintf(&bigbuff[lng], "%d", rp->fd);
                                cnt = strlen(&bigbuff[lng]);
                                lng += cnt;
                        }
#else
                        if  (rp->fd != 0)
                                lng += sprintf(&bigbuff[lng], "%d", rp->fd);
#endif
                        bigbuff[lng++] = '<';
                        break;
                case  RD_ACT_WRT:
#ifdef  CHARSPRINTF
                        if  (rp->fd != 1)  {
                                sprintf(&bigbuff[lng], "%d", rp->fd);
                                cnt = strlen(&bigbuff[lng]);
                                lng += cnt;
                        }
#else
                        if  (rp->fd != 1)
                                lng += sprintf(&bigbuff[lng], "%d", rp->fd);
#endif
                        bigbuff[lng++] = '>';
                        break;
                case  RD_ACT_APPEND:
#ifdef  CHARSPRINTF
                        if  (rp->fd != 1)  {
                                sprintf(&bigbuff[lng], "%d", rp->fd);
                                cnt = strlen(&bigbuff[lng]);
                                lng += cnt;
                        }
#else
                        if  (rp->fd != 1)
                                lng += sprintf(&bigbuff[lng], "%d", rp->fd);
#endif
                        bigbuff[lng++] = '>';
                        bigbuff[lng++] = '>';
                        break;
                case  RD_ACT_RDWR:
#ifdef  CHARSPRINTF
                        if  (rp->fd != 0)  {
                                sprintf(&bigbuff[lng], "%d", rp->fd);
                                cnt = strlen(&bigbuff[lng]);
                                lng += cnt;
                        }
#else
                        if  (rp->fd != 0)
                                lng += sprintf(&bigbuff[lng], "%d", rp->fd);
#endif
                        bigbuff[lng++] = '<';
                        bigbuff[lng++] = '>';
                        break;
                case  RD_ACT_RDWRAPP:
#ifdef  CHARSPRINTF
                        if  (rp->fd != 0)  {
                                sprintf(&bigbuff[lng], "%d", rp->fd);
                                cnt = strlen(&bigbuff[lng]);
                                lng += cnt;
                        }
#else
                        if  (rp->fd != 0)
                                lng += sprintf(&bigbuff[lng], "%d", rp->fd);
#endif
                        bigbuff[lng++] = '<';
                        bigbuff[lng++] = '>';
                        bigbuff[lng++] = '>';
                        break;
                case  RD_ACT_PIPEO:
#ifdef  CHARSPRINTF
                        if  (rp->fd != 1)  {
                                sprintf(&bigbuff[lng], "%d", rp->fd);
                                cnt = strlen(&bigbuff[lng]);
                                lng += cnt;
                        }
#else
                        if  (rp->fd != 1)
                                lng += sprintf(&bigbuff[lng], "%d", rp->fd);
#endif
                        bigbuff[lng++] = '|';
                        break;
                case  RD_ACT_PIPEI:
#ifdef  CHARSPRINTF
                        if  (rp->fd != 0)  {
                                sprintf(&bigbuff[lng], "%d", rp->fd);
                                cnt = strlen(&bigbuff[lng]);
                                lng += cnt;
                        }
#else
                        if  (rp->fd != 0)
                                lng += sprintf(&bigbuff[lng], "%d", rp->fd);
#endif
                        bigbuff[lng++] = '<';
                        bigbuff[lng++] = '|';
                        break;
                case  RD_ACT_CLOSE:
#ifdef  CHARSPRINTF
                        if  (rp->fd != 1)  {
                                sprintf(&bigbuff[lng], "%d", rp->fd);
                                cnt = strlen(&bigbuff[lng]);
                                lng += cnt;
                        }
#else
                        if  (rp->fd != 1)
                                lng += sprintf(&bigbuff[lng], "%d", rp->fd);
#endif
                        bigbuff[lng++] = '>';
                        bigbuff[lng++] = '&';
                        bigbuff[lng++] = '-';
                        continue;
                case  RD_ACT_DUP:
#ifdef  CHARSPRINTF
                        if  (rp->fd != 1)  {
                                sprintf(&bigbuff[lng], "%d", rp->fd);
                                cnt = strlen(&bigbuff[lng]);
                                lng += cnt;
                        }
                        sprintf(&bigbuff[lng], ">&%d", rp->un.arg);
                        cnt = strlen(&bigbuff[lng]);
                        lng += cnt;
#else
                        if  (rp->fd != 1)
                                lng += sprintf(&bigbuff[lng], "%d", rp->fd);
                        lng += sprintf(&bigbuff[lng], ">&%d", rp->un.arg);
#endif
                        continue;
                }
#ifdef  CHARSPRINTF
                strlen(strcpy(&bigbuff[lng], rp->un.buffer));
                cnt = strlen(&bigbuff[lng]);
                lng += cnt;
#else
                lng += strlen(strcpy(&bigbuff[lng], rp->un.buffer));
#endif
        }
        return  lng;
}

#include "inline/jfmt_repeat.c"
#include "inline/jfmt_ifnposs.c"

JFORMAT(fmt_assfull)
{
        fmt_t  lng = 0;
        int     uc;
        const   apiBtvar        *vp;
        const   apiJass         *ap;
#ifdef  CHARSPRINTF
        int     cnt;
#endif
        for  (uc = 0;  uc < MAXSEVARS;  uc++)  {
                ap = &jp->h.bj_asses[uc];
                if  (ap->bja_op == BJA_NONE)
                        break;
                if  (!(vp = find_var(ap->bja_var.slotno)))
                        continue;
                if  (uc != 0)
                        bigbuff[lng++] = ',';
                if  (ap->bja_op < BJA_SEXIT)  {
                        if  (ap->bja_flags & BJA_START)
                                bigbuff[lng++] = 'S';
                        if  (ap->bja_flags & BJA_REVERSE)
                                bigbuff[lng++] = 'R';
                        if  (ap->bja_flags & BJA_OK)
                                bigbuff[lng++] = 'N';
                        if  (ap->bja_flags & BJA_ERROR)
                                bigbuff[lng++] = 'E';
                        if  (ap->bja_flags & BJA_ABORT)
                                bigbuff[lng++] = 'A';
                        if  (ap->bja_flags & BJA_CANCEL)
                                bigbuff[lng++] = 'C';
                        bigbuff[lng++] = ':';
                };
#ifdef  CHARSPRINTF
                if  (vp->var_id.hostid != dest_hostid)  {
                        sprintf(&bigbuff[lng], "%s:", look_host(vp->var_id.hostid));
                        cnt = strlen(&bigbuff[lng]);
                        lng += cnt;
                }
                sprintf(&bigbuff[lng], "%s%s", vp->var_name, assname[ap->bja_op-1]);
                cnt = strlen(&bigbuff[lng]);
                lng += cnt;
                if  (ap->bja_op >= BJA_SEXIT)
                        strcpy(&bigbuff[lng], ap->bja_op == BJA_SEXIT? exitcodename: signalname);
                else  if  (ap->bja_con.const_type == CON_STRING)
                        sprintf(&bigbuff[lng],
                                strchr(ap->bja_con.con_un.con_string, ' ') ||
                                isdigit(ap->bja_con.con_un.con_string[0])?
                                "\"%s\"": "%s", ap->bja_con.con_un.con_string);
                else
                        sprintf(&bigbuff[lng], "%ld", (long) ap->bja_con.con_un.con_long);
                cnt = strlen(&bigbuff[lng]);
                lng += cnt;
#else
                if  (vp->var_id.hostid != dest_hostid)
                        lng += sprintf(&bigbuff[lng], "%s:", look_host(vp->var_id.hostid));
                lng += sprintf(&bigbuff[lng], "%s%s", vp->var_name, assname[ap->bja_op-1]);
                if  (ap->bja_op >= BJA_SEXIT)
                        lng += strlen(strcpy(&bigbuff[lng], ap->bja_op == BJA_SEXIT? exitcodename: signalname));
                else  if  (ap->bja_con.const_type == CON_STRING)
                        lng += sprintf(&bigbuff[lng],
                                       strchr(ap->bja_con.con_un.con_string, ' ') ||
                                       isdigit(ap->bja_con.con_un.con_string[0])?
                                       "\"%s\"": "%s", ap->bja_con.con_un.con_string);
                else
                        lng += sprintf(&bigbuff[lng], "%ld", (long) ap->bja_con.con_un.con_long);
#endif
        }
        return  lng;
}

JFORMAT(fmt_ass)
{
        fmt_t  lng = 0;
        int     uc;
        const   apiBtvar  *vp;
        const   apiJass *ap;
#ifdef  CHARSPRINTF
        int     cnt;
#endif
        for  (uc = 0;  uc < MAXSEVARS;  uc++)  {
                ap = &jp->h.bj_asses[uc];
                if  (ap->bja_op == BJA_NONE)
                        break;
                if  (!(vp = find_var(ap->bja_var.slotno)))
                        continue;
                if  (uc != 0)
                        bigbuff[lng++] = ',';
#ifdef  CHARSPRINTF
                if  (vp->var_id.hostid != dest_hostid)  {
                        sprintf(&bigbuff[lng], "%s:", look_host(vp->var_id.hostid));
                        cnt = strlen(&bigbuff[lng]);
                        lng += cnt;
                }
#else
                if  (vp->var_id.hostid != dest_hostid)
                        lng += sprintf(&bigbuff[lng], "%s:", look_host(vp->var_id.hostid));
#endif
                lng += strlen(strcpy(&bigbuff[lng], vp->var_name));
        }
        return  lng;
}

#include "inline/jfmt_timefull.c"
#include "inline/jfmt_time.c"
#include "inline/jfmt_user.c"
#include "inline/jfmt_ulimit.c"
#include "inline/jfmt_exits.c"
#include "inline/jfmt_orighost.c"
#include "inline/jfmt_export.c"
#include "inline/jfmt_xit.c"
#include "inline/jfmt_times.c"
#include "inline/jfmt_runt.c"

#define ISHOL(map, day) map[(day) >> 3] & (1 << (day & 7))

JFORMAT(fmt_hols)
{
        int     lng = 0;

        if  (jp->h.bj_times.tc_istime != 0  &&  (jp->h.bj_times.tc_nvaldays & TC_HOLIDAYBIT))  {
                time_t  now  =  time((time_t *) 0);
                struct  tm  *tp = localtime(&now);
                int     year = tp->tm_year + 1900;
                int     ydays = year % 4 == 0? 366: 365, syday, yday, mon, day;
                unsigned  char  *yearmap;

                if  (gbatch_holread(xbapi_fd, 0, year, &yearmap) < 0)
                        return  0;

                for  (syday = yday = tp->tm_yday;  yday < ydays;  now += 3600*24, yday++)
                        if  (ISHOL(yearmap, yday))  {
                                tp = localtime(&now);
                                mon = tp->tm_mon+1;
                                day = tp->tm_mday;
#ifdef  HAVE_TM_ZONE
                                if  (tp->tm_gmtoff <= -4 * 60 * 60)
#else
                                if  (timezone >= 4 * 60 * 60)
#endif
                                {
                                        day = mon;
                                        mon = tp->tm_mday;
                                }
                                if  (lng > 0)
                                        bigbuff[lng++] = ',';
#ifdef  CHARSPRINTF
                                sprintf(&bigbuff[lng], "%.2d/%.2d", day, mon);
                                lng += strlen(&bigbuff[lng]);
#else
                                lng += sprintf(&bigbuff[lng], "%.2d/%.2d", day, mon);
#endif
                        }

                if  (gbatch_holread(xbapi_fd, 0, year+1, &yearmap) < 0)
                        return  lng;

                for  (yday = 0;  yday < syday; now += 3600*24, yday++)
                        if  (ISHOL(yearmap, yday))  {
                                tp = localtime(&now);
                                mon = tp->tm_mon+1;
                                day = tp->tm_mday;
#ifdef  HAVE_TM_ZONE
                                if  (tp->tm_gmtoff <= -4 * 60 * 60)
#else
                                        if  (timezone >= 4 * 60 * 60)
#endif
                                {
                                        day = mon;
                                        mon = tp->tm_mday;
                                }
                                if  (lng > 0)
                                        bigbuff[lng++] = ',';
#ifdef  CHARSPRINTF
                                sprintf(&bigbuff[lng], "%.2d/%.2d", day, mon);
                                lng += strlen(&bigbuff[lng]);
#else
                                lng += sprintf(&bigbuff[lng], "%.2d/%.2d", day, mon);
#endif
                        }
        }
        return  lng;
}

/* Mapping of format characters (assumed A-Z a-z) and format routines */

struct  formatdef  {
        SHORT   statecode;      /* Code number for heading if applicable */
        char    *msg;           /* Heading */
        unsigned (*fmt_fn)(const apiBtjob *, const int, const int);
};

#define NULLCP  (char *) 0

struct  formatdef
        uppertab[] = { /* A-Z */
        {       $P{job fmt title}+'A',  NULLCP, fmt_args        },      /* A */
        {       0,                      NULLCP, 0               },      /* B */
        {       $P{job fmt title}+'C',  NULLCP, fmt_condfull    },      /* C */
        {       $P{job fmt title}+'D',  NULLCP, fmt_dir         },      /* D */
        {       $P{job fmt title}+'E',  NULLCP, fmt_env         },      /* E */
        {       0,                      NULLCP, 0               },      /* F */
        {       $P{job fmt title}+'G',  NULLCP, fmt_group       },      /* G */
        {       $P{job fmt title}+'H',  NULLCP, fmt_title       },      /* H */
        {       $P{job fmt title}+'I',  NULLCP, fmt_interp      },      /* I */
        {       0,                      NULLCP, 0               },      /* J */
        {       0,                      NULLCP, 0               },      /* K */
        {       $P{job fmt title}+'L',  NULLCP, fmt_loadlev     },      /* L */
        {       $P{job fmt title}+'M',  NULLCP, fmt_mode        },      /* M */
        {       $P{job fmt title}+'N',  NULLCP, fmt_jobno       },      /* N */
        {       $P{job fmt title}+'O',  NULLCP, fmt_orighost    },      /* O */
        {       $P{job fmt title}+'P',  NULLCP, fmt_progress    },      /* P */
        {       0,                      NULLCP, 0               },      /* Q */
        {       $P{job fmt title}+'R',  NULLCP, fmt_redirs      },      /* R */
        {       $P{job fmt title}+'S',  NULLCP, fmt_assfull     },      /* S */
        {       $P{job fmt title}+'T',  NULLCP, fmt_timefull    },      /* T */
        {       $P{job fmt title}+'U',  NULLCP, fmt_user        },      /* U */
        {       0,                      NULLCP, 0               },      /* V */
        {       $P{job fmt title}+'W',  NULLCP, fmt_itime       },      /* W */
        {       $P{job fmt title}+'X',  NULLCP, fmt_exits       },      /* X */
        {       $P{job fmt title}+'Y',  NULLCP, fmt_hols        },      /* Y */
        {       0,                      NULLCP, 0               }       /* Z */
},
        lowertab[] = { /* a-z */
        {       $P{job fmt title}+'a',  NULLCP, fmt_avoid       },      /* a */
        {       $P{job fmt title}+'b',  NULLCP, fmt_stime       },      /* b */
        {       $P{job fmt title}+'c',  NULLCP, fmt_cond        },      /* c */
        {       $P{job fmt title}+'d',  NULLCP, fmt_deltime     },      /* d */
        {       $P{job fmt title}+'e',  NULLCP, fmt_export      },      /* e */
        {       $P{job fmt title}+'f',  NULLCP, fmt_etime       },      /* f */
        {       $P{job fmt title}+'g',  NULLCP, fmt_gracetime   },      /* g */
        {       $P{job fmt title}+'h',  NULLCP, fmt_qtit        },      /* h */
        {       $P{job fmt title}+'i',  NULLCP, fmt_pid         },      /* i */
        {       0,                      NULLCP, 0               },      /* j */
        {       $P{job fmt title}+'k',  NULLCP, fmt_autoksig    },      /* k */
        {       $P{job fmt title}+'l',  NULLCP, fmt_runtime     },      /* l */
        {       $P{job fmt title}+'m',  NULLCP, fmt_umask       },      /* m */
        {       0,                      NULLCP, 0               },      /* n */
        {       $P{job fmt title}+'o',  NULLCP, fmt_otime       },      /* o */
        {       $P{job fmt title}+'p',  NULLCP, fmt_prio        },      /* p */
        {       $P{job fmt title}+'q',  NULLCP, fmt_queue       },      /* q */
        {       $P{job fmt title}+'r',  NULLCP, fmt_repeat      },      /* r */
        {       $P{job fmt title}+'s',  NULLCP, fmt_ass         },      /* s */
        {       $P{job fmt title}+'t',  NULLCP, fmt_time        },      /* t */
        {       $P{job fmt title}+'u',  NULLCP, fmt_ulimit      },      /* u */
        {       0,                      NULLCP, 0               },      /* v */
        {       $P{job fmt title}+'w',  NULLCP, fmt_ifnposs     },      /* w */
        {       $P{job fmt title}+'x',  NULLCP, fmt_xit         },      /* x */
        {       $P{job fmt title}+'y',  NULLCP, fmt_sig         },      /* y */
        {       0,                      NULLCP, 0               }       /* z */
};

void  print_hdrfmt(struct formatdef *fp)
{
        if  (!fp->fmt_fn)
                return;
        if  (!fp->msg)
                fp->msg = gprompt(fp->statecode);
        html_fldprint(fp->msg);
}

struct  altype  {
        char    *str;           /* Align string for html */
        char    ch;             /* Align char */
}  altypes[] = {
        {       "left",         'L' },
        {       "right",        'R' },
        {       "center",       'C' }
};

#define NALIGNTYPES (sizeof(altypes) / sizeof(struct altype))

struct  altype  *commonest_align = &altypes[0];

struct altype *lookup_align(const int alch)
{
        int     cnt;
        for  (cnt = 0;  cnt < NALIGNTYPES;  cnt++)
                if  (altypes[cnt].ch == alch)
                        return  &altypes[cnt];
        return  commonest_align;
}

struct  colfmt  {
        struct  formatdef  *form;
        char    *alstr;
};

struct  colfmt  *cflist;
int     ncolfmts, maxcolfmts;

#define INITCF  10
#define INCCF   5

void  find_commonest(char *fp)
{
        int     rvec[NALIGNTYPES];
        int     cnt, mx = 0, ind = 0, fmch;
        struct  altype  *whichalign;
        struct  formatdef  *fd;

        for  (cnt = 0;  cnt < NALIGNTYPES;  cnt++)
                rvec[cnt] = 0;

        if  (!(cflist = (struct colfmt *) malloc(INITCF * sizeof(struct colfmt))))
                ABORT_HTML_NOMEM;
        maxcolfmts = INITCF;

        while  (*fp)  {
                while  (*fp  &&  !isalpha(*fp))
                        fp++;
                if  (!*fp)
                        break;
                whichalign = lookup_align(*fp++);
                rvec[whichalign - &altypes[0]]++;
                fmch = *fp++;
                if  (!isalpha(fmch))
                        break;
                fd = isupper(fmch)? &uppertab[fmch - 'A']: &lowertab[fmch - 'a'];
                if  (!fd->fmt_fn)
                        continue;
                if  (ncolfmts >= maxcolfmts)  {
                        maxcolfmts += INCCF;
                        cflist = (struct colfmt *) realloc((char *) cflist, (unsigned) (maxcolfmts * sizeof(struct colfmt)));
                        if  (!cflist)
                                ABORT_HTML_NOMEM;
                }
                cflist[ncolfmts].form = fd;
                cflist[ncolfmts].alstr = whichalign->str;
                ncolfmts++;
        }

        for  (cnt = 0;  cnt < NALIGNTYPES;  cnt++)
                if  (mx < rvec[cnt])  {
                        mx = rvec[cnt];
                        ind = cnt;
                }
        commonest_align = &altypes[ind];

        for  (cnt = 0;  cnt < ncolfmts;  cnt++)
                if  (cflist[cnt].alstr == commonest_align->str)
                        cflist[cnt].alstr = (char *) 0;
}

void  startrow()
{
        printf("<tr align=%s>\n", commonest_align->str);
}

void  startcell(const int celltype, const char *str)
{
        if  (str)
                printf("<t%c align=%s>", celltype, str);
        else
                printf("<t%c>", celltype);
}

/* Display contents of job file.  */

void  jdisplay()
{
        int     jcnt, fcnt;
        unsigned  pflgs = 0;

        /* <TABLE> included in pre-list file so as to possibly include formatting elements */

        find_commonest(formatstring);

        /* Possibly insert header */

        if  (Dispflags & DF_HAS_HDR)  {
                /* Possibly insert first header showing options */
                if  (jobqueue || Restru || Restrg || exportflag != (BJ_EXPORT|BJ_REMRUNNABLE))  {
                        startrow();
                        printf("<th colspan=%d align=center>", ncolfmts+1);
                        fputs(gprompt($P{btjcgi restr start}), stdout);
                        html_fldprint(gprompt($P{btjcgi restr view}));
                        if  (jobqueue)  {
                                html_fldprint(gprompt($P{btjcgi restr queue}));
                                html_fldprint(jobqueue);
                                if  (!(Dispflags & DF_SUPPNULL))
                                        html_fldprint(gprompt($P{btjcgi restr null}));
                        }
                        if  (Restru)  {
                                html_fldprint(gprompt($P{btjcgi restr users}));
                                html_fldprint(Restru);
                        }
                        if  (Restrg)  {
                                html_fldprint(gprompt($P{btjcgi restr groups}));
                                html_fldprint(Restrg);
                        }
                        if  (exportflag != (BJ_EXPORT|BJ_REMRUNNABLE))
                                html_fldprint(gprompt(exportflag & BJ_REMRUNNABLE? $P{btjcgi restr plusrr}:
                                                      $P{btjcgi restr loco}));
                        fputs(gprompt($P{btjcgi restr end}), stdout);
                        fputs("</th></tr>\n", stdout);
                }
                startrow();
                startcell('h', commonest_align->str); /* Blank space in place of checkbox */
                fputs("</th>", stdout);
                for  (fcnt = 0;  fcnt < ncolfmts;  fcnt++)  {
                        startcell('h', cflist[fcnt].alstr);
                        print_hdrfmt(cflist[fcnt].form);
                        fputs("</th>", stdout);
                }
                fputs("</tr>\n", stdout);
        }

        /* Final run-through to output stuff */

        for  (jcnt = 0;  jcnt < Njobs;  jcnt++)  {
                apiBtjob        *jp = &job_list[jcnt];
                unsigned        hval = LV_MINEORVIEW|LV_LOCORRVIEW|LV_CHANGEABLE|LV_DELETEABLE|LV_KILLABLE;

                if  (jp->h.bj_hostid != dest_hostid  &&  (jp->h.bj_jflags & exportflag) == 0)
                        continue;

                if  ((jp->h.bj_mode.u_flags | jp->h.bj_mode.g_flags | jp->h.bj_mode.o_flags) != 0)
                        hval |= LV_CHMODABLE;
                if  (jp->h.bj_progress >= BJP_STARTUP1)
                        hval |= LV_PROCESS;

                startrow();
                startcell('d', commonest_align->str);
                fmt_jobno(jp, 1, 0);
                printf("<input type=checkbox name=jobs value=\"%s,%u\"></td>", bigbuff, hval);

                for  (fcnt = 0;  fcnt < ncolfmts;  fcnt++)  {
                        startcell('d', cflist[fcnt].alstr);
                        bigbuff[0] = '\0';
                        (cflist[fcnt].form->fmt_fn)(jp, 1, 0);
                        html_fldprint(bigbuff);
                        fputs("</td>", stdout);
                }
                fputs("</tr>\n", stdout);
        }

        pflgs |= GLV_ACCESSF | GLV_FREEZEF;
        if  (userpriv.btu_priv & BTM_CREATE)
                pflgs |= GLV_ANYCHANGES;
        printf("</table>\n<input type=hidden name=privs value=%u>\n", pflgs);
        printf("<input type=hidden name=prio value=\"%d,%d,%d\">\n",
               userpriv.btu_defp, userpriv.btu_minp, userpriv.btu_maxp);
}

struct  arginterp  {
        char    *argname;
        unsigned  short  flags;
#define AIF_NOARG       0
#define AIF_ARG         1
        int     (*arg_fn)(char *);
};

int  perf_listformat(char *notused)
{
        struct  formatdef   *fp;
        int     lett;
        char    *msg;

        html_out_or_err("listfmt_pre", 1);
        for  (fp = &uppertab[0],  lett = 'A';  fp < &uppertab[26];  lett++, fp++)
                if  (fp->statecode != 0)  {
                        msg = gprompt(fp->statecode + 400);
                        printf("list_format_code(\'%c\', \'L\', \"%s\");\n", lett, msg);
                        free(msg);
                }
        for  (fp = &lowertab[0],  lett = 'a';  fp < &lowertab[26];  lett++, fp++)
                if  (fp->statecode != 0)  {
                        msg = gprompt(fp->statecode + 400);
                        printf("list_format_code(\'%c\', \'L\', \"%s\");\n", lett, msg);
                        free(msg);
                }

        fmt_setup();
        printf("existing_formats(\"%s\");\n", formatstring);
        html_out_param_file("listfmt_post", 0, 0, html_cookexpiry());
        exit(0);
        return  0;
}

extern int  perf_optselect(char *);

int  set_queue(char *arg)
{
        jobqueue = *arg? arg: (char *) 0;
        return  1;
}

int  set_user(char *arg)
{
        Restru = *arg? arg: (char *) 0;
        return  1;
}

int  set_group(char *arg)
{
        Restrg = *arg? arg: (char *) 0;
        return  1;
}

int  set_incnull(char *arg)
{
        switch  (tolower(*arg))  {
        case  'a':case  'y':case  '1':
                Dispflags &= ~DF_SUPPNULL;
                return  1;
        case  'n':case  '0':
                Dispflags |= DF_SUPPNULL;
                return  1;
        default:
                return  0;
        }
}

int  set_header(char *arg)
{
        hadhdrarg = 1;
        switch  (tolower(*arg))  {
        case  'y':case '1':
                Dispflags |= DF_HAS_HDR;
                return  1;
        case  'n':case '0':
                Dispflags &= ~DF_HAS_HDR;
                return  1;
        default:
                return  0;
        }
}

int  set_exported(char *arg)
{
        switch  (tolower(*arg))  {
        case  'r':
                exportflag = BJ_REMRUNNABLE;
                return  1;
        case  'a':
                exportflag = BJ_EXPORT|BJ_REMRUNNABLE;
                return  1;
        case  'l':
                exportflag = 0;
                return  1;
        default:
                return  0;
        }
}

int  set_sort(char *arg)
{
        switch  (tolower(*arg))  {
        case  'y':case '1':
                sortflag = 1;
                return  1;
        case  'n':case '0':
                sortflag = 0;
                return  1;
        default:
                return  0;
        }
}

int  set_format(char *arg)
{
        formatstring = arg;
        return  1;
}

static char *tof(const USHORT x)
{
        return  x? "true": "false";
}

#define QMALLINIT       10
#define QMALLINC        5

char **gen_qlist()
{
        char    **result;
        unsigned  rcnt = 1, rmax = QMALLINIT, jcnt;

        if  (!(result = (char **) malloc((QMALLINIT+1) * sizeof(char *))))
                ABORT_HTML_NOMEM;

        result[0] = stracpy("");

        for  (jcnt = 0;  jcnt < Njobs;  jcnt++)  {
                const   char    *tit = gbatch_gettitle(-1, &job_list[jcnt]);
                char            *it, *ip;
                unsigned        lng, rt;

                if  (!(ip = strchr(tit, ':')))
                        continue;

                lng = ip - tit + 1;
                if  (!(it = malloc(lng)))
                        ABORT_HTML_NOMEM;

                strncpy(it, tit, lng-1);
                it[lng-1] = '\0';

                for  (rt = 0;  rt < rcnt;  rt++)
                        if  (strcmp(it, result[rt]) == 0)  {
                                free(it);
                                goto  clash;
                        }
                if  (rcnt >= rmax)  {
                        rmax += QMALLINC;
                        if  ((result = (char **) realloc((char *) result, (rmax+1) * sizeof(char *))) == 0)
                                ABORT_HTML_NOMEM;
                }
                result[rcnt++] = it;
        clash:
                ;
        }
        result[rcnt] = (char *) 0;
        return  result;
}

char **gen_cilist()
{
        int             Ci_num, cnt, ret;
        Cmdint          *inlist;
        char            **result;
        unsigned        countr;

        if  ((ret = gbatch_ciread(xbapi_fd, 0, &Ci_num, &inlist)) < 0)  {
                html_disperror($E{Base for API errors} + ret);
                exit(E_NETERR);
        }

        if  ((result = (char **) malloc((unsigned) ((Ci_num + 1) * sizeof(char *)))) == (char **) 0)
                ABORT_HTML_NOMEM;

        countr = 0;

        for  (cnt = 0;  cnt < Ci_num;  cnt++)
                if  (inlist[cnt].ci_name[0] != '\0')
                        result[countr++] = stracpy(inlist[cnt].ci_name);

        result[countr] = (char *) 0;
        return  result;
}

/* Generate a list of variables which the user has access to.
   "permflags" gives the required permission (read, write or read/write)
   expr is whether the thing is exported */

#define INCVLIST        10

char **gen_vlist(const USHORT permflags, const int expr)
{
        char    **result;
        unsigned  vcnt, maxr = (Nvars + 1) / 2, countr;

        /* We want the sorted list, so we bodge the last read list
           and reread, sorting.
           It didn't seem worth doing this from initially and have all
           sorts of parameters in the setup_... routines */


        if  ((result = (char **) malloc((maxr + 1) * sizeof(char *))) == (char **) 0)
                ABORT_HTML_NOMEM;

        for  (vcnt = countr = 0;  vcnt < Nvars;  vcnt++)  {
                apiBtvar        *vp = &var_sl_list[vcnt].var;

                if  (expr)  {
                        if  (!(vp->var_flags & VF_EXPORT)  &&  (vp->var_type != VT_MACHNAME || vp->var_id.hostid != dest_hostid))
                                continue;
                }
                else  if  (vp->var_flags & VF_EXPORT)
                        continue;

                if  (countr >= maxr)  {
                        maxr += INCVLIST;
                        if  ((result = (char**) realloc((char *) result, (maxr + 1) * sizeof(char *))) == (char **) 0)
                                ABORT_HTML_NOMEM;
                }

                if  (vp->var_id.hostid != dest_hostid)  {
                        char    hvname[HOSTNSIZE+BTV_NAME+2]; /* Max poss + ':' and final null */
                        sprintf(hvname, "%s:%s", look_host(vp->var_id.hostid), vp->var_name);
                        result[countr++] = stracpy(hvname);
                }
                else
                        result[countr++] = stracpy(vp->var_name);
        }

        result[countr] = (char *) 0;
        return  result;
}

char **gen_jobasses(const apiBtjob *jp)
{
        char    **result = malloc((MAXSEVARS+1)*sizeof(char *));
        int     cnt;

        if  (!result)
                ABORT_HTML_NOMEM;

        for  (cnt = 0;  cnt < MAXSEVARS;  cnt++)  {
                const apiJass  *ja = &jp->h.bj_asses[cnt];
                char    resbuf[HOSTNSIZE+BTV_NAME+BTC_VALUE+30];
                char    *rp = resbuf;
                const apiBtvar  *vp;

                if  (ja->bja_op == BJA_NONE)
                        break;

                if  (ja->bja_iscrit & ACRIT_NORUN)  {
                        *rp++ = 'C';
                        *rp++ = '/';
                }
                if  (ja->bja_op < BJA_SEXIT)
#ifdef  CHARSPRINTF
                        rp += strlen(sprintf(rp, "%u", ja->bja_flags));
#else
                        rp += sprintf(rp, "%u", ja->bja_flags);
#endif
                vp = find_var(ja->bja_var.slotno);
                if  (vp->var_id.hostid != dest_hostid)  {
                        rp += strlen(strcpy(rp, look_host(vp->var_id.hostid)));
                        *rp++ = ':';
                }
                rp += strlen(strcpy(rp, vp->var_name));
                if  (ja->bja_op >= BJA_SEXIT)  {
                        *rp++ = '=';
                        strcpy(rp, ja->bja_op == BJA_SEXIT? exitcodename: signalname);
                }
                else  {
                        rp += strlen(strcpy(rp, assname[ja->bja_op - BJA_ASSIGN]));
                        if  (ja->bja_con.const_type == CON_LONG)
                                sprintf(rp, "%ld", (long) ja->bja_con.con_un.con_long);
                        else
                                strcpy(rp, ja->bja_con.con_un.con_string);
                }
                result[cnt] = stracpy(resbuf);
        }
        result[cnt] = (char *) 0;
        return  result;
}

void  put_jslist(char **param)
{
        char  **pp, *p, *n;
        int     had = 0;

        putchar('[');
        for  (pp = param;  (p = *pp);  pp++)  {
                if  (had)
                        putchar(',');
                putchar('\"');
                for  (n = p;  *n;  n++)  {
                        if  (*n == '\"'  ||  *n == '\\')
                                putchar('\\');
                        putchar(*n);
                }
                putchar('\"');
                free(p);
                had++;
        }
        putchar(']');
        free(param);
}

int  perf_listtime(char *arg)
{
        const Timecon           *tp;
        time_t                  nexttime;
        unsigned  char          mday, repeat, nposs;
        USHORT                  nvaldays;
        ULONG                   rate;
        int                     ret;
        apiBtjob                jb;

        if  ((ret = jobextract(arg, &jb)) != 0)
                api_error(ret);

        tp = &jb.h.bj_times;
        mday = 1;

        if  (tp->tc_istime)  {
                nexttime = tp->tc_nexttime;
                repeat = tp->tc_repeat;
                if  ((rate = tp->tc_rate) == TC_MONTHSB  ||  rate == TC_MONTHSE)  {
                        mday = tp->tc_mday;
                        if  (rate == TC_MONTHSE)
                                mday++;
                }
                nvaldays = tp->tc_nvaldays;
                nposs = tp->tc_nposs;
        }
        else  {
                int             cnt, defaultrate;
                HelpaltRef      a;

                nexttime = time((time_t *) 0) + 60L;
                repeat = TC_RETAIN;
                if  ((a = helprdalt($Q{btq repeat options})))  {
                        if  (a->def_alt >= 0)  {
                                int     defrep = a->alt_nums[a->def_alt];
                                freealts(a);
                                if  (defrep > TC_RETAIN)
                                        repeat = repunit->def_alt >= 0?
                                                TC_MINUTES + a->alt_nums[a->def_alt]: TC_HOURS;
                                else
                                        repeat = defrep;
                        }
                        else
                                freealts(a);
                }
                if  ((defaultrate = helpnstate($N{Default number of units})) <= 0)
                        defaultrate = 10;
                rate = defaultrate;
                nvaldays = 0;
                for  (cnt = 0;  cnt < TC_NDAYS;  cnt++)
                        if  (helpnstate((int) ($N{Base for days to avoid}+cnt)) > 0)
                                nvaldays |= 1 << cnt;
                nposs = TC_WAIT1;
                if  ((a = helprdalt($Q{Wtime if not poss})))  {
                        if  (a->def_alt >= 0)
                                nposs = a->alt_nums[a->def_alt];
                        freealts(a);
                }
        }
        html_out_cparam_file("time_pre", 1, arg);
        printf("xbj_times2(\"%s\",%s,%ld,%u,%lu,%u,%u,%u);\n",
               arg, tof(tp->tc_istime), nexttime, repeat, (unsigned long) rate, mday, nvaldays, nposs);
        html_out_or_err("time_post", 0);
        exit(0);
        return  0;
}

int  perf_listconds(char *arg)
{
        int             cnt, ret;
        apiBtjob        jb;

        if  ((ret = jobextract(arg, &jb)) != 0)
                api_error(ret);

        api_readvars(0);

        html_out_cparam_file("conds_pre", 1, arg);
        printf("xbj_conds2(\"%s\",[", arg);
        for  (cnt = 0;  cnt < MAXCVARS;  cnt++)  {
                const  apiJcond  *jc = &jb.h.bj_conds[cnt];
                const  apiBtvar  *vp;

                if  (jc->bjc_compar == C_UNUSED)
                        break;
                if  (!(vp = find_var(jc->bjc_var.slotno)))
                        continue;
                if  (cnt != 0)
                        putchar(',');

                printf("[%d,\"", jc->bjc_iscrit & CCRIT_NORUN? 1: 0);
                if  (vp->var_id.hostid != dest_hostid)
                        printf("%s:", look_host(vp->var_id.hostid));
                printf("%s\",%d,", vp->var_name, jc->bjc_compar);
                if  (jc->bjc_value.const_type == CON_LONG)
                        printf("%ld]", (long) jc->bjc_value.con_un.con_long);
                else
                        printf("\"%s\"]", jc->bjc_value.con_un.con_string);
        }
        fputs("],", stdout);
        put_jslist(gen_vlist(BTM_READ, jb.h.bj_jflags & (BJ_EXPORT|BJ_REMRUNNABLE)));
        fputs(");\n", stdout);
        html_out_or_err("conds_post", 0);
        exit(0);
        return  0;
}

int  perf_listasses(char *arg)
{
        int             cnt, ret;
        apiBtjob        jb;

        if  ((ret = jobextract(arg, &jb)) != 0)
                api_error(ret);

        api_readvars(0);
        html_out_cparam_file("asses_pre", 1, arg);
        printf("xbj_asses2(\"%s\",[", arg);

        for  (cnt = 0;  cnt < MAXSEVARS;  cnt++)  {
                const  apiJass  *ja = &jb.h.bj_asses[cnt];
                const  apiBtvar *vp;

                if  (ja->bja_op == BJA_NONE)
                        break;

                if  (!(vp = find_var(ja->bja_var.slotno)))
                        continue;

                if  (cnt != 0)
                        putchar(',');

                printf("[%d,%u,\"", ja->bja_iscrit & ACRIT_NORUN? 1: 0, ja->bja_flags);

                if  (vp->var_id.hostid != dest_hostid)
                        printf("%s:", look_host(vp->var_id.hostid));
                printf("%s\",%d,", vp->var_name, ja->bja_op);
                if  (ja->bja_op >= BJA_SEXIT)
                        fputs("0]", stdout);
                else  if  (ja->bja_con.const_type == CON_LONG)
                        printf("%ld]", (long) ja->bja_con.con_un.con_long);
                else
                        printf("\"%s\"]", ja->bja_con.con_un.con_string);
        }

        fputs("],", stdout);
        put_jslist(gen_vlist(BTM_READ|BTM_WRITE, jb.h.bj_jflags & (BJ_EXPORT|BJ_REMRUNNABLE)));
        fputs(");\n", stdout);
        html_out_or_err("asses_post", 0);
        exit(0);
        return  0;
}

int  perf_listtp(char *arg)
{
        int             ret;
        apiBtjob        jb;

        if  ((ret = jobextract(arg, &jb)) != 0)
                api_error(ret);
        html_out_cparam_file("tpl_pre", 1, arg);
        printf("xbj_titprill2(\"%s\",\"%s\",[%d,%d,%d,%d],%d,\"%s\",%s,",
               arg, gbatch_gettitle(-1, &jb), jb.h.bj_pri, userpriv.btu_defp,
               userpriv.btu_minp, userpriv.btu_maxp,
               jb.h.bj_ll, jb.h.bj_cmdinterp, tof(userpriv.btu_priv & BTM_SPCREATE));
        put_jslist(gen_qlist());
        putchar(',');
        put_jslist(gen_cilist());
        fputs(");\n", stdout);
        html_out_or_err("tpl_post", 0);
        exit(0);
        return  0;
}

int  perf_listargs(char *arg)
{
        const   char    **args;
        unsigned        acnt;
        int             ret;
        apiBtjob        jb;

        if  ((ret = jobextract(arg, &jb)) != 0)
                api_error(ret);
        if  (!(args = (const  char **) malloc((jb.h.bj_nargs + 1) * sizeof(const char *))))
                ABORT_HTML_NOMEM;
        for  (acnt = 0;  acnt < jb.h.bj_nargs;  acnt++)
                args[acnt] = stracpy(gbatch_getarg(&jb, acnt));
        args[jb.h.bj_nargs] = (const char *) 0;
        html_out_cparam_file("args_pre", 1, arg);
        printf("xbj_args2(\"%s\",", arg);
        put_jslist((char **) args);
        fputs(");\n", stdout);
        html_out_or_err("args_post", 0);
        exit(0);
        return  0;
}

int  perf_listio(char *arg)
{
        unsigned        rcnt;
        int             ret;
        apiBtjob        jb;

        if  ((ret = jobextract(arg, &jb)) != 0)
                api_error(ret);

        html_out_cparam_file("io_pre", 1, arg);
        printf("xbj_io2(\"%s\",[", arg);
        for  (rcnt = 0;  rcnt < jb.h.bj_nredirs;  rcnt++)  {
                const  apiMredir  *rp = gbatch_getredir(&jb, rcnt);
                if  (rcnt != 0)
                        putchar(',');
                printf("[%d,%u,", rp->fd, rp->action);
                if  (rp->action >= RD_ACT_CLOSE)
                        printf("%d]", rp->action == RD_ACT_CLOSE? 0: rp->un.arg);
                else
                        printf("\'%s\']", rp->un.buffer);
        }
        fputs("]);\n", stdout);
        html_out_or_err("io_post", 0);
        exit(0);
        return  0;
}

int  perf_listprocp(char *arg)
{
        int             ret;
        apiBtjob        jb;

        if  ((ret = jobextract(arg, &jb)) != 0)
                api_error(ret);
        html_out_cparam_file("procp_pre", 1, arg);
        printf("xbj_procpar2(\"%s\", \"%s\",%d,%ld,[%d,%d,%d,%d],%s,%s,%s);\n",
               arg,
               gbatch_getdirect(&jb),
               jb.h.bj_umask, (long) jb.h.bj_ulimit,
               jb.h.bj_exits.nlower, jb.h.bj_exits.nupper,
               jb.h.bj_exits.elower, jb.h.bj_exits.eupper,
               tof(jb.h.bj_jflags & BJ_NOADVIFERR),
               tof(jb.h.bj_jflags & BJ_EXPORT),
               tof(jb.h.bj_jflags & BJ_REMRUNNABLE));
        html_out_or_err("procp_post", 0);
        exit(0);
        return  0;
}

static int  do_create_func(const int fromfile)
{
        setup_main_files();
        html_out_or_err("create_pre", 1);
        fputs("xbj_create2(", stdout);
        put_jslist(gen_qlist());
        printf(",\"%s\",", homedof(Realuid));
        put_jslist(gen_cilist());
        printf(", %s);\n", fromfile? "true": "false");
        html_out_or_err(fromfile? "create_postfl": "create_post", 0);
        exit(0);
        return  0;
}

int  perf_listcreate(char *notused)
{
        do_create_func(0);
        return  0;
}

int  perf_listcreatef(char *notused)
{
        do_create_func(1);
        return  0;
}

int  perf_listperms(char *arg)
{
        const   Btmode  *mp;
        int             modecurr, modenext = -1, modecnt, acnt, listorder[MAXMODE];
        int             ret;
        apiBtjob        jb;

        if  ((ret = jobextract(arg, &jb)) != 0)
                api_error(ret);
        mp = &jb.h.bj_mode;
        if  ((mp->u_flags | mp->g_flags | mp->o_flags) == 0)  {
                disp_str = arg;
                html_disperror($E{btjcgi no mode perm});
                exit(E_NOPRIV);
        }

        /* Protect against errors (loops) in data - acnt gives the number
           we've looked at, modenct the number excluding kill */
#ifdef  DONT_DEFINE             /* Force inclusion of them all */
$P{Read mode name}      $N{Read mode name}
$P{Write mode name}     $N{Write mode name}
$P{Reveal mode name}    $N{Reveal mode name}
$P{Display mode name}   $N{Display mode name}
$P{Set mode name}       $N{Set mode name}
$P{Assume owner mode name}      $N{Assume owner mode name}
$P{Assume group mode name}      $N{Assume group mode name}
$P{Give owner mode name}        $N{Give owner mode name}
$P{Give group mode name}        $N{Give group mode name}
$P{Delete mode name}    $N{Delete mode name}
$P{Kill mode name}      $N{Kill mode name}
#endif
        for  (acnt = modecnt = 0, modecurr = $N{Modes initial row};
              acnt < MAXMODE;
              modecurr = modenext, modecnt++, acnt++)  {
                modenext = helpnstate(modecurr);
                if  (modenext < 0)
                        break;
                listorder[modecnt] = modenext;
        }

        html_out_cparam_file("perm_pre", 1, arg);
        for  (acnt = 0;  acnt < modecnt;  acnt++)  {
                int     n = listorder[acnt];
                char    *msg = gprompt(n);
                USHORT  flg;
                n -= $N{Read mode name};
                flg = 1 << n;
                printf("btperm_checkboxes(\'%s\', \'%c\', %s, %s, %s);\n",
                       msg, "RWSMPUVGHDK"[n],
                       tof(mp->u_flags & flg),
                       tof(mp->g_flags & flg),
                       tof(mp->o_flags & flg));
                free(msg);
        }
        html_out_or_err("perm_post", 0);
        exit(0);
        return  0;
}

struct  arginterp  argtypes[] =  {
        {       "format",       AIF_ARG,        set_format      },
        {       "header",       AIF_ARG,        set_header      },
        {       "queue",        AIF_ARG,        set_queue       },
        {       "incnull",      AIF_ARG,        set_incnull     },
        {       "user",         AIF_ARG,        set_user        },
        {       "group",        AIF_ARG,        set_group       },
        {       "export",       AIF_ARG,        set_exported    },
        {       "sort",         AIF_ARG,        set_sort        },
        {       "create",       AIF_NOARG,      perf_listcreate },
        {       "createf",      AIF_NOARG,      perf_listcreatef },
        {       "listtime",     AIF_ARG,        perf_listtime   },
        {       "listconds",    AIF_ARG,        perf_listconds  },
        {       "listasses",    AIF_ARG,        perf_listasses  },
        {       "listtitprill", AIF_ARG,        perf_listtp     },
        {       "listprocpar",  AIF_ARG,        perf_listprocp  },
        {       "listargs",     AIF_ARG,        perf_listargs   },
        {       "listio",       AIF_ARG,        perf_listio     },
        {       "listperms",    AIF_ARG,        perf_listperms  },
        {       "listopts",     AIF_NOARG,      perf_optselect  },
        {       "listformat",   AIF_NOARG,      perf_listformat }
};

int  perf_optselect(char *notused)
{
        html_out_param_file("setopts", 1, 0, html_cookexpiry());
        exit(0);
        return  0;
}

void  interp_args(char **args)
{
        char    **ap, *arg, *cp = (char *) 0;
        int     cnt;

        for  (ap = args;  (arg = *ap);  ap++)  {
                if  ((cp = strchr(arg, ':')) || (cp = strchr(arg, '=')))
                        *cp++ = '\0';
                for  (cnt = 0;  cnt < sizeof(argtypes)/sizeof(struct arginterp);  cnt++)  {
                        if  (ncstrcmp(arg, argtypes[cnt].argname) == 0)  {
                                if  ((cp  &&  argtypes[cnt].flags == AIF_NOARG)  ||
                                     (!cp  &&  argtypes[cnt].flags == AIF_ARG)  ||
                                     !(argtypes[cnt].arg_fn)(cp))
                                        goto  badarg;
                                goto  ok;
                        }
                }
        badarg:
                html_out_or_err("badargs", 1);
                exit(E_USAGE);
        ok:
                ;
        }
}


/* Ye olde main routine.  */

MAINFN_TYPE  main(int argc, char **argv)
{
        char            **newargs;
        int_ugid_t      chku;

        versionprint(argv, "$Revision: 1.11 $", 0);

        if  ((progname = strrchr(argv[0], '/')))
                progname++;
        else
                progname = argv[0];

        init_mcfile();
        tzset();
        html_openini();
        hash_hostfile();
        Effuid = geteuid();
        Effgid = getegid();
        if  ((chku = lookup_uname(BATCHUNAME)) == UNKNOWN_UID)
                Daemuid = ROOTID;
        else
                Daemuid = chku;
        newargs = cgi_arginterp(argc, argv, CGI_AI_REMHOST);
        /* Side effect of cgi_arginterp is to set Realuid */
        Cfile = open_cfile(MISC_UCONFIG, "btrest.help");
        realuname = prin_uname(Realuid);        /* Realuid got set by cgi_arginterp */
        Realgid = lastgid;
        setgid(Realgid);
        setuid(Realuid);
        interp_args(newargs);
        if  (jobqueue  &&  Dispflags & DF_SUPPNULL)
                Jaccess_flags |= GBATCH_FLAG_QUEUEONLY;
        if  (Restru  &&  strcmp(Restru, realuname) == 0)
                Jaccess_flags |= GBATCH_FLAG_USERONLY;
        if  (Restrg  &&  strcmp(Restrg, prin_gname(getegid())) == 0)
                Jaccess_flags |= GBATCH_FLAG_GROUPONLY;

        setup_main_files();
        api_readvars(0);

        if  (!(progresslist = helprdalt($Q{Job progress code})))  {
                disp_arg[9] = $Q{Job progress code};
                html_disperror($E{Missing alternative code});
                return  E_SETUP;
        }
        if  (!(repunit = helprdalt($Q{Repeat unit abbrev})))  {
                disp_arg[9] = $Q{Repeat unit abbrev};
                html_disperror($E{Missing alternative code});
                return  E_SETUP;
        }
        if  (!(days_abbrev = helprdalt($Q{Weekdays})))  {
                disp_arg[9] = $Q{Weekdays};
                html_disperror($E{Missing alternative code});
                return  E_SETUP;
        }
        if  (!(ifnposses = helprdalt($Q{Ifnposses})))  {
                disp_arg[9] = $Q{Ifnposses};
                print_error($E{Missing alternative code});
        }
        exitcodename = gprompt($P{Assign exit code});
        signalname = gprompt($P{Assign signal});
        localrun = gprompt($P{Locally run});
        if  (!hadhdrarg  &&  html_inibool("headers", 0))
                Dispflags |= DF_HAS_HDR;
        fmt_setup();
        html_output_file("list_preamble", 1);
        jdisplay();
        html_output_file("list_postamble", 0);
        return  0;
}
