/* xbr_jlist.c -- job list handling for gbch-xr

   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 "config.h"
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#ifdef  HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <errno.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 <gtk/gtk.h>
#include "incl_unix.h"
#include "incl_sig.h"
#include "incl_net.h"
#include "defaults.h"
#include "network.h"
#include "incl_ugid.h"
#include "helpalt.h"
#include "files.h"
#include "btconst.h"
#include "timecon.h"
#include "btmode.h"
#include "bjparam.h"
#include "btjob.h"
#include "q_shm.h"
#include "cmdint.h"
#include "btvar.h"
#include "btuser.h"
#include "shreq.h"
#include "statenums.h"
#include "errnums.h"
#include "jvuprocs.h"
#include "xbr_ext.h"
#include "gtk_lib.h"
#include "stringvec.h"
#ifdef  HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#include "optflags.h"

static  char    Filename[] = __FILE__;

#define NAMESIZE        14      /* Padding for temp file */

#define C_MASK          037

static  char    *tmpfl;

unsigned                pend_njobs, pend_max;
struct  pend_job        *pend_list;

extern  char            *Curr_pwd;

char            *execprog, *ldsvprog, *gtkprog;
HelpaltRef      daynames_full, monnames, ifnposs_names, repunit_full;
char            *delend_full, *retend_full;
char            *no_title, *no_name;
#ifdef  SUFFIXES
char            *cmd_prefix, *cmd_suffix, *job_prefix, *job_suffix;
#endif

ULONG           default_rate;
unsigned char   default_interval,
                default_nposs;
USHORT          defavoid, def_assflags;

#define ppermitted(flg) (mypriv->btu_priv & flg)

HelpaltRef      progresslist;
extern  HelpaltRef      assnames, actnames, stdinnames;

#ifdef  HAVE_SYS_RESOURCE_H
extern  int     Max_files;
#endif

int  jlist_dirty()
{
        int     cnt;

        for  (cnt = 0;  cnt < pend_njobs;  cnt++)
                if  (pend_list[cnt].changes != 0)
                        return  1;
        return  0;
}

/* Generate list of queue names in ql */

void  gen_qlist(struct stringvec *ql)
{
        int     jcnt;

        rjobfile(1);

        stringvec_init(ql);
        stringvec_append(ql, "");

        for  (jcnt = 0;  jcnt < Job_seg.njobs;  jcnt++)  {
                const   char    *tit = title_of(jj_ptrs[jcnt]);
                const   char    *ip;
                char    *it;
                unsigned        lng;
                if  (!(ip = strchr(tit, ':')))
                        continue;
                lng = ip - tit + 1;
                if  (!(it = malloc(lng)))
                        ABORT_NOMEM;
                strncpy(it, tit, lng-1);
                it[lng-1] = '\0';
                stringvec_insert_unique(ql, it);
                free(it);
        }

        for  (jcnt = 0;  jcnt < pend_njobs;  jcnt++)
                if  (pend_list[jcnt].jobqueue)
                        stringvec_insert_unique(ql, pend_list[jcnt].jobqueue);
}

