diff options
Diffstat (limited to 'source3/printing/printing.c')
-rw-r--r-- | source3/printing/printing.c | 2229 |
1 files changed, 1397 insertions, 832 deletions
diff --git a/source3/printing/printing.c b/source3/printing/printing.c index 1dd8921800..ad5acb1505 100644 --- a/source3/printing/printing.c +++ b/source3/printing/printing.c @@ -1,8 +1,7 @@ /* - Unix SMB/Netbios implementation. - Version 1.9. - printing routines - Copyright (C) Andrew Tridgell 1992-1995 + Unix SMB/CIFS implementation. + 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 @@ -19,841 +18,1407 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include "includes.h" -#include "loadparm.h" -extern int DEBUGLEVEL; -extern connection_struct Connections[]; -extern files_struct Files[]; - -static BOOL * lpq_cache_reset=NULL; - -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; -} - -void lpq_reset(int snum) -{ - if (check_lpq_cache(snum) > snum) lpq_cache_reset[snum]=True; -} - - -/**************************************************************************** -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. -****************************************************************************/ -static char *build_print_command(int cnum, char *command, char *syscmd, char *filename1) -{ - 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); - - standard_sub(cnum,syscmd); - - return (syscmd); -} - - -/**************************************************************************** -print a file - called on closing the file -****************************************************************************/ -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; - - 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; - jobtime = mktime(t); - } - } - return jobtime; -} - - -/**************************************************************************** -parse a lpq line - -here is an example of lpq output under bsd - -Warning: no daemon present -Rank Owner Job Files Total Size -1st tridge 148 README 8096 bytes - -here is an example of lpq output under osf/1 - -Warning: no daemon present -Rank Pri Owner Job Files Total Size -1st 0 tridge 148 README 8096 bytes -****************************************************************************/ -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); - } - } +#include "printing.h" + +/* Current printer interface */ +struct printif *current_printif = &generic_printif; + +/* + 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 + + jobids are assigned when a job starts spooling. +*/ + +/* the open printing.tdb database */ +static TDB_CONTEXT *tdb; +static pid_t local_pid; + +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_log(lock_path("printing.tdb"), 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600); + if (!tdb) { + DEBUG(0,("print_backend_init: Failed to open printing backend database. Error = [%s]\n", + tdb_errorstr(tdb))); + return False; + } + local_pid = sys_getpid(); + + /* handle a Samba upgrade */ + tdb_lock_bystring(tdb, sversion); + if (tdb_fetch_int32(tdb, sversion) != PRINT_DATABASE_VERSION) { + tdb_traverse(tdb, tdb_traverse_delete_fn, NULL); + tdb_store_int32(tdb, sversion, PRINT_DATABASE_VERSION); + } + tdb_unlock_bystring(tdb, sversion); + + /* select the appropriate printing interface... */ +#ifdef HAVE_CUPS + if (strcmp(lp_printcapname(), "cups") == 0) + current_printif = &cups_printif; +#endif /* HAVE_CUPS */ + + /* do NT print initialization... */ + return nt_printing_init(); +} + +/**************************************************************************** +useful function to generate a tdb key +****************************************************************************/ +static TDB_DATA print_key(int jobid) +{ + 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; +} + +/**************************************************************************** +store a job structure back to the database +****************************************************************************/ +static BOOL print_job_store(int jobid, struct printjob *pjob) +{ + TDB_DATA d; + BOOL ret; + + d.dptr = (void *)pjob; + d.dsize = sizeof(*pjob); + ret = (tdb_store(tdb, print_key(jobid), d, TDB_REPLACE) == 0); + return ret; +} + +/**************************************************************************** +parse a file name from the system spooler to generate a jobid +****************************************************************************/ +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; +} + + +/**************************************************************************** +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 */ + + old_pj = print_job_find(jobid); + + ZERO_STRUCT(pj); + + 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->fs_file); + fstrcpy(pj.user, q->fs_user); + fstrcpy(pj.queuename, lp_servicename(snum)); + + print_job_store(jobid, &pj); +} + + +struct traverse_struct { + print_queue_struct *queue; + int qcount, snum, maxcount, total_jobs; +}; + +/* 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 (ts->snum != lp_servicenumber(pjob.queuename)) { + /* this isn't for the queue we are looking at */ + ts->total_jobs++; + 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); + else + ts->total_jobs++; + 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); + else + ts->total_jobs++; + return 0; + } + + for (i=0;i<ts->qcount;i++) { + int qid = print_parse_jobid(ts->queue[i].fs_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); + else + ts->total_jobs++; + } + else + ts->total_jobs++; + + return 0; +} + +/**************************************************************************** +check if the print queue has been updated recently enough +****************************************************************************/ +static void print_cache_flush(int snum) +{ + fstring key; + slprintf(key, sizeof(key)-1, "CACHE/%s", lp_servicename(snum)); + tdb_store_int32(tdb, key, -1); +} + +/**************************************************************************** + Check if someone already thinks they are doing the update. +****************************************************************************/ + +static pid_t get_updating_pid(fstring printer_name) +{ + fstring keystr; + TDB_DATA data, key; + pid_t updating_pid; + + slprintf(keystr, sizeof(keystr)-1, "UPDATING/%s", printer_name); + key.dptr = keystr; + key.dsize = strlen(keystr); + + data = tdb_fetch(tdb, key); + if (!data.dptr || data.dsize != sizeof(pid_t)) + return (pid_t)-1; + + memcpy(&updating_pid, data.dptr, sizeof(pid_t)); + free(data.dptr); + + if (process_exists(updating_pid)) + return updating_pid; + + return (pid_t)-1; +} + +/**************************************************************************** + Set the fact that we're doing the update, or have finished doing the update + in th tdb. +****************************************************************************/ + +static void set_updating_pid(fstring printer_name, BOOL delete) +{ + fstring keystr; + TDB_DATA key; + TDB_DATA data; + pid_t updating_pid = sys_getpid(); + + slprintf(keystr, sizeof(keystr)-1, "UPDATING/%s", printer_name); + key.dptr = keystr; + key.dsize = strlen(keystr); + + if (delete) { + tdb_delete(tdb, key); + return; + } + data.dptr = (void *)&updating_pid; + data.dsize = sizeof(pid_t); + + tdb_store(tdb, key, data, TDB_REPLACE); +} + +/**************************************************************************** + Send a message saying the queue changed. +****************************************************************************/ + +static void send_queue_message(const char *printer_name, uint32 high, uint32 low) +{ + char msg[8 + sizeof(fstring)]; + SIVAL(msg,0,low); + SIVAL(msg,4,high); + fstrcpy(&msg[8], printer_name); + + message_send_all(conn_tdb_ctx(), MSG_PRINTER_NOTIFY, msg, 8 + strlen(printer_name) + 1, False, NULL); +} + +/**************************************************************************** +update the internal database from the system print queue for a queue in the background +****************************************************************************/ + +static void print_queue_update_background(int snum) +{ + int 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, cachestr; + TDB_DATA data, key; + + fstrcpy(printer_name, lp_servicename(snum)); + + /* + * Check to see if someone else is doing this update. + * This is essentially a mutex on the update. + */ + + if (get_updating_pid(printer_name) != -1) + return; + + /* Lock the queue for the database update */ + + slprintf(keystr, sizeof(keystr) - 1, "LOCK/%s", printer_name); + tdb_lock_bystring(tdb, keystr); + + /* + * Ensure that no one else got in here. + * If the updating pid is still -1 then we are + * the winner. + */ + + if (get_updating_pid(printer_name) != -1) { + /* + * Someone else is doing the update, exit. + */ + tdb_unlock_bystring(tdb, keystr); + return; + } + + /* + * We're going to do the update ourselves. + */ + + /* Tell others we're doing the update. */ + set_updating_pid(printer_name, False); + + /* + * Allow others to enter and notice we're doing + * the update. + */ + + tdb_unlock_bystring(tdb, keystr); + + /* + * Update the cache time FIRST ! Stops others even + * attempting to get the lock and doing this + * if the lpq takes a long time. + */ + + slprintf(cachestr, sizeof(cachestr)-1, "CACHE/%s", printer_name); + tdb_store_int32(tdb, cachestr, (int)time(NULL)); - 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); -} - - -/**************************************************************************** -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 -****************************************************************************/ -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; + /* get the current queue using the appropriate interface */ + ZERO_STRUCT(status); + + qcount = (*(current_printif->queue_get))(snum, &queue, &status); + + DEBUG(3, ("%d job%s in queue for %s\n", qcount, (qcount != 1) ? + "s" : "", printer_name)); + + /* + 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].fs_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; + tstruct.total_jobs = 0; + + tdb_traverse(tdb, traverse_fn_delete, (void *)&tstruct); + + safe_free(tstruct.queue); + + tdb_store_int32(tdb, "INFO/total_jobs", tstruct.total_jobs); + + /* + * 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 )); + send_queue_message(printer_name, 0, PRINTER_CHANGE_JOB); + } + + /* store the new queue status structure */ + slprintf(keystr, sizeof(keystr)-1, "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 again. We want to do this call + * as little as possible... + */ + + slprintf(keystr, sizeof(keystr)-1, "CACHE/%s", printer_name); + tdb_store_int32(tdb, keystr, (int)time(NULL)); + + /* Delete our pid from the db. */ + set_updating_pid(printer_name, True); +} + +/**************************************************************************** +this is the receive function of the background lpq updater +****************************************************************************/ +static void print_queue_receive(int msg_type, pid_t src, void *buf, size_t len) +{ + int snum; + snum=*((int *)buf); + print_queue_update_background(snum); +} + +static pid_t background_lpq_updater_pid; + +/**************************************************************************** +main thread of the background lpq updater +****************************************************************************/ +void start_background_queue(void) +{ + DEBUG(3,("start_background_queue: Starting background LPQ thread\n")); + background_lpq_updater_pid = sys_fork(); + + if (background_lpq_updater_pid == -1) { + DEBUG(5,("start_background_queue: background LPQ thread failed to start. %s\n", strerror(errno) )); + exit(1); + } + + if(background_lpq_updater_pid == 0) { + /* Child. */ + DEBUG(5,("start_background_queue: background LPQ thread started\n")); + + claim_connection(NULL,"smbd lpq backend",0,False); + + if (!locking_init(0)) + exit(1); + + if (!print_backend_init()) + exit(1); + + message_register(MSG_PRINTER_UPDATE, print_queue_receive); + + DEBUG(5,("start_background_queue: background LPQ thread waiting for messages\n")); + while (1) { + pause(); + DEBUG(10,("start_background_queue: background LPQ thread got a message\n")); + message_dispatch(); + } + } +} + +/**************************************************************************** +update the internal database from the system print queue for a queue +****************************************************************************/ +static void print_queue_update(int snum) +{ + message_send_pid(background_lpq_updater_pid, MSG_PRINTER_UPDATE, &snum, sizeof(snum), False); +} + +/**************************************************************************** +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)); +} + + +/**************************************************************************** +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 +****************************************************************************/ +int print_job_snum(int jobid) +{ + struct printjob *pjob = print_job_find(jobid); + if (!pjob) return -1; + + return find_service(pjob->queuename); +} + +/**************************************************************************** +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; +} + +/**************************************************************************** +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; +} + + +/**************************************************************************** +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 +****************************************************************************/ +BOOL print_job_set_name(int jobid, char *name) +{ + struct printjob *pjob = print_job_find(jobid); + if (!pjob || pjob->pid != local_pid) return False; + + fstrcpy(pjob->jobname, name); + return print_job_store(jobid, pjob); +} + + +/**************************************************************************** +delete a print job - don't update queue +****************************************************************************/ +static BOOL print_job_delete1(int jobid) +{ + struct printjob *pjob = print_job_find(jobid); + int snum, result = 0; + + if (!pjob) return False; + + /* + * If already deleting just return. + */ + + if (pjob->status == LPQ_DELETING) + return True; + + snum = print_job_snum(jobid); + if (snum == -1) { + DEBUG(5,("print_job_delete1: unknown service number for jobid %d\n", jobid)); + return False; + } + + /* Hrm - we need to be able to cope with deleting a job before it + has reached the spooler. */ + + if (pjob->sysjob == -1) { + DEBUG(5, ("attempt to delete job %d not seen by lpr\n", + jobid)); + } + + /* Set the tdb entry to be deleting. */ + + pjob->status = LPQ_DELETING; + print_job_store(jobid, pjob); + + if (pjob->spooled && pjob->sysjob != -1) + result = (*(current_printif->job_delete))(snum, pjob); + + /* Delete the tdb entry if the delete suceeded or the job hasn't + been spooled. */ + + if (result == 0) { + tdb_delete(tdb, print_key(jobid)); + } + + return (result == 0); +} + +/**************************************************************************** +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; + + if (!pjob || !user) return False; + + 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)); + } +} + +/**************************************************************************** +delete a print job +****************************************************************************/ +BOOL print_job_delete(struct current_user *user, int jobid, WERROR *errcode) +{ + int snum = print_job_snum(jobid); + char *printer_name; + BOOL owner; + + if (snum == -1) { + DEBUG(5,("print_job_delete: unknown service number for jobid %d\n", jobid)); + return False; + } + + owner = is_owner(user, jobid); + + /* Check access against security descriptor or whether the user + owns their job. */ + + if (!owner && + !print_access_check(user, snum, JOB_ACCESS_ADMINISTER)) { + DEBUG(3, ("delete denied by security descriptor\n")); + *errcode = WERR_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 */ + + printer_name = PRINTERNAME(snum); + + send_queue_message(printer_name, 0, PRINTER_CHANGE_JOB); + + return !print_job_exists(jobid); +} + + +/**************************************************************************** +pause a job +****************************************************************************/ +BOOL print_job_pause(struct current_user *user, int jobid, WERROR *errcode) +{ + struct printjob *pjob = print_job_find(jobid); + int snum, ret = -1; + char *printer_name; + + if (!pjob || !user) return False; + + if (!pjob->spooled || pjob->sysjob == -1) return False; + + snum = print_job_snum(jobid); + if (snum == -1) { + DEBUG(5,("print_job_pause: unknown service number for jobid %d\n", jobid)); + return False; + } + if (snum == -1) { + DEBUG(5,("print_job_resume: unknown service number for jobid %d\n", jobid)); + return False; + } + + if (!is_owner(user, jobid) && + !print_access_check(user, snum, JOB_ACCESS_ADMINISTER)) { + DEBUG(3, ("pause denied by security descriptor\n")); + *errcode = WERR_ACCESS_DENIED; + return False; + } + + /* need to pause the spooled entry */ + ret = (*(current_printif->job_pause))(snum, pjob); + + if (ret != 0) { + *errcode = WERR_INVALID_PARAM; + return False; + } + + /* force update the database */ + print_cache_flush(snum); + + /* Send a printer notify message */ + + printer_name = PRINTERNAME(snum); + + send_queue_message(printer_name, 0, PRINTER_CHANGE_JOB); + + /* how do we tell if this succeeded? */ + + return True; +} + +/**************************************************************************** +resume a job +****************************************************************************/ +BOOL print_job_resume(struct current_user *user, int jobid, WERROR *errcode) +{ + struct printjob *pjob = print_job_find(jobid); + char *printer_name; + int snum, ret; + + if (!pjob || !user) return False; + + if (!pjob->spooled || pjob->sysjob == -1) return False; + + snum = print_job_snum(jobid); + + if (!is_owner(user, jobid) && + !print_access_check(user, snum, JOB_ACCESS_ADMINISTER)) { + DEBUG(3, ("resume denied by security descriptor\n")); + *errcode = WERR_ACCESS_DENIED; + return False; + } + + ret = (*(current_printif->job_resume))(snum, pjob); + + if (ret != 0) { + *errcode = WERR_INVALID_PARAM; + return False; + } + + /* force update the database */ + print_cache_flush(snum); + + /* Send a printer notify message */ + + printer_name = PRINTERNAME(snum); + + send_queue_message(printer_name, 0, PRINTER_CHANGE_JOB); + + return True; +} + +/**************************************************************************** +write to a print file +****************************************************************************/ +int print_job_write(int jobid, const char *buf, int size) +{ + int return_code; + 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_code = write(pjob->fd, buf, size); + if (return_code>0) { + pjob->size += size; + print_job_store(jobid, pjob); + } + return return_code; +} + +/**************************************************************************** + Check if the print queue has been updated recently enough. +****************************************************************************/ + +static BOOL print_cache_expired(int snum) +{ + fstring key; + time_t last_qscan_time, time_now = time(NULL); + + slprintf(key, sizeof(key), "CACHE/%s", lp_servicename(snum)); + last_qscan_time = (time_t)tdb_fetch_int32(tdb, key); + + /* + * Invalidate the queue for 3 reasons. + * (1). last queue scan time == -1. + * (2). Current time - last queue scan time > allowed cache time. + * (3). last queue scan time > current time + MAX_CACHE_VALID_TIME (1 hour by default). + * This last test picks up machines for which the clock has been moved + * forward, an lpq scan done and then the clock moved back. Otherwise + * that last lpq scan would stay around for a loooong loooong time... :-). JRA. + */ + + if (last_qscan_time == ((time_t)-1) || (time_now - last_qscan_time) >= lp_lpqcachetime() || + last_qscan_time > (time_now + MAX_CACHE_VALID_TIME)) { + DEBUG(3, ("print cache expired for queue %s \ +(last_qscan_time = %d, time now = %d, qcachetime = %d)\n", lp_servicename(snum), + (int)last_qscan_time, (int)time_now, (int)lp_lpqcachetime() )); + 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)-1, "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. +****************************************************************************/ + +int print_queue_length(int snum, print_status_struct *pstatus) +{ + print_status_struct status; + int len; - 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 */ - } -} - - -/**************************************************************************** -parse a lpq line - -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 - -****************************************************************************/ -static BOOL parse_lpq_sysv(char *line,print_queue_struct *buf,BOOL first) -{ - string tok[9]; - int count=0; - char *p; - - /* 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); -} - -/**************************************************************************** -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 -- -****************************************************************************/ -static BOOL parse_lpq_qnx(char *line,print_queue_struct *buf,BOOL first) -{ - string tok[7]; - int count=0; - - DEBUG(0,("antes [%s]\n", line)); - - /* 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)); - - string_sub(line,"[job #",""); - string_sub(line,"]",""); - DEBUG(0,("despues 2 [%s]\n", line)); - - - - 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); + /* make sure the database is up to date */ + if (print_cache_expired(snum)) + print_queue_update(snum); + + /* also fetch the queue status */ + memset(&status, 0, sizeof(status)); + len = get_queue_status(snum, &status); + if (pstatus) + *pstatus = status; + return len; +} + +/**************************************************************************** + Determine the number of jobs in all queues. +****************************************************************************/ +static int get_total_jobs(int snum) +{ + int total_jobs; + + /* make sure the database is up to date */ + if (print_cache_expired(snum)) print_queue_update(snum); + + total_jobs = tdb_fetch_int32(tdb, "INFO/total_jobs"); + if (total_jobs >0) + return total_jobs; + else + return 0; +} + +/*************************************************************************** +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; + int njobs = 0; + + 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)) { + DEBUG(3, ("print_job_start: disk space check failed.\n")); + 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)) { + DEBUG(3, ("print_job_start: printer name %s check failed.\n", lp_servicename(snum) )); + errno = ENOENT; + return -1; + } + + /* Insure the maximum queue size is not violated */ + if (lp_maxprintjobs(snum) && (njobs = print_queue_length(snum,NULL)) > lp_maxprintjobs(snum)) { + DEBUG(3, ("print_job_start: number of jobs (%d) larger than max printjobs per queue (%d).\n", + njobs, lp_maxprintjobs(snum) )); + errno = ENOSPC; + return -1; + } + + /* Insure the maximum print jobs in the system is not violated */ + if (lp_totalprintjobs() && get_total_jobs(snum) > lp_totalprintjobs()) { + DEBUG(3, ("print_job_start: number of jobs (%d) larger than max printjobs per system (%d).\n", + njobs, lp_totalprintjobs() )); + 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_SPOOLING; + 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.queuename, lp_servicename(snum)); + + /* lock the database */ + tdb_lock_bystring(tdb, "INFO/nextjob"); + + next_jobid = tdb_fetch_int32(tdb, "INFO/nextjob"); + if (next_jobid == -1) + next_jobid = 1; + + for (jobid = NEXT_JOBID(next_jobid); jobid != next_jobid; jobid = NEXT_JOBID(jobid)) { + if (!print_job_exists(jobid)) + break; + } + if (jobid == next_jobid || !print_job_store(jobid, &pjob)) { + DEBUG(3, ("print_job_start: either jobid (%d)==next_jobid(%d) or print_job_store failed.\n", + jobid, next_jobid )); + jobid = -1; + goto fail; + } + + tdb_store_int32(tdb, "INFO/nextjob", jobid); + + /* we have a job entry - now create the spool file */ + slprintf(pjob.filename, sizeof(pjob.filename)-1, "%s/%s%.6d.XXXXXX", + path, PRINT_SPOOL_PREFIX, jobid); + pjob.fd = smb_mkstemp(pjob.filename); + + if (pjob.fd == -1) { + if (errno == EACCES) { + /* Common setup error, force a report. */ + DEBUG(0, ("print_job_start: insufficient permissions \ +to open spool file %s.\n", pjob.filename)); + } else { + /* Normal case, report at level 3 and above. */ + DEBUG(3, ("print_job_start: can't open spool file %s,\n", pjob.filename)); + DEBUGADD(3, ("errno = %d (%s).\n", errno, strerror(errno))); + } + 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"); + + DEBUG(3, ("print_job_start: returning fail. Error = %s\n", strerror(errno) )); + return -1; +} + +/**************************************************************************** + Update the number of pages spooled to jobid +****************************************************************************/ + +void print_job_endpage(int jobid) +{ + struct printjob *pjob = print_job_find(jobid); + if (!pjob) + return; + /* don't allow another process to get this info - it is meaningless */ + if (pjob->pid != local_pid) + return; + + pjob->page_count++; + print_job_store(jobid, pjob); +} + +/**************************************************************************** + Print a file - called on closing the file. This spools the job. + If normal close is false then we're tearing down the jobs - treat as an + error. +****************************************************************************/ + +BOOL print_job_end(int jobid, BOOL normal_close) +{ + struct printjob *pjob = print_job_find(jobid); + int snum, ret; + SMB_STRUCT_STAT sbuf; + + if (!pjob) + return False; + + if (pjob->spooled || pjob->pid != local_pid) + return False; + + snum = print_job_snum(jobid); + if (snum == -1) { + DEBUG(5,("print_job_end: unknown service number for jobid %d\n", jobid)); + return False; + } + + if (normal_close && (sys_fstat(pjob->fd, &sbuf) == 0)) { + pjob->size = sbuf.st_size; + close(pjob->fd); + pjob->fd = -1; + } else { + + /* + * Not a normal close or we couldn't stat the job file, + * so something has gone wrong. Cleanup. + */ + close(pjob->fd); + pjob->fd = -1; + DEBUG(3,("print_job_end: failed to stat file for jobid %d\n", jobid )); + goto fail; + } + + /* Technically, this is not quit right. If the printer has a separator + * page turned on, the NT spooler prints the separator page even if the + * print job is 0 bytes. 010215 JRR */ + if (pjob->size == 0 || pjob->status == LPQ_DELETING) { + /* don't bother spooling empty files or something being deleted. */ + DEBUG(5,("print_job_end: canceling spool of %s (%s)\n", + pjob->filename, pjob->size ? "deleted" : "zero length" )); + unlink(pjob->filename); + tdb_delete(tdb, print_key(jobid)); + return True; + } + + ret = (*(current_printif->job_submit))(snum, pjob); + + if (ret) + goto fail; + + /* The print job has been sucessfully handed over to the back-end */ + + pjob->spooled = True; + pjob->status = LPQ_QUEUED; + print_job_store(jobid, pjob); + + /* make sure the database is up to date */ + if (print_cache_expired(snum)) + print_queue_update(snum); + + return True; + +fail: + + /* The print job was not succesfully started. Cleanup */ + /* Still need to add proper error return propagation! 010122:JRR */ + unlink(pjob->filename); + tdb_delete(tdb, print_key(jobid)); + return False; +} + +/* 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 != lp_servicenumber(pjob.queuename)) + 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].page_count = pjob.page_count; + ts->queue[i].status = pjob.status; + ts->queue[i].priority = 1; + ts->queue[i].time = pjob.starttime; + fstrcpy(ts->queue[i].fs_user, pjob.user); + fstrcpy(ts->queue[i].fs_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 != lp_servicenumber(pjob.queuename)) + 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; - /* 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); - } - } + /* 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)-1, "STATUS/%s", lp_servicename(snum)); + 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, WERROR *errcode) +{ + char *printer_name; + int ret; + + if (!print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) { + *errcode = WERR_ACCESS_DENIED; + return False; + } + + ret = (*(current_printif->queue_pause))(snum); + + if (ret != 0) { + *errcode = WERR_INVALID_PARAM; + return False; + } + + /* force update the database */ + print_cache_flush(snum); - 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); -} - - - -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 }; - -/**************************************************************************** -parse a lpq line. Choose printing style -****************************************************************************/ -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; - 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); -} - -/**************************************************************************** -get a printer queue -****************************************************************************/ -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); -} - - -/**************************************************************************** -delete a printer queue entry -****************************************************************************/ -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 */ -} - -/**************************************************************************** -change status of a printer queue entry -****************************************************************************/ -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); + /* Send a printer notify message */ - ret = smbrun(syscmd,NULL); - DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret)); - lpq_reset(snum); /* queue has changed */ + printer_name = PRINTERNAME(snum); + + send_queue_message(printer_name, 0, PRINTER_CHANGE_JOB); + + return True; } +/**************************************************************************** + resume a queue +****************************************************************************/ +BOOL print_queue_resume(struct current_user *user, int snum, WERROR *errcode) +{ + char *printer_name; + int ret; + + if (!print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) { + *errcode = WERR_ACCESS_DENIED; + return False; + } + + ret = (*(current_printif->queue_resume))(snum); + if (ret != 0) { + *errcode = WERR_INVALID_PARAM; + return False; + } + + /* make sure the database is up to date */ + if (print_cache_expired(snum)) print_queue_update(snum); + + /* Send a printer notify message */ + + printer_name = PRINTERNAME(snum); + + send_queue_message(printer_name, 0, PRINTER_CHANGE_JOB); + + return True; +} + +/**************************************************************************** + purge a queue - implemented by deleting all jobs that we can delete +****************************************************************************/ +BOOL print_queue_purge(struct current_user *user, int snum, WERROR *errcode) +{ + print_queue_struct *queue; + print_status_struct status; + char *printer_name; + int njobs, i; + BOOL can_job_admin; + + /* Force and update so the count is accurate (i.e. not a cached count) */ + print_queue_update(snum); + + can_job_admin = print_access_check(user, snum, JOB_ACCESS_ADMINISTER); + njobs = print_queue_status(snum, &queue, &status); + + for (i=0;i<njobs;i++) { + BOOL owner = is_owner(user, queue[i].job); + + if (owner || can_job_admin) { + print_job_delete1(queue[i].job); + } + } + + safe_free(queue); + + /* Send a printer notify message */ + + printer_name = PRINTERNAME(snum); + + send_queue_message(printer_name, 0, PRINTER_CHANGE_JOB); + + return True; +} |