summaryrefslogtreecommitdiffstats
path: root/source3/printing/printing.c
diff options
context:
space:
mode:
Diffstat (limited to 'source3/printing/printing.c')
-rw-r--r--source3/printing/printing.c3266
1 files changed, 3266 insertions, 0 deletions
diff --git a/source3/printing/printing.c b/source3/printing/printing.c
new file mode 100644
index 0000000..dbe5a20
--- /dev/null
+++ b/source3/printing/printing.c
@@ -0,0 +1,3266 @@
+/*
+ Unix SMB/Netbios implementation.
+ 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
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/globals.h"
+#include "system/syslog.h"
+#include "system/filesys.h"
+#include "printing.h"
+#include "../librpc/gen_ndr/ndr_spoolss.h"
+#include "nt_printing.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "printing/notify.h"
+#include "printing/pcap.h"
+#include "printing/printer_list.h"
+#include "printing/queue_process.h"
+#include "serverid.h"
+#include "smbd/smbd.h"
+#include "auth.h"
+#include "messages.h"
+#include "util_tdb.h"
+#include "lib/param/loadparm.h"
+#include "lib/util/sys_rw_data.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+#include "source3/printing/rap_jobid.h"
+#include "source3/lib/substitute.h"
+
+extern userdom_struct current_user_info;
+
+/* Current printer interface */
+static bool remove_from_jobs_added(const char* sharename, uint32_t jobid);
+
+static int get_queue_status(const char* sharename, print_status_struct *);
+
+/****************************************************************************
+ Initialise the printing backend. Called once at startup before the fork().
+****************************************************************************/
+
+bool print_backend_init(struct messaging_context *msg_ctx)
+{
+ const char *sversion = "INFO/version";
+ int services = lp_numservices();
+ int snum;
+ bool ok;
+ char *print_cache_path;
+
+ print_cache_path = cache_path(talloc_tos(), "printing");
+ if (print_cache_path == NULL) {
+ return false;
+ }
+ ok = directory_create_or_exist(print_cache_path, 0755);
+ TALLOC_FREE(print_cache_path);
+ if (!ok) {
+ return false;
+ }
+
+ /* handle a Samba upgrade */
+
+ for (snum = 0; snum < services; snum++) {
+ struct tdb_print_db *pdb;
+ if (!lp_printable(snum))
+ continue;
+
+ pdb = get_print_db_byname(lp_const_servicename(snum));
+ if (!pdb)
+ continue;
+ if (tdb_lock_bystring(pdb->tdb, sversion) != 0) {
+ DEBUG(0,("print_backend_init: Failed to open printer %s database\n", lp_const_servicename(snum) ));
+ release_print_db(pdb);
+ return False;
+ }
+ if (tdb_fetch_int32(pdb->tdb, sversion) != PRINT_DATABASE_VERSION) {
+ tdb_wipe_all(pdb->tdb);
+ tdb_store_int32(pdb->tdb, sversion, PRINT_DATABASE_VERSION);
+ }
+ tdb_unlock_bystring(pdb->tdb, sversion);
+ release_print_db(pdb);
+ }
+
+ close_all_print_db(); /* Don't leave any open. */
+
+ /* do NT print initialization... */
+ return nt_printing_init(msg_ctx);
+}
+
+/****************************************************************************
+ Shut down printing backend. Called once at shutdown to close the tdb.
+****************************************************************************/
+
+void printing_end(void)
+{
+ close_all_print_db(); /* Don't leave any open. */
+}
+
+/****************************************************************************
+ Retrieve the set of printing functions for a given service. This allows
+ us to set the printer function table based on the value of the 'printing'
+ service parameter.
+
+ Use the generic interface as the default and only use cups interface only
+ when asked for (and only when supported)
+****************************************************************************/
+
+static struct printif *get_printer_fns_from_type( enum printing_types type )
+{
+ struct printif *printer_fns = &generic_printif;
+
+#ifdef HAVE_CUPS
+ if ( type == PRINT_CUPS ) {
+ printer_fns = &cups_printif;
+ }
+#endif /* HAVE_CUPS */
+
+#ifdef HAVE_IPRINT
+ if ( type == PRINT_IPRINT ) {
+ printer_fns = &iprint_printif;
+ }
+#endif /* HAVE_IPRINT */
+
+ printer_fns->type = type;
+
+ return printer_fns;
+}
+
+static struct printif *get_printer_fns( int snum )
+{
+ return get_printer_fns_from_type( (enum printing_types)lp_printing(snum) );
+}
+
+
+/****************************************************************************
+ Useful function to generate a tdb key.
+****************************************************************************/
+
+static TDB_DATA print_key(uint32_t jobid, uint32_t *tmp)
+{
+ TDB_DATA ret;
+
+ SIVAL(tmp, 0, jobid);
+ ret.dptr = (uint8_t *)tmp;
+ ret.dsize = sizeof(*tmp);
+ return ret;
+}
+
+/****************************************************************************
+ Pack the devicemode to store it in a tdb.
+****************************************************************************/
+static int pack_devicemode(struct spoolss_DeviceMode *devmode, uint8_t *buf, int buflen)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob = { .data = NULL };
+ int len = 0;
+
+ if (devmode) {
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(),
+ devmode,
+ (ndr_push_flags_fn_t)
+ ndr_push_spoolss_DeviceMode);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(10, ("pack_devicemode: "
+ "error encoding spoolss_DeviceMode\n"));
+ goto done;
+ }
+ }
+
+ len = tdb_pack(buf, buflen, "B", blob.length, blob.data);
+
+ if (devmode) {
+ DEBUG(8, ("Packed devicemode [%s]\n", devmode->formname));
+ }
+
+done:
+ return len;
+}
+
+/****************************************************************************
+ Unpack the devicemode to store it in a tdb.
+****************************************************************************/
+static int unpack_devicemode(TALLOC_CTX *mem_ctx,
+ const uint8_t *buf, int buflen,
+ struct spoolss_DeviceMode **devmode)
+{
+ struct spoolss_DeviceMode *dm;
+ enum ndr_err_code ndr_err;
+ char *data = NULL;
+ uint32_t data_len = 0;
+ DATA_BLOB blob;
+ int len = 0;
+
+ *devmode = NULL;
+
+ len = tdb_unpack(buf, buflen, "B", &data_len, &data);
+ if (!data) {
+ return len;
+ }
+
+ dm = talloc_zero(mem_ctx, struct spoolss_DeviceMode);
+ if (!dm) {
+ goto done;
+ }
+
+ blob = data_blob_const(data, data_len);
+
+ ndr_err = ndr_pull_struct_blob(&blob, dm, dm,
+ (ndr_pull_flags_fn_t)ndr_pull_spoolss_DeviceMode);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(10, ("unpack_devicemode: "
+ "error parsing spoolss_DeviceMode\n"));
+ goto done;
+ }
+
+ DEBUG(8, ("Unpacked devicemode [%s](%s)\n",
+ dm->devicename, dm->formname));
+ if (dm->driverextra_data.data) {
+ DEBUG(8, ("with a private section of %d bytes\n",
+ dm->__driverextra_length));
+ }
+
+ *devmode = dm;
+
+done:
+ SAFE_FREE(data);
+ return len;
+}
+
+/***********************************************************************
+ unpack a pjob from a tdb buffer
+***********************************************************************/
+
+static int unpack_pjob(TALLOC_CTX *mem_ctx, uint8_t *buf, int buflen,
+ struct printjob *pjob)
+{
+ int len = 0;
+ int used;
+ uint32_t pjpid, pjjobid, pjsysjob, pjfd, pjstarttime, pjstatus;
+ uint32_t pjsize, pjpage_count, pjspooled, pjsmbjob;
+
+ if (!buf || !pjob) {
+ return -1;
+ }
+
+ len += tdb_unpack(buf+len, buflen-len, "ddddddddddfffff",
+ &pjpid,
+ &pjjobid,
+ &pjsysjob,
+ &pjfd,
+ &pjstarttime,
+ &pjstatus,
+ &pjsize,
+ &pjpage_count,
+ &pjspooled,
+ &pjsmbjob,
+ pjob->filename,
+ pjob->jobname,
+ pjob->user,
+ pjob->clientmachine,
+ pjob->queuename);
+
+ if (len == -1) {
+ return -1;
+ }
+
+ used = unpack_devicemode(mem_ctx, buf+len, buflen-len, &pjob->devmode);
+ if (used == -1) {
+ return -1;
+ }
+
+ len += used;
+
+ pjob->pid = pjpid;
+ pjob->jobid = pjjobid;
+ pjob->sysjob = pjsysjob;
+ pjob->fd = pjfd;
+ pjob->starttime = pjstarttime;
+ pjob->status = pjstatus;
+ pjob->size = pjsize;
+ pjob->page_count = pjpage_count;
+ pjob->spooled = pjspooled;
+ pjob->smbjob = pjsmbjob;
+
+ return len;
+
+}
+
+/****************************************************************************
+ Useful function to find a print job in the database.
+****************************************************************************/
+
+static struct printjob *print_job_find(TALLOC_CTX *mem_ctx,
+ const char *sharename,
+ uint32_t jobid)
+{
+ struct printjob *pjob;
+ uint32_t tmp;
+ TDB_DATA ret;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ DEBUG(10,("print_job_find: looking up job %u for share %s\n",
+ (unsigned int)jobid, sharename ));
+
+ if (!pdb) {
+ return NULL;
+ }
+
+ ret = tdb_fetch(pdb->tdb, print_key(jobid, &tmp));
+ release_print_db(pdb);
+
+ if (!ret.dptr) {
+ DEBUG(10, ("print_job_find: failed to find jobid %u.\n",
+ jobid));
+ return NULL;
+ }
+
+ pjob = talloc_zero(mem_ctx, struct printjob);
+ if (pjob == NULL) {
+ goto err_out;
+ }
+
+ if (unpack_pjob(mem_ctx, ret.dptr, ret.dsize, pjob) == -1) {
+ DEBUG(10, ("failed to unpack jobid %u.\n", jobid));
+ talloc_free(pjob);
+ pjob = NULL;
+ goto err_out;
+ }
+
+ DEBUG(10,("print_job_find: returning system job %d for jobid %u.\n",
+ pjob->sysjob, jobid));
+ SMB_ASSERT(pjob->jobid == jobid);
+
+err_out:
+ SAFE_FREE(ret.dptr);
+ return pjob;
+}
+
+struct job_traverse_state {
+ int sysjob;
+ uint32_t jobid;
+};
+
+/* find spoolss jobid based on sysjob */
+static int sysjob_to_jobid_traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA key,
+ TDB_DATA data, void *private_data)
+{
+ struct printjob *pjob;
+ struct job_traverse_state *state =
+ (struct job_traverse_state *)private_data;
+
+ if (!data.dptr || data.dsize == 0)
+ return 0;
+
+ pjob = (struct printjob *)data.dptr;
+ if (key.dsize != sizeof(uint32_t))
+ return 0;
+
+ if (state->sysjob == pjob->sysjob) {
+ state->jobid = pjob->jobid;
+ return 1;
+ }
+
+ return 0;
+}
+
+uint32_t sysjob_to_jobid_pdb(struct tdb_print_db *pdb, int sysjob)
+{
+ struct job_traverse_state state;
+
+ state.sysjob = sysjob;
+ state.jobid = (uint32_t)-1;
+
+ tdb_traverse(pdb->tdb, sysjob_to_jobid_traverse_fn, &state);
+
+ return state.jobid;
+}
+
+/****************************************************************************
+ 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_t sysjob_to_jobid(int unix_jobid)
+{
+ int services = lp_numservices();
+ int snum;
+ struct job_traverse_state state;
+
+ state.sysjob = unix_jobid;
+ state.jobid = (uint32_t)-1;
+
+ for (snum = 0; snum < services; snum++) {
+ struct tdb_print_db *pdb;
+ if (!lp_printable(snum))
+ continue;
+ pdb = get_print_db_byname(lp_const_servicename(snum));
+ if (!pdb) {
+ continue;
+ }
+ tdb_traverse(pdb->tdb, sysjob_to_jobid_traverse_fn, &state);
+ release_print_db(pdb);
+ if (state.jobid != (uint32_t)-1)
+ return state.jobid;
+ }
+ return (uint32_t)-1;
+}
+
+/* find sysjob based on spoolss jobid */
+static int jobid_to_sysjob_traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA key,
+ TDB_DATA data, void *private_data)
+{
+ struct printjob *pjob;
+ struct job_traverse_state *state =
+ (struct job_traverse_state *)private_data;
+
+ if (!data.dptr || data.dsize == 0)
+ return 0;
+
+ pjob = (struct printjob *)data.dptr;
+ if (key.dsize != sizeof(uint32_t))
+ return 0;
+
+ if (state->jobid == pjob->jobid) {
+ state->sysjob = pjob->sysjob;
+ return 1;
+ }
+
+ return 0;
+}
+
+int jobid_to_sysjob_pdb(struct tdb_print_db *pdb, uint32_t jobid)
+{
+ struct job_traverse_state state;
+
+ state.sysjob = -1;
+ state.jobid = jobid;
+
+ tdb_traverse(pdb->tdb, jobid_to_sysjob_traverse_fn, &state);
+
+ return state.sysjob;
+}
+
+/****************************************************************************
+ Send notifications based on what has changed after a pjob_store.
+****************************************************************************/
+
+static const struct {
+ uint32_t lpq_status;
+ uint32_t 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_DEVQ },
+ { LPQ_USER_INTERVENTION, JOB_STATUS_USER_INTERVENTION },
+ { (uint32_t)-1, 0 }
+};
+
+/* Convert a lpq status value stored in printing.tdb into the
+ appropriate win32 API constant. */
+
+static uint32_t map_to_spoolss_status(uint32_t 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;
+}
+
+/***************************************************************************
+ Append a jobid to a list
+***************************************************************************/
+
+static bool add_to_jobs_list(
+ struct tdb_print_db *pdb, uint32_t jobid, const char *key)
+{
+ uint8_t store_jobid[sizeof(uint32_t)];
+ TDB_DATA data = {
+ .dptr = store_jobid, .dsize = sizeof(store_jobid)
+ };
+ int ret;
+
+ SIVAL(&store_jobid, 0, jobid);
+
+ DBG_DEBUG("Added jobid %"PRIu32" to %s\n", jobid, key);
+
+ ret = tdb_append(pdb->tdb, string_tdb_data(key), data);
+ return ret == 0;
+}
+
+/***************************************************************************
+ Remove a jobid from the 'jobs changed' list.
+***************************************************************************/
+
+static bool remove_from_jobs_list(
+ const char *keystr, const char *sharename, uint32_t jobid)
+{
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ TDB_DATA data, key;
+ size_t job_count, i;
+ bool ret = False;
+ bool gotlock = False;
+
+ if (!pdb) {
+ return False;
+ }
+
+ ZERO_STRUCT(data);
+
+ key = string_tdb_data(keystr);
+
+ if (tdb_chainlock_with_timeout(pdb->tdb, key, 5) != 0)
+ goto out;
+
+ gotlock = True;
+
+ data = tdb_fetch(pdb->tdb, key);
+
+ if (data.dptr == NULL || data.dsize == 0 || (data.dsize % 4 != 0))
+ goto out;
+
+ job_count = data.dsize / 4;
+ for (i = 0; i < job_count; i++) {
+ uint32_t ch_jobid;
+
+ ch_jobid = IVAL(data.dptr, i*4);
+ if (ch_jobid == jobid) {
+ if (i < job_count -1 )
+ memmove(data.dptr + (i*4), data.dptr + (i*4) + 4, (job_count - i - 1)*4 );
+ data.dsize -= 4;
+ if (tdb_store(pdb->tdb, key, data, TDB_REPLACE) != 0)
+ goto out;
+ break;
+ }
+ }
+
+ ret = True;
+ out:
+
+ if (gotlock)
+ tdb_chainunlock(pdb->tdb, key);
+ SAFE_FREE(data.dptr);
+ release_print_db(pdb);
+ if (ret)
+ DBG_DEBUG("removed jobid %"PRIu32"\n", jobid);
+ else
+ DBG_DEBUG("Failed to remove jobid %"PRIu32"\n", jobid);
+ return ret;
+}
+
+static bool remove_from_jobs_changed(const char* sharename, uint32_t jobid)
+{
+ bool ret = remove_from_jobs_list(
+ "INFO/jobs_changed", sharename, jobid);
+ return ret;
+}
+
+static void pjob_store_notify(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char* sharename, uint32_t jobid,
+ struct printjob *old_data,
+ struct printjob *new_data,
+ bool *pchanged)
+{
+ bool new_job = false;
+ bool changed = false;
+
+ if (old_data == NULL) {
+ new_job = true;
+ }
+
+ /* ACHTUNG! Due to a bug in Samba's spoolss parsing of the
+ NOTIFY_INFO_DATA buffer, we *have* to send the job submission
+ time first or else we'll end up with potential alignment
+ errors. I don't think the systemtime should be spooled as
+ a string, but this gets us around that error.
+ --jerry (i'll feel dirty for this) */
+
+ if (new_job) {
+ notify_job_submitted(ev, msg_ctx,
+ sharename, jobid, new_data->starttime);
+ notify_job_username(ev, msg_ctx,
+ sharename, jobid, new_data->user);
+ notify_job_name(ev, msg_ctx,
+ sharename, jobid, new_data->jobname);
+ notify_job_status(ev, msg_ctx,
+ sharename, jobid, map_to_spoolss_status(new_data->status));
+ notify_job_total_bytes(ev, msg_ctx,
+ sharename, jobid, new_data->size);
+ notify_job_total_pages(ev, msg_ctx,
+ sharename, jobid, new_data->page_count);
+ } else {
+ if (!strequal(old_data->jobname, new_data->jobname)) {
+ notify_job_name(ev, msg_ctx, sharename,
+ jobid, new_data->jobname);
+ changed = true;
+ }
+
+ if (old_data->status != new_data->status) {
+ notify_job_status(ev, msg_ctx,
+ sharename, jobid,
+ map_to_spoolss_status(new_data->status));
+ }
+
+ if (old_data->size != new_data->size) {
+ notify_job_total_bytes(ev, msg_ctx,
+ sharename, jobid, new_data->size);
+ }
+
+ if (old_data->page_count != new_data->page_count) {
+ notify_job_total_pages(ev, msg_ctx,
+ sharename, jobid,
+ new_data->page_count);
+ }
+ }
+
+ *pchanged = changed;
+}
+
+/****************************************************************************
+ Store a job structure back to the database.
+****************************************************************************/
+
+static bool pjob_store(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char* sharename, uint32_t jobid,
+ struct printjob *pjob)
+{
+ uint32_t tmp;
+ TDB_DATA old_data, new_data;
+ bool ret = False;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ uint8_t *buf = NULL;
+ int len, newlen, buflen;
+
+
+ if (!pdb)
+ return False;
+
+ /* Get old data */
+
+ old_data = tdb_fetch(pdb->tdb, print_key(jobid, &tmp));
+
+ /* Doh! Now we have to pack/unpack data since the NT_DEVICEMODE was added */
+
+ newlen = 0;
+
+ do {
+ len = 0;
+ buflen = newlen;
+ len += tdb_pack(buf+len, buflen-len, "ddddddddddfffff",
+ (uint32_t)pjob->pid,
+ (uint32_t)pjob->jobid,
+ (uint32_t)pjob->sysjob,
+ (uint32_t)pjob->fd,
+ (uint32_t)pjob->starttime,
+ (uint32_t)pjob->status,
+ (uint32_t)pjob->size,
+ (uint32_t)pjob->page_count,
+ (uint32_t)pjob->spooled,
+ (uint32_t)pjob->smbjob,
+ pjob->filename,
+ pjob->jobname,
+ pjob->user,
+ pjob->clientmachine,
+ pjob->queuename);
+
+ len += pack_devicemode(pjob->devmode, buf+len, buflen-len);
+
+ if (buflen != len) {
+ buf = (uint8_t *)SMB_REALLOC(buf, len);
+ if (!buf) {
+ DEBUG(0,("pjob_store: failed to enlarge buffer!\n"));
+ goto done;
+ }
+ newlen = len;
+ }
+ } while ( buflen != len );
+
+
+ /* Store new data */
+
+ new_data.dptr = buf;
+ new_data.dsize = len;
+ ret = (tdb_store(pdb->tdb, print_key(jobid, &tmp), new_data,
+ TDB_REPLACE) == 0);
+
+ /* Send notify updates for what has changed */
+
+ if (ret) {
+ bool changed = false;
+ struct printjob old_pjob;
+
+ if (old_data.dsize) {
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL)
+ goto done;
+
+ len = unpack_pjob(tmp_ctx, old_data.dptr,
+ old_data.dsize, &old_pjob);
+ if (len != -1 ) {
+ pjob_store_notify(ev,
+ msg_ctx,
+ sharename, jobid, &old_pjob,
+ pjob,
+ &changed);
+ if (changed) {
+ add_to_jobs_list(
+ pdb,
+ jobid,
+ "INFO/jobs_changed");
+ }
+ }
+ talloc_free(tmp_ctx);
+
+ } else {
+ /* new job */
+ pjob_store_notify(ev, msg_ctx,
+ sharename, jobid, NULL, pjob,
+ &changed);
+ }
+ }
+
+done:
+ release_print_db(pdb);
+ SAFE_FREE( old_data.dptr );
+ SAFE_FREE( buf );
+
+ return ret;
+}
+
+/****************************************************************************
+ Remove a job structure from the database.
+****************************************************************************/
+
+static void pjob_delete(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char* sharename, uint32_t jobid)
+{
+ uint32_t tmp;
+ struct printjob *pjob;
+ uint32_t job_status = 0;
+ struct tdb_print_db *pdb;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ pdb = get_print_db_byname(sharename);
+ if (!pdb) {
+ goto err_out;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ DEBUG(5, ("we were asked to delete nonexistent job %u\n",
+ jobid));
+ goto err_release;
+ }
+
+ /* 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|JOB_STATUS_DELETED;
+ notify_job_status(ev, msg_ctx, sharename, jobid, job_status);
+
+ /* Remove from printing.tdb */
+
+ tdb_delete(pdb->tdb, print_key(jobid, &tmp));
+ remove_from_jobs_added(sharename, jobid);
+ rap_jobid_delete(sharename, jobid);
+err_release:
+ release_print_db(pdb);
+err_out:
+ talloc_free(tmp_ctx);
+}
+
+/****************************************************************************
+ List a unix job in the print database.
+****************************************************************************/
+
+static void print_unix_job(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, print_queue_struct *q,
+ uint32_t jobid)
+{
+ struct printjob pj, *old_pj;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ if (jobid == (uint32_t)-1) {
+ jobid = q->sysjob + UNIX_JOB_START;
+ }
+
+ /* Preserve the timestamp on an existing unix print job */
+
+ old_pj = print_job_find(tmp_ctx, sharename, jobid);
+
+ ZERO_STRUCT(pj);
+
+ pj.pid = (pid_t)-1;
+ pj.jobid = jobid;
+ pj.sysjob = q->sysjob;
+ pj.fd = -1;
+ pj.starttime = old_pj ? old_pj->starttime : q->time;
+ pj.status = q->status;
+ pj.size = q->size;
+ pj.spooled = True;
+ fstrcpy(pj.filename, old_pj ? old_pj->filename : "");
+ if (jobid < UNIX_JOB_START) {
+ pj.smbjob = True;
+ fstrcpy(pj.jobname, old_pj ? old_pj->jobname : "Remote Downlevel Document");
+ } else {
+ pj.smbjob = False;
+ fstrcpy(pj.jobname, old_pj ? old_pj->jobname : q->fs_file);
+ }
+ fstrcpy(pj.user, old_pj ? old_pj->user : q->fs_user);
+ fstrcpy(pj.queuename, old_pj ? old_pj->queuename : sharename );
+
+ pjob_store(ev, msg_ctx, sharename, jobid, &pj);
+ talloc_free(tmp_ctx);
+}
+
+
+struct traverse_struct {
+ print_queue_struct *queue;
+ size_t qcount, snum, maxcount, total_jobs;
+ const char *sharename;
+ time_t lpq_time;
+ const char *lprm_command;
+ struct printif *print_if;
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ TALLOC_CTX *mem_ctx;
+};
+
+/****************************************************************************
+ 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_t jobid;
+ size_t i = 0;
+
+ if ( key.dsize != sizeof(jobid) )
+ return 0;
+
+ if (unpack_pjob(ts->mem_ctx, data.dptr, data.dsize, &pjob) == -1)
+ return 0;
+ talloc_free(pjob.devmode);
+ jobid = pjob.jobid;
+
+ if (!pjob.smbjob) {
+ /* remove a unix job if it isn't in the system queue any more */
+ for (i=0;i<ts->qcount;i++) {
+ if (ts->queue[i].sysjob == pjob.sysjob) {
+ break;
+ }
+ }
+ if (i == ts->qcount) {
+ DEBUG(10,("traverse_fn_delete: pjob %u deleted due to !smbjob\n",
+ (unsigned int)jobid ));
+ pjob_delete(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid);
+ return 0;
+ }
+
+ /* need to continue the the bottom of the function to
+ save the correct attributes */
+ }
+
+ /* 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_by_pid(pjob.pid)) {
+ DEBUG(10,("traverse_fn_delete: pjob %u deleted due to !process_exists (%u)\n",
+ (unsigned int)jobid, (unsigned int)pjob.pid ));
+ pjob_delete(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid);
+ } else
+ ts->total_jobs++;
+ return 0;
+ }
+
+ /* this check only makes sense for jobs submitted from Windows clients */
+
+ if (pjob.smbjob) {
+ for (i=0;i<ts->qcount;i++) {
+ if ( pjob.status == LPQ_DELETED )
+ continue;
+
+ if (ts->queue[i].sysjob == pjob.sysjob) {
+
+ /* try to clean up any jobs that need to be deleted */
+
+ if ( pjob.status == LPQ_DELETING ) {
+ int result;
+
+ result = (*(ts->print_if->job_delete))(
+ ts->sharename, ts->lprm_command, &pjob );
+
+ if ( result != 0 ) {
+ /* if we can't delete, then reset the job status */
+ pjob.status = LPQ_QUEUED;
+ pjob_store(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid, &pjob);
+ }
+ else {
+ /* if we deleted the job, the remove the tdb record */
+ pjob_delete(ts->ev,
+ ts->msg_ctx,
+ ts->sharename, jobid);
+ pjob.status = LPQ_DELETED;
+ }
+
+ }
+
+ 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) {
+
+ /* 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.
+ Don't delete the job if it was submitted after the lpq_time. */
+
+ if (pjob.starttime < ts->lpq_time) {
+ DEBUG(10,("traverse_fn_delete: pjob %u deleted due to pjob.starttime (%u) < ts->lpq_time (%u)\n",
+ (unsigned int)jobid,
+ (unsigned int)pjob.starttime,
+ (unsigned int)ts->lpq_time ));
+ pjob_delete(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid);
+ } else
+ ts->total_jobs++;
+ return 0;
+ }
+
+ /* Save the pjob attributes we will store. */
+ ts->queue[i].sysjob = pjob.sysjob;
+ 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->total_jobs++;
+
+ return 0;
+}
+
+/****************************************************************************
+ Check if the print queue has been updated recently enough.
+****************************************************************************/
+
+static void print_cache_flush(const char *sharename)
+{
+ fstring key;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return;
+ slprintf(key, sizeof(key)-1, "CACHE/%s", sharename);
+ tdb_store_int32(pdb->tdb, key, -1);
+ release_print_db(pdb);
+}
+
+/****************************************************************************
+ Check if someone already thinks they are doing the update.
+****************************************************************************/
+
+static pid_t get_updating_pid(const char *sharename)
+{
+ fstring keystr;
+ TDB_DATA data, key;
+ pid_t updating_pid;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return (pid_t)-1;
+ slprintf(keystr, sizeof(keystr)-1, "UPDATING/%s", sharename);
+ key = string_tdb_data(keystr);
+
+ data = tdb_fetch(pdb->tdb, key);
+ release_print_db(pdb);
+ if (!data.dptr || data.dsize != sizeof(pid_t)) {
+ SAFE_FREE(data.dptr);
+ return (pid_t)-1;
+ }
+
+ updating_pid = IVAL(data.dptr, 0);
+ SAFE_FREE(data.dptr);
+
+ if (process_exists_by_pid(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 sharename, bool updating)
+{
+ fstring keystr;
+ TDB_DATA key;
+ TDB_DATA data;
+ pid_t updating_pid = getpid();
+ uint8_t buffer[4];
+
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return;
+
+ slprintf(keystr, sizeof(keystr)-1, "UPDATING/%s", sharename);
+ key = string_tdb_data(keystr);
+
+ DEBUG(5, ("set_updating_pid: %supdating lpq cache for print share %s\n",
+ updating ? "" : "not ",
+ sharename ));
+
+ if ( !updating ) {
+ tdb_delete(pdb->tdb, key);
+ release_print_db(pdb);
+ return;
+ }
+
+ SIVAL( buffer, 0, updating_pid);
+ data.dptr = buffer;
+ data.dsize = 4; /* we always assume this is a 4 byte value */
+
+ tdb_store(pdb->tdb, key, data, TDB_REPLACE);
+ release_print_db(pdb);
+}
+
+/****************************************************************************
+ 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;
+}
+
+/****************************************************************************
+ Store the sorted queue representation for later portmon retrieval.
+ Skip deleted jobs
+****************************************************************************/
+
+static void store_queue_struct(struct tdb_print_db *pdb, struct traverse_struct *pts)
+{
+ TDB_DATA data;
+ int max_reported_jobs = lp_max_reported_print_jobs(pts->snum);
+ print_queue_struct *queue = pts->queue;
+ size_t len;
+ size_t i;
+ unsigned int qcount;
+
+ if (max_reported_jobs && (max_reported_jobs < pts->qcount))
+ pts->qcount = max_reported_jobs;
+ qcount = 0;
+
+ /* Work out the size. */
+ data.dsize = 0;
+ data.dsize += tdb_pack(NULL, 0, "d", qcount);
+
+ for (i = 0; i < pts->qcount; i++) {
+ if ( queue[i].status == LPQ_DELETED )
+ continue;
+
+ qcount++;
+ data.dsize += tdb_pack(NULL, 0, "ddddddff",
+ (uint32_t)queue[i].sysjob,
+ (uint32_t)queue[i].size,
+ (uint32_t)queue[i].page_count,
+ (uint32_t)queue[i].status,
+ (uint32_t)queue[i].priority,
+ (uint32_t)queue[i].time,
+ queue[i].fs_user,
+ queue[i].fs_file);
+ }
+
+ if ((data.dptr = (uint8_t *)SMB_MALLOC(data.dsize)) == NULL)
+ return;
+
+ len = 0;
+ len += tdb_pack(data.dptr + len, data.dsize - len, "d", qcount);
+ for (i = 0; i < pts->qcount; i++) {
+ if ( queue[i].status == LPQ_DELETED )
+ continue;
+
+ len += tdb_pack(data.dptr + len, data.dsize - len, "ddddddff",
+ (uint32_t)queue[i].sysjob,
+ (uint32_t)queue[i].size,
+ (uint32_t)queue[i].page_count,
+ (uint32_t)queue[i].status,
+ (uint32_t)queue[i].priority,
+ (uint32_t)queue[i].time,
+ queue[i].fs_user,
+ queue[i].fs_file);
+ }
+
+ tdb_store(pdb->tdb, string_tdb_data("INFO/linear_queue_array"), data,
+ TDB_REPLACE);
+ SAFE_FREE(data.dptr);
+ return;
+}
+
+static TDB_DATA get_jobs_added_data(struct tdb_print_db *pdb)
+{
+ TDB_DATA data;
+
+ ZERO_STRUCT(data);
+
+ data = tdb_fetch(pdb->tdb, string_tdb_data("INFO/jobs_added"));
+ if (data.dptr == NULL || data.dsize == 0 || (data.dsize % 4 != 0)) {
+ SAFE_FREE(data.dptr);
+ ZERO_STRUCT(data);
+ }
+
+ return data;
+}
+
+static void check_job_added(const char *sharename, TDB_DATA data, uint32_t jobid)
+{
+ unsigned int i;
+ unsigned int job_count = data.dsize / 4;
+
+ for (i = 0; i < job_count; i++) {
+ uint32_t ch_jobid;
+
+ ch_jobid = IVAL(data.dptr, i*4);
+ if (ch_jobid == jobid)
+ remove_from_jobs_added(sharename, jobid);
+ }
+}
+
+/****************************************************************************
+ Check if the print queue has been updated recently enough.
+****************************************************************************/
+
+static bool print_cache_expired(const char *sharename, bool check_pending)
+{
+ fstring key;
+ time_t last_qscan_time, time_now = time(NULL);
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ bool result = False;
+
+ if (!pdb)
+ return False;
+
+ snprintf(key, sizeof(key), "CACHE/%s", sharename);
+ 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_lpq_cache_time()
+ || last_qscan_time > (time_now + MAX_CACHE_VALID_TIME))
+ {
+ uint32_t u;
+ time_t msg_pending_time;
+
+ DEBUG(4, ("print_cache_expired: cache expired for queue %s "
+ "(last_qscan_time = %d, time now = %d, qcachetime = %d)\n",
+ sharename, (int)last_qscan_time, (int)time_now,
+ (int)lp_lpq_cache_time() ));
+
+ /* check if another smbd has already sent a message to update the
+ queue. Give the pending message one minute to clear and
+ then send another message anyways. Make sure to check for
+ clocks that have been run forward and then back again. */
+
+ snprintf(key, sizeof(key), "MSG_PENDING/%s", sharename);
+
+ if ( check_pending
+ && tdb_fetch_uint32( pdb->tdb, key, &u )
+ && (msg_pending_time=u) > 0
+ && msg_pending_time <= time_now
+ && (time_now - msg_pending_time) < 60 )
+ {
+ DEBUG(4,("print_cache_expired: message already pending for %s. Accepting cache\n",
+ sharename));
+ goto done;
+ }
+
+ result = True;
+ }
+
+done:
+ release_print_db(pdb);
+ return result;
+}
+
+/****************************************************************************
+ main work for updating the lpq cache for a printer queue
+****************************************************************************/
+
+static void print_queue_update_internal(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ struct printif *current_printif,
+ char *lpq_command, char *lprm_command)
+{
+ size_t i, qcount;
+ print_queue_struct *queue = NULL;
+ print_status_struct status;
+ print_status_struct old_status;
+ struct printjob *pjob;
+ struct traverse_struct tstruct;
+ TDB_DATA data, key;
+ TDB_DATA jcdata;
+ fstring keystr, cachestr;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+
+ if ((pdb == NULL) || (tmp_ctx == NULL)) {
+ return;
+ }
+
+ DEBUG(5,("print_queue_update_internal: printer = %s, type = %d, lpq command = [%s]\n",
+ sharename, current_printif->type, lpq_command));
+
+ /*
+ * 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", sharename);
+ 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))(sharename,
+ current_printif->type,
+ lpq_command, &queue, &status);
+
+ DBG_NOTICE("%zu job%s in queue for %s\n",
+ qcount,
+ (qcount != 1) ? "s" : "",
+ sharename);
+
+ /* Sort the queue by submission time otherwise they are displayed
+ in hash order. */
+
+ TYPESAFE_QSORT(queue, qcount, printjob_comp);
+
+ /*
+ 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
+ */
+ jcdata = get_jobs_added_data(pdb);
+
+ for (i=0; i<qcount; i++) {
+ uint32_t jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob);
+ if (jobid == (uint32_t)-1) {
+ /* assume its a unix print job */
+ print_unix_job(ev, msg_ctx,
+ sharename, &queue[i], jobid);
+ continue;
+ }
+
+ /* we have an active SMB print job - update its status */
+ pjob = print_job_find(tmp_ctx, sharename, 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. */
+ DEBUG(1, ("queued print job %d not found in jobs list, "
+ "assuming unix job\n", jobid));
+ print_unix_job(ev, msg_ctx,
+ sharename, &queue[i], jobid);
+ continue;
+ }
+
+ /* don't reset the status on jobs to be deleted */
+
+ if ( pjob->status != LPQ_DELETING )
+ pjob->status = queue[i].status;
+
+ pjob_store(ev, msg_ctx, sharename, jobid, pjob);
+
+ check_job_added(sharename, jcdata, jobid);
+ }
+
+ SAFE_FREE(jcdata.dptr);
+
+ /* now delete any queued entries that don't appear in the
+ system queue */
+ tstruct.queue = queue;
+ tstruct.qcount = qcount;
+ tstruct.snum = -1;
+ tstruct.total_jobs = 0;
+ tstruct.lpq_time = time(NULL);
+ tstruct.sharename = sharename;
+ tstruct.lprm_command = lprm_command;
+ tstruct.print_if = current_printif;
+ tstruct.ev = ev;
+ tstruct.msg_ctx = msg_ctx;
+ tstruct.mem_ctx = tmp_ctx;
+
+ tdb_traverse(pdb->tdb, traverse_fn_delete, (void *)&tstruct);
+
+ /* Store the linearised queue, max jobs only. */
+ store_queue_struct(pdb, &tstruct);
+
+ SAFE_FREE(tstruct.queue);
+ talloc_free(tmp_ctx);
+
+ DBG_DEBUG("printer %s INFO, total_jobs = %zu\n",
+ sharename,
+ tstruct.total_jobs);
+
+ tdb_store_int32(pdb->tdb, "INFO/total_jobs", tstruct.total_jobs);
+
+ get_queue_status(sharename, &old_status);
+ if (old_status.qcount != qcount) {
+ DBG_DEBUG("Queue status change %zu jobs -> %zu jobs "
+ "for printer %s\n",
+ old_status.qcount,
+ qcount,
+ sharename);
+ }
+
+ /* store the new queue status structure */
+ slprintf(keystr, sizeof(keystr)-1, "STATUS/%s", sharename);
+ key = string_tdb_data(keystr);
+
+ status.qcount = qcount;
+ data.dptr = (uint8_t *)&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", sharename);
+ tdb_store_int32(pdb->tdb, keystr, (int32_t)time(NULL));
+
+ /* clear the msg pending record for this queue */
+
+ snprintf(keystr, sizeof(keystr), "MSG_PENDING/%s", sharename);
+
+ if ( !tdb_store_uint32( pdb->tdb, keystr, 0 ) ) {
+ /* log a message but continue on */
+
+ DEBUG(0,("print_queue_update: failed to store MSG_PENDING flag for [%s]!\n",
+ sharename));
+ }
+
+ release_print_db( pdb );
+
+ return;
+}
+
+/****************************************************************************
+ Update the internal database from the system print queue for a queue.
+ obtain a lock on the print queue before proceeding (needed when mutiple
+ smbd processes maytry to update the lpq cache concurrently).
+****************************************************************************/
+
+static void print_queue_update_with_lock( struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ struct printif *current_printif,
+ char *lpq_command, char *lprm_command )
+{
+ fstring keystr;
+ struct tdb_print_db *pdb;
+
+ DEBUG(5,("print_queue_update_with_lock: printer share = %s\n", sharename));
+ pdb = get_print_db_byname(sharename);
+ if (!pdb)
+ return;
+
+ if ( !print_cache_expired(sharename, False) ) {
+ DEBUG(5,("print_queue_update_with_lock: print cache for %s is still ok\n", sharename));
+ release_print_db(pdb);
+ return;
+ }
+
+ /*
+ * Check to see if someone else is doing this update.
+ * This is essentially a mutex on the update.
+ */
+
+ if (get_updating_pid(sharename) != -1) {
+ release_print_db(pdb);
+ return;
+ }
+
+ /* Lock the queue for the database update */
+
+ slprintf(keystr, sizeof(keystr) - 1, "LOCK/%s", sharename);
+ /* Only wait 10 seconds for this. */
+ if (tdb_lock_bystring_with_timeout(pdb->tdb, keystr, 10) != 0) {
+ DEBUG(0,("print_queue_update_with_lock: Failed to lock printer %s database\n", sharename));
+ release_print_db(pdb);
+ return;
+ }
+
+ /*
+ * Ensure that no one else got in here.
+ * If the updating pid is still -1 then we are
+ * the winner.
+ */
+
+ if (get_updating_pid(sharename) != -1) {
+ /*
+ * Someone else is doing the update, exit.
+ */
+ tdb_unlock_bystring(pdb->tdb, keystr);
+ release_print_db(pdb);
+ return;
+ }
+
+ /*
+ * We're going to do the update ourselves.
+ */
+
+ /* Tell others we're doing the update. */
+ set_updating_pid(sharename, True);
+
+ /*
+ * Allow others to enter and notice we're doing
+ * the update.
+ */
+
+ tdb_unlock_bystring(pdb->tdb, keystr);
+
+ /* do the main work now */
+
+ print_queue_update_internal(ev, msg_ctx,
+ sharename, current_printif,
+ lpq_command, lprm_command);
+
+ /* Delete our pid from the db. */
+ set_updating_pid(sharename, False);
+ release_print_db(pdb);
+}
+
+/****************************************************************************
+this is the receive function of the background lpq updater
+****************************************************************************/
+void print_queue_receive(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ fstring sharename;
+ char *lpqcommand = NULL, *lprmcommand = NULL;
+ int printing_type;
+ size_t len;
+
+ len = tdb_unpack( (uint8_t *)data->data, data->length, "fdPP",
+ sharename,
+ &printing_type,
+ &lpqcommand,
+ &lprmcommand );
+
+ if ( len == -1 ) {
+ SAFE_FREE(lpqcommand);
+ SAFE_FREE(lprmcommand);
+ DEBUG(0,("print_queue_receive: Got invalid print queue update message\n"));
+ return;
+ }
+
+ print_queue_update_with_lock(global_event_context(), msg, sharename,
+ get_printer_fns_from_type((enum printing_types)printing_type),
+ lpqcommand, lprmcommand );
+
+ SAFE_FREE(lpqcommand);
+ SAFE_FREE(lprmcommand);
+ return;
+}
+
+/****************************************************************************
+update the internal database from the system print queue for a queue
+****************************************************************************/
+
+static void print_queue_update(struct messaging_context *msg_ctx,
+ int snum, bool force)
+{
+ char key[268];
+ fstring sharename;
+ char *lpqcommand = NULL;
+ char *lprmcommand = NULL;
+ uint8_t *buffer = NULL;
+ size_t len = 0;
+ size_t newlen;
+ struct tdb_print_db *pdb;
+ int type;
+ struct printif *current_printif;
+ TALLOC_CTX *ctx = talloc_tos();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ fstrcpy( sharename, lp_const_servicename(snum));
+
+ /* don't strip out characters like '$' from the printername */
+
+ lpqcommand = talloc_string_sub2(ctx,
+ lp_lpq_command(snum),
+ "%p",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ false, false, false);
+ if (!lpqcommand) {
+ return;
+ }
+ lpqcommand = talloc_sub_full(ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ current_user_info.unix_name,
+ "",
+ get_current_gid(NULL),
+ get_current_username(),
+ current_user_info.domain,
+ lpqcommand);
+ if (!lpqcommand) {
+ return;
+ }
+
+ lprmcommand = talloc_string_sub2(ctx,
+ lp_lprm_command(snum),
+ "%p",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ false, false, false);
+ if (!lprmcommand) {
+ return;
+ }
+ lprmcommand = talloc_sub_full(ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ current_user_info.unix_name,
+ "",
+ get_current_gid(NULL),
+ get_current_username(),
+ current_user_info.domain,
+ lprmcommand);
+ if (!lprmcommand) {
+ return;
+ }
+
+ /*
+ * Make sure that the background queue process exists.
+ * Otherwise just do the update ourselves
+ */
+
+ if ( force || background_lpq_updater_pid == -1 ) {
+ DEBUG(4,("print_queue_update: updating queue [%s] myself\n", sharename));
+ current_printif = get_printer_fns( snum );
+ print_queue_update_with_lock(global_event_context(), msg_ctx,
+ sharename, current_printif,
+ lpqcommand, lprmcommand);
+
+ return;
+ }
+
+ type = lp_printing(snum);
+
+ /* get the length */
+
+ len = tdb_pack( NULL, 0, "fdPP",
+ sharename,
+ type,
+ lpqcommand,
+ lprmcommand );
+
+ buffer = SMB_XMALLOC_ARRAY( uint8_t, len );
+
+ /* now pack the buffer */
+ newlen = tdb_pack( buffer, len, "fdPP",
+ sharename,
+ type,
+ lpqcommand,
+ lprmcommand );
+
+ SMB_ASSERT( newlen == len );
+
+ DEBUG(10,("print_queue_update: Sending message -> printer = %s, "
+ "type = %d, lpq command = [%s] lprm command = [%s]\n",
+ sharename, type, lpqcommand, lprmcommand ));
+
+ /* here we set a msg pending record for other smbd processes
+ to throttle the number of duplicate print_queue_update msgs
+ sent. */
+
+ pdb = get_print_db_byname(sharename);
+ if (!pdb) {
+ SAFE_FREE(buffer);
+ return;
+ }
+
+ snprintf(key, sizeof(key), "MSG_PENDING/%s", sharename);
+
+ if ( !tdb_store_uint32( pdb->tdb, key, time(NULL) ) ) {
+ /* log a message but continue on */
+
+ DEBUG(0,("print_queue_update: failed to store MSG_PENDING flag for [%s]!\n",
+ sharename));
+ }
+
+ release_print_db( pdb );
+
+ /* finally send the message */
+
+ send_to_bgqd(msg_ctx, MSG_PRINTER_UPDATE, (uint8_t *)buffer, len);
+
+ SAFE_FREE( buffer );
+
+ return;
+}
+
+/****************************************************************************
+ Create/Update an entry in the print tdb that will allow us to send notify
+ updates only to interested smbd's.
+****************************************************************************/
+
+bool print_notify_register_pid(int snum)
+{
+ TDB_DATA data;
+ struct tdb_print_db *pdb = NULL;
+ TDB_CONTEXT *tdb = NULL;
+ const char *printername;
+ uint32_t mypid = (uint32_t)getpid();
+ bool ret = False;
+ size_t i;
+
+ /* if (snum == -1), then the change notify request was
+ on a print server handle and we need to register on
+ all print queus */
+
+ if (snum == -1)
+ {
+ int num_services = lp_numservices();
+ int idx;
+
+ for ( idx=0; idx<num_services; idx++ ) {
+ if (lp_snum_ok(idx) && lp_printable(idx) )
+ print_notify_register_pid(idx);
+ }
+
+ return True;
+ }
+ else /* register for a specific printer */
+ {
+ printername = lp_const_servicename(snum);
+ pdb = get_print_db_byname(printername);
+ if (!pdb)
+ return False;
+ tdb = pdb->tdb;
+ }
+
+ if (tdb_lock_bystring_with_timeout(tdb, NOTIFY_PID_LIST_KEY, 10) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to lock printer %s\n",
+ printername));
+ if (pdb)
+ release_print_db(pdb);
+ return False;
+ }
+
+ data = get_printer_notify_pid_list( tdb, printername, True );
+
+ /* Add ourselves and increase the refcount. */
+
+ for (i = 0; i < data.dsize; i += 8) {
+ if (IVAL(data.dptr,i) == mypid) {
+ uint32_t new_refcount = IVAL(data.dptr, i+4) + 1;
+ SIVAL(data.dptr, i+4, new_refcount);
+ break;
+ }
+ }
+
+ if (i == data.dsize) {
+ /* We weren't in the list. Realloc. */
+ data.dptr = (uint8_t *)SMB_REALLOC(data.dptr, data.dsize + 8);
+ if (!data.dptr) {
+ DEBUG(0,("print_notify_register_pid: Relloc fail for printer %s\n",
+ printername));
+ goto done;
+ }
+ data.dsize += 8;
+ SIVAL(data.dptr,data.dsize - 8,mypid);
+ SIVAL(data.dptr,data.dsize - 4,1); /* Refcount. */
+ }
+
+ /* Store back the record. */
+ if (tdb_store_bystring(tdb, NOTIFY_PID_LIST_KEY, data, TDB_REPLACE) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to update pid \
+list for printer %s\n", printername));
+ goto done;
+ }
+
+ ret = True;
+
+ done:
+
+ tdb_unlock_bystring(tdb, NOTIFY_PID_LIST_KEY);
+ if (pdb)
+ release_print_db(pdb);
+ SAFE_FREE(data.dptr);
+ return ret;
+}
+
+/****************************************************************************
+ Update an entry in the print tdb that will allow us to send notify
+ updates only to interested smbd's.
+****************************************************************************/
+
+bool print_notify_deregister_pid(int snum)
+{
+ TDB_DATA data;
+ struct tdb_print_db *pdb = NULL;
+ TDB_CONTEXT *tdb = NULL;
+ const char *printername;
+ uint32_t mypid = (uint32_t)getpid();
+ size_t i;
+ bool ret = False;
+
+ /* if ( snum == -1 ), we are deregister a print server handle
+ which means to deregister on all print queues */
+
+ if (snum == -1)
+ {
+ int num_services = lp_numservices();
+ int idx;
+
+ for ( idx=0; idx<num_services; idx++ ) {
+ if ( lp_snum_ok(idx) && lp_printable(idx) )
+ print_notify_deregister_pid(idx);
+ }
+
+ return True;
+ }
+ else /* deregister a specific printer */
+ {
+ printername = lp_const_servicename(snum);
+ pdb = get_print_db_byname(printername);
+ if (!pdb)
+ return False;
+ tdb = pdb->tdb;
+ }
+
+ if (tdb_lock_bystring_with_timeout(tdb, NOTIFY_PID_LIST_KEY, 10) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to lock \
+printer %s database\n", printername));
+ if (pdb)
+ release_print_db(pdb);
+ return False;
+ }
+
+ data = get_printer_notify_pid_list( tdb, printername, True );
+
+ /* Reduce refcount. Remove ourselves if zero. */
+
+ for (i = 0; i < data.dsize; ) {
+ if (IVAL(data.dptr,i) == mypid) {
+ uint32_t refcount = IVAL(data.dptr, i+4);
+
+ refcount--;
+
+ if (refcount == 0) {
+ if (data.dsize - i > 8)
+ memmove( &data.dptr[i], &data.dptr[i+8], data.dsize - i - 8);
+ data.dsize -= 8;
+ continue;
+ }
+ SIVAL(data.dptr, i+4, refcount);
+ }
+
+ i += 8;
+ }
+
+ if (data.dsize == 0)
+ SAFE_FREE(data.dptr);
+
+ /* Store back the record. */
+ if (tdb_store_bystring(tdb, NOTIFY_PID_LIST_KEY, data, TDB_REPLACE) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to update pid \
+list for printer %s\n", printername));
+ goto done;
+ }
+
+ ret = True;
+
+ done:
+
+ tdb_unlock_bystring(tdb, NOTIFY_PID_LIST_KEY);
+ if (pdb)
+ release_print_db(pdb);
+ SAFE_FREE(data.dptr);
+ return ret;
+}
+
+/****************************************************************************
+ Check if a jobid is valid. It is valid if it exists in the database.
+****************************************************************************/
+
+bool print_job_exists(const char* sharename, uint32_t jobid)
+{
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ bool ret;
+ uint32_t tmp;
+
+ if (!pdb)
+ return False;
+ ret = tdb_exists(pdb->tdb, print_key(jobid, &tmp));
+ release_print_db(pdb);
+ return ret;
+}
+
+/****************************************************************************
+ Return the device mode asigned to a specific print job.
+ Only valid for the process doing the spooling and when the job
+ has not been spooled.
+****************************************************************************/
+
+struct spoolss_DeviceMode *print_job_devmode(TALLOC_CTX *mem_ctx,
+ const char *sharename,
+ uint32_t jobid)
+{
+ struct printjob *pjob = print_job_find(mem_ctx, sharename, jobid);
+ if (pjob == NULL) {
+ return NULL;
+ }
+
+ return pjob->devmode;
+}
+
+/****************************************************************************
+ Set the name of a job. Only possible for owner.
+****************************************************************************/
+
+bool print_job_set_name(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, const char *name)
+{
+ struct printjob *pjob;
+ bool ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || pjob->pid != getpid()) {
+ ret = false;
+ goto err_out;
+ }
+
+ fstrcpy(pjob->jobname, name);
+ ret = pjob_store(ev, msg_ctx, sharename, jobid, pjob);
+err_out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Get the name of a job. Only possible for owner.
+****************************************************************************/
+
+bool print_job_get_name(TALLOC_CTX *mem_ctx, const char *sharename, uint32_t jobid, char **name)
+{
+ struct printjob *pjob;
+
+ pjob = print_job_find(mem_ctx, sharename, jobid);
+ if (!pjob || pjob->pid != getpid()) {
+ return false;
+ }
+
+ *name = pjob->jobname;
+ return true;
+}
+
+
+/***************************************************************************
+ Remove a jobid from the 'jobs added' list.
+***************************************************************************/
+
+static bool remove_from_jobs_added(const char* sharename, uint32_t jobid)
+{
+ bool ret = remove_from_jobs_list("INFO/jobs_added", sharename, jobid);
+ return ret;
+}
+
+/****************************************************************************
+ Delete a print job - don't update queue.
+****************************************************************************/
+
+static bool print_job_delete1(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int result = 0;
+ struct printif *current_printif = get_printer_fns( snum );
+ bool ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ ret = false;
+ goto err_out;
+ }
+
+ /*
+ * If already deleting just return.
+ */
+
+ if (pjob->status == LPQ_DELETING) {
+ ret = true;
+ goto err_out;
+ }
+
+ /* Hrm - we need to be able to cope with deleting a job before it
+ has reached the spooler. Just mark it as LPQ_DELETING and
+ let the print_queue_update() code rmeove the record */
+
+
+ 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(ev, msg_ctx, sharename, jobid, pjob);
+
+ if (pjob->spooled && pjob->sysjob != -1)
+ {
+ result = (*(current_printif->job_delete))(
+ lp_printername(talloc_tos(), lp_sub, snum),
+ lp_lprm_command(snum),
+ pjob);
+
+ /* Delete the tdb entry if the delete succeeded or the job hasn't
+ been spooled. */
+
+ if (result == 0) {
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ int njobs = 1;
+
+ if (!pdb) {
+ ret = false;
+ goto err_out;
+ }
+ pjob_delete(ev, msg_ctx, sharename, jobid);
+ /* Ensure we keep a rough count of the number of total jobs... */
+ tdb_change_int32_atomic(pdb->tdb, "INFO/total_jobs", &njobs, -1);
+ release_print_db(pdb);
+ }
+ }
+
+ remove_from_jobs_added( sharename, jobid );
+
+ ret = (result == 0);
+err_out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Return true if the current user owns the print job.
+****************************************************************************/
+
+static bool is_owner(const struct auth_session_info *server_info,
+ const char *servicename,
+ uint32_t jobid)
+{
+ struct printjob *pjob;
+ bool ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(server_info);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ pjob = print_job_find(tmp_ctx, servicename, jobid);
+ if (!pjob || !server_info) {
+ ret = false;
+ goto err_out;
+ }
+
+ ret = strequal(pjob->user, server_info->unix_info->sanitized_username);
+err_out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Delete a print job.
+****************************************************************************/
+
+WERROR print_job_delete(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ bool owner;
+ WERROR werr;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ owner = is_owner(server_info, lp_const_servicename(snum), jobid);
+
+ /* Check access against security descriptor or whether the user
+ owns their job. */
+
+ if (!owner &&
+ !W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ JOB_ACCESS_ADMINISTER))) {
+ DEBUG(0, ("print job delete denied."
+ "User name: %s, Printer name: %s.",
+ uidtoname(server_info->unix_token->uid),
+ lp_printername(tmp_ctx, lp_sub, snum)));
+
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ /*
+ * get the spooled filename of the print job
+ * if this works, then the file has not been spooled
+ * to the underlying print system. Just delete the
+ * spool file & return.
+ */
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || pjob->spooled || pjob->pid != getpid()) {
+ DEBUG(10, ("Skipping spool file removal for job %u\n", jobid));
+ } else {
+ DEBUG(10, ("Removing spool file [%s]\n", pjob->filename));
+ if (unlink(pjob->filename) == -1) {
+ werr = map_werror_from_unix(errno);
+ goto err_out;
+ }
+ }
+
+ if (!print_job_delete1(global_event_context(), msg_ctx, snum, jobid)) {
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ /* force update the database and say the delete failed if the
+ job still exists */
+
+ print_queue_update(msg_ctx, snum, True);
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (pjob && (pjob->status != LPQ_DELETING)) {
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+ werr = WERR_PRINTER_HAS_JOBS_QUEUED;
+
+err_out:
+ talloc_free(tmp_ctx);
+ return werr;
+}
+
+/****************************************************************************
+ Pause a job.
+****************************************************************************/
+
+WERROR print_job_pause(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int ret = -1;
+ struct printif *current_printif = get_printer_fns( snum );
+ WERROR werr;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || !server_info) {
+ DEBUG(10, ("print_job_pause: no pjob or user for jobid %u\n",
+ (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!pjob->spooled || pjob->sysjob == -1) {
+ DEBUG(10, ("print_job_pause: not spooled or bad sysjob = %d for jobid %u\n",
+ (int)pjob->sysjob, (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!is_owner(server_info, lp_const_servicename(snum), jobid) &&
+ !W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ JOB_ACCESS_ADMINISTER))) {
+ DEBUG(0, ("print job pause denied."
+ "User name: %s, Printer name: %s.",
+ uidtoname(server_info->unix_token->uid),
+ lp_printername(tmp_ctx, lp_sub, snum)));
+
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ /* need to pause the spooled entry */
+ ret = (*(current_printif->job_pause))(snum, pjob);
+
+ if (ret != 0) {
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ /* force update the database */
+ print_cache_flush(lp_const_servicename(snum));
+
+ /* Send a printer notify message */
+
+ notify_job_status(global_event_context(), msg_ctx, sharename, jobid,
+ JOB_STATUS_PAUSED);
+
+ /* how do we tell if this succeeded? */
+ werr = WERR_OK;
+err_out:
+ talloc_free(tmp_ctx);
+ return werr;
+}
+
+/****************************************************************************
+ Resume a job.
+****************************************************************************/
+
+WERROR print_job_resume(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char *sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int ret;
+ struct printif *current_printif = get_printer_fns( snum );
+ WERROR werr;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL)
+ return WERR_NOT_ENOUGH_MEMORY;
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || !server_info) {
+ DEBUG(10, ("print_job_resume: no pjob or user for jobid %u\n",
+ (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!pjob->spooled || pjob->sysjob == -1) {
+ DEBUG(10, ("print_job_resume: not spooled or bad sysjob = %d for jobid %u\n",
+ (int)pjob->sysjob, (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!is_owner(server_info, lp_const_servicename(snum), jobid) &&
+ !W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ JOB_ACCESS_ADMINISTER))) {
+ DEBUG(0, ("print job resume denied."
+ "User name: %s, Printer name: %s.",
+ uidtoname(server_info->unix_token->uid),
+ lp_printername(tmp_ctx, lp_sub, snum)));
+
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ ret = (*(current_printif->job_resume))(snum, pjob);
+
+ if (ret != 0) {
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ /* force update the database */
+ print_cache_flush(lp_const_servicename(snum));
+
+ /* Send a printer notify message */
+
+ notify_job_status(global_event_context(), msg_ctx, sharename, jobid,
+ JOB_STATUS_QUEUED);
+
+ werr = WERR_OK;
+err_out:
+ talloc_free(tmp_ctx);
+ return werr;
+}
+
+/****************************************************************************
+ Write to a print file.
+****************************************************************************/
+
+ssize_t print_job_write(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid, const char *buf, size_t size)
+{
+ const char* sharename = lp_const_servicename(snum);
+ ssize_t return_code;
+ struct printjob *pjob;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ return_code = -1;
+ goto err_out;
+ }
+
+ /* don't allow another process to get this info - it is meaningless */
+ if (pjob->pid != getpid()) {
+ return_code = -1;
+ goto err_out;
+ }
+
+ /* if SMBD is spooling this can't be allowed */
+ if (pjob->status == PJOB_SMBD_SPOOLING) {
+ return_code = -1;
+ goto err_out;
+ }
+
+ return_code = write_data(pjob->fd, buf, size);
+ if (return_code > 0) {
+ pjob->size += size;
+ pjob_store(ev, msg_ctx, sharename, jobid, pjob);
+ }
+err_out:
+ talloc_free(tmp_ctx);
+ return return_code;
+}
+
+/****************************************************************************
+ Get the queue status - do not update if db is out of date.
+****************************************************************************/
+
+static int get_queue_status(const char* sharename, print_status_struct *status)
+{
+ fstring keystr;
+ TDB_DATA data;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ int len;
+
+ if (status) {
+ ZERO_STRUCTP(status);
+ }
+
+ if (!pdb)
+ return 0;
+
+ if (status) {
+ fstr_sprintf(keystr, "STATUS/%s", sharename);
+ data = tdb_fetch(pdb->tdb, string_tdb_data(keystr));
+ if (data.dptr) {
+ if (data.dsize == sizeof(print_status_struct))
+ /* this memcpy is ok since the status struct was
+ not packed before storing it in the tdb */
+ memcpy(status, data.dptr, sizeof(print_status_struct));
+ SAFE_FREE(data.dptr);
+ }
+ }
+ len = tdb_fetch_int32(pdb->tdb, "INFO/total_jobs");
+ release_print_db(pdb);
+ return (len == -1 ? 0 : len);
+}
+
+/****************************************************************************
+ Determine the number of jobs in a queue.
+****************************************************************************/
+
+int print_queue_length(struct messaging_context *msg_ctx, int snum,
+ print_status_struct *pstatus)
+{
+ const char* sharename = lp_const_servicename( snum );
+ print_status_struct status;
+ int len;
+
+ ZERO_STRUCT( status );
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ /* also fetch the queue status */
+ memset(&status, 0, sizeof(status));
+ len = get_queue_status(sharename, &status);
+
+ if (pstatus)
+ *pstatus = status;
+
+ return len;
+}
+
+/***************************************************************************
+ Allocate a jobid. Hold the lock for as short a time as possible.
+***************************************************************************/
+
+static WERROR allocate_print_jobid(struct tdb_print_db *pdb, int snum,
+ const char *sharename, uint32_t *pjobid)
+{
+ int i;
+ uint32_t jobid;
+ enum TDB_ERROR terr;
+ int ret;
+
+ *pjobid = (uint32_t)-1;
+
+ for (i = 0; i < 3; i++) {
+ /* Lock the database - only wait 20 seconds. */
+ ret = tdb_lock_bystring_with_timeout(pdb->tdb,
+ "INFO/nextjob", 20);
+ if (ret != 0) {
+ DEBUG(0, ("allocate_print_jobid: "
+ "Failed to lock printing database %s\n",
+ sharename));
+ terr = tdb_error(pdb->tdb);
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+
+ if (!tdb_fetch_uint32(pdb->tdb, "INFO/nextjob", &jobid)) {
+ terr = tdb_error(pdb->tdb);
+ if (terr != TDB_ERR_NOEXIST) {
+ DEBUG(0, ("allocate_print_jobid: "
+ "Failed to fetch INFO/nextjob "
+ "for print queue %s\n", sharename));
+ tdb_unlock_bystring(pdb->tdb, "INFO/nextjob");
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+ DEBUG(10, ("allocate_print_jobid: "
+ "No existing jobid in %s\n", sharename));
+ jobid = 0;
+ }
+
+ DEBUG(10, ("allocate_print_jobid: "
+ "Read jobid %u from %s\n", jobid, sharename));
+
+ jobid = NEXT_JOBID(jobid);
+
+ ret = tdb_store_int32(pdb->tdb, "INFO/nextjob", jobid);
+ if (ret != 0) {
+ terr = tdb_error(pdb->tdb);
+ DEBUG(3, ("allocate_print_jobid: "
+ "Failed to store INFO/nextjob.\n"));
+ tdb_unlock_bystring(pdb->tdb, "INFO/nextjob");
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+
+ /* We've finished with the INFO/nextjob lock. */
+ tdb_unlock_bystring(pdb->tdb, "INFO/nextjob");
+
+ if (!print_job_exists(sharename, jobid)) {
+ break;
+ }
+ DEBUG(10, ("allocate_print_jobid: "
+ "Found jobid %u in %s\n", jobid, sharename));
+ }
+
+ if (i > 2) {
+ DEBUG(0, ("allocate_print_jobid: "
+ "Failed to allocate a print job for queue %s\n",
+ sharename));
+ /* Probably full... */
+ return WERR_NO_SPOOL_SPACE;
+ }
+
+ /* Store a dummy placeholder. */
+ {
+ uint32_t tmp;
+ TDB_DATA dum;
+ dum.dptr = NULL;
+ dum.dsize = 0;
+ if (tdb_store(pdb->tdb, print_key(jobid, &tmp), dum,
+ TDB_INSERT) != 0) {
+ DEBUG(3, ("allocate_print_jobid: "
+ "jobid (%d) failed to store placeholder.\n",
+ jobid ));
+ terr = tdb_error(pdb->tdb);
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+ }
+
+ *pjobid = jobid;
+ return WERR_OK;
+}
+
+/***************************************************************************
+ Do all checks needed to determine if we can start a job.
+***************************************************************************/
+
+static WERROR print_job_checks(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, int *njobs)
+{
+ const char *sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint64_t dspace, dsize;
+ uint64_t minspace;
+ int ret;
+
+ if (!W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ PRINTER_ACCESS_USE))) {
+ DEBUG(3, ("print_job_checks: "
+ "job start denied by security descriptor\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (!print_time_access_check(server_info, msg_ctx, sharename)) {
+ DEBUG(3, ("print_job_checks: "
+ "job start denied by time check\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* see if we have sufficient disk space */
+ if (lp_min_print_space(snum)) {
+ minspace = lp_min_print_space(snum);
+ ret = sys_fsusage(lp_path(talloc_tos(), lp_sub, snum), &dspace, &dsize);
+ if (ret == 0 && dspace < 2*minspace) {
+ DEBUG(3, ("print_job_checks: "
+ "disk space check failed.\n"));
+ return WERR_NO_SPOOL_SPACE;
+ }
+ }
+
+ /* for autoloaded printers, check that the printcap entry still exists */
+ if (lp_autoloaded(snum) &&
+ !printer_list_printername_exists(sharename)) {
+ DEBUG(3, ("print_job_checks: printer name %s check failed.\n",
+ sharename));
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* Insure the maximum queue size is not violated */
+ *njobs = print_queue_length(msg_ctx, snum, NULL);
+ if (*njobs > lp_maxprintjobs(snum)) {
+ DEBUG(3, ("print_job_checks: Queue %s number of jobs (%d) "
+ "larger than max printjobs per queue (%d).\n",
+ sharename, *njobs, lp_maxprintjobs(snum)));
+ return WERR_NO_SPOOL_SPACE;
+ }
+
+ return WERR_OK;
+}
+
+/***************************************************************************
+ Create a job file.
+***************************************************************************/
+
+static WERROR print_job_spool_file(int snum, uint32_t jobid,
+ const char *output_file,
+ struct printjob *pjob)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ WERROR werr;
+ SMB_STRUCT_STAT st;
+ const char *path;
+ int len;
+ mode_t mask;
+
+ /* if this file is within the printer path, it means that smbd
+ * is spooling it and will pass us control when it is finished.
+ * Verify that the file name is ok, within path, and it is
+ * already already there */
+ if (output_file) {
+ path = lp_path(talloc_tos(), lp_sub, snum);
+ len = strlen(path);
+ if (strncmp(output_file, path, len) == 0 &&
+ (output_file[len - 1] == '/' || output_file[len] == '/')) {
+
+ /* verify path is not too long */
+ if (strlen(output_file) >= sizeof(pjob->filename)) {
+ return WERR_INVALID_NAME;
+ }
+
+ /* verify that the file exists */
+ if (sys_stat(output_file, &st, false) != 0) {
+ return WERR_INVALID_NAME;
+ }
+
+ fstrcpy(pjob->filename, output_file);
+
+ DEBUG(3, ("print_job_spool_file:"
+ "External spooling activated\n"));
+
+ /* we do not open the file until spooling is done */
+ pjob->fd = -1;
+ pjob->status = PJOB_SMBD_SPOOLING;
+
+ return WERR_OK;
+ }
+ }
+
+ slprintf(pjob->filename, sizeof(pjob->filename)-1,
+ "%s/%sXXXXXX", lp_path(talloc_tos(), lp_sub, snum),
+ PRINT_SPOOL_PREFIX);
+ mask = umask(S_IRWXO | S_IRWXG);
+ pjob->fd = mkstemp(pjob->filename);
+ umask(mask);
+
+ if (pjob->fd == -1) {
+ werr = map_werror_from_unix(errno);
+ if (W_ERROR_EQUAL(werr, WERR_ACCESS_DENIED)) {
+ /* Common setup error, force a report. */
+ DEBUG(0, ("print_job_spool_file: "
+ "insufficient permissions to open spool "
+ "file %s.\n", pjob->filename));
+ } else {
+ /* Normal case, report at level 3 and above. */
+ DEBUG(3, ("print_job_spool_file: "
+ "can't open spool file %s\n",
+ pjob->filename));
+ }
+ return werr;
+ }
+
+ return WERR_OK;
+}
+
+/***************************************************************************
+ Start spooling a job - return the jobid.
+***************************************************************************/
+
+WERROR print_job_start(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ const char *clientmachine,
+ int snum, const char *docname, const char *filename,
+ struct spoolss_DeviceMode *devmode, uint32_t *_jobid)
+{
+ uint32_t jobid;
+ char *path = NULL, *userstr = NULL;
+ struct printjob pjob;
+ const char *sharename = lp_const_servicename(snum);
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int njobs;
+ WERROR werr;
+
+ if (!pdb) {
+ return WERR_INTERNAL_DB_CORRUPTION;
+ }
+
+ path = lp_path(talloc_tos(), lp_sub, snum);
+
+ werr = print_job_checks(server_info, msg_ctx, snum, &njobs);
+ if (!W_ERROR_IS_OK(werr)) {
+ release_print_db(pdb);
+ return werr;
+ }
+
+ DEBUG(10, ("print_job_start: "
+ "Queue %s number of jobs (%d), max printjobs = %d\n",
+ sharename, njobs, lp_maxprintjobs(snum)));
+
+ werr = allocate_print_jobid(pdb, snum, sharename, &jobid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ /* create the database entry */
+
+ ZERO_STRUCT(pjob);
+
+ pjob.pid = getpid();
+ pjob.jobid = jobid;
+ pjob.sysjob = -1;
+ pjob.fd = -1;
+ pjob.starttime = time(NULL);
+ pjob.status = LPQ_SPOOLING;
+ pjob.size = 0;
+ pjob.spooled = False;
+ pjob.smbjob = True;
+ pjob.devmode = devmode;
+
+ fstrcpy(pjob.jobname, docname);
+
+ fstrcpy(pjob.clientmachine, clientmachine);
+
+ userstr = talloc_sub_full(talloc_tos(),
+ sharename,
+ server_info->unix_info->sanitized_username,
+ path, server_info->unix_token->gid,
+ server_info->unix_info->sanitized_username,
+ server_info->info->domain_name,
+ lp_printjob_username(snum));
+ if (userstr == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto fail;
+ }
+ strlcpy(pjob.user, userstr, sizeof(pjob.user));
+ TALLOC_FREE(userstr);
+
+ fstrcpy(pjob.queuename, lp_const_servicename(snum));
+
+ /* we have a job entry - now create the spool file */
+ werr = print_job_spool_file(snum, jobid, filename, &pjob);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ pjob_store(global_event_context(), msg_ctx, sharename, jobid, &pjob);
+
+ /* Update the 'jobs added' entry used by print_queue_status. */
+ add_to_jobs_list(pdb, jobid, "INFO/jobs_added");
+
+ /* Ensure we keep a rough count of the number of total jobs... */
+ tdb_change_int32_atomic(pdb->tdb, "INFO/total_jobs", &njobs, 1);
+
+ release_print_db(pdb);
+
+ *_jobid = jobid;
+ return WERR_OK;
+
+fail:
+ if (jobid != -1) {
+ pjob_delete(global_event_context(), msg_ctx, sharename, jobid);
+ }
+
+ release_print_db(pdb);
+
+ DEBUG(3, ("print_job_start: returning fail. "
+ "Error = %s\n", win_errstr(werr)));
+ return werr;
+}
+
+/****************************************************************************
+ Update the number of pages spooled to jobid
+****************************************************************************/
+
+void print_job_endpage(struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ struct printjob *pjob;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ goto err_out;
+ }
+ /* don't allow another process to get this info - it is meaningless */
+ if (pjob->pid != getpid()) {
+ goto err_out;
+ }
+
+ pjob->page_count++;
+ pjob_store(global_event_context(), msg_ctx, sharename, jobid, pjob);
+err_out:
+ talloc_free(tmp_ctx);
+}
+
+/****************************************************************************
+ 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.
+****************************************************************************/
+
+NTSTATUS print_job_end(struct messaging_context *msg_ctx, int snum,
+ uint32_t jobid, enum file_close_type close_type)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int ret;
+ SMB_STRUCT_STAT sbuf;
+ struct printif *current_printif = get_printer_fns(snum);
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ char *lpq_cmd;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto err_out;
+ }
+
+ if (pjob->spooled || pjob->pid != getpid()) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ if (close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE) {
+ if (pjob->status == PJOB_SMBD_SPOOLING) {
+ /* take over the file now, smbd is done */
+ if (sys_stat(pjob->filename, &sbuf, false) != 0) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(3, ("print_job_end: "
+ "stat file failed for jobid %d\n",
+ jobid));
+ goto fail;
+ }
+
+ pjob->status = LPQ_SPOOLING;
+
+ } else {
+
+ if ((sys_fstat(pjob->fd, &sbuf, false) != 0)) {
+ status = map_nt_error_from_unix(errno);
+ close(pjob->fd);
+ DEBUG(3, ("print_job_end: "
+ "stat file failed for jobid %d\n",
+ jobid));
+ goto fail;
+ }
+
+ close(pjob->fd);
+ }
+
+ pjob->size = sbuf.st_ex_size;
+ } else {
+
+ /*
+ * Not a normal close, something has gone wrong. Cleanup.
+ */
+ if (pjob->fd != -1) {
+ close(pjob->fd);
+ }
+ 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(global_event_context(), msg_ctx, sharename, jobid);
+ return NT_STATUS_OK;
+ }
+
+ /* don't strip out characters like '$' from the printername */
+ lpq_cmd = talloc_string_sub2(tmp_ctx,
+ lp_lpq_command(snum),
+ "%p",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ false, false, false);
+ if (lpq_cmd == NULL) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto fail;
+ }
+ lpq_cmd = talloc_sub_full(tmp_ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ current_user_info.unix_name,
+ "",
+ get_current_gid(NULL),
+ get_current_username(),
+ current_user_info.domain,
+ lpq_cmd);
+ if (lpq_cmd == NULL) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto fail;
+ }
+
+ ret = (*(current_printif->job_submit))(snum, pjob,
+ current_printif->type, lpq_cmd);
+ if (ret) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto fail;
+ }
+
+ /* The print job has been successfully handed over to the back-end */
+
+ pjob->spooled = True;
+ pjob->status = LPQ_QUEUED;
+ pjob_store(global_event_context(), msg_ctx, sharename, jobid, pjob);
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ return NT_STATUS_OK;
+
+fail:
+
+ /* The print job was not successfully started. Cleanup */
+ /* Still need to add proper error return propagation! 010122:JRR */
+ pjob->fd = -1;
+ unlink(pjob->filename);
+ pjob_delete(global_event_context(), msg_ctx, sharename, jobid);
+err_out:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/****************************************************************************
+ Get a snapshot of jobs in the system without traversing.
+****************************************************************************/
+
+static bool get_stored_queue_info(struct messaging_context *msg_ctx,
+ struct tdb_print_db *pdb, int snum,
+ int *pcount, print_queue_struct **ppqueue)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ TDB_DATA data, cgdata, jcdata;
+ print_queue_struct *queue = NULL;
+ uint32_t qcount = 0;
+ uint32_t extra_count = 0;
+ uint32_t changed_count = 0;
+ int total_count = 0;
+ size_t len = 0;
+ uint32_t i;
+ int max_reported_jobs = lp_max_reported_print_jobs(snum);
+ bool ret = false;
+ const char* sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ *pcount = 0;
+ *ppqueue = NULL;
+
+ ZERO_STRUCT(data);
+ ZERO_STRUCT(cgdata);
+
+ /* Get the stored queue data. */
+ data = tdb_fetch(pdb->tdb, string_tdb_data("INFO/linear_queue_array"));
+
+ if (data.dptr && data.dsize >= sizeof(qcount))
+ len += tdb_unpack(data.dptr + len, data.dsize - len, "d", &qcount);
+
+ /* Get the added jobs list. */
+ cgdata = tdb_fetch(pdb->tdb, string_tdb_data("INFO/jobs_added"));
+ if (cgdata.dptr != NULL && (cgdata.dsize % 4 == 0))
+ extra_count = cgdata.dsize/4;
+
+ /* Get the changed jobs list. */
+ jcdata = tdb_fetch(pdb->tdb, string_tdb_data("INFO/jobs_changed"));
+ if (jcdata.dptr != NULL && (jcdata.dsize % 4 == 0))
+ changed_count = jcdata.dsize / 4;
+
+ DEBUG(5,("get_stored_queue_info: qcount = %u, extra_count = %u\n", (unsigned int)qcount, (unsigned int)extra_count));
+
+ /* Allocate the queue size. */
+ if (qcount == 0 && extra_count == 0)
+ goto out;
+
+ if ((queue = SMB_MALLOC_ARRAY(print_queue_struct, qcount + extra_count)) == NULL)
+ goto out;
+
+ /* Retrieve the linearised queue data. */
+
+ for(i = 0; i < qcount; i++) {
+ uint32_t qjob, qsize, qpage_count, qstatus, qpriority, qtime;
+ len += tdb_unpack(data.dptr + len, data.dsize - len, "ddddddff",
+ &qjob,
+ &qsize,
+ &qpage_count,
+ &qstatus,
+ &qpriority,
+ &qtime,
+ queue[i].fs_user,
+ queue[i].fs_file);
+ queue[i].sysjob = qjob;
+ queue[i].size = qsize;
+ queue[i].page_count = qpage_count;
+ queue[i].status = qstatus;
+ queue[i].priority = qpriority;
+ queue[i].time = qtime;
+ }
+
+ total_count = qcount;
+
+ /* Add new jobids to the queue. */
+ for (i = 0; i < extra_count; i++) {
+ uint32_t jobid;
+ struct printjob *pjob;
+
+ jobid = IVAL(cgdata.dptr, i*4);
+ DEBUG(5,("get_stored_queue_info: added job = %u\n", (unsigned int)jobid));
+ pjob = print_job_find(tmp_ctx, lp_const_servicename(snum), jobid);
+ if (!pjob) {
+ DEBUG(5,("get_stored_queue_info: failed to find added job = %u\n", (unsigned int)jobid));
+ remove_from_jobs_added(sharename, jobid);
+ continue;
+ }
+
+ queue[total_count].sysjob = pjob->sysjob;
+ queue[total_count].size = pjob->size;
+ queue[total_count].page_count = pjob->page_count;
+ queue[total_count].status = pjob->status;
+ queue[total_count].priority = 1;
+ queue[total_count].time = pjob->starttime;
+ fstrcpy(queue[total_count].fs_user, pjob->user);
+ fstrcpy(queue[total_count].fs_file, pjob->jobname);
+ total_count++;
+ talloc_free(pjob);
+ }
+
+ /* Update the changed jobids. */
+ for (i = 0; i < changed_count; i++) {
+ uint32_t jobid = IVAL(jcdata.dptr, i * 4);
+ struct printjob *pjob;
+ uint32_t j;
+ bool found = false;
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (pjob == NULL) {
+ DEBUG(5,("get_stored_queue_info: failed to find "
+ "changed job = %u\n",
+ (unsigned int)jobid));
+ remove_from_jobs_changed(sharename, jobid);
+ continue;
+ }
+
+ for (j = 0; j < total_count; j++) {
+ if (queue[j].sysjob == pjob->sysjob) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ DEBUG(5,("get_stored_queue_info: changed job: %u\n",
+ (unsigned int)jobid));
+
+ queue[j].sysjob = pjob->sysjob;
+ queue[j].size = pjob->size;
+ queue[j].page_count = pjob->page_count;
+ queue[j].status = pjob->status;
+ queue[j].priority = 1;
+ queue[j].time = pjob->starttime;
+ fstrcpy(queue[j].fs_user, pjob->user);
+ fstrcpy(queue[j].fs_file, pjob->jobname);
+ talloc_free(pjob);
+
+ DEBUG(5,("updated queue[%u], jobid: %u, sysjob: %u, "
+ "jobname: %s\n",
+ (unsigned int)j, (unsigned int)jobid,
+ (unsigned int)queue[j].sysjob, pjob->jobname));
+ }
+
+ remove_from_jobs_changed(sharename, jobid);
+ }
+
+ /* Sort the queue by submission time otherwise they are displayed
+ in hash order. */
+
+ TYPESAFE_QSORT(queue, total_count, printjob_comp);
+
+ DEBUG(5,("get_stored_queue_info: total_count = %u\n", (unsigned int)total_count));
+
+ if (max_reported_jobs && total_count > max_reported_jobs)
+ total_count = max_reported_jobs;
+
+ *ppqueue = queue;
+ *pcount = total_count;
+
+ ret = true;
+
+ out:
+
+ SAFE_FREE(data.dptr);
+ SAFE_FREE(cgdata.dptr);
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Get a printer queue listing.
+ set queue = NULL and status = NULL if you just want to update the cache
+****************************************************************************/
+
+int print_queue_status(struct messaging_context *msg_ctx, int snum,
+ print_queue_struct **ppqueue,
+ print_status_struct *status)
+{
+ fstring keystr;
+ TDB_DATA data, key;
+ const char *sharename;
+ struct tdb_print_db *pdb;
+ int count = 0;
+
+ /* make sure the database is up to date */
+
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ /* return if we are done */
+ if ( !ppqueue || !status )
+ return 0;
+
+ *ppqueue = NULL;
+ sharename = lp_const_servicename(snum);
+ pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return 0;
+
+ /*
+ * 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", sharename);
+ key = string_tdb_data(keystr);
+
+ data = tdb_fetch(pdb->tdb, key);
+ if (data.dptr) {
+ if (data.dsize == sizeof(*status)) {
+ /* this memcpy is ok since the status struct was
+ not packed before storing it in the tdb */
+ 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.
+ */
+
+ if (!get_stored_queue_info(msg_ctx, pdb, snum, &count, ppqueue)) {
+ release_print_db(pdb);
+ return 0;
+ }
+
+ release_print_db(pdb);
+ return count;
+}
+
+/****************************************************************************
+ Pause a queue.
+****************************************************************************/
+
+WERROR print_queue_pause(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum)
+{
+ int ret;
+ struct printif *current_printif = get_printer_fns( snum );
+
+ if (!W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ PRINTER_ACCESS_ADMINISTER))) {
+ return WERR_ACCESS_DENIED;
+ }
+
+
+ become_root();
+
+ ret = (*(current_printif->queue_pause))(snum);
+
+ unbecome_root();
+
+ if (ret != 0) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /* force update the database */
+ print_cache_flush(lp_const_servicename(snum));
+
+ /* Send a printer notify message */
+
+ notify_printer_status(global_event_context(), msg_ctx, snum,
+ PRINTER_STATUS_PAUSED);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ Resume a queue.
+****************************************************************************/
+
+WERROR print_queue_resume(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum)
+{
+ int ret;
+ struct printif *current_printif = get_printer_fns( snum );
+
+ if (!W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ PRINTER_ACCESS_ADMINISTER))) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ become_root();
+
+ ret = (*(current_printif->queue_resume))(snum);
+
+ unbecome_root();
+
+ if (ret != 0) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, True);
+
+ /* Send a printer notify message */
+
+ notify_printer_status(global_event_context(), msg_ctx, snum,
+ PRINTER_STATUS_OK);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ Purge a queue - implemented by deleting all jobs that we can delete.
+****************************************************************************/
+
+WERROR print_queue_purge(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum)
+{
+ 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(msg_ctx, snum, True);
+
+ can_job_admin = W_ERROR_IS_OK(print_access_check(server_info,
+ msg_ctx,
+ snum,
+ JOB_ACCESS_ADMINISTER));
+ njobs = print_queue_status(msg_ctx, snum, &queue, &status);
+
+ if ( can_job_admin )
+ become_root();
+
+ for (i = 0; i < njobs; i++) {
+ struct tdb_print_db *pdb;
+ int jobid;
+ bool owner;
+ pdb = get_print_db_byname(lp_const_servicename(snum));
+ if (pdb == NULL) {
+ DEBUG(1, ("failed to find printdb for %s\n",
+ lp_const_servicename(snum)));
+ continue;
+ }
+ jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob);
+ if (jobid == (uint32_t)-1) {
+ DEBUG(2, ("jobid for system job %d not found\n",
+ queue[i].sysjob));
+ continue; /* unix job */
+ }
+ owner = is_owner(server_info, lp_const_servicename(snum),
+ jobid);
+
+ if (owner || can_job_admin) {
+ print_job_delete1(global_event_context(), msg_ctx,
+ snum, jobid);
+ }
+ }
+
+ if ( can_job_admin )
+ unbecome_root();
+
+ /* update the cache */
+ print_queue_update(msg_ctx, snum, True);
+
+ SAFE_FREE(queue);
+
+ return WERR_OK;
+}