void  initmoremsgs()
{
        int     n;
#ifdef  HAVE_SYS_RESOURCE_H
        struct  rlimit  cfiles;
        getrlimit(RLIMIT_NOFILE, &cfiles);
        Max_files = cfiles.rlim_cur;
#endif
        if  (!(tmpfl = malloc((unsigned)(strlen(spdir) + 2 + NAMESIZE))))
                ABORT_NOMEM;

        if  (!(progresslist = helprdalt($Q{Job progress code})))  {
                disp_arg[9] = $Q{Job progress code};
                print_error($E{Missing alternative code});
        }
        if  (!(assnames = helprdalt($Q{Assignment names})))  {
                disp_arg[9] = $Q{Assignment names};
                print_error($E{Missing alternative code});
        }
        delend_full = gprompt($P{Delete at end full});
        retend_full = gprompt($P{Retain at end full});
        repunit_full = helprdalt($Q{Repeat unit full});
        ifnposs_names = helprdalt($Q{Wtime if not poss});
        exitcodename = gprompt($P{Assign exit code});
        signalname = gprompt($P{Assign signal});
        no_title = gprompt($P{xbtr no title});
        no_name = gprompt($P{xbtr no file name});
        if  (!(actnames = helprdalt($Q{Redirection type names})))  {
                disp_arg[9] = $Q{Redirection type names};
                print_error($E{Missing alternative code});
        }
        if  (!(days_abbrev = helprdalt($Q{Weekdays})))  {
                disp_arg[9] = $Q{Weekdays};
                print_error($E{Missing alternative code});
        }
        daynames_full = helprdalt($Q{Weekdays full});
        monnames = helprdalt($Q{Months full});
        if  (!(repunit = helprdalt($Q{Repeat unit abbrev})))  {
                disp_arg[9] = $Q{Repeat unit abbrev};
                print_error($E{Missing alternative code});
        }
        if  ((n = helpnstate($N{Default repeat alternative})) < 0  ||  n > TC_YEARS)
                n = TC_RETAIN;
        default_interval = (unsigned char) n;
        if  ((n = helpnstate($N{Default number of units})) <= 0)
                n = 10;
        default_rate = (ULONG) n;
        if  ((n = helpnstate($N{Default skip delay option})) < 0  ||  n > TC_CATCHUP)
                n = TC_WAIT1;
        default_nposs = (unsigned char) n;
        defavoid = 0;
        for  (n = 0;  n < TC_NDAYS;  n++)
                if  (helpnstate($N{Base for days to avoid}+n) > 0)
                        defavoid |= 1 << n;

#ifdef  SUFFIXES
        job_prefix = helpprmpt($P{Default job file prefix});
        job_suffix = helpprmpt($P{Default job file suffix});
        cmd_prefix = helpprmpt($P{Default cmd file prefix});
        cmd_suffix = helpprmpt($P{Default cmd file suffix});
        if  (!job_prefix)
                job_prefix = "";
        if  (!job_suffix)
                job_suffix = "";
        if  (!cmd_prefix)
                cmd_prefix = "";
        if  (!cmd_prefix)
                cmd_suffix = "";
#endif

        ldsvprog = envprocess(GTKLDSAV);
        execprog = envprocess(EXECPROG);
        gtkprog = envprocess(GTKSAVE);
        Save_umask = umask(C_MASK);
}

/* Update job title display */

void  update_title(struct pend_job *pj)
{
        GtkTreeSelection  *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(jwid));
        GtkTreeIter  iter, citer;

        if  (gtk_tree_selection_get_selected(sel, NULL, &iter))  {
                const  char  *ip = title_of(pj->job);
                if  (strlen(ip) == 0)
                        ip = no_title;
                gtk_tree_model_sort_convert_iter_to_child_iter(sorted_jlist_store, &citer, &iter);
                gtk_list_store_set(raw_jlist_store, &citer, JLIST_TITLE_COL, ip, -1);
        }
}

void  update_state(struct pend_job *pj)
{
        GtkTreeSelection  *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(jwid));
        GtkTreeIter  iter, citer;
        if  (gtk_tree_selection_get_selected(sel, NULL, &iter))  {
                gtk_tree_model_sort_convert_iter_to_child_iter(sorted_jlist_store, &citer, &iter);
                gtk_list_store_set(raw_jlist_store, &citer, JLIST_PROGRESS_COL, disp_alt(pj->job->h.bj_progress, progresslist), -1);
        }
}

