Changes from APPLIANCE_HEAD:
[ira/wip.git] / source3 / printing / printing.c
index b6265798a122671b4dd2374c66b409ba36b64675..3f8c542ec7d57575ad8a0c996a0f34036f07da57 100644 (file)
@@ -1,8 +1,9 @@
+#define OLD_NTDOMAIN 1
 /* 
    Unix SMB/Netbios implementation.
-   Version 1.9.
-   printing routines
-   Copyright (C) Andrew Tridgell 1992-1995
+   Version 3.0
+   printing backend routines
+   Copyright (C) Andrew Tridgell 1992-2000
    
    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
 */
 
 #include "includes.h"
+
 extern int DEBUGLEVEL;
-extern connection_struct Connections[];
-extern files_struct Files[];
 
-static BOOL * lpq_cache_reset=NULL;
+/* 
+   the printing backend revolves around a tdb database that stores the
+   SMB view of the print queue 
+   
+   The key for this database is a jobid - a internally generated number that
+   uniquely identifies a print job
+
+   reading the print queue involves two steps:
+     - possibly running lpq and updating the internal database from that
+     - reading entries from the database
 
-static int check_lpq_cache(int snum) {
-  static int lpq_caches=0;
-  
-  if (lpq_caches <= snum) {
-      BOOL * p;
-      p = (BOOL *) Realloc(lpq_cache_reset,(snum+1)*sizeof(BOOL));
-      if (p) {
-        lpq_cache_reset=p;
-        lpq_caches = snum+1;
-      }
-  }
-  return lpq_caches;
+   jobids are assigned when a job starts spooling. 
+*/
+
+struct printjob {
+       pid_t pid; /* which process launched the job */
+       int sysjob; /* the system (lp) job number */
+       int fd; /* file descriptor of open file if open */
+       time_t starttime; /* when the job started spooling */
+       int status; /* the status of this job */
+       size_t size; /* the size of the job so far */
+       BOOL spooled; /* has it been sent to the spooler yet? */
+       BOOL smbjob; /* set if the job is a SMB job */
+       fstring filename; /* the filename used to spool the file */
+       fstring jobname; /* the job name given to us by the client */
+       fstring user; /* the user who started the job */
+       fstring qname; /* name of the print queue the job was sent to */
+};
+
+/* the open printing.tdb database */
+static TDB_CONTEXT *tdb;
+static pid_t local_pid;
+
+#define PRINT_MAX_JOBID 10000
+#define UNIX_JOB_START PRINT_MAX_JOBID
+
+#define PRINT_SPOOL_PREFIX "smbprn."
+#define PRINT_DATABASE_VERSION 2
+
+static int get_queue_status(int, print_status_struct *);
+
+/****************************************************************************
+initialise the printing backend. Called once at startup. 
+Does not survive a fork
+****************************************************************************/
+BOOL print_backend_init(void)
+{
+       char *sversion = "INFO/version";
+
+       if (tdb && local_pid == sys_getpid()) return True;
+       tdb = tdb_open(lock_path("printing.tdb"), 0, 0, O_RDWR|O_CREAT, 0600);
+       if (!tdb) {
+               DEBUG(0,("Failed to open printing backend database\n"));
+       }
+       local_pid = sys_getpid();
+
+       /* handle a Samba upgrade */
+       tdb_lock_bystring(tdb, sversion);
+       if (tdb_fetch_int(tdb, sversion) != PRINT_DATABASE_VERSION) {
+               tdb_traverse(tdb, (tdb_traverse_func)tdb_delete, NULL);
+               tdb_store_int(tdb, sversion, PRINT_DATABASE_VERSION);
+       }
+       tdb_unlock_bystring(tdb, sversion);
+
+       return nt_printing_init();
 }
 
-void lpq_reset(int snum)
+/****************************************************************************
+useful function to generate a tdb key
+****************************************************************************/
+static TDB_DATA print_key(int jobid)
 {
-  if (check_lpq_cache(snum) > snum) lpq_cache_reset[snum]=True;
+       static int j;
+       TDB_DATA ret;
+
+       j = jobid;
+       ret.dptr = (void *)&j;
+       ret.dsize = sizeof(j);
+       return ret;
 }
 
+/****************************************************************************
+useful function to find a print job in the database
+****************************************************************************/
+static struct printjob *print_job_find(int jobid)
+{
+       static struct printjob pjob;
+       TDB_DATA ret;
+
+       ret = tdb_fetch(tdb, print_key(jobid));
+       if (!ret.dptr || ret.dsize != sizeof(pjob)) return NULL;
+
+       memcpy(&pjob, ret.dptr, sizeof(pjob));
+       free(ret.dptr);
+       return &pjob;
+}
 
 /****************************************************************************
-Build the print command in the supplied buffer. This means getting the
-print command for the service and inserting the printer name and the
-print file name. Return NULL on error, else the passed buffer pointer.
+store a job structure back to the database
 ****************************************************************************/
-static char *build_print_command(int cnum, char *command, char *syscmd, char *filename1)
+static BOOL print_job_store(int jobid, struct printjob *pjob)
 {
-  int snum = SNUM(cnum);
-  char *tstr;
-  pstring filename;
-  
-  /* get the print command for the service. */
-  tstr = command;
-  if (!syscmd || !tstr) {
-    DEBUG(0,("No print command for service `%s'\n", SERVICE(snum)));
-    return (NULL);
-  }
-
-  /* copy the command into the buffer for extensive meddling. */
-  StrnCpy(syscmd, tstr, sizeof(pstring) - 1);
-  
-  /* look for "%s" in the string. If there is no %s, we cannot print. */   
-  if (!strstr(syscmd, "%s") && !strstr(syscmd, "%f")) {
-    DEBUG(2,("WARNING! No placeholder for the filename in the print command for service %s!\n", SERVICE(snum)));
-  }
-  
-  if (strstr(syscmd,"%s")) {
-    int iOffset = strstr(syscmd, "%s") - syscmd;
-    
-    /* construct the full path for the filename, shouldn't be necessary unless
-       the subshell causes a "cd" to be executed.
-       Only use the full path if there isn't a / preceding the %s */
-    if (iOffset==0 || syscmd[iOffset-1] != '/') {
-      StrnCpy(filename,Connections[cnum].connectpath,sizeof(filename)-1);
-      trim_string(filename,"","/");
-      strcat(filename,"/");
-      strcat(filename,filename1);
-    }
-    else
-      strcpy(filename,filename1);
-    
-    string_sub(syscmd, "%s", filename);
-  }
-  
-  string_sub(syscmd, "%f", filename1);
-  
-  /* Does the service have a printername? If not, make a fake and empty    */
-  /* printer name. That way a %p is treated sanely if no printer */
-  /* name was specified to replace it. This eventuality is logged.         */
-  tstr = PRINTERNAME(snum);
-  if (tstr == NULL || tstr[0] == '\0') {
-    DEBUG(3,( "No printer name - using %s.\n", SERVICE(snum)));
-    tstr = SERVICE(snum);
-  }
-  
-  string_sub(syscmd, "%p", tstr);
+       TDB_DATA d;
+       d.dptr = (void *)pjob;
+       d.dsize = sizeof(*pjob);
+
+       return (tdb_store(tdb, print_key(jobid), d, TDB_REPLACE) == 0);
+}
+
+/****************************************************************************
+run a given print command 
+a null terminated list of value/substitute pairs is provided
+for local substitution strings
+****************************************************************************/
+static int print_run_command(int snum,char *command, 
+                            char *outfile,
+                            ...)
+{
+       pstring syscmd;
+       char *p, *arg;
+       int ret;
+       va_list ap;
+
+       if (!command || !*command) return -1;
+
+       if (!VALID_SNUM(snum)) {
+               DEBUG(0,("Invalid snum %d for command %s\n", snum, command));
+               return -1;
+       }
+
+       pstrcpy(syscmd, command);
+
+       va_start(ap, outfile);
+       while ((arg = va_arg(ap, char *))) {
+               char *value = va_arg(ap,char *);
+               pstring_sub(syscmd, arg, value);
+       }
+       va_end(ap);
   
-  standard_sub(cnum,syscmd);
+       p = PRINTERNAME(snum);
+       if (!p || !*p) p = SERVICE(snum);
   
-  return (syscmd);
+       pstring_sub(syscmd, "%p", p);
+       standard_sub_snum(snum,syscmd);
+
+       /* Convert script args to unix-codepage */
+       dos_to_unix(syscmd, True);
+       ret = smbrun(syscmd,outfile,False);
+
+       DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
+
+       return ret;
 }
 
 
 /****************************************************************************
-print a file - called on closing the file
+parse a file name from the system spooler to generate a jobid
 ****************************************************************************/
