Changes from APPLIANCE_HEAD:
[ira/wip.git] / source3 / printing / printing.c
index b884c5ac99a6f23f3a2413b60fc72aacaede7608..b670908049c41b2720a0af1deab86ae0334fc2d4 100644 (file)
@@ -21,6 +21,7 @@
 */
 
 #include "includes.h"
+
 extern int DEBUGLEVEL;
 
 /* 
@@ -60,7 +61,9 @@ static pid_t local_pid;
 #define UNIX_JOB_START PRINT_MAX_JOBID
 
 #define PRINT_SPOOL_PREFIX "smbprn."
-#define PRINT_DATABASE_VERSION 1
+#define PRINT_DATABASE_VERSION 2
+
+static int get_queue_status(int, print_status_struct *);
 
 /****************************************************************************
 initialise the printing backend. Called once at startup. 
@@ -126,7 +129,8 @@ static BOOL print_job_store(int jobid, struct printjob *pjob)
        TDB_DATA d;
        d.dptr = (void *)pjob;
        d.dsize = sizeof(*pjob);
-       return (0 == tdb_store(tdb, print_key(jobid), d, TDB_REPLACE));
+
+       return (tdb_store(tdb, print_key(jobid), d, TDB_REPLACE) == 0);
 }
 
 /****************************************************************************
@@ -168,6 +172,7 @@ static int print_run_command(int snum,char *command,
        ret = smbrun(syscmd,outfile,False);
 
        DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
+
        return ret;
 }
 
@@ -195,14 +200,18 @@ 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;
+       struct printjob pj, *old_pj;
+
+       /* Preserve the timestamp on an existing unix print job */
+
+       old_pj = print_job_find(jobid);
 
        ZERO_STRUCT(pj);
 
        pj.pid = (pid_t)-1;
        pj.sysjob = q->job;
        pj.fd = -1;
-       pj.starttime = q->time;
+       pj.starttime = old_pj ? old_pj->starttime : q->time;
        pj.status = q->status;
        pj.size = q->size;
        pj.spooled = True;
@@ -218,7 +227,7 @@ static void print_unix_job(int snum, print_queue_struct *q)
 
 struct traverse_struct {
        print_queue_struct *queue;
-       int qcount, snum;
+       int qcount, snum, maxcount;
 };
 
 /* utility fn to delete any jobs that are no longer active */
@@ -240,6 +249,7 @@ static int traverse_fn_delete(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void
        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;
                }
@@ -263,11 +273,23 @@ static int traverse_fn_delete(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void
                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) {
-               /* the job isn't in the system queue - we have to
-                   assume it has completed, so delete the database
-                   entry */
-               tdb_delete(t, key);
+               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;
@@ -295,38 +317,56 @@ static void print_queue_update(int snum)
        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;
+       fstring keystr, printer_name;
        TDB_DATA data, key;
  
+       fstrcpy(printer_name, lp_servicename(snum));
+       
+       /*
+        * 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);
+       print_run_command(snum, cmd, tmp_file, NULL);
 
        numlines = 0;
-       qlines = file_lines_load(tmp_file, &numlines);
+       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);
-       for (i=0; i<numlines; i++) {
-               queue = Realloc(queue,sizeof(print_queue_struct)*(qcount+1));
-               if (!queue) {
-                       qcount = 0;
-                       break;
-               }
-               /* parse the line */
-               if (parse_lpq_entry(snum,qlines[i],
-                                   &queue[qcount],&status,qcount==0)) {
-                       qcount++;
-               }
-       }               
+       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);
+
        /*
          any job in the internal database that is marked as spooled
          and doesn't exist in the system queue is considered finished
@@ -372,16 +412,39 @@ static void print_queue_update(int snum)
 
        safe_free(tstruct.queue);
 
-       /* store the queue status structure */
-       slprintf(keystr, sizeof(keystr), "STATUS/%s", lp_servicename(snum));
-       data.dptr = (void *)&status;
-       data.dsize = sizeof(status);
+       /*
+        * 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(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); 
 
-       /* update the cache time */
-       slprintf(keystr, sizeof(keystr), "CACHE/%s", lp_servicename(snum));
+       /* 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));
 }
 
@@ -500,13 +563,12 @@ static BOOL is_owner(struct current_user *user, int jobid)
 /****************************************************************************
 delete a print job
 ****************************************************************************/