int  getselectedjob(const int moan)
{
        GtkTreeSelection  *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(jwid));
        GtkTreeIter  iter;

        if  (gtk_tree_selection_get_selected(sel, NULL, &iter))  {
                guint  seq;
                gtk_tree_model_get(GTK_TREE_MODEL(sorted_jlist_store), &iter, JLIST_SEQ_COL, &seq, -1);
                return  (int) seq;
        }
        if  (moan)
                doerror(pend_njobs != 0? $EH{xmbtq no job selected}: $EH{xmbtq no jobs to select});
        return  -1;
}

struct pend_job *job_or_deflt(GtkAction *action)
{
        const   char    *act = gtk_action_get_name(action);
        if  (act[strlen(act)-1] != 'd')  {
                int     indx;
                if  ((indx = getselectedjob(1)) < 0)
                        return  (struct pend_job *) 0;
                return  &pend_list[indx];
        }
        return  &default_pend;
}

#define INIT_PEND       20
#define INC_PEND        5

static void  chkalloc()
{
        if  (pend_njobs >= pend_max)  {
                if  (pend_max == 0)  {
                        pend_max = INIT_PEND;
                        pend_list = (struct pend_job *) malloc(INIT_PEND * sizeof(struct pend_job));
                }
                else  {
                        pend_max += INC_PEND;
                        pend_list = (struct pend_job *) realloc((char *) pend_list, INC_PEND * sizeof(struct pend_job));
                }
                if  (!pend_list)
                        ABORT_NOMEM;
        }
}

static void  append_job(struct pend_job *pj)
{
        GtkTreeSelection  *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(jwid));
        GtkTreeIter     iter, piter;
        const  char     *tit;
        char    *cf, *jf;

        tit = title_of(pj->job);
        if  (strlen(tit) == 0)
                tit = no_title;
        cf = !pj->cmdfile_name || strlen(pj->cmdfile_name) == 0? no_name: pj->cmdfile_name;
        jf = !pj->jobfile_name || strlen(pj->jobfile_name) == 0? no_name: pj->jobfile_name;

        gtk_list_store_append(raw_jlist_store, &iter);
        gtk_list_store_set(raw_jlist_store, &iter,
                           JLIST_SEQ_COL,       pend_njobs,
                           JLIST_PROGRESS_COL,  disp_alt(pj->job->h.bj_progress, progresslist),
                           JLIST_TITLE_COL,     tit,
                           JLIST_CMDFILE_COL,   cf,
                           JLIST_JOBFILE_COL,   jf,
                           JLIST_DIRECT_COL,    pj->directory,
                           JLIST_UNSAVED_COL,   pj->changes != 0,
                           -1);

        gtk_tree_model_sort_convert_child_iter_to_iter(sorted_jlist_store, &piter, &iter);
        gtk_tree_selection_select_iter(sel, &piter);
}

void  cb_jnew()
{
        struct  pend_job  *pj;

        chkalloc();
        pj = &pend_list[pend_njobs];
        job_initialise(pj, stracpy(Curr_pwd), (char *) 0);
        append_job(pj);
        pend_njobs++;
}

char *gen_path(char *dir, char *fil)
{
        char    *result;
        if  (!(result = malloc((unsigned) (strlen(dir) + strlen(fil) + 2))))
                ABORT_NOMEM;
        sprintf(result, "%s/%s", dir, fil);
        return  result;
}


static gboolean  isit_cmd(const GtkFileFilterInfo *filter_info, gpointer data)
{
        FILE    *fp;
        struct  stat    sbuf;
        char    Pbuf[2000];

        if  (stat(filter_info->filename, &sbuf) < 0  ||  (sbuf.st_mode & S_IFMT) != S_IFREG  ||  sbuf.st_size == 0)
                return  FALSE;

        sprintf(Pbuf, "%s -r %s", ldsvprog, filter_info->filename);

        fp = popen(Pbuf, "r");
        if  (!fp)
             return  FALSE;

        while  (fgets(Pbuf, sizeof(Pbuf), fp))  {
                char    *lp;
                if  (Pbuf[0] == '#')
                        continue;
                if  ((lp = strchr(Pbuf, '\n')))
                        *lp = '\0';
                if  (strncmp(Pbuf, BTR_PROGRAM " ", sizeof(BTR_PROGRAM)) == 0)  {
                        pclose(fp);
                        return  TRUE;
                }
                if  (!strchr(Pbuf, '='))  {
                        pclose(fp);
                        return  FALSE;
                }
        }
        pclose(fp);
        return  FALSE;
}

