diff options
Diffstat (limited to 'source/printing/printing.c')
-rw-r--r-- | source/printing/printing.c | 2566 |
1 files changed, 1735 insertions, 831 deletions
diff --git a/source/printing/printing.c b/source/printing/printing.c index 1dd8921800a..cb689c05d66 100644 --- a/source/printing/printing.c +++ b/source/printing/printing.c @@ -1,8 +1,9 @@ /* 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 + Copyright (C) Jeremy Allison 2002 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 +20,1744 @@ 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 */ +static 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. +*/ + +/*************************************************************************** + Nightmare. LANMAN jobid's are 16 bit numbers..... We must map them to 32 + bit RPC jobids.... JRA. +***************************************************************************/ + +static TDB_CONTEXT *rap_tdb; +static uint16 next_rap_jobid; + +uint16 pjobid_to_rap(int snum, uint32 jobid) +{ + uint16 rap_jobid; + TDB_DATA data, key; + char jinfo[8]; + + if (!rap_tdb) { + /* Create the in-memory tdb. */ + rap_tdb = tdb_open_log(NULL, 0, TDB_INTERNAL, (O_RDWR|O_CREAT), 0644); + if (!rap_tdb) + return 0; + } + + SIVAL(&jinfo,0,(int32)snum); + SIVAL(&jinfo,4,jobid); + + key.dptr = (char *)&jinfo; + key.dsize = sizeof(jinfo); + data = tdb_fetch(rap_tdb, key); + if (data.dptr && data.dsize == sizeof(uint16)) { + memcpy(&rap_jobid, data.dptr, sizeof(uint16)); + SAFE_FREE(data.dptr); + return rap_jobid; + } + /* Not found - create and store mapping. */ + rap_jobid = ++next_rap_jobid; + if (rap_jobid == 0) + rap_jobid = ++next_rap_jobid; + data.dptr = (char *)&rap_jobid; + data.dsize = sizeof(rap_jobid); + tdb_store(rap_tdb, key, data, TDB_REPLACE); + tdb_store(rap_tdb, data, key, TDB_REPLACE); + return rap_jobid; +} + +BOOL rap_to_pjobid(uint16 rap_jobid, int *psnum, uint32 *pjobid) +{ + TDB_DATA data, key; + char jinfo[8]; + + if (!rap_tdb) + return False; + + key.dptr = (char *)&rap_jobid; + key.dsize = sizeof(rap_jobid); + data = tdb_fetch(rap_tdb, key); + if (data.dptr && data.dsize == sizeof(jinfo)) { + *psnum = IVAL(&jinfo,0); + *pjobid = IVAL(&jinfo,4); + SAFE_FREE(data.dptr); + return True; + } + return False; +} + +static void rap_jobid_delete(int snum, uint32 jobid) +{ + TDB_DATA key, data; + uint16 rap_jobid; + char jinfo[8]; + + if (!rap_tdb) + return; + + SIVAL(&jinfo,0,(int32)snum); + SIVAL(&jinfo,4,jobid); + + key.dptr = (char *)&jinfo; + key.dsize = sizeof(jinfo); + data = tdb_fetch(rap_tdb, key); + if (!data.dptr || (data.dsize != sizeof(uint16))) + return; + + memcpy(&rap_jobid, data.dptr, sizeof(uint16)); + SAFE_FREE(data.dptr); + data.dptr = (char *)&rap_jobid; + data.dsize = sizeof(rap_jobid); + tdb_delete(rap_tdb, key); + tdb_delete(rap_tdb, data); +} + +static pid_t local_pid; + +static int get_queue_status(int, print_status_struct *); + +#define MAX_PRINT_DBS_OPEN 1 + +struct tdb_print_db { + struct tdb_print_db *next, *prev; + TDB_CONTEXT *tdb; + fstring printer_name; +}; + +static struct tdb_print_db *print_db_head; + +/**************************************************************************** + Function to find or create the printer specific job tdb given a printername. + Limits the number of tdb's open to MAX_PRINT_DBS_OPEN. +****************************************************************************/ + +static struct tdb_print_db *get_print_db_byname(const char *printername) +{ + struct tdb_print_db *p, *last_entry; + int num_open = 0; + pstring printdb_path; + + for (p = print_db_head, last_entry = print_db_head; p; p = p->next) { + if (p->tdb && strequal(p->printer_name, printername)) { + DLIST_PROMOTE(print_db_head, p); + return p; + } + num_open++; + last_entry = p; + } + /* Not found. */ + if (num_open >= MAX_PRINT_DBS_OPEN) { + /* Recycle the last entry. */ + DLIST_PROMOTE(print_db_head, last_entry); + if (print_db_head->tdb) { + if (tdb_close(print_db_head->tdb)) { + DEBUG(0,("get_print_db: Failed to close tdb for printer %s\n", + print_db_head->printer_name )); + return NULL; + } + } + p = print_db_head; + ZERO_STRUCTP(p); + } else { + /* Create one. */ + p = (struct tdb_print_db *)malloc(sizeof(struct tdb_print_db)); + if (!p) { + DEBUG(0,("get_print_db: malloc fail !\n")); + return NULL; + } + ZERO_STRUCTP(p); + DLIST_ADD(print_db_head, p); + } + + pstrcpy(printdb_path, lock_path("printing/")); + pstrcat(printdb_path, printername); + pstrcat(printdb_path, ".tdb"); + + become_root(); + p->tdb = tdb_open_log(printdb_path, 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600); + unbecome_root(); + + if (!p->tdb) { + DEBUG(0,("get_print_db: Failed to open printer backend database %s.\n", + printdb_path )); + DLIST_REMOVE(print_db_head, p); + SAFE_FREE(p); + return NULL; + } + fstrcpy(p->printer_name, printername); + return p; +} + +/**************************************************************************** + Initialise the printing backend. Called once at startup. + Does not survive a fork +****************************************************************************/ + +BOOL print_backend_init(void) +{ + char *sversion = "INFO/version"; + pstring printing_path; + int services = lp_numservices(); + int snum; + + if (local_pid == sys_getpid()) + return True; + + unlink(lock_path("printing.tdb")); + pstrcpy(printing_path,lock_path("printing")); + mkdir(printing_path,0755); + + local_pid = sys_getpid(); + + /* handle a Samba upgrade */ + + for (snum = 0; snum < services; snum++) { + struct tdb_print_db *pdb; + if (!lp_print_ok(snum)) + continue; + + pdb = get_print_db_byname(lp_const_servicename(snum)); + if (!pdb) + continue; + tdb_lock_bystring(pdb->tdb, sversion); + if (tdb_fetch_int32(pdb->tdb, sversion) != PRINT_DATABASE_VERSION) { + tdb_traverse(pdb->tdb, tdb_traverse_delete_fn, NULL); + tdb_store_int32(pdb->tdb, sversion, PRINT_DATABASE_VERSION); + } + tdb_unlock_bystring(pdb->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(); +} + +/**************************************************************************** + Shut down printing backend. Called once at shutdown to close the tdb. +****************************************************************************/ + +void printing_end(void) +{ + struct tdb_print_db *p; + + for (p = print_db_head; p; ) { + struct tdb_print_db *next_p = p->next; + if (p->tdb) + tdb_close(p->tdb); + DLIST_REMOVE(print_db_head, p); + SAFE_FREE(p); + p = next_p; + } +} + +/**************************************************************************** + Useful function to generate a tdb key. +****************************************************************************/ + +static TDB_DATA print_key(uint32 jobid) +{ + static uint32 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 snum, uint32 jobid) +{ + static struct printjob pjob; + TDB_DATA ret; + struct tdb_print_db *pdb = get_print_db_byname(lp_const_servicename(snum)); + + if (!pdb) + return NULL; + + ret = tdb_fetch(pdb->tdb, print_key(jobid)); + if (!ret.dptr || ret.dsize != sizeof(pjob)) + return NULL; + + memcpy(&pjob, ret.dptr, sizeof(pjob)); + SAFE_FREE(ret.dptr); + return &pjob; +} + +/* Convert a unix jobid to a smb jobid */ + +static uint32 sysjob_to_jobid_value; + +static int unixjob_traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA key, + TDB_DATA data, void *state) +{ + struct printjob *pjob = (struct printjob *)data.dptr; + int *sysjob = (int *)state; + + if (key.dsize != sizeof(uint32)) + return 0; + + if (*sysjob == pjob->sysjob) { + uint32 *jobid = (uint32 *)key.dptr; + + sysjob_to_jobid_value = *jobid; + return 1; + } + + return 0; +} + +/**************************************************************************** + This is a *horribly expensive call as we have to iterate through all the + current printer tdb's. Don't do this often ! JRA. +****************************************************************************/ + +uint32 sysjob_to_jobid(int unix_jobid) +{ + int services = lp_numservices(); + int snum; + + sysjob_to_jobid_value = (uint32)-1; + + for (snum = 0; snum < services; snum++) { + struct tdb_print_db *pdb; + if (!lp_print_ok(snum)) + continue; + pdb = get_print_db_byname(lp_const_servicename(snum)); + if (pdb) + tdb_traverse(pdb->tdb, unixjob_traverse_fn, &unix_jobid); + if (sysjob_to_jobid_value != (uint32)-1) + return sysjob_to_jobid_value; + } + return (uint32)-1; +} + +/**************************************************************************** + Send notifications based on what has changed after a pjob_store. +****************************************************************************/ + +static struct { + uint32 lpq_status; + uint32 spoolss_status; +} lpq_to_spoolss_status_map[] = { + { LPQ_QUEUED, JOB_STATUS_QUEUED }, + { LPQ_PAUSED, JOB_STATUS_PAUSED }, + { LPQ_SPOOLING, JOB_STATUS_SPOOLING }, + { LPQ_PRINTING, JOB_STATUS_PRINTING }, + { LPQ_DELETING, JOB_STATUS_DELETING }, + { LPQ_OFFLINE, JOB_STATUS_OFFLINE }, + { LPQ_PAPEROUT, JOB_STATUS_PAPEROUT }, + { LPQ_PRINTED, JOB_STATUS_PRINTED }, + { LPQ_DELETED, JOB_STATUS_DELETED }, + { LPQ_BLOCKED, JOB_STATUS_BLOCKED }, + { LPQ_USER_INTERVENTION, JOB_STATUS_USER_INTERVENTION }, + { -1, 0 } +}; + +/* Convert a lpq status value stored in printing.tdb into the + appropriate win32 API constant. */ + +static uint32 map_to_spoolss_status(uint32 lpq_status) +{ + int i = 0; + + while (lpq_to_spoolss_status_map[i].lpq_status != -1) { + if (lpq_to_spoolss_status_map[i].lpq_status == lpq_status) + return lpq_to_spoolss_status_map[i].spoolss_status; + i++; + } + + return 0; +} + +static void pjob_store_notify(int snum, uint32 jobid, struct printjob *old_data, + struct printjob *new_data) +{ + BOOL new_job = False; + + if (!old_data) + new_job = True; + + /* Notify the job name first */ + + if (new_job || !strequal(old_data->jobname, new_data->jobname)) + notify_job_name(snum, jobid, new_data->jobname); + + /* Job attributes that can't be changed. We only send + notification for these on a new job. */ + + if (new_job) { + notify_job_submitted(snum, jobid, new_data->starttime); + notify_job_username(snum, jobid, new_data->user); + } + + /* Job attributes of a new job or attributes that can be + modified. */ + + if (new_job || old_data->status != new_data->status) + notify_job_status(snum, jobid, map_to_spoolss_status(new_data->status)); + + if (new_job || old_data->size != new_data->size) + notify_job_total_bytes(snum, jobid, new_data->size); + + if (new_job || old_data->page_count != new_data->page_count) + notify_job_total_pages(snum, jobid, new_data->page_count); +} + +/**************************************************************************** + Store a job structure back to the database. +****************************************************************************/ + +static BOOL pjob_store(int snum, uint32 jobid, struct printjob *pjob) +{ + TDB_DATA old_data, new_data; + BOOL ret; + struct tdb_print_db *pdb = get_print_db_byname(lp_const_servicename(snum)); + + if (!pdb) + return False; + + /* Get old data */ + + old_data = tdb_fetch(pdb->tdb, print_key(jobid)); + + /* Store new data */ + + new_data.dptr = (void *)pjob; + new_data.dsize = sizeof(*pjob); + ret = (tdb_store(pdb->tdb, print_key(jobid), new_data, TDB_REPLACE) == 0); + + /* Send notify updates for what has changed */ + + if (ret && (old_data.dsize == 0 || old_data.dsize == sizeof(*pjob))) { + pjob_store_notify( + snum, jobid, (struct printjob *)old_data.dptr, + (struct printjob *)new_data.dptr); + free(old_data.dptr); + } + + return ret; +} + +/**************************************************************************** + Remove a job structure from the database. +****************************************************************************/ + +static void pjob_delete(int snum, uint32 jobid) +{ + struct printjob *pjob = print_job_find(snum, jobid); + uint32 job_status = 0; + struct tdb_print_db *pdb = get_print_db_byname(lp_const_servicename(snum)); + + if (!pdb) + return; + + if (!pjob) { + DEBUG(5, ("pjob_delete(): we were asked to delete nonexistent job %u\n", + (unsigned int)jobid)); + return; + } + + /* Send a notification that a job has been deleted */ + + job_status = map_to_spoolss_status(pjob->status); + + /* We must cycle through JOB_STATUS_DELETING and + JOB_STATUS_DELETED for the port monitor to delete the job + properly. */ + + job_status |= JOB_STATUS_DELETING; + notify_job_status(snum, jobid, job_status); + + job_status |= JOB_STATUS_DELETED; + notify_job_status(snum, jobid, job_status); + + /* Remove from printing.tdb */ + + tdb_delete(pdb->tdb, print_key(jobid)); + rap_jobid_delete(snum, jobid); +} + +/**************************************************************************** + Parse a file name from the system spooler to generate a jobid. +****************************************************************************/ + +static uint32 print_parse_jobid(char *fname) +{ + int jobid; + + if (strncmp(fname,PRINT_SPOOL_PREFIX,strlen(PRINT_SPOOL_PREFIX)) != 0) + return (uint32)-1; + fname += strlen(PRINT_SPOOL_PREFIX); + + jobid = atoi(fname); + if (jobid <= 0) + return (uint32)-1; + + return (uint32)jobid; +} + +/**************************************************************************** + List a unix job in the print database. +****************************************************************************/ + +static void print_unix_job(int snum, print_queue_struct *q) +{ + uint32 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(snum, 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_const_servicename(snum)); + + pjob_store(snum, 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; + uint32 jobid; + int i; + + if (data.dsize != sizeof(pjob) || key.dsize != sizeof(jobid)) + 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 - this cannot happen with the split tdb's. JRA */ + 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++) { + uint32 u_jobid = (ts->queue[i].job + UNIX_JOB_START); + if (jobid == u_jobid) + break; + } + if (i == ts->qcount) + pjob_delete(ts->snum, jobid); + 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)) + pjob_delete(ts->snum, jobid); + else + ts->total_jobs++; + return 0; + } + + for (i=0;i<ts->qcount;i++) { + uint32 curr_jobid = print_parse_jobid(ts->queue[i].fs_file); + if (jobid == curr_jobid) + 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()) + pjob_delete(ts->snum, jobid); + 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; + const char *printername = lp_const_servicename(snum); + struct tdb_print_db *pdb = get_print_db_byname(printername); + + if (!pdb) + return; + slprintf(key, sizeof(key)-1, "CACHE/%s", printername); + tdb_store_int32(pdb->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; + struct tdb_print_db *pdb = get_print_db_byname(printer_name); + + if (!pdb) + return (pid_t)-1; + slprintf(keystr, sizeof(keystr)-1, "UPDATING/%s", printer_name); + key.dptr = keystr; + key.dsize = strlen(keystr); + + data = tdb_fetch(pdb->tdb, key); + if (!data.dptr || data.dsize != sizeof(pid_t)) + return (pid_t)-1; + + memcpy(&updating_pid, data.dptr, sizeof(pid_t)); + SAFE_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 the tdb. +****************************************************************************/ + +static void set_updating_pid(const fstring printer_name, BOOL delete) +{ + fstring keystr; + TDB_DATA key; + TDB_DATA data; + pid_t updating_pid = sys_getpid(); + struct tdb_print_db *pdb = get_print_db_byname(printer_name); + + if (!pdb) + return; + + slprintf(keystr, sizeof(keystr)-1, "UPDATING/%s", printer_name); + key.dptr = keystr; + key.dsize = strlen(keystr); + + if (delete) { + tdb_delete(pdb->tdb, key); + return; + } + + data.dptr = (void *)&updating_pid; + data.dsize = sizeof(pid_t); + + tdb_store(pdb->tdb, key, data, TDB_REPLACE); +} + +/**************************************************************************** + Update the internal database from the system print queue for a queue. +****************************************************************************/ + +static void print_queue_update(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; + struct tdb_print_db *pdb; + + fstrcpy(printer_name, lp_const_servicename(snum)); + pdb = get_print_db_byname(printer_name); + if (!pdb) + return; + + /* + * 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(pdb->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(pdb->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(pdb->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(pdb->tdb, cachestr, (int)time(NULL)); + + /* 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++) { + uint32 jobid = print_parse_jobid(queue[i].fs_file); + + if (jobid == (uint32)-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(snum, 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; + + pjob_store(snum, 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(pdb->tdb, traverse_fn_delete, (void *)&tstruct); + + SAFE_FREE(tstruct.queue); + + tdb_store_int32(pdb->tdb, "INFO/total_jobs", tstruct.total_jobs); + + 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 )); + + /* 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(pdb->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(pdb->tdb, keystr, (int32)time(NULL)); + + /* Delete our pid from the db. */ + set_updating_pid(printer_name, True); +} + +/**************************************************************************** + Check if a jobid is valid. It is valid if it exists in the database. +****************************************************************************/ + +BOOL print_job_exists(int snum, uint32 jobid) +{ + struct tdb_print_db *pdb = get_print_db_byname(lp_const_servicename(snum)); + if (!pdb) + return False; + return tdb_exists(pdb->tdb, print_key(jobid)); +} + +/**************************************************************************** + Give the fd used for a jobid. +****************************************************************************/ + +int print_job_fd(int snum, uint32 jobid) +{ + struct printjob *pjob = print_job_find(snum, 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 snum, uint32 jobid) +{ + struct printjob *pjob = print_job_find(snum, 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 snum, uint32 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 snum, uint32 jobid, char *name) +{ + struct printjob *pjob = print_job_find(snum, jobid); + if (!pjob || pjob->pid != local_pid) + return False; + + fstrcpy(pjob->jobname, name); + return pjob_store(snum, jobid, pjob); +} + +/**************************************************************************** + Delete a print job - don't update queue. +****************************************************************************/ + +static BOOL print_job_delete1(int snum, uint32 jobid) +{ + struct printjob *pjob = print_job_find(snum, jobid); + int result = 0; + + if (!pjob) + return False; + + /* + * If already deleting just return. + */ + + if (pjob->status == LPQ_DELETING) + return True; + + /* 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 %u not seen by lpr\n", (unsigned int)jobid)); + } + + /* Set the tdb entry to be deleting. */ + + pjob->status = LPQ_DELETING; + pjob_store(snum, 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) + pjob_delete(snum, jobid); + + return (result == 0); +} + +/**************************************************************************** + Return true if the current user owns the print job. +****************************************************************************/ + +static BOOL is_owner(struct current_user *user, int snum, uint32 jobid) +{ + struct printjob *pjob = print_job_find(snum, 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 snum, uint32 jobid, WERROR *errcode) +{ + BOOL owner; + + owner = is_owner(user, snum, 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(snum, jobid)) + return False; + + /* force update the database and say the delete failed if the + job still exists */ + + print_queue_update(snum); + + return !print_job_exists(snum, jobid); +} + +/**************************************************************************** + Pause a job. +****************************************************************************/ + +BOOL print_job_pause(struct current_user *user, int snum, uint32 jobid, WERROR *errcode) +{ + struct printjob *pjob = print_job_find(snum, jobid); + int ret = -1; + + if (!pjob || !user) + return False; + + if (!pjob->spooled || pjob->sysjob == -1) + return False; + + if (!is_owner(user, snum, 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 */ + + notify_job_status(snum, jobid, JOB_STATUS_PAUSED); + + /* how do we tell if this succeeded? */ + + return True; +} + +/**************************************************************************** + Resume a job. +****************************************************************************/ + +BOOL print_job_resume(struct current_user *user, int snum, uint32 jobid, WERROR *errcode) +{ + struct printjob *pjob = print_job_find(snum, jobid); + int ret; + if (!pjob || !user) + return False; - 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; + if (!pjob->spooled || pjob->sysjob == -1) + return False; + + if (!is_owner(user, snum, 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 */ + + notify_job_status(snum, jobid, JOB_STATUS_QUEUED); + + return True; +} + +/**************************************************************************** + Write to a print file. +****************************************************************************/ + +int print_job_write(int snum, uint32 jobid, const char *buf, int size) +{ + int return_code; + struct printjob *pjob = print_job_find(snum, 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; + pjob_store(snum, 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); + const char *printername = lp_const_servicename(snum); + struct tdb_print_db *pdb = get_print_db_byname(printername); + + if (!pdb) + return False; + + slprintf(key, sizeof(key), "CACHE/%s", printername); + last_qscan_time = (time_t)tdb_fetch_int32(pdb->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", printername, + (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; + const char *printername = lp_const_servicename(snum); + struct tdb_print_db *pdb = get_print_db_byname(printername); + if (!pdb) + return 0; + + ZERO_STRUCTP(status); + slprintf(keystr, sizeof(keystr)-1, "STATUS/%s", printername); + key.dptr = keystr; + key.dsize = strlen(keystr); + data = tdb_fetch(pdb->tdb, key); + if (data.dptr) { + if (data.dsize == sizeof(print_status_struct)) { + memcpy(status, data.dptr, sizeof(print_status_struct)); + } + SAFE_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; + + /* make sure the database is up to date */ + if (print_cache_expired(snum)) + print_queue_update(snum); - 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); + /* 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. This is very expensive. Don't + call ! JRA. +****************************************************************************/ + +static int get_total_jobs(void) +{ + int total_jobs = 0; + int snum; + int services = lp_numservices(); + + for (snum = 0; snum < services; snum++) { + struct tdb_print_db *pdb; + int jobs; + + if (!lp_print_ok(snum)) + continue; + + pdb = get_print_db_byname(lp_const_servicename(snum)); + if (!pdb) + continue; + + /* make sure the database is up to date */ + if (print_cache_expired(snum)) + print_queue_update(snum); + + jobs = tdb_fetch_int32(pdb->tdb, "INFO/total_jobs"); + if (jobs > 0) + total_jobs += jobs; + } + return total_jobs; +} + +/*************************************************************************** + Start spooling a job - return the jobid. +***************************************************************************/ + +uint32 print_job_start(struct current_user *user, int snum, char *jobname) +{ + uint32 jobid; + char *path; + struct printjob pjob; + int next_jobid; + user_struct *vuser; + int njobs = 0; + const char *printername = lp_const_servicename(snum); + struct tdb_print_db *pdb = get_print_db_byname(printername); + + errno = 0; + + if (!pdb) + return (uint32)-1; + + if (!print_access_check(user, snum, PRINTER_ACCESS_USE)) { + DEBUG(3, ("print_job_start: job start denied by security descriptor\n")); + return (uint32)-1; + } + + if (!print_time_access_check(snum)) { + DEBUG(3, ("print_job_start: job start denied by time check\n")); + return (uint32)-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 (uint32)-1; + } + } + + /* for autoloaded printers, check that the printcap entry still exists */ + if (lp_autoloaded(snum) && !pcap_printername_ok(lp_const_servicename(snum), NULL)) { + DEBUG(3, ("print_job_start: printer name %s check failed.\n", lp_const_servicename(snum) )); + errno = ENOENT; + return (uint32)-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 (uint32)-1; + } + + /* Insure the maximum print jobs in the system is not violated */ + if (lp_totalprintjobs() && get_total_jobs() > 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 (uint32)-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_const_servicename(snum)); - /* 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); - } - } + /* lock the database */ + tdb_lock_bystring(pdb->tdb, "INFO/nextjob"); + + next_jobid = tdb_fetch_int32(pdb->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(snum, jobid)) + break; + } + if (jobid == next_jobid || !pjob_store(snum, jobid, &pjob)) { + DEBUG(3, ("print_job_start: either jobid (%d)==next_jobid(%d) or pjob_store failed.\n", + jobid, next_jobid )); + jobid = -1; + goto fail; + } + + if (tdb_store_int32(pdb->tdb, "INFO/nextjob", jobid)==-1) { + DEBUG(3, ("print_job_start: failed to store INFO/nextjob.\n")); + jobid = -1; + goto fail; + } + + /* we have a job entry - now create the spool file */ + slprintf(pjob.filename, sizeof(pjob.filename)-1, "%s/%s%.8u.XXXXXX", + path, PRINT_SPOOL_PREFIX, (unsigned int)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; + } + + pjob_store(snum, jobid, &pjob); + + tdb_unlock_bystring(pdb->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(snum, jobid, "%!\n",3); + } + + return jobid; + + fail: + if (jobid != -1) + pjob_delete(snum, jobid); + + tdb_unlock_bystring(pdb->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 snum, uint32 jobid) +{ + struct printjob *pjob = print_job_find(snum, 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++; + pjob_store(snum, 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 snum, uint32 jobid, BOOL normal_close) +{ + struct printjob *pjob = print_job_find(snum, jobid); + int ret; + SMB_STRUCT_STAT sbuf; + + if (!pjob) + return False; + + if (pjob->spooled || pjob->pid != local_pid) + 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 quite 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); + pjob_delete(snum, 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; + pjob_store(snum, jobid, pjob); + + /* make sure the database is up to date */ + if (print_cache_expired(snum)) + print_queue_update(snum); + + return True; - 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); +fail: - ret = smbrun(syscmd,NULL); - DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret)); - lpq_reset(snum); /* queue has changed */ + /* The print job was not succesfully started. Cleanup */ + /* Still need to add proper error return propagation! 010122:JRR */ + unlink(pjob->filename); + pjob_delete(snum, 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; + uint32 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; + uint32 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 - this cannot happen with the tdb/printer code. JRA */ + 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; + const char *printername = lp_const_servicename(snum); + struct tdb_print_db *pdb = get_print_db_byname(printername); + + *queue = NULL; + + if (!pdb) + return 0; + + /* make sure the database is up to date */ + if (print_cache_expired(snum)) + print_queue_update(snum); + + /* + * 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", printername); + key.dptr = keystr; + key.dsize = strlen(keystr); + data = tdb_fetch(pdb->tdb, key); + if (data.dptr) { + if (data.dsize == sizeof(*status)) { + memcpy(status, data.dptr, sizeof(*status)); + } + SAFE_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(pdb->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(pdb->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(const 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) +{ + 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); + + /* Send a printer notify message */ + + notify_printer_status(snum, PRINTER_STATUS_PAUSED); + + return True; +} + +/**************************************************************************** + Resume a queue. +****************************************************************************/ + +BOOL print_queue_resume(struct current_user *user, int snum, WERROR *errcode) +{ + 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 */ + + notify_printer_status(snum, PRINTER_STATUS_OK); + + 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; + 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, snum, queue[i].job); + + if (owner || can_job_admin) { + print_job_delete1(snum, queue[i].job); + } + } + + SAFE_FREE(queue); + + return True; +} |