-BOOL print_job_delete(struct current_user *user, int jobid)
+BOOL print_job_delete(struct current_user *user, int jobid, int *errcode)
 {
        int snum = print_job_snum(jobid);
+       char *printer_name;
        BOOL owner;
        
-       if (!user) return False;
-
        owner = is_owner(user, jobid);
        
        /* Check access against security descriptor or whether the user
@@ -515,6 +577,7 @@ BOOL print_job_delete(struct current_user *user, int jobid)
        if (!owner && 
            !print_access_check(user, snum, JOB_ACCESS_ADMINISTER)) {
                DEBUG(3, ("delete denied by security descriptor\n"));
+               *errcode = ERROR_ACCESS_DENIED;
                return False;
        }
 
@@ -525,6 +588,12 @@ BOOL print_job_delete(struct current_user *user, int jobid)
 
        print_queue_update(snum);
 
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
        return !print_job_exists(jobid);
 }
 
@@ -532,14 +601,14 @@ BOOL print_job_delete(struct current_user *user, int jobid)
 /****************************************************************************
 pause a job
 ****************************************************************************/
-BOOL print_job_pause(struct current_user *user, int jobid)
+BOOL print_job_pause(struct current_user *user, int jobid, int *errcode)
 {
        struct printjob *pjob = print_job_find(jobid);
        int snum, ret = -1;
+       char *printer_name;
        fstring jobstr;
        BOOL owner;
        
-
        if (!pjob || !user) return False;
 
        if (!pjob->spooled || pjob->sysjob == -1) return False;
@@ -550,6 +619,7 @@ BOOL print_job_pause(struct current_user *user, int jobid)
        if (!owner &&
            !print_access_check(user, snum, JOB_ACCESS_ADMINISTER)) {
                DEBUG(3, ("pause denied by security descriptor\n"));
+               *errcode = ERROR_ACCESS_DENIED;
                return False;
        }
 
@@ -560,19 +630,32 @@ BOOL print_job_pause(struct current_user *user, int jobid)
                                "%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(MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
        /* how do we tell if this succeeded? */
-       return ret == 0;
+
+       return True;
 }
 
 /****************************************************************************
 resume a job
 ****************************************************************************/
-BOOL print_job_resume(struct current_user *user, int jobid)
+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;
@@ -587,6 +670,7 @@ BOOL print_job_resume(struct current_user *user, int jobid)
        if (!is_owner(user, jobid) &&
            !print_access_check(user, snum, JOB_ACCESS_ADMINISTER)) {
                DEBUG(3, ("resume denied by security descriptor\n"));
+               *errcode = ERROR_ACCESS_DENIED;
                return False;
        }
 
@@ -596,11 +680,21 @@ BOOL print_job_resume(struct current_user *user, int jobid)
                                "%j", jobstr,
                                NULL);
 
+       if (ret != 0) {
+               *errcode = ERROR_INVALID_PARAMETER;
+               return False;
+       }
+
        /* force update the database */
        print_cache_flush(snum);
 
-       /* how do we tell if this succeeded? */
-       return ret == 0;
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
 }
 
 /****************************************************************************
@@ -616,6 +710,61 @@ int print_job_write(int jobid, const char *buf, int size)
        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));
+       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;
+}
+
+/****************************************************************************
+ Get the queue status - do not update if db is out of date.
+****************************************************************************/
+
+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));
+       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
@@ -658,6 +807,11 @@ int print_job_start(struct current_user *user, int snum, char *jobname)
                return -1;
        }
 