void  cb_jopen()
{
        GtkWidget  *fsb;
        char    *pr;
        static  GtkFileFilter  *isit_cmd_filt = 0;

        pr = gprompt($P{xbtr open job dlgtit});
        fsb = gtk_file_chooser_dialog_new(pr, GTK_WINDOW(toplevel),
                                          GTK_FILE_CHOOSER_ACTION_OPEN,
                                          GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
                                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                          NULL);
        free(pr);
        gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fsb), Curr_pwd);

        if  (!isit_cmd_filt)  {
                pr = gprompt($P{xbtr poss job cmd files});
                isit_cmd_filt = gtk_file_filter_new();
                gtk_file_filter_set_name(isit_cmd_filt, pr);
                free(pr);
                gtk_file_filter_add_custom(isit_cmd_filt, GTK_FILE_FILTER_FILENAME, (GtkFileFilterFunc) isit_cmd, NULL, NULL);
        }

        gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fsb), isit_cmd_filt);
        g_object_ref(G_OBJECT(isit_cmd_filt));

        while  (gtk_dialog_run(GTK_DIALOG(fsb)) == GTK_RESPONSE_ACCEPT)  {
                char  *pathname = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fsb));
                char  *sp = strrchr(pathname, '/');
                char  *dirname = pathname;
                char  *filename = sp+1;
                int     cnt, ret;

                if  (sp)  {
                        if  (sp == pathname)
                                dirname = "/";
                        *sp = '\0';
                }
                else  {
                        dirname = Curr_pwd;
                        filename = pathname;
                }
                for  (cnt = 0;  cnt < pend_njobs;  cnt++)  {
                        if  (pend_list[cnt].directory  &&  strcmp(dirname, pend_list[cnt].directory) == 0  &&
                             pend_list[cnt].cmdfile_name  &&  strcmp(filename, pend_list[cnt].cmdfile_name) == 0)  {
                                doerror($EH{xmbtr file already loaded});
                                g_free(pathname);
                                goto  endl;
                        }
                }

                chkalloc();
                job_initialise(&pend_list[pend_njobs], stracpy(dirname), stracpy(filename));

                /* Carry on even if we have an error.  We may as well include the thing.  */

                if  ((ret = job_load(&pend_list[pend_njobs])))
                        doerror(ret);
                else
                        pend_list[pend_njobs].changes = 0;

                append_job(&pend_list[pend_njobs]);
                pend_njobs++;
                break;
        endl:
                ;
        }
        gtk_widget_destroy(fsb);
}

