+#define OLD_NTDOMAIN 1
/*
Unix SMB/Netbios implementation.
- Version 1.9.
- printing routines
- Copyright (C) Andrew Tridgell 1992-1998
+ Version 3.0
+ printing backend routines
+ Copyright (C) Andrew Tridgell 1992-2000
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
#include "includes.h"
+
extern int DEBUGLEVEL;
-static BOOL * lpq_cache_reset=NULL;
+/*
+ the printing backend revolves around a tdb database that stores the
+ SMB view of the print queue
+
+ The key for this database is a jobid - a internally generated number that
+ uniquely identifies a print job
+
+ reading the print queue involves two steps:
+ - possibly running lpq and updating the internal database from that
+ - reading entries from the database
-static int check_lpq_cache(int snum) {
- static int lpq_caches=0;
-
- if (lpq_caches <= snum) {
- BOOL * p;
- p = (BOOL *) Realloc(lpq_cache_reset,(snum+1)*sizeof(BOOL));
- if (p) {
- lpq_cache_reset=p;
- lpq_caches = snum+1;
- }
- }
- return lpq_caches;
+ jobids are assigned when a job starts spooling.
+*/
+
+struct printjob {
+ pid_t pid; /* which process launched the job */
+ int sysjob; /* the system (lp) job number */
+ int fd; /* file descriptor of open file if open */
+ time_t starttime; /* when the job started spooling */
+ int status; /* the status of this job */
+ size_t size; /* the size of the job so far */
+ BOOL spooled; /* has it been sent to the spooler yet? */
+ BOOL smbjob; /* set if the job is a SMB job */
+ fstring filename; /* the filename used to spool the file */
+ fstring jobname; /* the job name given to us by the client */
+ fstring user; /* the user who started the job */
+ fstring qname; /* name of the print queue the job was sent to */
+};
+
+/* the open printing.tdb database */
+static TDB_CONTEXT *tdb;
+static pid_t local_pid;
+
+#define PRINT_MAX_JOBID 10000
+#define UNIX_JOB_START PRINT_MAX_JOBID
+
+#define PRINT_SPOOL_PREFIX "smbprn."
+#define PRINT_DATABASE_VERSION 2
+
+static int get_queue_status(int, print_status_struct *);
+
+/****************************************************************************
+initialise the printing backend. Called once at startup.
+Does not survive a fork
+****************************************************************************/
+BOOL print_backend_init(void)
+{
+ char *sversion = "INFO/version";
+
+ if (tdb && local_pid == sys_getpid()) return True;
+ tdb = tdb_open(lock_path("printing.tdb"), 0, 0, O_RDWR|O_CREAT, 0600);
+ if (!tdb) {
+ DEBUG(0,("Failed to open printing backend database\n"));
+ }
+ local_pid = sys_getpid();
+
+ /* handle a Samba upgrade */
+ tdb_lock_bystring(tdb, sversion);
+ if (tdb_fetch_int(tdb, sversion) != PRINT_DATABASE_VERSION) {
+ tdb_traverse(tdb, (tdb_traverse_func)tdb_delete, NULL);
+ tdb_store_int(tdb, sversion, PRINT_DATABASE_VERSION);
+ }
+ tdb_unlock_bystring(tdb, sversion);
+
+ return nt_printing_init();
+}
+
+/****************************************************************************
+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;
}
-void lpq_reset(int snum)
+/****************************************************************************
+useful function to find a print job in the database
+****************************************************************************/
+static struct printjob *print_job_find(int jobid)
{
- if (check_lpq_cache(snum) > snum) lpq_cache_reset[snum]=True;
+ 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;
+ d.dptr = (void *)pjob;
+ d.dsize = sizeof(*pjob);
+
+ return (tdb_store(tdb, print_key(jobid), d, TDB_REPLACE) == 0);
+}
/****************************************************************************
-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.
+run a given print command
+a null terminated list of value/substitute pairs is provided
+for local substitution strings
****************************************************************************/
-static char *build_print_command(connection_struct *conn,
- char *command,
- char *syscmd, char *filename)
+static int print_run_command(int snum,char *command,
+ char *outfile,
+ ...)
{
- int snum = SNUM(conn);
- char *tstr;
-
- /* 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);
- }
+ pstring syscmd;
+ char *p, *arg;
+ int ret;
+ va_list ap;
- /* 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 (!command || !*command) return -1;
+
+ if (!VALID_SNUM(snum)) {
+ DEBUG(0,("Invalid snum %d for command %s\n", snum, command));
+ return -1;
}
-
- pstring_sub(syscmd, "%s", filename);
- pstring_sub(syscmd, "%f", filename);
-
- /* 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);
+
+ pstrcpy(syscmd, command);
+
+ va_start(ap, outfile);
+ while ((arg = va_arg(ap, char *))) {
+ char *value = va_arg(ap,char *);
+ pstring_sub(syscmd, arg, value);
}
+ va_end(ap);
- pstring_sub(syscmd, "%p", tstr);
-
- standard_sub(conn,syscmd);
+ p = PRINTERNAME(snum);
+ if (!p || !*p) p = SERVICE(snum);
- return (syscmd);
+ pstring_sub(syscmd, "%p", p);
+ standard_sub_snum(snum,syscmd);
+
+ /* Convert script args to unix-codepage */
+ dos_to_unix(syscmd, True);
+ ret = smbrun(syscmd,outfile,False);
+
+ DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
+
+ return ret;
}
/****************************************************************************
-print a file - called on closing the file
+parse a file name from the system spooler to generate a jobid
****************************************************************************/
-void print_file(connection_struct *conn, files_struct *file)
+static int print_parse_jobid(char *fname)
{
- pstring syscmd;
- int snum = SNUM(conn);
- char *tempstr;
+ int jobid;
- *syscmd = 0;
+ if (strncmp(fname,PRINT_SPOOL_PREFIX,strlen(PRINT_SPOOL_PREFIX)) != 0) return -1;
+ fname += strlen(PRINT_SPOOL_PREFIX);
- if (dos_file_size(file->fsp_name) <= 0) {
- DEBUG(3,("Discarding null print job %s\n",file->fsp_name));
- dos_unlink(file->fsp_name);
- return;
- }
+ jobid = atoi(fname);
+ if (jobid <= 0) return -1;
- tempstr = build_print_command(conn,
- PRINTCOMMAND(snum),
- syscmd, file->fsp_name);
- if (tempstr != NULL) {
- int ret = smbrun(syscmd,NULL,False);
- DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
- } else {
- DEBUG(0,("Null print command?\n"));
- }
-
- lpq_reset(snum);
+ return jobid;
}
/****************************************************************************
-get a printer queue
+list a unix job in the print database
****************************************************************************/
-int get_printqueue(int snum,
- connection_struct *conn,print_queue_struct **queue,
- print_status_struct *status)
+static void print_unix_job(int snum, print_queue_struct *q)
{
- char *lpq_command = lp_lpqcommand(snum);
- char *printername = PRINTERNAME(snum);
- int ret=0,count=0;
- pstring syscmd;
- fstring outfile;
- pstring line;
- FILE *f;
- SMB_STRUCT_STAT sbuf;
- BOOL dorun=True;
- int cachetime = lp_lpqcachetime();
-
- *line = 0;
- check_lpq_cache(snum);
-
- if (!printername || !*printername) {
- DEBUG(6,("xx 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);
+ 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->file);
+ fstrcpy(pj.user, q->user);
+ fstrcpy(pj.qname, lp_servicename(snum));
+
+ print_job_store(jobid, &pj);
+}
+
+
+struct traverse_struct {
+ print_queue_struct *queue;
+ int qcount, snum, maxcount;
+};
+
+/* utility fn to delete any jobs that are no longer active */
+static int traverse_fn_delete(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *state)
+{
+ struct traverse_struct *ts = (struct traverse_struct *)state;
+ struct printjob pjob;
+ int i, jobid;
+
+ if (data.dsize != sizeof(pjob) || key.dsize != sizeof(int)) return 0;
+ memcpy(&jobid, key.dptr, sizeof(jobid));
+ memcpy(&pjob, data.dptr, sizeof(pjob));
+
+ if (strcmp(lp_servicename(ts->snum), pjob.qname)) {
+ /* this isn't for the queue we are looking at */
+ return 0;
}
-
- pstrcpy(syscmd,lpq_command);
- pstring_sub(syscmd,"%p",printername);
- standard_sub(conn,syscmd);
+ if (!pjob.smbjob) {
+ /* remove a unix job if it isn't in the system queue
+ any more */
- slprintf(outfile,sizeof(outfile)-1, "%s/lpq.%08x",tmpdir(),str_checksum(syscmd));
-
- if (!lpq_cache_reset[snum] && cachetime && !sys_stat(outfile,&sbuf)) {
- if (time(NULL) - sbuf.st_mtime < cachetime) {
- DEBUG(3,("Using cached lpq output\n"));
- dorun = False;
+ for (i=0;i<ts->qcount;i++) {
+ if (jobid == ts->queue[i].job + UNIX_JOB_START) break;
+ }
+ if (i == ts->qcount) tdb_delete(tdb, key);
+ return 0;
+ }
+
+ /* maybe it hasn't been spooled yet */
+ if (!pjob.spooled) {
+ /* if a job is not spooled and the process doesn't
+ exist then kill it. This cleans up after smbd
+ deaths */
+ if (!process_exists(pjob.pid)) {
+ tdb_delete(tdb, key);
}
+ return 0;
}
- if (dorun) {
- ret = smbrun(syscmd,outfile,True);
- DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
+ for (i=0;i<ts->qcount;i++) {
+ int qid = print_parse_jobid(ts->queue[i].file);
+ if (jobid == qid) break;
}
+
+ /* The job isn't in the system queue - we have to assume it has
+ completed, so delete the database entry. */
- lpq_cache_reset[snum] = False;
+ if (i == ts->qcount) {
+ time_t cur_t = time(NULL);
- f = sys_fopen(outfile,"r");
- if (!f) {
- return(0);
- }
+ /* 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 (status) {
- fstrcpy(status->message,"");
- status->status = LPSTAT_OK;
+ if ((cur_t - pjob.starttime) > lp_lpqcachetime()) {
+ tdb_delete(t, key);
+ }
}
+
+ 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), "CACHE/%s", lp_servicename(snum));
+ dos_to_unix(key, True); /* Convert key to unix-codepage */
+ tdb_store_int(tdb, key, -1);
+}
+
+/****************************************************************************
+update the internal database from the system print queue for a queue
+****************************************************************************/
+static void print_queue_update(int snum)
+{
+ char *path = lp_pathname(snum);
+ char *cmd = lp_lpqcommand(snum);
+ char **qlines;
+ pstring tmp_file;
+ int numlines, i, qcount;
+ print_queue_struct *queue = NULL;
+ print_status_struct status;
+ print_status_struct old_status;
+ struct printjob *pjob;
+ struct traverse_struct tstruct;
+ fstring keystr, printer_name;
+ TDB_DATA data, key;
+
+ /* Convert printer name (i.e. share name) to unix-codepage for all of the
+ * following tdb key generation */
+ fstrcpy(printer_name, lp_servicename(snum));
+ dos_to_unix(printer_name, True);
- 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;
+ /*
+ * Update the cache time FIRST ! Stops others doing this
+ * if the lpq takes a long time.
+ */
+
+ slprintf(keystr, sizeof(keystr), "CACHE/%s", printer_name);
+ tdb_store_int(tdb, keystr, (int)time(NULL));
+
+ slprintf(tmp_file, sizeof(tmp_file), "%s/smblpq.%d", path, local_pid);
+
+ unlink(tmp_file);
+ print_run_command(snum, cmd, tmp_file, NULL);
+
+ numlines = 0;
+ qlines = file_lines_load(tmp_file, &numlines, True);
+ unlink(tmp_file);
+
+ /* turn the lpq output into a series of job structures */
+ qcount = 0;
+ ZERO_STRUCT(status);
+ if (numlines)
+ queue = (print_queue_struct *)malloc(sizeof(print_queue_struct)*(numlines+1));
+
+ if (queue) {
+ for (i=0; i<numlines; i++) {
+ /* parse the line */
+ if (parse_lpq_entry(snum,qlines[i],
+ &queue[qcount],&status,qcount==0)) {
+ qcount++;
+ }
+ }
+ }
+ file_lines_free(qlines);
+
+ DEBUG(3, ("%d job%s in queue for %s\n", qcount, (qcount != 1) ?
+ "s" : "", printer_name));
+
+ /* Lock the queue for the database update */
+
+ slprintf(keystr, sizeof(keystr) - 1, "LOCK/%s", printer_name);
+ tdb_lock_bystring(tdb, keystr);
+
+ /*
+ any job in the internal database that is marked as spooled
+ and doesn't exist in the system queue is considered finished
+ and removed from the database
+
+ any job in the system database but not in the internal database
+ is added as a unix job
+
+ fill in any system job numbers as we go
+ */
+ for (i=0; i<qcount; i++) {
+ int jobid = print_parse_jobid(queue[i].file);
+
+ if (jobid == -1) {
+ /* assume its a unix print job */
+ print_unix_job(snum, &queue[i]);
+ continue;
}
- memset((char *)&(*queue)[count],'\0',sizeof(**queue));
-
- /* parse it */
- if (!parse_lpq_entry(snum,line,
- &(*queue)[count],status,count==0))
+ /* 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;
-
- count++;
- }
+ }
- fclose(f);
-
- if (!cachetime) {
- unlink(outfile);
- } else {
- /* we only expect this to succeed on trapdoor systems,
- on normal systems the file is owned by root */
- chmod(outfile,0666);
+ pjob->sysjob = queue[i].job;
+ pjob->status = queue[i].status;
+
+ print_job_store(jobid, pjob);
+ }
+
+ /* now delete any queued entries that don't appear in the
+ system queue */
+ tstruct.queue = queue;
+ tstruct.qcount = qcount;
+ tstruct.snum = snum;
+
+ tdb_traverse(tdb, traverse_fn_delete, (void *)&tstruct);
+
+ safe_free(tstruct.queue);
+
+ /*
+ * Get the old print status. We will use this to compare the
+ * number of jobs. If they have changed we need to send a
+ * "changed" message to the smbds.
+ */
+
+ if( qcount != get_queue_status(snum, &old_status)) {
+ DEBUG(10,("print_queue_update: queue status change %d jobs -> %d jobs for printer %s\n",
+ old_status.qcount, qcount, printer_name ));
+ message_send_all(conn_tdb_ctx(), MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
}
- return(count);
+
+ /* store the new queue status structure */
+ slprintf(keystr, sizeof(keystr), "STATUS/%s", printer_name);
+ key.dptr = keystr;
+ key.dsize = strlen(keystr);
+
+ status.qcount = qcount;
+ data.dptr = (void *)&status;
+ data.dsize = sizeof(status);
+ tdb_store(tdb, key, data, TDB_REPLACE);
+
+ /* Unlock for database update */
+
+ slprintf(keystr, sizeof(keystr) - 1, "LOCK/%s", printer_name);
+ tdb_unlock_bystring(tdb, keystr);
+
+ /*
+ * Update the cache time again. We want to do this call
+ * as little as possible...
+ */
+
+ slprintf(keystr, sizeof(keystr), "CACHE/%s", printer_name);
+ tdb_store_int(tdb, keystr, (int)time(NULL));
+}
+
+/****************************************************************************
+check if a jobid is valid. It is valid if it exists in the database
+****************************************************************************/
+BOOL print_job_exists(int jobid)
+{
+ return tdb_exists(tdb, print_key(jobid));
}
/****************************************************************************
-delete a printer queue entry
+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
****************************************************************************/
-void del_printqueue(connection_struct *conn,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;
- }
-
- slprintf(jobstr,sizeof(jobstr)-1,"%d",jobid);
-
- pstrcpy(syscmd,lprm_command);
- pstring_sub(syscmd,"%p",printername);
- pstring_sub(syscmd,"%j",jobstr);
- standard_sub(conn,syscmd);
-
- ret = smbrun(syscmd,NULL,False);
- DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
- lpq_reset(snum); /* queue has changed */
+int print_job_snum(int jobid)
+{
+ struct printjob *pjob = print_job_find(jobid);
+ if (!pjob) return -1;
+
+ return lp_servicenumber(pjob->qname);
}
/****************************************************************************
-change status of a printer queue entry
+give the fd used for a jobid
****************************************************************************/
-void status_printjob(connection_struct *conn,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;
- }
-
- slprintf(jobstr,sizeof(jobstr)-1,"%d",jobid);
-
- pstrcpy(syscmd,lpstatus_command);
- pstring_sub(syscmd,"%p",printername);
- pstring_sub(syscmd,"%j",jobstr);
- standard_sub(conn,syscmd);
-
- ret = smbrun(syscmd,NULL,False);
- DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
- lpq_reset(snum); /* queue has changed */
+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;
+}
/****************************************************************************
-we encode print job numbers over the wire so that when we get them back we can
-tell not only what print job they are but also what service it belongs to,
-this is to overcome the problem that windows clients tend to send the wrong
-service number when doing print queue manipulation!
+set the place in the queue for a job
****************************************************************************/
-int printjob_encode(int snum, int job)
+BOOL print_job_set_place(int jobid, int place)
{
- return ((snum&0xFF)<<8) | (job & 0xFF);
+ DEBUG(2,("print_job_set_place not implemented yet\n"));
+ return False;
}
/****************************************************************************
-and now decode them again ...
+set the name of a job. Only possible for owner
****************************************************************************/
-void printjob_decode(int jobid, int *snum, int *job)
+BOOL print_job_set_name(int jobid, char *name)
{
- (*snum) = (jobid >> 8) & 0xFF;
- (*job) = jobid & 0xFF;
+ 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);
}
+
/****************************************************************************
- Change status of a printer queue
+delete a print job - don't update queue
****************************************************************************/
-
-void status_printqueue(connection_struct *conn,int snum,int status)
+static BOOL print_job_delete1(int jobid)
{
- char *queuestatus_command = (status==LPSTAT_STOPPED ?
- lp_queuepausecommand(snum):lp_queueresumecommand(snum));
- char *printername = PRINTERNAME(snum);
- pstring syscmd;
- int ret;
+ struct printjob *pjob = print_job_find(jobid);
+ int snum;
- if (!printername || !*printername) {
- DEBUG(6,("replacing printer name with service (snum=(%s,%d))\n",
- lp_servicename(snum),snum));
- printername = lp_servicename(snum);
- }
+ if (!pjob) return False;
- if (!queuestatus_command || !(*queuestatus_command)) {
- DEBUG(5,("No queuestatus command to %s job\n",
- (status==LPSTAT_STOPPED?"pause":"resume")));
- return;
- }
+ snum = print_job_snum(jobid);
- pstrcpy(syscmd,queuestatus_command);
- pstring_sub(syscmd,"%p",printername);
- standard_sub(conn,syscmd);
+ if (pjob->spooled && pjob->sysjob != -1) {
+ /* need to delete the spooled entry */
+ fstring jobstr;
+ slprintf(jobstr, sizeof(jobstr), "%d", pjob->sysjob);
+ print_run_command(snum,
+ lp_lprmcommand(snum), NULL,
+ "%j", jobstr,
+ "%T", http_timestring(pjob->starttime),
+ NULL);
+ }
- ret = smbrun(syscmd,NULL,False);
- DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
- lpq_reset(snum); /* queue has changed */
+ return True;
}
+/****************************************************************************
+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;
-/***************************************************************************
-auto-load printer services
-***************************************************************************/
-static void add_all_printers(void)
+ 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, int *errcode)
{
- int printers = lp_servicenumber(PRINTERS_NAME);
+ int snum = print_job_snum(jobid);
+ char *printer_name;
+ BOOL owner;
+
+ owner = is_owner(user, jobid);
+
+ /* Check access against security descriptor or whether the user
+ owns their job. */
+
+ if (!owner &&
+ !print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+ DEBUG(3, ("delete denied by security descriptor\n"));
+ *errcode = ERROR_ACCESS_DENIED;
+ return False;
+ }
+
+ if (!print_job_delete1(jobid)) return False;
+
+ /* force update the database and say the delete failed if the
+ job still exists */
+
+ print_queue_update(snum);
+
+ /* Send a printer notify message */
+
+ printer_name = PRINTERNAME(snum);
- if (printers < 0) return;
+ message_send_all(conn_tdb_ctx(), MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
- pcap_printer_fn(lp_add_one_printer);
+ return !print_job_exists(jobid);
}
-/***************************************************************************
-auto-load some homes and printer services
-***************************************************************************/
-static void add_auto_printers(void)
+
+/****************************************************************************
+pause a job
+****************************************************************************/
+BOOL print_job_pause(struct current_user *user, int jobid, int *errcode)
{
- char *p;
- int printers;
- char *str = lp_auto_services();
+ struct printjob *pjob = print_job_find(jobid);
+ int snum, ret = -1;
+ char *printer_name;
+ fstring jobstr;
+ BOOL owner;
+
+ if (!pjob || !user) return False;
- if (!str) return;
+ if (!pjob->spooled || pjob->sysjob == -1) return False;
- printers = lp_servicenumber(PRINTERS_NAME);
+ snum = print_job_snum(jobid);
+ owner = is_owner(user, jobid);
- if (printers < 0) return;
+ if (!owner &&
+ !print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+ DEBUG(3, ("pause denied by security descriptor\n"));
+ *errcode = ERROR_ACCESS_DENIED;
+ return False;
+ }
+
+ /* need to pause the spooled entry */
+ slprintf(jobstr, sizeof(jobstr), "%d", pjob->sysjob);
+ ret = print_run_command(snum,
+ lp_lppausecommand(snum), NULL,
+ "%j", jobstr,
+ NULL);
+
+ if (ret != 0) {
+ *errcode = ERROR_INVALID_PARAMETER;
+ return False;
+ }
+
+ /* force update the database */
+ print_cache_flush(snum);
+
+ /* Send a printer notify message */
+
+ printer_name = PRINTERNAME(snum);
+
+ message_send_all(conn_tdb_ctx(), MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+ /* how do we tell if this succeeded? */
+
+ return True;
+}
+
+/****************************************************************************
+resume a job
+****************************************************************************/
+BOOL print_job_resume(struct current_user *user, int jobid, int *errcode)
+{
+ struct printjob *pjob = print_job_find(jobid);
+ char *printer_name;
+ int snum, ret;
+ fstring jobstr;
+ BOOL owner;
- for (p=strtok(str,LIST_SEP);p;p=strtok(NULL,LIST_SEP)) {
- if (lp_servicenumber(p) >= 0) continue;
-
- if (pcap_printername_ok(p,NULL)) {
- lp_add_printer(p,printers);
+ if (!pjob || !user) return False;
+
+ if (!pjob->spooled || pjob->sysjob == -1) return False;
+
+ snum = print_job_snum(jobid);
+ owner = is_owner(user, jobid);
+
+ if (!is_owner(user, jobid) &&
+ !print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+ DEBUG(3, ("resume denied by security descriptor\n"));
+ *errcode = ERROR_ACCESS_DENIED;
+ return False;
+ }
+
+ slprintf(jobstr, sizeof(jobstr), "%d", pjob->sysjob);
+ ret = print_run_command(snum,
+ lp_lpresumecommand(snum), NULL,
+ "%j", jobstr,
+ NULL);
+
+ if (ret != 0) {
+ *errcode = ERROR_INVALID_PARAMETER;
+ return False;
+ }
+
+ /* force update the database */
+ print_cache_flush(snum);
+
+ /* Send a printer notify message */
+
+ printer_name = PRINTERNAME(snum);
+
+ message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+ return True;
+}
+
+/****************************************************************************
+write to a print file
+****************************************************************************/
+int print_job_write(int jobid, const char *buf, int size)
+{
+ int fd;
+
+ fd = print_job_fd(jobid);
+ if (fd == -1) return -1;
+
+ return write(fd, buf, size);
+}
+
+/****************************************************************************
+ Check if the print queue has been updated recently enough.
+****************************************************************************/
+
+static BOOL print_cache_expired(int snum)
+{
+ fstring key;
+ time_t t2, t = time(NULL);
+
+ slprintf(key, sizeof(key), "CACHE/%s", lp_servicename(snum));
+ dos_to_unix(key, True); /* Convert key to unix-codepage */
+ t2 = tdb_fetch_int(tdb, key);
+ if (t2 == ((time_t)-1) || (t - t2) >= lp_lpqcachetime()) {
+ DEBUG(3, ("print cache expired\n"));
+ return True;
+ }
+ return False;
+}
+
+/****************************************************************************
+ Get the queue status - do not update if db is out of date.
+****************************************************************************/
+
+static int get_queue_status(int snum, print_status_struct *status)
+{
+ fstring keystr;
+ TDB_DATA data, key;
+
+ ZERO_STRUCTP(status);
+ slprintf(keystr, sizeof(keystr), "STATUS/%s", lp_servicename(snum));
+ dos_to_unix(keystr, True); /* Convert key to unix-codepage */
+ key.dptr = keystr;
+ key.dsize = strlen(keystr);
+ data = tdb_fetch(tdb, key);
+ if (data.dptr) {
+ if (data.dsize == sizeof(print_status_struct)) {
+ memcpy(status, data.dptr, sizeof(print_status_struct));
}
+ free(data.dptr);
}
+ return status->qcount;
+}
+
+/****************************************************************************
+ Determine the number of jobs in a queue.
+****************************************************************************/
+
+static int print_queue_length(int snum)
+{
+ print_status_struct status;
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(snum)) print_queue_update(snum);
+
+ /* also fetch the queue status */
+ return get_queue_status(snum, &status);
}
/***************************************************************************
-load automatic printer services
+start spooling a job - return the jobid
***************************************************************************/
-void load_printers(void)
+int print_job_start(struct current_user *user, int snum, char *jobname)
{
- add_auto_printers();
- if (lp_load_printers())
- add_all_printers();
-}
+ int jobid;
+ char *path;
+ struct printjob pjob;
+ int next_jobid;
+ user_struct *vuser;
+ errno = 0;
-void print_open_file(files_struct *fsp,connection_struct *conn,char *fname)
-{
- pstring template;
- char *p;
- SMB_STRUCT_STAT sbuf;
- extern struct current_user current_user;
+ 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(conn))) {
- SMB_BIG_UINT dum1,dum2,dum3;
- if (conn->vfs_ops.disk_free(conn->connectpath,False,&dum1,&dum2,&dum3) <
- (SMB_BIG_UINT)lp_minprintspace(SNUM(conn))) {
+ if (lp_minprintspace(snum)) {
+ SMB_BIG_UINT dspace, dsize;
+ if (sys_fsusage(path, &dspace, &dsize) == 0 &&
+ dspace < 2*(SMB_BIG_UINT)lp_minprintspace(snum)) {
errno = ENOSPC;
- return;
+ return -1;
}
}
- slprintf(template, sizeof(template), "%s/smb.XXXXXX", conn->connectpath);
- p = smbd_mktemp(template);
- if (!p) {
- DEBUG(2,("Error creating temporary filename in %s\n",
- conn->connectpath));
+ /* for autoloaded printers, check that the printcap entry still exists */
+ if (lp_autoloaded(snum) && !pcap_printername_ok(lp_servicename(snum), NULL)) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (lp_maxprintjobs(snum) && print_queue_length(snum) > lp_maxprintjobs(snum)) {
errno = ENOSPC;
- return;
+ return -1;
}
- fsp->fd = conn->vfs_ops.open(p,O_RDWR|O_CREAT|O_EXCL,0600);
+ /* create the database entry */
+ ZERO_STRUCT(pjob);
+ pjob.pid = local_pid;
+ pjob.sysjob = -1;
+ pjob.fd = -1;
+ pjob.starttime = time(NULL);
+ pjob.status = LPQ_QUEUED;
+ pjob.size = 0;
+ pjob.spooled = False;
+ pjob.smbjob = True;
+
+ fstrcpy(pjob.jobname, jobname);
- if (fsp->fd == -1) {
- DEBUG(3,("Error opening file %s\n",p));
- return;
+ if ((vuser = get_valid_user_struct(user->vuid)) != NULL) {
+ fstrcpy(pjob.user, vuser->user.smb_name);
+ } else {
+ fstrcpy(pjob.user, uidtoname(user->uid));
}
-
+
+ fstrcpy(pjob.qname, lp_servicename(snum));
+
+ /* lock the database */
+ tdb_lock_bystring(tdb, "INFO/nextjob");
+
+ next_jobnum:
+ next_jobid = tdb_fetch_int(tdb, "INFO/nextjob");
+ if (next_jobid == -1) next_jobid = 1;
+
+ for (jobid = next_jobid+1; jobid != next_jobid; ) {
+ if (!print_job_exists(jobid)) break;
+ jobid = (jobid + 1) % PRINT_MAX_JOBID;
+ if (jobid == 0) jobid = 1;
+ }
+ if (jobid == next_jobid || !print_job_store(jobid, &pjob)) {
+ jobid = -1;
+ goto fail;
+ }
+
+ tdb_store_int(tdb, "INFO/nextjob", jobid);
+
+ /* we have a job entry - now create the spool file
+
+ we unlink first to cope with old spool files and also to beat
+ a symlink security hole - it allows us to use O_EXCL
+ There may be old spool files owned by other users lying around.
+ */
+ slprintf(pjob.filename, sizeof(pjob.filename), "%s/%s%d",
+ path, PRINT_SPOOL_PREFIX, jobid);
+ if (unlink(pjob.filename) == -1 && errno != ENOENT) {
+ goto next_jobnum;
+ }
+ pjob.fd = sys_open(pjob.filename,O_WRONLY|O_CREAT|O_EXCL,0600);
+ if (pjob.fd == -1) goto fail;
+
+ print_job_store(jobid, &pjob);
+
+ tdb_unlock_bystring(tdb, "INFO/nextjob");
+
/*
* If the printer is marked as postscript output a leading
* file identifier to ensure the file is treated as a raw
* This has a similar effect as CtrlD=0 in WIN.INI file.
* tim@fsg.com 09/06/94
*/
- if (lp_postscript(SNUM(conn))) {
- DEBUG(3,("Writing postscript line\n"));
- conn->vfs_ops.write(fsp->fd,"%!\n",3);
- }
-
- /* setup a full fsp */
- conn->vfs_ops.fstat(fsp->fd, &sbuf);
- conn->num_files_open++;
- fsp->mode = sbuf.st_mode;
- fsp->inode = sbuf.st_ino;
- fsp->dev = sbuf.st_dev;
- GetTimeOfDay(&fsp->open_time);
- fsp->vuid = current_user.vuid;
- fsp->size = 0;
- fsp->pos = -1;
- fsp->open = True;
- fsp->can_lock = True;
- fsp->can_read = False;
- fsp->can_write = True;
- fsp->share_mode = 0;
- fsp->print_file = True;
- fsp->modified = False;
- fsp->oplock_type = NO_OPLOCK;
- fsp->sent_oplock_break = NO_BREAK_SENT;
- fsp->is_directory = False;
- fsp->stat_open = False;
- fsp->directory_delete_on_close = False;
- fsp->conn = conn;
- string_set(&fsp->fsp_name,p);
- fsp->wbmpx_ptr = NULL;
- fsp->wcp = NULL;
+ if (lp_postscript(snum)) {
+ print_job_write(jobid, "%!\n",3);
+ }
+
+ return jobid;
+
+ fail:
+ if (jobid != -1) {
+ tdb_delete(tdb, print_key(jobid));
+ }
+
+ tdb_unlock_bystring(tdb, "INFO/nextjob");
+ return -1;
+}
+
+/****************************************************************************
+ Print a file - called on closing the file. This spools the job.
+****************************************************************************/
+
+BOOL print_job_end(int jobid)
+{
+ struct printjob *pjob = print_job_find(jobid);
+ int snum;
+ SMB_STRUCT_STAT sbuf;
+ pstring current_directory;
+ pstring print_directory;
+ char *wd, *p, *printer_name;
+ pstring jobname;
+
+ if (!pjob)
+ return False;
+
+ if (pjob->spooled || pjob->pid != local_pid)
+ return False;
+
+ snum = print_job_snum(jobid);
+
+ if (sys_fstat(pjob->fd, &sbuf) == 0)
+ pjob->size = sbuf.st_size;
+
+ close(pjob->fd);
+ pjob->fd = -1;
+
+ if (pjob->size == 0) {
+ /* don't bother spooling empty files */
+ unlink(pjob->filename);
+ tdb_delete(tdb, print_key(jobid));
+ return True;
+ }
+
+ /* we print from the directory path to give the best chance of
+ parsing the lpq output */
+ wd = sys_getwd(current_directory);
+ if (!wd)
+ return False;
+
+ pstrcpy(print_directory, pjob->filename);
+ p = strrchr(print_directory,'/');
+ if (!p)
+ return False;
+ *p++ = 0;
+
+ if (chdir(print_directory) != 0)
+ return False;
+
+ pstrcpy(jobname, pjob->jobname);
+ pstring_sub(jobname, "'", "_");
+
+ /* send it to the system spooler */
+ print_run_command(snum,
+ lp_printcommand(snum), NULL,
+ "%s", p,
+ "%J", jobname,
+ "%f", p,
+ NULL);
+
+ chdir(wd);
+
+ pjob->spooled = True;
+ print_job_store(jobid, pjob);
+
+ /* force update the database */
+ print_cache_flush(snum);
+
+ /* Send a printer notify message */
+
+ printer_name = PRINTERNAME(snum);
+
+ message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+ return True;
+}
+
+/* utility fn to enumerate the print queue */
+static int traverse_fn_queue(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *state)
+{
+ struct traverse_struct *ts = (struct traverse_struct *)state;
+ struct printjob pjob;
+ int i, jobid;
+
+ if (data.dsize != sizeof(pjob) || key.dsize != sizeof(int)) return 0;
+ memcpy(&jobid, key.dptr, sizeof(jobid));
+ memcpy(&pjob, data.dptr, sizeof(pjob));
+
+ /* maybe it isn't for this queue */
+ if (ts->snum != print_queue_snum(pjob.qname)) return 0;
+
+ if (ts->qcount >= ts->maxcount) return 0;
+
+ i = ts->qcount;
+
+ ts->queue[i].job = jobid;
+ ts->queue[i].size = pjob.size;
+ ts->queue[i].status = pjob.status;
+ ts->queue[i].priority = 1;
+ ts->queue[i].time = pjob.starttime;
+ fstrcpy(ts->queue[i].user, pjob.user);
+ fstrcpy(ts->queue[i].file, pjob.jobname);
+
+ ts->qcount++;
+
+ return 0;
+}
+
+struct traverse_count_struct {
+ int snum, count;
+};
+
+/* utility fn to count the number of entries in the print queue */
+static int traverse_count_fn_queue(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *state)
+{
+ struct traverse_count_struct *ts = (struct traverse_count_struct *)state;
+ struct printjob pjob;
+ int jobid;
+
+ if (data.dsize != sizeof(pjob) || key.dsize != sizeof(int)) return 0;
+ memcpy(&jobid, key.dptr, sizeof(jobid));
+ memcpy(&pjob, data.dptr, sizeof(pjob));
+
+ /* maybe it isn't for this queue */
+ if (ts->snum != print_queue_snum(pjob.qname)) return 0;
+
+ ts->count++;
+
+ return 0;
+}
+
+/* Sort print jobs by submittal time */
+
+static int printjob_comp(print_queue_struct *j1, print_queue_struct *j2)
+{
+ /* Silly cases */
+
+ if (!j1 && !j2) return 0;
+ if (!j1) return -1;
+ if (!j2) return 1;
+
+ /* Sort on job start time */
+
+ if (j1->time == j2->time) return 0;
+ return (j1->time > j2->time) ? 1 : -1;
+}
+
+/****************************************************************************
+get a printer queue listing
+****************************************************************************/
+int print_queue_status(int snum,
+ print_queue_struct **queue,
+ print_status_struct *status)
+{
+ struct traverse_struct tstruct;
+ struct traverse_count_struct tsc;
+ fstring keystr;
+ TDB_DATA data, key;
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(snum)) print_queue_update(snum);
+
+ *queue = NULL;
+
+ /*
+ * Fetch the queue status. We must do this first, as there may
+ * be no jobs in the queue.
+ */
+ ZERO_STRUCTP(status);
+ slprintf(keystr, sizeof(keystr), "STATUS/%s", lp_servicename(snum));
+ dos_to_unix(keystr, True); /* Convert key to unix-codepage */
+ key.dptr = keystr;
+ key.dsize = strlen(keystr);
+ data = tdb_fetch(tdb, key);
+ if (data.dptr) {
+ if (data.dsize == sizeof(*status)) {
+ memcpy(status, data.dptr, sizeof(*status));
+ }
+ free(data.dptr);
+ }
+
+ /*
+ * Now, fetch the print queue information. We first count the number
+ * of entries, and then only retrieve the queue if necessary.
+ */
+ tsc.count = 0;
+ tsc.snum = snum;
+
+ tdb_traverse(tdb, traverse_count_fn_queue, (void *)&tsc);
+
+ if (tsc.count == 0)
+ return 0;
+
+ /* Allocate the queue size. */
+ if ((tstruct.queue = (print_queue_struct *)
+ malloc(sizeof(print_queue_struct)*tsc.count))
+ == NULL)
+ return 0;
+
+ /*
+ * Fill in the queue.
+ * We need maxcount as the queue size may have changed between
+ * the two calls to tdb_traverse.
+ */
+ tstruct.qcount = 0;
+ tstruct.maxcount = tsc.count;
+ tstruct.snum = snum;
+
+ tdb_traverse(tdb, traverse_fn_queue, (void *)&tstruct);
+
+ /* Sort the queue by submission time otherwise they are displayed
+ in hash order. */
+
+ qsort(tstruct.queue, tstruct.qcount, sizeof(print_queue_struct),
+ QSORT_CAST(printjob_comp));
+
+ *queue = tstruct.queue;
+ return tstruct.qcount;
+}
+
+
+/****************************************************************************
+turn a queue name into a snum
+****************************************************************************/
+int print_queue_snum(char *qname)
+{
+ int snum = lp_servicenumber(qname);
+ if (snum == -1 || !lp_print_ok(snum)) return -1;
+ return snum;
+}
+
+
+/****************************************************************************
+ pause a queue
+****************************************************************************/
+BOOL print_queue_pause(struct current_user *user, int snum, int *errcode)
+{
+ char *printer_name;
+ int ret;
+
+ if (!print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+ *errcode = ERROR_ACCESS_DENIED;
+ return False;
+ }
+
+ ret = print_run_command(snum, lp_queuepausecommand(snum), NULL,
+ NULL);
+
+ if (ret != 0) {
+ *errcode = ERROR_INVALID_PARAMETER;
+ return False;
+ }
+
+ /* force update the database */
+ print_cache_flush(snum);
+
+ /* Send a printer notify message */
+
+ printer_name = PRINTERNAME(snum);
+
+ message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+ return True;
+}
+
+/****************************************************************************
+ resume a queue
+****************************************************************************/
+BOOL print_queue_resume(struct current_user *user, int snum, int *errcode)
+{
+ char *printer_name;
+ int ret;
+
+ if (!print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+ *errcode = ERROR_ACCESS_DENIED;
+ return False;
+ }
+
+ ret = print_run_command(snum, lp_queueresumecommand(snum), NULL,
+ NULL);
+
+ if (ret != 0) {
+ *errcode = ERROR_INVALID_PARAMETER;
+ return False;
+ }
+
+ /* force update the database */
+ print_cache_flush(snum);
+
+ /* Send a printer notify message */
+
+ printer_name = PRINTERNAME(snum);
+
+ message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+ return True;
+}
+
+/****************************************************************************
+ purge a queue - implemented by deleting all jobs that we can delete
+****************************************************************************/
+BOOL print_queue_purge(struct current_user *user, int snum, int *errcode)
+{
+ print_queue_struct *queue;
+ print_status_struct status;
+ char *printer_name;
+ int njobs, i;
+
+ njobs = print_queue_status(snum, &queue, &status);
+
+ if (print_access_check(user, snum, PRINTER_ACCESS_ADMINISTER)) {
+ for (i=0;i<njobs;i++) {
+ print_job_delete1(queue[i].job);
+ }
+ }
+
+ print_cache_flush(snum);
+ safe_free(queue);
+
+ /* Send a printer notify message */
+
+ printer_name = PRINTERNAME(snum);
+
+ message_send_all(conn_tdb_ctx(),MSG_PRINTER_NOTIFY, printer_name, strlen(printer_name) + 1, False);
+
+ return True;
}
+#undef OLD_NTDOMAIN