+       if (print_queue_length(snum) > lp_maxprintjobs(snum)) {
+               errno = ENOSPC;
+               return -1;
+       }
+
        /* create the database entry */
        ZERO_STRUCT(pjob);
        pjob.pid = local_pid;
@@ -749,7 +903,7 @@ BOOL print_job_end(int jobid)
        SMB_STRUCT_STAT sbuf;
        pstring current_directory;
        pstring print_directory;
-       char *wd, *p;
+       char *wd, *p, *printer_name;
        pstring jobname;
 
        if (!pjob)
@@ -807,23 +961,13 @@ BOOL print_job_end(int jobid)
        /* force update the database */
        print_cache_flush(snum);
        
-       return True;
-}
+       /* Send a printer notify message */
 
-/****************************************************************************
- Check if the print queue has been updated recently enough.
-****************************************************************************/
+       printer_name = PRINTERNAME(snum);
 
-static BOOL print_cache_expired(int snum)
-{
-       fstring key;
-       time_t t2, t = time(NULL);
-       slprintf(key, sizeof(key), "CACHE/%s", lp_servicename(snum));
-       t2 = tdb_fetch_int(tdb, key);
-       if (t2 == ((time_t)-1) || (t - t2) >= lp_lpqcachetime()) {
-               return True;
-       }
-       return False;
+       message_send_all(MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
 }
 
 /* utility fn to enumerate the print queue */
@@ -840,8 +984,8 @@ static int traverse_fn_queue(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *
        /* maybe it isn't for this queue */
        if (ts->snum != print_queue_snum(pjob.qname)) return 0;
 
-       ts->queue = Realloc(ts->queue,sizeof(print_queue_struct)*(ts->qcount+1));
-       if (!ts->queue) return -1;
+       if (ts->qcount >= ts->maxcount) return 0;
+
        i = ts->qcount;
 
        ts->queue[i].job = jobid;
@@ -857,6 +1001,45 @@ static int traverse_fn_queue(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *
        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
 ****************************************************************************/
@@ -865,20 +1048,19 @@ int print_queue_status(int snum,
                       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);
-       
-       /* fill in the queue */
-       tstruct.queue = NULL;
-       tstruct.qcount = 0;
-       tstruct.snum = snum;
-
-       tdb_traverse(tdb, traverse_fn_queue, (void *)&tstruct);
 
-       /* also fetch the queue status */
+       *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));
        key.dptr = keystr;
@@ -891,6 +1073,41 @@ int print_queue_status(int snum,
                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;
 }
@@ -912,10 +1129,9 @@ int print_queue_snum(char *qname)
 ****************************************************************************/
 BOOL print_queue_pause(struct current_user *user, int snum, int *errcode)
 {
+       char *printer_name;
        int ret;
        
-       if (!user) return False;
-       
        if (!print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
                *errcode = ERROR_ACCESS_DENIED;
                return False;
@@ -924,10 +1140,21 @@ BOOL print_queue_pause(struct current_user *user, int snum, int *errcode)
        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);
 
-       return ret == 0;
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
 }
 
 /****************************************************************************
@@ -935,6 +1162,7 @@ BOOL print_queue_pause(struct current_user *user, int snum, int *errcode)
 ****************************************************************************/
 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)) {
@@ -945,10 +1173,21 @@ BOOL print_queue_resume(struct current_user *user, int snum, int *errcode)
        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);
 
-       return ret == 0;
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+       return True;
 }
 
 /****************************************************************************
@@ -958,6 +1197,7 @@ 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);
@@ -968,6 +1208,13 @@ BOOL print_queue_purge(struct current_user *user, int snum, int *errcode)
        }
 
        print_cache_flush(snum);
+       safe_free(queue);
+
+       /* Send a printer notify message */
+
+       printer_name = PRINTERNAME(snum);
+
+       message_send_all(MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
 
        return True;
 }