void  cb_jclosedel(GtkAction *action)
{
        int             indx, isdel;
        unsigned        cnt;
        struct          pend_job        *pj;
        GtkTreeIter     iter;

        if  ((indx = getselectedjob(1)) < 0)
                return;

        /* indx should be the right place in raw_jlist_store */

        isdel = strcmp(gtk_action_get_name(action), "Delete") == 0;

        pj = &pend_list[indx];
        if  (pj->changes > 0  &&  !Confirm(isdel? $PH{xmbtr confirm delete}: $PH{xmbtr confirm close}))
                return;

        if  (isdel)  {
                GString *cmdname = g_string_new(NULL);

                if  (pj->jobfile_name)  {
                        g_string_printf(cmdname, "%s -d %s/%s", ldsvprog, pj->directory, pj->jobfile_name);
                        if  (system(cmdname->str) != 0)
                                doerror($EH{xmbtr cannot delete job file});
                }
                if  (pj->cmdfile_name)  {
                        g_string_printf(cmdname, "%s -d %s/%s", ldsvprog, pj->directory, pj->cmdfile_name);
                        if  (system(cmdname->str) != 0)
                                doerror($EH{xmbtr cannot delete cmd file});
                }
                g_string_free(cmdname, TRUE);
        }

        if  (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(raw_jlist_store), &iter, NULL, indx))
                gtk_list_store_remove(raw_jlist_store, &iter);

        free((char *) pj->job);
        free(pj->directory);
        if  (pj->jobfile_name)
                free(pj->jobfile_name);
        if  (pj->cmdfile_name)
                free(pj->cmdfile_name);
        pend_njobs--;

        /* Move up all the jobs below that and change the index */

        for  (cnt = indx;  cnt < pend_njobs;  cnt++)  {
                pend_list[cnt] = pend_list[cnt+1];
                if  (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(raw_jlist_store), &iter, NULL, cnt))
                        gtk_list_store_set(raw_jlist_store, &iter, JLIST_SEQ_COL, cnt, -1);
        }
}

void  cb_jcmdfile(GtkAction *action)
{
        int             isjobfile = strcmp(gtk_action_get_name(action), "Jobfile") == 0;
        int             indx;
        GtkWidget       *fsb;
        char            *pr;
        struct  pend_job        *pj;

        if  ((indx = getselectedjob(1)) < 0)
                return;

        pj = &pend_list[indx];

        pr = gprompt(isjobfile? $P{xbtr set job file dlgtit}: $P{xbtr set cmd file dlgtit});
        fsb = gtk_file_chooser_dialog_new(pr, GTK_WINDOW(toplevel),
                                          GTK_FILE_CHOOSER_ACTION_SAVE,
                                          GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
                                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                          NULL);
        free(pr);
        gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(fsb), TRUE);
        gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fsb), pj->directory);
        if  (gtk_dialog_run(GTK_DIALOG(fsb)) == GTK_RESPONSE_ACCEPT)  {
                char  *pathname = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fsb));
                char  *sp = strrchr(pathname, '/');
                char  *dirname = pathname;
                char  *filename = sp+1;
                int     col;
                GtkTreeSelection  *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(jwid));
                GtkTreeIter     iter, citer;

                if  (sp)  {
                        if  (sp == pathname)
                                dirname = "/";
                        *sp = '\0';
                }
                else  {
                        dirname = pj->directory;
                        filename = pathname;
                }
                if  (strcmp(pj->directory, dirname) != 0)  {
                        free(pj->directory);
                        pj->directory = stracpy(dirname);
                }
                if  (isjobfile)  {
                        if  (pj->jobfile_name)
                                free(pj->jobfile_name);
                        pj->jobfile_name = stracpy(filename);
                        col = JLIST_JOBFILE_COL;
                }
                else  {
                        if  (pj->cmdfile_name)
                                free(pj->cmdfile_name);
                        pj->cmdfile_name = stracpy(filename);
                        col = JLIST_CMDFILE_COL;
                }
                if  (gtk_tree_selection_get_selected(sel, NULL, &iter))  {
                        gtk_tree_model_sort_convert_iter_to_child_iter(sorted_jlist_store, &citer, &iter);
                        gtk_list_store_set(raw_jlist_store, &citer, JLIST_DIRECT_COL, dirname, col, filename, -1);
                }
                g_free(pathname);
        }
        gtk_widget_destroy(fsb);
}