-void print_file(int fnum)
-{
-  pstring syscmd;
-  int cnum = Files[fnum].cnum;
-  int snum=SNUM(cnum);
-  char *tempstr;
-
-  *syscmd = 0;
-
-  if (file_size(Files[fnum].name) <= 0) {
-    DEBUG(3,("Discarding null print job %s\n",Files[fnum].name));
-    sys_unlink(Files[fnum].name);
-    return;
-  }
-
-  tempstr = build_print_command(cnum, PRINTCOMMAND(snum), syscmd, Files[fnum].name);
-  if (tempstr != NULL)
-    {
-      int ret = smbrun(syscmd,NULL);
-      DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
-    }
-  else
-    DEBUG(0,("Null print command?\n"));
-  
-  lpq_reset(snum);
-}
-
-static char *Months[13] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
-                             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Err"};
-
-
-/*******************************************************************
-process time fields
-********************************************************************/
-static time_t EntryTime(string tok[], int ptr, int count, int minimum)
-{
-  time_t jobtime,jobtime1;
-
-  jobtime = time(NULL);                /* default case: take current time */
-  if (count >= minimum) {
-    struct tm *t;
-    int i, day, hour, min, sec;
-    char   *c;
-
-    for (i=0; i<13; i++) if (!strncmp(tok[ptr], Months[i],3)) break; /* Find month */
-    if (i<12) {
-      t = localtime(&jobtime);
-      day = atoi(tok[ptr+1]);
-      c=(char *)(tok[ptr+2]);
-      *(c+2)=0;
-      hour = atoi(c);
-      *(c+5)=0;
-      min = atoi(c+3);
-      if(*(c+6) != 0)sec = atoi(c+6);
-      else  sec=0;
-
-      if ((t->tm_mon < i)||
-         ((t->tm_mon == i)&&
-          ((t->tm_mday < day)||
-           ((t->tm_mday == day)&&
-            (t->tm_hour*60+t->tm_min < hour*60+min)))))
-       t->tm_year--;           /* last year's print job */
-
-      t->tm_mon = i;
-      t->tm_mday = day;
-      t->tm_hour = hour;
-      t->tm_min = min;
-      t->tm_sec = sec;
-      jobtime1 = mktime(t);
-      if (jobtime1 != (time_t)-1)
-       jobtime = jobtime1;
-    }
-  }
-  return jobtime;
+static int print_parse_jobid(char *fname)
+{
+       int jobid;
+
+       if (strncmp(fname,PRINT_SPOOL_PREFIX,strlen(PRINT_SPOOL_PREFIX)) != 0) return -1;
+       fname += strlen(PRINT_SPOOL_PREFIX);
+
+       jobid = atoi(fname);
+       if (jobid <= 0) return -1;
+
+       return jobid;
 }
 
 
 /****************************************************************************
-parse a lpq line
+list a unix job in the print database
+****************************************************************************/
+static void print_unix_job(int snum, print_queue_struct *q)
+{
+       int jobid = q->job + UNIX_JOB_START;
+       struct printjob pj, *old_pj;
+
+       /* Preserve the timestamp on an existing unix print job */
 
-here is an example of lpq output under bsd
+       old_pj = print_job_find(jobid);
 
-Warning: no daemon present
-Rank   Owner      Job  Files                                 Total Size
-1st    tridge     148  README                                8096 bytes
+       ZERO_STRUCT(pj);
 
-here is an example of lpq output under osf/1
+       pj.pid = (pid_t)-1;
+       pj.sysjob = q->job;
+       pj.fd = -1;
+       pj.starttime = old_pj ? old_pj->starttime : q->time;
+       pj.status = q->status;
+       pj.size = q->size;
+       pj.spooled = True;
+       pj.smbjob = False;
+       fstrcpy(pj.filename, "");
+       fstrcpy(pj.jobname, q->file);
+       fstrcpy(pj.user, q->user);
+       fstrcpy(pj.qname, lp_servicename(snum));
 
-Warning: no daemon present
-Rank   Pri Owner      Job  Files                             Total Size
-1st    0   tridge     148  README                            8096 bytes
+       print_job_store(jobid, &pj);
+}
+
+
+struct traverse_struct {
+       print_queue_struct *queue;
+       int qcount, snum, maxcount;
+};
+
+/* utility fn to delete any jobs that are no longer active */
+static int traverse_fn_delete(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *state)
+{
+       struct traverse_struct *ts = (struct traverse_struct *)state;
+       struct printjob pjob;
+       int i, jobid;
+
+       if (data.dsize != sizeof(pjob) || key.dsize != sizeof(int)) return 0;
+       memcpy(&jobid, key.dptr, sizeof(jobid));
+       memcpy(&pjob,  data.dptr, sizeof(pjob));
+
+       if (strcmp(lp_servicename(ts->snum), pjob.qname)) {
+               /* this isn't for the queue we are looking at */
+               return 0;
+       }
+
+       if (!pjob.smbjob) {
+               /* remove a unix job if it isn't in the system queue
+                   any more */
+
+               for (i=0;i<ts->qcount;i++) {
+                       if (jobid == ts->queue[i].job + UNIX_JOB_START) break;
+               }
+               if (i == ts->qcount) tdb_delete(tdb, key);
+               return 0;
+       }
+
+       /* maybe it hasn't been spooled yet */
+       if (!pjob.spooled) {
+               /* if a job is not spooled and the process doesn't
+                   exist then kill it. This cleans up after smbd
+                   deaths */
+               if (!process_exists(pjob.pid)) {
+                       tdb_delete(tdb, key);
+               }
+               return 0;
+       }
+
+       for (i=0;i<ts->qcount;i++) {
+               int qid = print_parse_jobid(ts->queue[i].file);
+               if (jobid == qid) break;
+       }
+       
+       /* The job isn't in the system queue - we have to assume it has
+          completed, so delete the database entry. */
+
+       if (i == ts->qcount) {
+               time_t cur_t = time(NULL);
+
+               /* A race can occur between the time a job is spooled and
+                  when it appears in the lpq output.  This happens when
+                  the job is added to printing.tdb when another smbd
+                  running print_queue_update() has completed a lpq and
+                  is currently traversing the printing tdb and deleting jobs.
+                  A workaround is to not delete the job if it has been 
+                  submitted less than lp_lpqcachetime() seconds ago. */
+
+               if ((cur_t - pjob.starttime) > lp_lpqcachetime()) {
+                       tdb_delete(t, key);
+               }
+       }
+
+       return 0;
+}
+
+/****************************************************************************
+check if the print queue has been updated recently enough
 ****************************************************************************/
-static BOOL parse_lpq_bsd(char *line,print_queue_struct *buf,BOOL first)
-{
-#ifdef OSF1
-#define        RANKTOK 0
-#define        PRIOTOK 1
-#define        USERTOK 2
-#define        JOBTOK  3
-#define        FILETOK 4
-#define        TOTALTOK 5
-#define        NTOK    6
-#else  /* OSF1 */
-#define        RANKTOK 0
-#define        USERTOK 1
-#define        JOBTOK  2
-#define        FILETOK 3
-#define        TOTALTOK 4
-#define        NTOK    5
-#endif /* OSF1 */
-
-  string tok[NTOK];
-  int count=0;
-
-#ifdef OSF1
-  int length;
-  length = strlen(line);
-  if (line[length-3] == ':')
-       return(False);
-#endif /* OSF1 */
-
-  /* handle the case of "(standard input)" as a filename */
-  string_sub(line,"standard input","STDIN");
-  string_sub(line,"(","\"");
-  string_sub(line,")","\"");
-  
-  for (count=0; count<NTOK && next_token(&line,tok[count],NULL); count++) ;
-
-  /* we must get NTOK tokens */
-  if (count < NTOK)
-    return(False);
-
-  /* the Job and Total columns must be integer */
-  if (!isdigit(*tok[JOBTOK]) || !isdigit(*tok[TOTALTOK])) return(False);
-
-  /* if the fname contains a space then use STDIN */
-  if (strchr(tok[FILETOK],' '))
-    strcpy(tok[FILETOK],"STDIN");
-
-  /* only take the last part of the filename */
-  {
-    string tmp;
-    char *p = strrchr(tok[FILETOK],'/');
-    if (p)
-      {
-       strcpy(tmp,p+1);
-       strcpy(tok[FILETOK],tmp);
-      }
-  }
+static void print_cache_flush(int snum)
+{
+       fstring key;
+       slprintf(key, sizeof(key), "CACHE/%s", lp_servicename(snum));
+       dos_to_unix(key, True);                /* Convert key to unix-codepage */
+       tdb_store_int(tdb, key, -1);
+}
+
+/****************************************************************************
+update the internal database from the system print queue for a queue
+****************************************************************************/
+static void print_queue_update(int snum)
+{
+       char *path = lp_pathname(snum);
+       char *cmd = lp_lpqcommand(snum);
+       char **qlines;
+       pstring tmp_file;
+       int numlines, i, qcount;
+       print_queue_struct *queue = NULL;
+       print_status_struct status;
+       print_status_struct old_status;
+       struct printjob *pjob;
+       struct traverse_struct tstruct;
+       fstring keystr, printer_name;
+       TDB_DATA data, key;
+              
+       /* Convert printer name (i.e. share name) to unix-codepage for all of the 
+        * following tdb key generation */
+       fstrcpy(printer_name, lp_servicename(snum));
+       dos_to_unix(printer_name, True);
        
+       /*
+        * Update the cache time FIRST ! Stops others doing this
+        * if the lpq takes a long time.
+        */
+
+       slprintf(keystr, sizeof(keystr), "CACHE/%s", printer_name);
+       tdb_store_int(tdb, keystr, (int)time(NULL));
+
+       slprintf(tmp_file, sizeof(tmp_file), "%s/smblpq.%d", path, local_pid);
+
+       unlink(tmp_file);
+       print_run_command(snum, cmd, tmp_file, NULL);
+
+       numlines = 0;
+       qlines = file_lines_load(tmp_file, &numlines, True);
+       unlink(tmp_file);
+
+       /* turn the lpq output into a series of job structures */
+       qcount = 0;
+       ZERO_STRUCT(status);
+       if (numlines)
+               queue = (print_queue_struct *)malloc(sizeof(print_queue_struct)*(numlines+1));
+
+       if (queue) {
+               for (i=0; i<numlines; i++) {
+                       /* parse the line */
+                       if (parse_lpq_entry(snum,qlines[i],
+                                           &queue[qcount],&status,qcount==0)) {
+                               qcount++;
+                       }
+               }               
+       }
+       file_lines_free(qlines);
+
+       DEBUG(3, ("%d job%s in queue for %s\n", qcount, (qcount != 1) ?
+               "s" : "", printer_name));
+
+       /* Lock the queue for the database update */
+
+       slprintf(keystr, sizeof(keystr) - 1, "LOCK/%s", printer_name);
+       tdb_lock_bystring(tdb, keystr);
 
-  buf->job = atoi(tok[JOBTOK]);
-  buf->size = atoi(tok[TOTALTOK]);
-  buf->status = strequal(tok[RANKTOK],"active")?LPQ_PRINTING:LPQ_QUEUED;
-  buf->time = time(NULL);
-  StrnCpy(buf->user,tok[USERTOK],sizeof(buf->user)-1);
-  StrnCpy(buf->file,tok[FILETOK],sizeof(buf->file)-1);
-#ifdef PRIOTOK
-  buf->priority = atoi(tok[PRIOTOK]);
-#else
-  buf->priority = 1;
-#endif
-  return(True);
-}
-
-
-
-/*******************************************************************
-parse lpq on an aix system
-
-Queue   Dev   Status    Job Files              User         PP %   Blks  Cp Rnk
-------- ----- --------- --- ------------------ ---------- ---- -- ----- --- ---
-lazer   lazer READY
-lazer   lazer RUNNING   537 6297doc.A          kvintus@IE    0 10  2445   1   1
-              QUEUED    538 C.ps               root@IEDVB           124   1   2
-              QUEUED    539 E.ps               root@IEDVB            28   1   3
-              QUEUED    540 L.ps               root@IEDVB           172   1   4
-              QUEUED    541 P.ps               root@IEDVB            22   1   5
-********************************************************************/
-static BOOL parse_lpq_aix(char *line,print_queue_struct *buf,BOOL first)
-{
-  string tok[11];
-  int count=0;
-
-  /* handle the case of "(standard input)" as a filename */
-  string_sub(line,"standard input","STDIN");
-  string_sub(line,"(","\"");
-  string_sub(line,")","\"");
-
-  for (count=0; count<10 && next_token(&line,tok[count],NULL); count++) ;
-
-  /* we must get 6 tokens */
-  if (count < 10)
-  {
-      if ((count == 7) && (strcmp(tok[0],"QUEUED") == 0))
-      {
-          /* the 2nd and 5th columns must be integer */
-          if (!isdigit(*tok[1]) || !isdigit(*tok[4])) return(False);
-          buf->size = atoi(tok[4]) * 1024;
-          /* if the fname contains a space then use STDIN */
-          if (strchr(tok[2],' '))
-            strcpy(tok[2],"STDIN");
-
-          /* only take the last part of the filename */
-          {
-            string tmp;
-            char *p = strrchr(tok[2],'/');
-            if (p)
-              {
-                strcpy(tmp,p+1);
-                strcpy(tok[2],tmp);
-              }
-          }
-
-
-          buf->job = atoi(tok[1]);
-          buf->status = LPQ_QUEUED;
-         buf->priority = 0;
-          buf->time = time(NULL);
-          StrnCpy(buf->user,tok[3],sizeof(buf->user)-1);
-          StrnCpy(buf->file,tok[2],sizeof(buf->file)-1);
-      }
-      else
-      {
-          DEBUG(6,("parse_lpq_aix count=%d\n", count));
-          return(False);
-      }
-  }
-  else
-  {
-      /* the 4th and 9th columns must be integer */
-      if (!isdigit(*tok[3]) || !isdigit(*tok[8])) return(False);
-      buf->size = atoi(tok[8]) * 1024;
-      /* if the fname contains a space then use STDIN */
-      if (strchr(tok[4],' '))
-        strcpy(tok[4],"STDIN");
-
-      /* only take the last part of the filename */
-      {
-        string tmp;
-        char *p = strrchr(tok[4],'/');
-        if (p)
-          {
-            strcpy(tmp,p+1);
-            strcpy(tok[4],tmp);
-          }
-      }
-
-
-      buf->job = atoi(tok[3]);
-      buf->status = strequal(tok[2],"RUNNING")?LPQ_PRINTING:LPQ_QUEUED;
-      buf->priority = 0;
-      buf->time = time(NULL);
-      StrnCpy(buf->user,tok[5],sizeof(buf->user)-1);
-      StrnCpy(buf->file,tok[4],sizeof(buf->file)-1);
-  }
-
-
-  return(True);
+       /*
+         any job in the internal database that is marked as spooled
+         and doesn't exist in the system queue is considered finished
+         and removed from the database
+
+         any job in the system database but not in the internal database 
+         is added as a unix job
+
+         fill in any system job numbers as we go
+       */
+       for (i=0; i<qcount; i++) {
+               int jobid = print_parse_jobid(queue[i].file);
+
+               if (jobid == -1) {
+                       /* assume its a unix print job */
+                       print_unix_job(snum, &queue[i]);
+                       continue;
+               }
+
+               /* we have an active SMB print job - update its status */
+               pjob = print_job_find(jobid);
+               if (!pjob) {
+                       /* err, somethings wrong. Probably smbd was restarted
+                          with jobs in the queue. All we can do is treat them
+                          like unix jobs. Pity. */
+                       print_unix_job(snum, &queue[i]);
+                       continue;
+               }
+
+               pjob->sysjob = queue[i].job;
+               pjob->status = queue[i].status;
+
+               print_job_store(jobid, pjob);
+       }
+
+       /* now delete any queued entries that don't appear in the
+           system queue */
+       tstruct.queue = queue;
+       tstruct.qcount = qcount;
+       tstruct.snum = snum;
+
+       tdb_traverse(tdb, traverse_fn_delete, (void *)&tstruct);
+
+       safe_free(tstruct.queue);
+
+       /*
+        * Get the old print status. We will use this to compare the
+        * number of jobs. If they have changed we need to send a
+        * "changed" message to the smbds.
+        */
+
+       if( qcount != get_queue_status(snum, &old_status)) {
+               DEBUG(10,("print_queue_update: queue status change %d jobs -> %d jobs for printer %s\n",
+                               old_status.qcount, qcount, printer_name ));
+               message_send_all(conn_tdb_ctx(), MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+       }
+
+       /* store the new queue status structure */
+       slprintf(keystr, sizeof(keystr), "STATUS/%s", printer_name);
+    key.dptr = keystr;
+       key.dsize = strlen(keystr);
+
+       status.qcount = qcount;
+       data.dptr = (void *)&status;
+       data.dsize = sizeof(status);
+       tdb_store(tdb, key, data, TDB_REPLACE); 
+
+       /* Unlock for database update */
+
+       slprintf(keystr, sizeof(keystr) - 1, "LOCK/%s", printer_name);
+       tdb_unlock_bystring(tdb, keystr);
+
+       /*
+        * Update the cache time again. We want to do this call
+        * as little as possible...
+        */
+
+       slprintf(keystr, sizeof(keystr), "CACHE/%s", printer_name);
+       tdb_store_int(tdb, keystr, (int)time(NULL));
+}
+
+/****************************************************************************
+check if a jobid is valid. It is valid if it exists in the database
+****************************************************************************/
+BOOL print_job_exists(int jobid)
+{
+       return tdb_exists(tdb, print_key(jobid));
 }
 
 
 /****************************************************************************
-parse a lpq line
-here is an example of lpq output under hpux; note there's no space after -o !
-$> lpstat -oljplus
-ljplus-2153         user           priority 0  Jan 19 08:14 on ljplus
-      util.c                                  125697 bytes
-      server.c                               110712 bytes
-ljplus-2154         user           priority 0  Jan 19 08:14 from client
-      (standard input)                          7551 bytes
+work out which service a jobid is for
+note that we have to look up by queue name to ensure that it works for 
+other than the process that started the job
 ****************************************************************************/
-static BOOL parse_lpq_hpux(char * line, print_queue_struct *buf, BOOL first)
-{
-  /* must read two lines to process, therefore keep some values static */
-  static BOOL header_line_ok=False, base_prio_reset=False;
-  static string jobuser;
-  static int jobid;
-  static int jobprio;
-  static time_t jobtime;
-  static int jobstat=LPQ_QUEUED;
-  /* to store minimum priority to print, lpstat command should be invoked
-     with -p option first, to work */
-  static int base_prio;
-  int count;
-  char TAB = '\011';  
-  string tok[12];
-
-  /* If a line begins with a horizontal TAB, it is a subline type */
-  
-  if (line[0] == TAB) { /* subline */
-    /* check if it contains the base priority */
-    if (!strncmp(line,"\tfence priority : ",18)) {
-       base_prio=atoi(&line[18]);
-       DEBUG(4, ("fence priority set at %d\n", base_prio));
-    }
-    if (!header_line_ok) return (False); /* incorrect header line */
-    /* handle the case of "(standard input)" as a filename */
-    string_sub(line,"standard input","STDIN");
-    string_sub(line,"(","\"");
-    string_sub(line,")","\"");
-    
-    for (count=0; count<2 && next_token(&line,tok[count],NULL); count++) ;
-    /* we must get 2 tokens */
-    if (count < 2) return(False);
-    
-    /* the 2nd column must be integer */
-    if (!isdigit(*tok[1])) return(False);
-    
-    /* if the fname contains a space then use STDIN */
-    if (strchr(tok[0],' '))
-      strcpy(tok[0],"STDIN");
-    
-    buf->size = atoi(tok[1]);
-    StrnCpy(buf->file,tok[0],sizeof(buf->file)-1);
-    
-    /* fill things from header line */
-    buf->time = jobtime;
-    buf->job = jobid;
-    buf->status = jobstat;
-    buf->priority = jobprio;
-    StrnCpy(buf->user,jobuser,sizeof(buf->user)-1);
-    
-    return(True);
-  }
-  else { /* header line */
-    header_line_ok=False; /* reset it */
-    if (first) {
-       if (!base_prio_reset) {
-         base_prio=0; /* reset it */
-         base_prio_reset=True;
-       }
-    }
-    else if (base_prio) base_prio_reset=False;
-    
-    /* handle the dash in the job id */
-    string_sub(line,"-"," ");
-    
-    for (count=0; count<12 && next_token(&line,tok[count],NULL); count++) ;
-      
-    /* we must get 8 tokens */
-    if (count < 8) return(False);
-    
-    /* first token must be printer name (cannot check ?) */
-    /* the 2nd, 5th & 7th column must be integer */
-    if (!isdigit(*tok[1]) || !isdigit(*tok[4]) || !isdigit(*tok[6])) return(False);
-    jobid = atoi(tok[1]);
-    StrnCpy(jobuser,tok[2],sizeof(buf->user)-1);
-    jobprio = atoi(tok[4]);
-    
-    /* process time */
-    jobtime=EntryTime(tok, 5, count, 8);
-    if (jobprio < base_prio) {
-       jobstat = LPQ_PAUSED;
-       DEBUG (4, ("job %d is paused: prio %d < %d; jobstat=%d\n", jobid, jobprio, base_prio, jobstat));
-    }
-    else {
-       jobstat = LPQ_QUEUED;
-       if ((count >8) && (((strequal(tok[8],"on")) ||
-                          ((strequal(tok[8],"from")) && 
-                           ((count > 10)&&(strequal(tok[10],"on")))))))
-        jobstat = LPQ_PRINTING;
-    }
-    
-    header_line_ok=True; /* information is correct */
-    return(False); /* need subline info to include into queuelist */
-  }
+int print_job_snum(int jobid)
+{
+       struct printjob *pjob = print_job_find(jobid);
+       if (!pjob) return -1;
+
+       return lp_servicenumber(pjob->qname);
 }
 
+/****************************************************************************
+give the fd used for a jobid
+****************************************************************************/
+int print_job_fd(int jobid)
+{
+       struct printjob *pjob = print_job_find(jobid);
+       if (!pjob) return -1;
+       /* don't allow another process to get this info - it is meaningless */
+       if (pjob->pid != local_pid) return -1;
+       return pjob->fd;
+}
 
 /****************************************************************************
-parse a lpq line
+give the filename used for a jobid
+only valid for the process doing the spooling and when the job
+has not been spooled
+****************************************************************************/
+char *print_job_fname(int jobid)
+{
+       struct printjob *pjob = print_job_find(jobid);
+       if (!pjob || pjob->spooled || pjob->pid != local_pid) return NULL;
+       return pjob->filename;
+}
 
-here is an example of "lpstat -o dcslw" output under sysv
 
-dcslw-896               tridge            4712   Dec 20 10:30:30 on dcslw
-dcslw-897               tridge            4712   Dec 20 10:30:30 being held
+/****************************************************************************
+set the place in the queue for a job
+****************************************************************************/
+BOOL print_job_set_place(int jobid, int place)
+{
+       DEBUG(2,("print_job_set_place not implemented yet\n"));
+       return False;
+}
 
+/****************************************************************************
+set the name of a job. Only possible for owner
 ****************************************************************************/
-static BOOL parse_lpq_sysv(char *line,print_queue_struct *buf,BOOL first)
+BOOL print_job_set_name(int jobid, char *name)
 {
-  string tok[9];
-  int count=0;
-  char *p;
+       struct printjob *pjob = print_job_find(jobid);
+       if (!pjob || pjob->pid != local_pid) return False;
 
-  /* handle the dash in the job id */
-  string_sub(line,"-"," ");
-  
-  for (count=0; count<9 && next_token(&line,tok[count],NULL); count++) ;
-
-  /* we must get 7 tokens */
-  if (count < 7)
-    return(False);
-
-  /* the 2nd and 4th, 6th columns must be integer */
-  if (!isdigit(*tok[1]) || !isdigit(*tok[3])) return(False);
-  if (!isdigit(*tok[5])) return(False);
-
-  /* if the user contains a ! then trim the first part of it */  
-  if ((p=strchr(tok[2],'!')))
-    {
-      string tmp;
-      strcpy(tmp,p+1);
-      strcpy(tok[2],tmp);
-    }
-    
-
-  buf->job = atoi(tok[1]);
-  buf->size = atoi(tok[3]);
-  if (count > 7 && strequal(tok[7],"on"))
-    buf->status = LPQ_PRINTING;
-  else if (count > 8 && strequal(tok[7],"being") && strequal(tok[8],"held"))
-    buf->status = LPQ_PAUSED;
-  else
-    buf->status = LPQ_QUEUED;
-  buf->priority = 0;
-  buf->time = EntryTime(tok, 4, count, 7);
-  StrnCpy(buf->user,tok[2],sizeof(buf->user)-1);
-  StrnCpy(buf->file,tok[2],sizeof(buf->file)-1);
-  return(True);
+       fstrcpy(pjob->jobname, name);
+       return print_job_store(jobid, pjob);
 }
 
+
 /****************************************************************************
-parse a lpq line
-
-here is an example of lpq output under qnx
-Spooler: /qnx/spooler, on node 1
-Printer: txt        (ready) 
-0000:     root [job #1    ]   active 1146 bytes        /etc/profile
-0001:     root [job #2    ]    ready 2378 bytes        /etc/install
-0002:     root [job #3    ]    ready 1146 bytes        -- standard input --
+delete a print job - don't update queue
 ****************************************************************************/
-static BOOL parse_lpq_qnx(char *line,print_queue_struct *buf,BOOL first)
+static BOOL print_job_delete1(int jobid)
 {
-  string tok[7];
-  int count=0;
+       struct printjob *pjob = print_job_find(jobid);
+       int snum;
+
+       if (!pjob) return False;
+
+       snum = print_job_snum(jobid);
+
+       if (pjob->spooled && pjob->sysjob != -1) {
+               /* need to delete the spooled entry */
+               fstring jobstr;
+               slprintf(jobstr, sizeof(jobstr), "%d", pjob->sysjob);
+               print_run_command(snum, 
+                                 lp_lprmcommand(snum), NULL,
+                                 "%j", jobstr,
+                                 "%T", http_timestring(pjob->starttime),
+                                 NULL);
+       }
+
+       return True;
+}
 
-  DEBUG(0,("antes [%s]\n", line));
+/****************************************************************************
+return true if the current user owns the print job
+****************************************************************************/
+static BOOL is_owner(struct current_user *user, int jobid)
+{
+       struct printjob *pjob = print_job_find(jobid);
+       user_struct *vuser;
 
-  /* handle the case of "-- standard input --" as a filename */
-  string_sub(line,"standard input","STDIN");
-  DEBUG(0,("despues [%s]\n", line));
-  string_sub(line,"-- ","\"");
-  string_sub(line," --","\"");
-  DEBUG(0,("despues 1 [%s]\n", line));
+       if (!pjob || !user) return False;
 
-  string_sub(line,"[job #","");
-  string_sub(line,"]","");
-  DEBUG(0,("despues 2 [%s]\n", line));
+       if ((vuser = get_valid_user_struct(user->vuid)) != NULL) {
+               return strequal(pjob->user, vuser->user.smb_name);
+       } else {
+               return strequal(pjob->user, uidtoname(user->uid));
+       }
+}
 
-  
-  
-  for (count=0; count<7 && next_token(&line,tok[count],NULL); count++) ;
-
-  /* we must get 7 tokens */
-  if (count < 7)
-    return(False);
-
-  /* the 3rd and 5th columns must be integer */
-  if (!isdigit(*tok[2]) || !isdigit(*tok[4])) return(False);
-
-  /* only take the last part of the filename */
-  {
-    string tmp;
-    char *p = strrchr(tok[6],'/');
-    if (p)
-      {
-       strcpy(tmp,p+1);
-       strcpy(tok[6],tmp);
-      }
-  }
+/****************************************************************************
+delete a print job
+****************************************************************************/
+BOOL print_job_delete(struct current_user *user, int jobid, int *errcode)
+{
+       int snum = print_job_snum(jobid);
+       char *printer_name;
+       BOOL owner;
        
+       owner = is_owner(user, jobid);
+       
+       /* Check access against security descriptor or whether the user
+          owns their job. */
+
+       if (!owner && 
+           !print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+               DEBUG(3, ("delete denied by security descriptor\n"));
+               *errcode = ERROR_ACCESS_DENIED;
+               return False;
+       }
+
+       if (!print_job_delete1(jobid)) return False;
+
+       /* force update the database and say the delete failed if the
+           job still exists */
+
+       print_queue_update(snum);
+
+       /* Send a printer notify message */
 
-  buf->job = atoi(tok[2]);
-  buf->size = atoi(tok[4]);
-  buf->status = strequal(tok[3],"active")?LPQ_PRINTING:LPQ_QUEUED;
-  buf->priority = 0;
-  buf->time = time(NULL);
-  StrnCpy(buf->user,tok[1],sizeof(buf->user)-1);
-  StrnCpy(buf->file,tok[6],sizeof(buf->file)-1);
-  return(True);
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(), MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return !print_job_exists(jobid);
 }
 
 
 /****************************************************************************
-  parse a lpq line for the plp printing system
-  Bertrand Wallrich <Bertrand.Wallrich@loria.fr>
+pause a job
 ****************************************************************************/
-static BOOL parse_lpq_plp(char *line,print_queue_struct *buf,BOOL first)
+BOOL print_job_pause(struct current_user *user, int jobid, int *errcode)
 {
-  string tok[5];
-  int count=0;
+       struct printjob *pjob = print_job_find(jobid);
+       int snum, ret = -1;
+       char *printer_name;
+       fstring jobstr;
+       BOOL owner;
+       
+       if (!pjob || !user) return False;
 
-  /* handle the case of "(standard input)" as a filename */
-  string_sub(line,"stdin","STDIN");
-  string_sub(line,"(","\"");
-  string_sub(line,")","\"");
-  
-  for (count=0; count<8 && next_token(&line,tok[count],NULL); count++) ;
-
-  /* we must get 5 tokens */
-  if (count < 8)
-    return(False);
-
-  /* the 4rd must be integer */
-  if (!isdigit(*tok[3])) return(False);
-
-  /* if the fname contains a space then use STDIN */
-  if (strchr(tok[3],' '))
-    strcpy(tok[3],"STDIN");
-
-  /* only take the last part of the filename */
-  {
-    string tmp;
-    char *p = strrchr(tok[5],'/');
-    if (p)
-      {
-        strcpy(tmp,p+1);
-        strcpy(tok[5],tmp);
-      }
-  }
-
-
-  buf->job = atoi(tok[3]);
-
-  /* calcul de la taille du fichier */
-  if (!isdigit(*tok[7])) {  buf->size = atoi(tok[7]) * 1.0 ; }
-  else {
-    string tmp;
-    strcpy(tmp,tok[7]);
-    if (strchr(tok[7],'K')) {
-      strncpy(tok[7],tmp,strlen(tmp)-1);
-      buf->size = atoi(tok[7]);
-      buf->size = buf->size * 1024;
-    }
-    if (strchr(tok[7],'M')) {
-      strncpy(tok[7],tmp,strlen(tmp)-1);
-      buf->size = atoi(tok[7]);
-      buf->size = buf->size * 1024.0 * 1000.0;
-    }
-    if (strchr(tok[7],'G')) {
-      strncpy(tok[7],tmp,strlen(tmp)-1);
-      buf->size = atoi(tok[7]);
-      buf->size = buf->size * 1024.0 * 1000000.0;
-    }
-
-  }
-  buf->status = strequal(tok[0],"active")?LPQ_PRINTING:LPQ_QUEUED;
-  buf->priority = 0;
-  buf->time = time(NULL);
-  StrnCpy(buf->user,tok[1],sizeof(buf->user)-1);
-  StrnCpy(buf->file,tok[5],sizeof(buf->file)-1);
-  return(True);
-}
-
-
-
-char *stat0_strings[] = { "enabled", "online", "idle", "no entries", "free", "ready", NULL };
-char *stat1_strings[] = { "offline", "disabled", "down", "off", "waiting", "no daemon", NULL };
-char *stat2_strings[] = { "jam", "paper", "error", "responding", "not accepting", "not running", "turned off", NULL };
+       if (!pjob->spooled || pjob->sysjob == -1) return False;
+
+       snum = print_job_snum(jobid);
+       owner = is_owner(user, jobid);
+
+       if (!owner &&
+           !print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+               DEBUG(3, ("pause denied by security descriptor\n"));
+               *errcode = ERROR_ACCESS_DENIED;
+               return False;
+       }
+
+       /* need to pause the spooled entry */
+       slprintf(jobstr, sizeof(jobstr), "%d", pjob->sysjob);
+       ret = print_run_command(snum, 
+                               lp_lppausecommand(snum), NULL,
+                               "%j", jobstr,
+                               NULL);
+
+       if (ret != 0) {
+               *errcode = ERROR_INVALID_PARAMETER;
+               return False;
+       }
+
+       /* force update the database */
+       print_cache_flush(snum);
+
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(), MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       /* how do we tell if this succeeded? */
+
+       return True;
+}
 
 /****************************************************************************
-parse a lpq line. Choose printing style
+resume a job
 ****************************************************************************/
-static BOOL parse_lpq_entry(int snum,char *line,
-                           print_queue_struct *buf,
-                           print_status_struct *status,BOOL first)
-{
-  BOOL ret;
-
-  switch (lp_printing())
-    {
-    case PRINT_SYSV:
-      ret = parse_lpq_sysv(line,buf,first);
-      break;
-    case PRINT_AIX:      
-      ret = parse_lpq_aix(line,buf,first);
-      break;
-    case PRINT_HPUX:
-      ret = parse_lpq_hpux(line,buf,first);
-      break;
-    case PRINT_QNX:
-      ret = parse_lpq_qnx(line,buf,first);
-      break;
-    case PRINT_PLP:
-      ret = parse_lpq_plp(line,buf,first);
-      break;
-    default:
-      ret = parse_lpq_bsd(line,buf,first);
-      break;
-    }
-
-#ifdef LPQ_GUEST_TO_USER
-  if (ret) {
-    extern pstring sesssetup_user;
-    /* change guest entries to the current logged in user to make
-       them appear deletable to windows */
-    if (sesssetup_user[0] && strequal(buf->user,lp_guestaccount(snum)))
-      strcpy(buf->user,sesssetup_user);
-  }
-#endif
-
-  if (status && !ret)
-    {
-      /* a few simple checks to see if the line might be a
-         printer status line: 
-        handle them so that most severe condition is shown */
-      int i;
-      strlower(line);
-      
-      switch (status->status) {
-      case LPSTAT_OK:
-       for (i=0; stat0_strings[i]; i++)
-         if (strstr(line,stat0_strings[i])) {
-           StrnCpy(status->message,line,sizeof(status->message)-1);
-           status->status=LPSTAT_OK;
-         }
-      case LPSTAT_STOPPED:
-       for (i=0; stat1_strings[i]; i++)
-         if (strstr(line,stat1_strings[i])) {
-           StrnCpy(status->message,line,sizeof(status->message)-1);
-           status->status=LPSTAT_STOPPED;
-         }
-      case LPSTAT_ERROR:
-       for (i=0; stat2_strings[i]; i++)
-         if (strstr(line,stat2_strings[i])) {
-           StrnCpy(status->message,line,sizeof(status->message)-1);
-           status->status=LPSTAT_ERROR;
-         }
-       break;
-      }
-    }
-
-  return(ret);
+BOOL print_job_resume(struct current_user *user, int jobid, int *errcode)
+{
+       struct printjob *pjob = print_job_find(jobid);
+       char *printer_name;
+       int snum, ret;
+       fstring jobstr;
+       BOOL owner;
+       
+       if (!pjob || !user) return False;
+
+       if (!pjob->spooled || pjob->sysjob == -1) return False;
+
+       snum = print_job_snum(jobid);
+       owner = is_owner(user, jobid);
+
+       if (!is_owner(user, jobid) &&
+           !print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+               DEBUG(3, ("resume denied by security descriptor\n"));
+               *errcode = ERROR_ACCESS_DENIED;
+               return False;
+       }
+
+       slprintf(jobstr, sizeof(jobstr), "%d", pjob->sysjob);
+       ret = print_run_command(snum, 
+                               lp_lpresumecommand(snum), NULL,
+                               "%j", jobstr,
+                               NULL);
+
+       if (ret != 0) {
+               *errcode = ERROR_INVALID_PARAMETER;
+               return False;
+       }
+
+       /* force update the database */
+       print_cache_flush(snum);
+
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
 }
 
 /****************************************************************************
-get a printer queue
+write to a print file
 ****************************************************************************/
-int get_printqueue(int snum,int cnum,print_queue_struct **queue,
-                  print_status_struct *status)
-{
-  char *lpq_command = lp_lpqcommand(snum);
-  char *printername = PRINTERNAME(snum);
-  int ret=0,count=0;
-  pstring syscmd;
-  fstring outfile;
-  pstring line;
-  FILE *f;
-  struct stat sbuf;
-  BOOL dorun=True;
-  int cachetime = lp_lpqcachetime();
-  int lfd = -1;
-
-  *line = 0;
-  check_lpq_cache(snum);
-  
-  if (!printername || !*printername)
-    {
-      DEBUG(6,("replacing printer name with service (snum=(%s,%d))\n",
-           lp_servicename(snum),snum));
-      printername = lp_servicename(snum);
-    }
-    
-  if (!lpq_command || !(*lpq_command))
-    {
-      DEBUG(5,("No lpq command\n"));
-      return(0);
-    }
-    
-  strcpy(syscmd,lpq_command);
-  string_sub(syscmd,"%p",printername);
-
-  standard_sub(cnum,syscmd);
-
-  sprintf(outfile,"/tmp/lpq.%08x",str_checksum(syscmd));
-  
-  if (!lpq_cache_reset[snum] && cachetime && !stat(outfile,&sbuf)) 
-    {
-      if (time(NULL) - sbuf.st_mtime < cachetime) {
-       DEBUG(3,("Using cached lpq output\n"));
-       dorun = False;
-      }
-
-      if (dorun) {
-       lfd = file_lock(outfile,LPQ_LOCK_TIMEOUT);
-       if (lfd<0 || 
-           (!fstat(lfd,&sbuf) && (time(NULL) - sbuf.st_mtime)<cachetime)) {
-         DEBUG(3,("Using cached lpq output\n"));
-         dorun = False;
-         file_unlock(lfd); lfd = -1;
-       }
-      }
-    }
-
-  if (dorun) {
-    ret = smbrun(syscmd,outfile);
-    DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
-  }
-
-  lpq_cache_reset[snum] = False;
-
-  f = fopen(outfile,"r");
-  if (!f) {
-    if (lfd >= 0) file_unlock(lfd);
-    return(0);
-  }
-
-  if (status) {
-    strcpy(status->message,"");
-    status->status = LPSTAT_OK;
-  }
-      
-  while (fgets(line,sizeof(pstring),f))
-    {
-      DEBUG(6,("QUEUE2: %s\n",line));
-
-      *queue = Realloc(*queue,sizeof(print_queue_struct)*(count+1));
-      if (! *queue)
-       {
-         count = 0;
-         break;
-       }
-
-      bzero((char *)&(*queue)[count],sizeof(**queue));
-         
-      /* parse it */
-      if (!parse_lpq_entry(snum,line,&(*queue)[count],status,count==0))
-       continue;
-         
-      count++;
-    }        
-
-  fclose(f);
-
-  if (lfd >= 0) file_unlock(lfd);
-
-  if (!cachetime) 
-    unlink(outfile);
-  else
-    chmod(outfile,0666);
-  return(count);
+int print_job_write(int jobid, const char *buf, int size)
+{
+       int fd;
+
+       fd = print_job_fd(jobid);
+       if (fd == -1) return -1;
+
+       return write(fd, buf, size);
 }
 
+/****************************************************************************
+ Check if the print queue has been updated recently enough.
+****************************************************************************/
+
+static BOOL print_cache_expired(int snum)
+{
+       fstring key;
+       time_t t2, t = time(NULL);
+
+       slprintf(key, sizeof(key), "CACHE/%s", lp_servicename(snum));
+       dos_to_unix(key, True);                /* Convert key to unix-codepage */
+       t2 = tdb_fetch_int(tdb, key);
+       if (t2 == ((time_t)-1) || (t - t2) >= lp_lpqcachetime()) {
+               DEBUG(3, ("print cache expired\n"));
+               return True;
+       }
+       return False;
+}
 
 /****************************************************************************
-delete a printer queue entry
+ Get the queue status - do not update if db is out of date.
 ****************************************************************************/
-void del_printqueue(int cnum,int snum,int jobid)
-{
-  char *lprm_command = lp_lprmcommand(snum);
-  char *printername = PRINTERNAME(snum);
-  pstring syscmd;
-  char jobstr[20];
-  int ret;
-
-  if (!printername || !*printername)
-    {
-      DEBUG(6,("replacing printer name with service (snum=(%s,%d))\n",
-           lp_servicename(snum),snum));
-      printername = lp_servicename(snum);
-    }
-    
-  if (!lprm_command || !(*lprm_command))
-    {
-      DEBUG(5,("No lprm command\n"));
-      return;
-    }
-    
-  sprintf(jobstr,"%d",jobid);
-
-  strcpy(syscmd,lprm_command);
-  string_sub(syscmd,"%p",printername);
-  string_sub(syscmd,"%j",jobstr);
-  standard_sub(cnum,syscmd);
-
-  ret = smbrun(syscmd,NULL);
-  DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));  
-  lpq_reset(snum); /* queue has changed */
+
+static int get_queue_status(int snum, print_status_struct *status)
+{
+       fstring keystr;
+       TDB_DATA data, key;
+
+       ZERO_STRUCTP(status);
+       slprintf(keystr, sizeof(keystr), "STATUS/%s", lp_servicename(snum));
+       dos_to_unix(keystr, True);             /* Convert key to unix-codepage */
+       key.dptr = keystr;
+       key.dsize = strlen(keystr);
+       data = tdb_fetch(tdb, key);
+       if (data.dptr) {
+               if (data.dsize == sizeof(print_status_struct)) {
+                       memcpy(status, data.dptr, sizeof(print_status_struct));
+               }
+               free(data.dptr);
+       }
+       return status->qcount;
+}
+
+/****************************************************************************
+ Determine the number of jobs in a queue.
+****************************************************************************/
+
+static int print_queue_length(int snum)
+{
+       print_status_struct status;
+
+       /* make sure the database is up to date */
+       if (print_cache_expired(snum)) print_queue_update(snum);
+
+       /* also fetch the queue status */
+       return get_queue_status(snum, &status);
+}
+
+/***************************************************************************
+start spooling a job - return the jobid
+***************************************************************************/
+int print_job_start(struct current_user *user, int snum, char *jobname)
+{
+       int jobid;
+       char *path;
+       struct printjob pjob;
+       int next_jobid;
+       user_struct *vuser;
+
+       errno = 0;
+
+       if (!print_access_check(user, snum, PRINTER_ACCESS_USE)) {
+               DEBUG(3, ("print_job_start: job start denied by security descriptor\n"));
+               return -1;
+       }
+
+       if (!print_time_access_check(snum)) {
+               DEBUG(3, ("print_job_start: job start denied by time check\n"));
+               return -1;
+       }
+
+       path = lp_pathname(snum);
+
+       /* see if we have sufficient disk space */
+       if (lp_minprintspace(snum)) {
+               SMB_BIG_UINT dspace, dsize;
+               if (sys_fsusage(path, &dspace, &dsize) == 0 &&
+                   dspace < 2*(SMB_BIG_UINT)lp_minprintspace(snum)) {
+                       errno = ENOSPC;
+                       return -1;
+               }
+       }
+
+       /* for autoloaded printers, check that the printcap entry still exists */
+       if (lp_autoloaded(snum) && !pcap_printername_ok(lp_servicename(snum), NULL)) {
+               errno = ENOENT;
+               return -1;
+       }
+
+       if (lp_maxprintjobs(snum) && print_queue_length(snum) > lp_maxprintjobs(snum)) {
+               errno = ENOSPC;
+               return -1;
+       }
+
+       /* create the database entry */
+       ZERO_STRUCT(pjob);
+       pjob.pid = local_pid;
+       pjob.sysjob = -1;
+       pjob.fd = -1;
+       pjob.starttime = time(NULL);
+       pjob.status = LPQ_QUEUED;
+       pjob.size = 0;
+       pjob.spooled = False;
+       pjob.smbjob = True;
+
+       fstrcpy(pjob.jobname, jobname);
+
+       if ((vuser = get_valid_user_struct(user->vuid)) != NULL) {
+               fstrcpy(pjob.user, vuser->user.smb_name);
+       } else {
+               fstrcpy(pjob.user, uidtoname(user->uid));
+       }
+
+       fstrcpy(pjob.qname, lp_servicename(snum));
+
+       /* lock the database */
+       tdb_lock_bystring(tdb, "INFO/nextjob");
+
+ next_jobnum:
+       next_jobid = tdb_fetch_int(tdb, "INFO/nextjob");
+       if (next_jobid == -1) next_jobid = 1;
+
+       for (jobid = next_jobid+1; jobid != next_jobid; ) {
+               if (!print_job_exists(jobid)) break;
+               jobid = (jobid + 1) % PRINT_MAX_JOBID;
+               if (jobid == 0) jobid = 1;
+       }
+       if (jobid == next_jobid || !print_job_store(jobid, &pjob)) {
+               jobid = -1;
+               goto fail;
+       }
+
+       tdb_store_int(tdb, "INFO/nextjob", jobid);
+
+       /* we have a job entry - now create the spool file 
+
+          we unlink first to cope with old spool files and also to beat
+          a symlink security hole - it allows us to use O_EXCL 
+          There may be old spool files owned by other users lying around.
+       */
+       slprintf(pjob.filename, sizeof(pjob.filename), "%s/%s%d", 
+                path, PRINT_SPOOL_PREFIX, jobid);
+       if (unlink(pjob.filename) == -1 && errno != ENOENT) {
+               goto next_jobnum;
+       }
+       pjob.fd = sys_open(pjob.filename,O_WRONLY|O_CREAT|O_EXCL,0600);
+       if (pjob.fd == -1) goto fail;
+
+       print_job_store(jobid, &pjob);
+
+       tdb_unlock_bystring(tdb, "INFO/nextjob");
+
+       /*
+        * If the printer is marked as postscript output a leading
+        * file identifier to ensure the file is treated as a raw
+        * postscript file.
+        * This has a similar effect as CtrlD=0 in WIN.INI file.
+        * tim@fsg.com 09/06/94
+        */
+       if (lp_postscript(snum)) {
+               print_job_write(jobid, "%!\n",3);
+       }
+
+       return jobid;
+
+ fail:
+       if (jobid != -1) {
+               tdb_delete(tdb, print_key(jobid));
+       }
+
+       tdb_unlock_bystring(tdb, "INFO/nextjob");
+       return -1;
 }
 
 /****************************************************************************
-change status of a printer queue entry
+ Print a file - called on closing the file. This spools the job.
 ****************************************************************************/
-void status_printjob(int cnum,int snum,int jobid,int status)
-{
-  char *lpstatus_command = 
-    (status==LPQ_PAUSED?lp_lppausecommand(snum):lp_lpresumecommand(snum));
-  char *printername = PRINTERNAME(snum);
-  pstring syscmd;
-  char jobstr[20];
-  int ret;
-
-  if (!printername || !*printername)
-    {
-      DEBUG(6,("replacing printer name with service (snum=(%s,%d))\n",
-           lp_servicename(snum),snum));
-      printername = lp_servicename(snum);
-    }
-    
-  if (!lpstatus_command || !(*lpstatus_command))
-    {
-      DEBUG(5,("No lpstatus command to %s job\n",
-              (status==LPQ_PAUSED?"pause":"resume")));
-      return;
-    }
-    
-  sprintf(jobstr,"%d",jobid);
-
-  strcpy(syscmd,lpstatus_command);
-  string_sub(syscmd,"%p",printername);
-  string_sub(syscmd,"%j",jobstr);
-  standard_sub(cnum,syscmd);
-
-  ret = smbrun(syscmd,NULL);
-  DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));  
-  lpq_reset(snum); /* queue has changed */
+
+BOOL print_job_end(int jobid)
+{
+       struct printjob *pjob = print_job_find(jobid);
+       int snum;
+       SMB_STRUCT_STAT sbuf;
+       pstring current_directory;
+       pstring print_directory;
+       char *wd, *p, *printer_name;
+       pstring jobname;
+
+       if (!pjob)
+               return False;
+
+       if (pjob->spooled || pjob->pid != local_pid)
+               return False;
+
+       snum = print_job_snum(jobid);
+
+       if (sys_fstat(pjob->fd, &sbuf) == 0)
+               pjob->size = sbuf.st_size;
+
+       close(pjob->fd);
+       pjob->fd = -1;
+
+       if (pjob->size == 0) {
+               /* don't bother spooling empty files */
+               unlink(pjob->filename);
+               tdb_delete(tdb, print_key(jobid));
+               return True;
+       }
+
+       /* we print from the directory path to give the best chance of
+           parsing the lpq output */
+       wd = sys_getwd(current_directory);
+       if (!wd)
+               return False;           
+
+       pstrcpy(print_directory, pjob->filename);
+       p = strrchr(print_directory,'/');
+       if (!p)
+               return False;
+       *p++ = 0;
+
+       if (chdir(print_directory) != 0)
+               return False;
+
+       pstrcpy(jobname, pjob->jobname);
+       pstring_sub(jobname, "'", "_");
+
+       /* send it to the system spooler */
+       print_run_command(snum, 
+                         lp_printcommand(snum), NULL,
+                         "%s", p,
+                         "%J", jobname,
+                         "%f", p,
+                         NULL);
+
+       chdir(wd);
+
+       pjob->spooled = True;
+       print_job_store(jobid, pjob);
+
+       /* force update the database */
+       print_cache_flush(snum);
+       
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
 }
 
+/* utility fn to enumerate the print queue */
+static int traverse_fn_queue(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *state)
+{
+       struct traverse_struct *ts = (struct traverse_struct *)state;
+       struct printjob pjob;
+       int i, jobid;
+
+       if (data.dsize != sizeof(pjob) || key.dsize != sizeof(int)) return 0;
+       memcpy(&jobid, key.dptr, sizeof(jobid));
+       memcpy(&pjob,  data.dptr, sizeof(pjob));
+
+       /* maybe it isn't for this queue */
+       if (ts->snum != print_queue_snum(pjob.qname)) return 0;
+
+       if (ts->qcount >= ts->maxcount) return 0;
+
+       i = ts->qcount;
+
+       ts->queue[i].job = jobid;
+       ts->queue[i].size = pjob.size;
+       ts->queue[i].status = pjob.status;
+       ts->queue[i].priority = 1;
+       ts->queue[i].time = pjob.starttime;
+       fstrcpy(ts->queue[i].user, pjob.user);
+       fstrcpy(ts->queue[i].file, pjob.jobname);
 
+       ts->qcount++;
+
+       return 0;
+}
+
+struct traverse_count_struct {
+       int snum, count;
+};
+
+/* utility fn to count the number of entries in the print queue */
+static int traverse_count_fn_queue(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *state)
+{
+       struct traverse_count_struct *ts = (struct traverse_count_struct *)state;
+       struct printjob pjob;
+       int jobid;
+
+       if (data.dsize != sizeof(pjob) || key.dsize != sizeof(int)) return 0;
+       memcpy(&jobid, key.dptr, sizeof(jobid));
+       memcpy(&pjob,  data.dptr, sizeof(pjob));
+
+       /* maybe it isn't for this queue */
+       if (ts->snum != print_queue_snum(pjob.qname)) return 0;
+
+       ts->count++;
+
+       return 0;
+}
+
+/* Sort print jobs by submittal time */
+
+static int printjob_comp(print_queue_struct *j1, print_queue_struct *j2)
+{
+       /* Silly cases */
+
+       if (!j1 && !j2) return 0;
+       if (!j1) return -1;
+       if (!j2) return 1;
+
+       /* Sort on job start time */
+
+       if (j1->time == j2->time) return 0;
+       return (j1->time > j2->time) ? 1 : -1;
+}
+
+/****************************************************************************
+get a printer queue listing
+****************************************************************************/
+int print_queue_status(int snum, 
+                      print_queue_struct **queue,
+                      print_status_struct *status)
+{
+       struct traverse_struct tstruct;
+       struct traverse_count_struct tsc;
+       fstring keystr;
+       TDB_DATA data, key;
+
+       /* make sure the database is up to date */
+       if (print_cache_expired(snum)) print_queue_update(snum);
+
+       *queue = NULL;
+       
+       /*
+        * Fetch the queue status.  We must do this first, as there may
+        * be no jobs in the queue.
+        */
+       ZERO_STRUCTP(status);
+       slprintf(keystr, sizeof(keystr), "STATUS/%s", lp_servicename(snum));
+       dos_to_unix(keystr, True);             /* Convert key to unix-codepage */
+       key.dptr = keystr;
+       key.dsize = strlen(keystr);
+       data = tdb_fetch(tdb, key);
+       if (data.dptr) {
+               if (data.dsize == sizeof(*status)) {
+                       memcpy(status, data.dptr, sizeof(*status));
+               }
+               free(data.dptr);
+       }
+
+       /*
+        * Now, fetch the print queue information.  We first count the number
+        * of entries, and then only retrieve the queue if necessary.
+        */
+       tsc.count = 0;
+       tsc.snum = snum;
+       
+       tdb_traverse(tdb, traverse_count_fn_queue, (void *)&tsc);
+
+       if (tsc.count == 0)
+               return 0;
+
+       /* Allocate the queue size. */
+       if ((tstruct.queue = (print_queue_struct *)
+            malloc(sizeof(print_queue_struct)*tsc.count))
+                               == NULL)
+               return 0;
+
+       /*
+        * Fill in the queue.
+        * We need maxcount as the queue size may have changed between
+        * the two calls to tdb_traverse.
+        */
+       tstruct.qcount = 0;
+       tstruct.maxcount = tsc.count;
+       tstruct.snum = snum;
+
+       tdb_traverse(tdb, traverse_fn_queue, (void *)&tstruct);
+
+       /* Sort the queue by submission time otherwise they are displayed
+          in hash order. */
+
+       qsort(tstruct.queue, tstruct.qcount, sizeof(print_queue_struct),
+             QSORT_CAST(printjob_comp));
+
+       *queue = tstruct.queue;
+       return tstruct.qcount;
+}
+
+
+/****************************************************************************
+turn a queue name into a snum
+****************************************************************************/
+int print_queue_snum(char *qname)
+{
+       int snum = lp_servicenumber(qname);
+       if (snum == -1 || !lp_print_ok(snum)) return -1;
+       return snum;
+}
+
+
+/****************************************************************************
+ pause a queue
+****************************************************************************/
+BOOL print_queue_pause(struct current_user *user, int snum, int *errcode)
+{
+       char *printer_name;
+       int ret;
+       
+       if (!print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+               *errcode = ERROR_ACCESS_DENIED;
+               return False;
+       }
+
+       ret = print_run_command(snum, lp_queuepausecommand(snum), NULL, 
+                               NULL);
+
+       if (ret != 0) {
+               *errcode = ERROR_INVALID_PARAMETER;
+               return False;
+       }
+
+       /* force update the database */
+       print_cache_flush(snum);
+
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
+}
+
+/****************************************************************************
+ resume a queue
+****************************************************************************/
+BOOL print_queue_resume(struct current_user *user, int snum, int *errcode)
+{
+       char *printer_name;
+       int ret;
+
+       if (!print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+               *errcode = ERROR_ACCESS_DENIED;
+               return False;
+       }
+
+       ret = print_run_command(snum, lp_queueresumecommand(snum), NULL, 
+                               NULL);
+
+       if (ret != 0) {
+               *errcode = ERROR_INVALID_PARAMETER;
+               return False;
+       }
+
+       /* force update the database */
+       print_cache_flush(snum);
+
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
+}
+
+/****************************************************************************
+ purge a queue - implemented by deleting all jobs that we can delete
+****************************************************************************/
+BOOL print_queue_purge(struct current_user *user, int snum, int *errcode)
+{
+       print_queue_struct *queue;
+       print_status_struct status;
+       char *printer_name;
+       int njobs, i;
+
+       njobs = print_queue_status(snum, &queue, &status);
+
+       if (print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+               for (i=0;i<njobs;i++) {
+                       print_job_delete1(queue[i].job);
+               }
+       }
+
+       print_cache_flush(snum);
+       safe_free(queue);
+
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
+}
+#undef OLD_NTDOMAIN