void  cb_jsave()
{
        int     indx, ret;
        struct  pend_job        *pj;
        char    *jfpath;

        if  ((indx = getselectedjob(1)) < 0)
                return;
        pj = &pend_list[indx];
        if  (pj->changes <= 0  &&  !Confirm($PH{xmbtr file save confirm}))
                return;
        if  (!pj->jobfile_name)  {
                doerror($EH{xmbtr no job file name});
                return;
        }
        if  (!pj->cmdfile_name)  {
                doerror($EH{xmbtr no cmd file name});
                return;
        }

        jfpath = gen_path(pj->directory, pj->jobfile_name);
        ret = access(jfpath, F_OK);
        free(jfpath);
        if  (ret < 0)  {
                doerror($EH{xbtr no job file set up});
                return;
        }
        if  ((ret = job_save(pj)))
                doerror(ret);
        else  {
                GtkTreeIter     iter;
                pj->changes = 0;
                if  (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(raw_jlist_store), &iter, NULL, indx))
                        gtk_list_store_set(raw_jlist_store, &iter, JLIST_UNSAVED_COL, FALSE, -1);
        }
}

void  note_changes(struct pend_job *pj)
{
        GtkTreeIter     iter;

        pj->changes++;
        pj->nosubmit++;
        if  (pj == &default_pend)
                return;
        if  (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(raw_jlist_store), &iter, NULL, pj - pend_list))
                gtk_list_store_set(raw_jlist_store, &iter, JLIST_UNSAVED_COL, TRUE, -1);
}

void  cb_edit()
{
        int     indx;
        struct  pend_job        *pj;
        char    *path;
        PIDTYPE pid;

        if  ((indx = getselectedjob(1)) < 0)
                return;
        pj = &pend_list[indx];
        if  (!pj->directory || !pj->jobfile_name)  {
                doerror($EH{xmbtr no job file name});
                return;
        }
        path = gen_path(pj->directory, pj->jobfile_name);

        if  ((pid = fork()) != 0)  {
                int     status;
                free(path);
                if  (pid < 0)  {
                        doerror($EH{xmbtr cannot fork for editor});
                        return;
                }
#ifdef  HAVE_WAITPID
                while  (waitpid(pid, &status, 0) < 0)
                        ;
#else
                while  (wait(&status) != pid)
                        ;
#endif
                if  (status != 0)  {
                        disp_str = editor_name;
                        doerror($EH{xmbtr cannot execute editor});
                }
                return;
        }

        umask((int) Save_umask);
        Ignored_error = chdir(pj->directory);
        if  (xterm_edit)  {
                char    *termname = envprocess("${XTERM:-xterm}");
                execl(execprog, termname, "-e", editor_name, path, (char *) 0);
        }
        else
                execl(execprog, editor_name, path, (char *) 0);
        exit(255);
}

void  cb_direct()
{
        GtkWidget       *dlg;
        char            *pr;

        pr = gprompt($P{xbtr select working dir});
        dlg = gtk_file_chooser_dialog_new(pr,
                                          NULL,
                                          GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
                                          GTK_STOCK_OK, GTK_RESPONSE_OK,
                                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                          NULL);
        free(pr);
        gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dlg), Curr_pwd);
        if  (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_OK)  {
                gchar  *selected = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg));
                free(Curr_pwd);
                Curr_pwd = stracpy(selected);
                g_free(selected);
        }
        gtk_widget_destroy(dlg);
}

/* Generate output file name */

static FILE *goutfile(jobno_t *jn)
{
        FILE    *res;
        int     fid;

        for  (;;)  {
                sprintf(tmpfl, "%s/%s", spdir, mkspid(SPNAM, *jn));
                if  ((fid = open(tmpfl, O_WRONLY|O_CREAT|O_EXCL, 0666)) >= 0)
                        break;
                *jn += JN_INC;
        }
        if  ((res = fdopen(fid, "w")) == (FILE *) 0)  {
                unlink(tmpfl);
                ABORT_NOMEM;
        }
        return  res;
}

void  cb_submit()
{
        int                     indx, ch;
        ULONG                   jindx;
        struct  pend_job        *pj;
        BtjobRef                jreq;
        char                    *path;
        FILE                    *outf, *inf;
        GString                 *readcmd;
        static  jobno_t         jn;
        Shipc                   Oreq;
        Repmess                 rr;
        extern  long            mymtype;

        if  ((indx = getselectedjob(1)) < 0)
                return;
        pj = &pend_list[indx];
        if  (pj->nosubmit == 0  &&  !Confirm($PH{xmbtr job unchanged confirm}))
                return;
        if  (pj->job->h.bj_times.tc_istime  &&  pj->job->h.bj_times.tc_nexttime < time((time_t *) 0))  {
                doerror($EH{xmbtr cannot submit not future});
                return;
        }
        if  (!pj->jobfile_name)  {
                doerror($EH{xmbtr no job file name});
                return;
        }
        path = gen_path(pj->directory, pj->jobfile_name);
        if  (access(path, F_OK))  {
                free(path);
                doerror($EH{xmbtr no such file});
                return;
        }

        readcmd = g_string_new(NULL);
        g_string_printf(readcmd, "%s -r %s", ldsvprog, path);
        inf = popen(readcmd->str, "r");
        g_string_free(readcmd, TRUE);
        free(path);

        if  (!inf)  {
                doerror($EH{xmbtr cannot open job file});
                return;
        }

        jn = jn? jn+1: getpid();
        outf = goutfile(&jn);

        while  ((ch = getc(inf)) != EOF)  {
                if  (putc(ch, outf) == EOF)  {
                        unlink(tmpfl);
                        pclose(inf);
                        fclose(outf);
                        doerror($EH{No room for job});
                        return;
                }
        }

        fclose(outf);
        if  (pclose(inf) != 0)  {
                unlink(tmpfl);
                doerror($EH{xmbtr cannot open job file});
                return;
        }

        jreq = &Xbuffer->Ring[jindx = getxbuf()];
        BLOCK_COPY(jreq, pj->job, sizeof(Btjob));
        jreq->h.bj_slotno = -1;
        jreq->h.bj_job = jn;
        time(&jreq->h.bj_time);

        Oreq.sh_mtype = TO_SCHED;
        Oreq.sh_params.mcode = J_CREATE;
        Oreq.sh_params.uuid = pj->userid;
        Oreq.sh_params.ugid = pj->grpid;
        Oreq.sh_params.hostid = 0;
        Oreq.sh_params.param = 0;
        Oreq.sh_un.sh_jobindex = jindx;
        Oreq.sh_params.upid = getpid();
#ifdef  USING_MMAP
        sync_xfermmap();
#endif
        if  (msgsnd(Ctrl_chan, (struct msgbuf *) &Oreq, sizeof(Shreq) + sizeof(ULONG), 0) < 0)  {
                int     savee = errno;
                unlink(tmpfl);
                freexbuf(jindx);
                errno = savee;
                doerror(savee == EAGAIN? $EH{IPC msg q full}: $EH{IPC msg q error});
                return;
        }

        while  (msgrcv(Ctrl_chan, (struct msgbuf *) &rr, sizeof(Shreq), mymtype, 0) < 0)  {
                if  (errno == EINTR)
                        continue;
                freexbuf(jindx);
                doerror($E{Error on IPC});
                return;
        }

        freexbuf(jindx);

        if  (rr.outmsg.mcode != J_OK) {
                unlink(tmpfl);
                if  ((rr.outmsg.mcode & REQ_TYPE) != JOB_REPLY)  { /* Not expecting net errors */
                        disp_arg[0] = rr.outmsg.mcode;
                        doerror($EH{Unexpected sched message});
                }
                else  {
                        disp_str = title_of(pj->job);
                        doerror((int) ((rr.outmsg.mcode & ~REQ_TYPE) + $EH{Base for scheduler job errors}));
                }
                return;
        }

        if  (pj->Verbose)  {
                disp_arg[0] = jn;
                disp_str = title_of(pj->job);
                doinfo(disp_str[0]? $E{xmbtr job created ok title}: $E{xmbtr job created ok no title});
        }
        pj->nosubmit = 0;
}
