/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*LINTLIBRARY*/

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libintl.h>
#include <pwd.h>
#include <sys/stat.h>
#include <papi_impl.h>

/*
 * for an older application that may have been linked with a pre-v1.0
 * PAPI implementation.
 */
papi_status_t
papiAttributeListAdd(papi_attribute_t ***attrs, int flags, char *name,
		papi_attribute_value_type_t type, papi_attribute_value_t *value)
{
	return (papiAttributeListAddValue(attrs, flags, name, type, value));
}

#ifdef LP_USE_PAPI_ATTR
static papi_status_t psm_modifyAttrsFile(papi_attribute_t **attrs, char *file);
static papi_status_t psm_modifyAttrsList(char *file, papi_attribute_t **attrs,
					papi_attribute_t ***newAttrs);
#endif


void
papiJobFree(papi_job_t job)
{
	job_t *tmp = (job_t *)job;

	if (tmp != NULL) {
		papiAttributeListFree(tmp->attributes);
		free(tmp);
	}
}

void
papiJobListFree(papi_job_t *jobs)
{
	if (jobs != NULL) {
		int i;

		for (i = 0; jobs[i] != NULL; i++) {
			papiJobFree(jobs[i]);
		}
		free(jobs);
	}
}

papi_attribute_t **
papiJobGetAttributeList(papi_job_t job)
{
	job_t *tmp = (job_t *)job;

	if (tmp != NULL)
		return (tmp->attributes);

	return (NULL);
}

char *
papiJobGetPrinterName(papi_job_t job)
{
	job_t *tmp = (job_t *)job;
	char *result = NULL;

	if (tmp != NULL)
		papiAttributeListGetString(tmp->attributes, NULL,
		    "printer-name", &result);

	return (result);
}

int32_t
papiJobGetId(papi_job_t job)
{
	job_t *tmp = (job_t *)job;
	int result = -1;

	if (tmp != NULL)
		papiAttributeListGetInteger(tmp->attributes, NULL, "job-id",
		    &result);

	return (result);
}

static REQUEST *
create_request(papi_service_t svc, char *printer, papi_attribute_t **attributes)
{
	REQUEST *r;

	if ((r = calloc(1, sizeof (*r))) != NULL) {
		char *hostname = NULL;

		r->priority = -1;
		r->destination = printer_name_from_uri_id(printer, -1);

		papiAttributeListGetString(attributes, NULL,
		    "job-originating-host-name", &hostname);

		if (hostname == NULL) {
			char host[BUFSIZ];

			if (gethostname(host, sizeof (host)) == 0)
				papiAttributeListAddString(&attributes,
				    PAPI_ATTR_REPLACE,
				    "job-originating-host-name",
				    host);
		}

		job_attributes_to_lpsched_request(svc, r, attributes);
	}

	return (r);
}

static papi_status_t
authorized(service_t *svc, int32_t id)
{
	papi_status_t result = PAPI_NOT_AUTHORIZED;	/* assume the worst */
	char file[32];
	REQUEST *r;

	snprintf(file, sizeof (file), "%d-0", id);
	if ((r = getrequest(file)) != NULL) {
		uid_t uid = getuid();
		struct passwd *pw = NULL;
		char *user = "intruder";	/* assume an intruder */

		if ((pw = getpwuid(uid)) != NULL)
			user = pw->pw_name;	/* use the process owner */

		if ((uid == 0) || (uid == 71)) { /* root/lp can forge this */
			papi_status_t s;
			s = papiAttributeListGetString(svc->attributes, NULL,
			    "user-name", &user);
			if (s != PAPI_OK)	/* true root/lp are almighty */
				result = PAPI_OK;
		}

		if (result != PAPI_OK) {
			if (strcmp(user, r->user) == 0)
				result = PAPI_OK;
			else {
				/*
				 * user and r->user might contain the
				 * host info also
				 */
				char *token1 = strtok(r->user, "@");
				char *token2 = strtok(NULL, "@");
				char *token3 = strtok(user, "@");
				char *token4 = strtok(NULL, "@");

				/*
				 * token1 and token3 contain usernames
				 * token2 and token4 contain hostnames
				 */
				if ((token1 == NULL) || (token3 == NULL))
					result = PAPI_NOT_AUTHORIZED;
				else if ((token4 != NULL) &&
				    (strcmp(token4, "localhost") == 0) &&
				    (strcmp(token3, "root") == 0) ||
				    (strcmp(token3, "lp") == 0)) {
					/*
					 * root/lp user on server can
					 * cancel any requset
					 */
					result = PAPI_OK;
				} else if (strcmp(token1, token3) == 0) {
					/*
					 * usernames are same
					 * compare the hostnames
					 */
					if ((token4 != NULL) &&
					    (token2 != NULL) &&
					    (strcmp(token4, "localhost") ==
					    0)) {
						/*
						 * Its server machine
						 */
						static char host[256];
						if (gethostname(host,
						    sizeof (host)) == 0) {
							if ((host != NULL) &&
							    (strcmp(host,
							    token2) == 0))
								result =
								    PAPI_OK;
						}

					} else if ((token4 != NULL) &&
					    (token2 != NULL) &&
					    (strcmp(token4, token2) == 0)) {
						result = PAPI_OK;
					} else if ((token4 == NULL) &&
					    (token2 != NULL)) {
						/*
						 * When the request is sent from
						 * client to server using ipp
						 * token4 is NULL
						 */
						result = PAPI_OK;
					}
				}
			}
		}

		freerequest(r);
	} else
		result = PAPI_NOT_FOUND;

	return (result);
}

static papi_status_t
copy_file(char *from, char *to)
{
	int ifd, ofd;
	char buf[BUFSIZ];
	int rc;

	if ((ifd = open(from, O_RDONLY)) < 0)
		return (PAPI_DOCUMENT_ACCESS_ERROR);

	if ((ofd = open(to, O_WRONLY)) < 0) {
		close(ifd);
		return (PAPI_NOT_POSSIBLE);
	}

	while ((rc = read(ifd, buf, sizeof (buf))) > 0)
		write(ofd, buf, rc);

	close(ifd);
	close(ofd);

	return (PAPI_OK);
}


#ifdef LP_USE_PAPI_ATTR
/*
 * *****************************************************************************
 *
 * Description: Create a file containing all the attributes in the attribute
 *              list passed to this function.
 *              This file is then passed through lpsched and given to either
 *              a slow-filter or to the printer's interface script to process
 *              the attributes.
 *
 * Parameters:  attrs - list of attributes and their values
 *              file  - file pathname to create and put the attributes into.
 *
 * *****************************************************************************
 */

static papi_status_t
psm_copy_attrsToFile(papi_attribute_t **attrs, char *file)

{
	papi_status_t result = PAPI_OK;

	if ((attrs != NULL) && (*attrs != NULL)) {
		FILE *out = NULL;

		if ((out = fopen(file, "w")) != NULL) {
			papiAttributeListPrint(out, attrs, "");
			fclose(out);
		} else {
			result = PAPI_NOT_POSSIBLE;
		}
	}

	return (result);
} /* psm_copy_attrsToFile */


/*
 * *****************************************************************************
 *
 * Description: Modify the given attribute 'file' with the attributes from the
 *              'attrs' list. Attributes already in the file will be replaced
 *              with the new value. New attributes will be added into the file.
 *
 * Parameters:  attrs - list of attributes and their values
 *              file  - file pathname to create and put the attributes into.
 *
 * *****************************************************************************
 */

static papi_status_t
psm_modifyAttrsFile(papi_attribute_t **attrs, char *file)

{
	papi_status_t result = PAPI_OK;
	papi_attribute_t **newAttrs = NULL;
	struct stat   tmpBuf;
	FILE *fd = NULL;

	if ((attrs != NULL) && (*attrs != NULL) && (file != NULL)) {

		/*
		 * check file exist before try to modify it, if it doesn't
		 * exist assume there is an error
		 */
		if (stat(file, &tmpBuf) == 0) {
			/*
			 * if file is currently empty just write the given
			 * attributes to the file otherwise exact the attributes
			 * from the file and modify them accordingly before
			 * writing them back to the file
			 */
			if (tmpBuf.st_size == 0) {
				newAttrs = (papi_attribute_t **)attrs;

				fd = fopen(file, "w");
				if (fd != NULL) {
					papiAttributeListPrint(fd,
							newAttrs, "");
					fclose(fd);
				} else {
					result = PAPI_NOT_POSSIBLE;
				}
			} else {
				result =
				    psm_modifyAttrsList(file, attrs, &newAttrs);

				fd = fopen(file, "w");
				if (fd != NULL) {
					papiAttributeListPrint(fd,
								newAttrs, "");
					fclose(fd);
				} else {
					result = PAPI_NOT_POSSIBLE;
				}

				papiAttributeListFree(newAttrs);
			}
		} else {
			result = PAPI_NOT_POSSIBLE;
		}
	}

	return (result);
} /* psm_modifyAttrsFile */


/*
 * *****************************************************************************
 *
 * Description: Extracts the attributes in the given attribute 'file' and
 *              creates a new list 'newAttrs' containing the modified list of
 *              attributes.
 *
 * Parameters:  file  - pathname of file containing attributes to be modified
 *              attrs - list of attributes and their values to modify
 *              newAttrs - returns the modified list of attributes
 *
 * *****************************************************************************
 */

static papi_status_t
psm_modifyAttrsList(char *file, papi_attribute_t **attrs,
    papi_attribute_t ***newAttrs)

{
	papi_status_t result = PAPI_OK;
	papi_attribute_t  *nextAttr = NULL;
	papi_attribute_value_t  **values = NULL;
	void *iter = NULL;
	FILE *fd = NULL;
	register int fD = 0;
	char aBuff[200];
	char *a = NULL;
	char *p = NULL;
	int count = 0;
	int n = 0;

	fd = fopen(file, "r");
	if (fd != NULL) {
		fD = fileno(fd);
		a = &aBuff[0];
		p = &aBuff[0];
		count = read(fD, &aBuff[0], sizeof (aBuff) - 1);
		while ((result == PAPI_OK) && (count > 0)) {
			aBuff[count+n] = '\0';
			if (count == sizeof (aBuff) - n - 1) {
				p = strrchr(aBuff, '\n');
				if (p != NULL) {
					/* terminate at last complete line */
					*p = '\0';
				}
			}
			result = papiAttributeListFromString(
				newAttrs, PAPI_ATTR_EXCL, aBuff);

			if (result == PAPI_OK) {
				/*
				 * handle any part lines and then read the next
				 * buffer from the file
				 */
				n = 0;
				if (p != a) {
					p++; /* skip NL */
					n = sizeof (aBuff) - 1 - (p - a);
					strncpy(aBuff, p, n);
				}
				count = read(fD, &aBuff[n],
					sizeof (aBuff) - n - 1);
				p = &aBuff[0];
			}
		}
		fclose(fd);
	}

	/* now modify the attribute list with the new attributes in 'attrs' */

	nextAttr = papiAttributeListGetNext((papi_attribute_t **)attrs, &iter);
	while ((result == PAPI_OK) && (nextAttr != NULL)) {
		values = nextAttr->values;

		if ((values != NULL) && (*values != NULL)) {
			result = papiAttributeListAddValue(newAttrs,
						    PAPI_ATTR_REPLACE,
						    nextAttr->name,
						    nextAttr->type, *values);
			values++;
		}

		while ((result == PAPI_OK) &&
			(values != NULL) && (*values != NULL)) {
			result = papiAttributeListAddValue(newAttrs,
						    PAPI_ATTR_APPEND,
						    nextAttr->name,
						    nextAttr->type, *values);
			values++;
		}
		nextAttr =
		    papiAttributeListGetNext((papi_attribute_t **)attrs, &iter);
	}

	return (result);
} /* papi_modifyAttrsList() */
#endif


papi_status_t
papiJobSubmit(papi_service_t handle, char *printer,
		papi_attribute_t **job_attributes,
		papi_job_ticket_t *job_ticket,
		char **files, papi_job_t *job)
{
	papi_status_t status;
	service_t *svc = handle;
	struct stat statbuf;
	job_t *j;
	int file_no;
	char *request_id = NULL;
	REQUEST *request;
	int i;
	char *c;
	char *tmp = NULL;
	char lpfile[BUFSIZ];

	if ((svc == NULL) || (printer == NULL) || (files == NULL) ||
	    (job == NULL))
		return (PAPI_BAD_ARGUMENT);

	if (job_ticket != NULL)
		return (PAPI_OPERATION_NOT_SUPPORTED);

	if (files != NULL)
		for (file_no = 0; files[file_no] != NULL; file_no++) {
			if (access(files[file_no], R_OK) < 0) {
				detailed_error(svc,
				    gettext("Cannot access file: %s: %s"),
				    files[file_no], strerror(errno));
				return (PAPI_BAD_ARGUMENT);
			}
			if (stat(files[file_no], &statbuf) < 0) {
				detailed_error(svc,
				    gettext("Cannot access file: %s: %s"),
				    files[file_no], strerror(errno));
				return (PAPI_DOCUMENT_ACCESS_ERROR);
			}
			if (statbuf.st_size == 0) {
				detailed_error(svc,
				    gettext("Zero byte (empty) file: %s"),
				    files[file_no]);
				return (PAPI_BAD_ARGUMENT);
			}
		}

	if ((*job = j = calloc(1, sizeof (*j))) == NULL)
		return (PAPI_TEMPORARY_ERROR);

	/* file_no + 1 for the control file (-0) */
	status = lpsched_alloc_files(svc, file_no + 1, &request_id);
	if (status != PAPI_OK)
		return (status);

	request = create_request(svc, (char *)printer,
	    (papi_attribute_t **)job_attributes);

	for (i = 0; files[i] != NULL; i++) {
		papi_status_t status;
		snprintf(lpfile, sizeof (lpfile), "%s%s-%d",
		    "/var/spool/lp/temp/", request_id, i+1);
		status = copy_file(files[i], lpfile);
		if (status != PAPI_OK) {
			detailed_error(svc,
			    gettext("unable to copy: %s -> %s: %s"),
			    files[i], lpfile, strerror(errno));
				freerequest(request);
			return (PAPI_DEVICE_ERROR);
		}
		addlist(&(request->file_list), lpfile);
	}

#ifdef LP_USE_PAPI_ATTR
	/*
	 * store the job attributes in the PAPI job attribute file that was
	 * created by lpsched_alloc_files(), the attributes will then pass
	 * through lpsched and be given to the slow-filters and the printer's
	 * interface script to process them
	 */
	snprintf(lpfile, sizeof (lpfile), "%s%s-%s",
	    "/var/spool/lp/temp/", request_id, LP_PAPIATTRNAME);
	status = psm_copy_attrsToFile(job_attributes, lpfile);
	if (status != PAPI_OK) {
		detailed_error(svc, "unable to copy attributes to file: %s: %s",
		    lpfile, strerror(errno));
		return (PAPI_DEVICE_ERROR);
	}
#endif

	/* store the meta-data file */
	snprintf(lpfile, sizeof (lpfile), "%s-0", request_id);
	if (putrequest(lpfile, request) < 0) {
		detailed_error(svc, gettext("unable to save request: %s: %s"),
		    lpfile, strerror(errno));
		freerequest(request);
		return (PAPI_DEVICE_ERROR);
	}

	status = lpsched_commit_job(svc, lpfile, &tmp);
	if (status != PAPI_OK) {
		unlink(lpfile);
		freerequest(request);
		return (status);
	}

	lpsched_request_to_job_attributes(request, j);
	freerequest(request);

	if ((c = strrchr(tmp, '-')) != NULL)
		c++;
	papiAttributeListAddInteger(&j->attributes, PAPI_ATTR_REPLACE,
	    "job-id", atoi(c));
	papiAttributeListAddString(&j->attributes, PAPI_ATTR_REPLACE,
	    "job-uri", tmp);

	return (PAPI_OK);
}

papi_status_t
papiJobSubmitByReference(papi_service_t handle, char *printer,
		papi_attribute_t **job_attributes,
		papi_job_ticket_t *job_ticket,
		char **files, papi_job_t *job)
{
	service_t *svc = handle;
	struct stat statbuf;
	job_t *j;
	int file_no;
	short status;
	char *request_id = NULL;
	REQUEST *request;
	char *c;
	char *tmp = NULL;
	char lpfile[BUFSIZ];
	char **file_list = NULL;

	if ((svc == NULL) || (printer == NULL) || (files == NULL) ||
	    (job == NULL))
		return (PAPI_BAD_ARGUMENT);

	if (job_ticket != NULL)
		return (PAPI_OPERATION_NOT_SUPPORTED);

	if (files != NULL)
		for (file_no = 0; files[file_no] != NULL; file_no++) {
			if (access(files[file_no], R_OK) < 0) {
				detailed_error(svc,
				    gettext("Cannot access file: %s: %s"),
				    files[file_no], strerror(errno));
				return (PAPI_DOCUMENT_ACCESS_ERROR);
			}
			if (stat(files[file_no], &statbuf) < 0) {
				detailed_error(svc,
				    gettext("Cannot access file: %s: %s"),
				    files[file_no], strerror(errno));
				return (PAPI_DOCUMENT_ACCESS_ERROR);
			}
			if (statbuf.st_size == 0) {
				detailed_error(svc,
				    gettext("Zero byte (empty) file: %s"),
				    files[file_no]);
				return (PAPI_BAD_ARGUMENT);
			}

			if (files[file_no][0] != '/') {
				char path[MAXPATHLEN];

				if (getcwd(path, sizeof (path)) == NULL) {
					detailed_error(svc, gettext(
					    "getcwd for file: %s: %s"),
					    files[file_no],
					    strerror(errno));
					return (PAPI_DOCUMENT_ACCESS_ERROR);
				}
				strlcat(path, "/", sizeof (path));
				if (strlcat(path, files[file_no], sizeof (path))
				    >= sizeof (path)) {
					detailed_error(svc, gettext(
					    "pathname too long: %s"),
					    files[file_no]);
					return (PAPI_DOCUMENT_ACCESS_ERROR);
				}
				addlist(&file_list, path);
			} else
				addlist(&file_list, (char *)files[file_no]);
		}

	if ((*job = j = calloc(1, sizeof (*j))) == NULL)
		return (PAPI_TEMPORARY_ERROR);

	/* 1 for the control file (-0) */
	status = lpsched_alloc_files(svc, 1, &request_id);
	if (status != PAPI_OK)
		return (status);

	request = create_request(svc, (char *)printer,
	    (papi_attribute_t **)job_attributes);
	request->file_list = file_list;

#ifdef LP_USE_PAPI_ATTR
	/*
	 * store the job attributes in the PAPI job attribute file that was
	 * created by lpsched_alloc_files(), the attributes will then pass
	 * through lpsched and be given to the slow-filters and the printer's
	 * interface script to process them
	 */
	snprintf(lpfile, sizeof (lpfile), "%s%s-%s",
	    "/var/spool/lp/temp/", request_id, LP_PAPIATTRNAME);
	status = psm_copy_attrsToFile(job_attributes, lpfile);
	if (status != PAPI_OK) {
		detailed_error(svc, "unable to copy attributes to file: %s: %s",
		    lpfile, strerror(errno));
		return (PAPI_DEVICE_ERROR);
	}
#endif

	/* store the meta-data file */
	snprintf(lpfile, sizeof (lpfile), "%s-0", request_id);
	if (putrequest(lpfile, request) < 0) {
		detailed_error(svc, gettext("unable to save request: %s: %s"),
		    lpfile, strerror(errno));
		freerequest(request);
		return (PAPI_DEVICE_ERROR);
	}

	status = lpsched_commit_job(svc, lpfile, &tmp);
	if (status != PAPI_OK) {
		unlink(lpfile);
		freerequest(request);
		return (status);
	}

	lpsched_request_to_job_attributes(request, j);

	freerequest(request);

	if ((c = strrchr(tmp, '-')) != NULL)
		c++;
	papiAttributeListAddInteger(&j->attributes, PAPI_ATTR_REPLACE,
	    "job-id", atoi(c));
	papiAttributeListAddString(&j->attributes, PAPI_ATTR_REPLACE,
	    "job-uri", tmp);

	return (PAPI_OK);
}

papi_status_t
papiJobValidate(papi_service_t handle, char *printer,
		papi_attribute_t **job_attributes,
		papi_job_ticket_t *job_ticket,
		char **files, papi_job_t *job)
{
	papi_status_t status;
	papi_attribute_t **attributes = NULL;
	int i;

	papiAttributeListAddString(&attributes, PAPI_ATTR_REPLACE,
	    "job-hold-until", "indefinite");
	for (i = 0; job_attributes[i]; i++)
		list_append(&attributes, job_attributes[i]);

	status = papiJobSubmitByReference(handle, printer,
	    (papi_attribute_t **)attributes,
	    job_ticket, files, job);
	if (status == PAPI_OK) {
		int id = papiJobGetId(*job);

		if (id != -1)
			papiJobCancel(handle, printer, id);
	}

	attributes[1] = NULL;	/* after attr[0], they are in another list */
	papiAttributeListFree(attributes);

	return (status);
}

papi_status_t
papiJobStreamOpen(papi_service_t handle, char *printer,
		papi_attribute_t **job_attributes,
		papi_job_ticket_t *job_ticket, papi_stream_t *stream)
{
	papi_status_t status;
	service_t *svc = handle;
	job_stream_t *s = NULL;
	char *request_id = NULL;
	char lpfile[BUFSIZ];

	if ((svc == NULL) || (printer == NULL) || (stream == NULL))
		return (PAPI_BAD_ARGUMENT);

	if (job_ticket != NULL)
		return (PAPI_OPERATION_NOT_SUPPORTED);

	if ((*stream = s = calloc(1, sizeof (*s))) == NULL)
		return (PAPI_TEMPORARY_ERROR);

	/* 1 for data, 1 for the meta-data (-0) */
	status = lpsched_alloc_files(svc, 2, &request_id);
	if (status != PAPI_OK)
		return (status);

	s->request = create_request(svc, (char *)printer,
	    (papi_attribute_t **)job_attributes);
	snprintf(lpfile, sizeof (lpfile), "/var/spool/lp/temp/%s-1",
	    request_id);
	s->fd = open(lpfile, O_WRONLY);
	addlist(&(s->request->file_list), lpfile);

#ifdef LP_USE_PAPI_ATTR
	/*
	 * store the job attributes in the PAPI job attribute file that was
	 * created by lpsched_alloc_files(), the attributes will then pass
	 * through lpsched and be given to the slow-filters and the printer's
	 * interface script to process them
	 */
	snprintf(lpfile, sizeof (lpfile), "%s%s-%s",
	    "/var/spool/lp/temp/", request_id, LP_PAPIATTRNAME);
	status = psm_copy_attrsToFile(job_attributes, lpfile);
	if (status != PAPI_OK) {
		detailed_error(svc, "unable to copy attributes to file: %s: %s",
		    lpfile, strerror(errno));
		close(s->fd);
		free(s);
		return (PAPI_DEVICE_ERROR);
	}
#endif

	/* store the meta-data file */
	snprintf(lpfile, sizeof (lpfile), "%s-0", request_id);
	s->meta_data_file = strdup(lpfile);
	if (putrequest(lpfile, s->request) < 0) {
		detailed_error(svc, gettext("unable to save request: %s: %s"),
		    lpfile, strerror(errno));
		s->request = NULL;
		return (PAPI_DEVICE_ERROR);
	}

	return (PAPI_OK);
}

papi_status_t
papiJobStreamWrite(papi_service_t handle,
		papi_stream_t stream, void *buffer, size_t buflen)
{
	service_t *svc = handle;
	job_stream_t *s = stream;

	if ((svc == NULL) || (stream == NULL) || (buffer == NULL))
		return (PAPI_BAD_ARGUMENT);

	if (write(s->fd, buffer, buflen) != buflen)
		return (PAPI_DEVICE_ERROR);

	return (PAPI_OK);
}
papi_status_t
papiJobStreamClose(papi_service_t handle,
		papi_stream_t stream, papi_job_t *job)
{
	papi_status_t status = PAPI_OK;
	service_t *svc = handle;
	job_stream_t *s = stream;
	job_t *j = NULL;
	char *tmp = NULL, *c;

	if ((svc == NULL) || (stream == NULL) || (job == NULL))
		return (PAPI_BAD_ARGUMENT);

	if ((*job = j = calloc(1, sizeof (*j))) == NULL)
		return (PAPI_TEMPORARY_ERROR);

	close(s->fd);

	lpsched_request_to_job_attributes(s->request, j);

	if (s->meta_data_file != NULL) {
		status = lpsched_commit_job(svc, s->meta_data_file, &tmp);
		if (status != PAPI_OK) {
			unlink(s->meta_data_file);
			return (status);
		}
		if ((c = strrchr(tmp, '-')) != NULL)
			c++;
		papiAttributeListAddInteger(&j->attributes, PAPI_ATTR_REPLACE,
		    "job-id", atoi(c));
		papiAttributeListAddString(&j->attributes, PAPI_ATTR_REPLACE,
		    "job-uri", tmp);
		free(s->meta_data_file);
	}
	freerequest(s->request);
	free(s);

	return (PAPI_OK);
}

papi_status_t
papiJobQuery(papi_service_t handle, char *printer, int32_t job_id,
		char **requested_attrs,
		papi_job_t *job)
{
	service_t *svc = handle;
	job_t *j;
	char *dest;
	char req_id[32];
	short rc;
	char *form = NULL,
	    *request_id = NULL,
	    *charset = NULL,
	    *user = NULL,
	    *slabel = NULL,
	    *file = NULL;
	time_t date = 0;
	size_t size = 0;
	short  rank = 0,
	    state = 0;

	if ((handle == NULL) || (printer == NULL) || (job_id < 0))
		return (PAPI_BAD_ARGUMENT);

	dest = printer_name_from_uri_id(printer, job_id);
	snprintf(req_id, sizeof (req_id), "%s-%d", dest, job_id);
	free(dest);

	rc = snd_msg(svc, S_INQUIRE_REQUEST_RANK, 0, "", "", req_id, "", "");
	if (rc < 0)
		return (PAPI_SERVICE_UNAVAILABLE);

	if (rcv_msg(svc, R_INQUIRE_REQUEST_RANK, &rc, &request_id,
	    &user, &slabel, &size, &date, &state, &dest, &form,
	    &charset, &rank, &file) < 0) {
		detailed_error(svc,
		    gettext("failed to read response from scheduler"));
		return (PAPI_DEVICE_ERROR);
	}

	if ((request_id == NULL) || (request_id[0] == NULL))
		return (PAPI_NOT_FOUND);

	if ((*job = j = calloc(1, sizeof (*j))) == NULL)
		return (PAPI_TEMPORARY_ERROR);

	snprintf(req_id, sizeof (req_id), "%d-0", job_id);
	lpsched_read_job_configuration(svc, j, req_id);

	job_status_to_attributes(j, request_id, user, slabel, size, date, state,
	    dest, form, charset, rank, file);

	return (PAPI_OK);
}

papi_status_t
papiJobMove(papi_service_t handle, char *printer, int32_t job_id,
		char *destination)
{
	papi_status_t result = PAPI_OK;
	long bits;
	service_t *svc = handle;
	char req_id[64];
	char *queue;
	char *user = NULL;

	if ((svc == NULL) || (printer == NULL) || (job_id < 0) ||
	    (destination == NULL))
		return (PAPI_BAD_ARGUMENT);

	queue = printer_name_from_uri_id(printer, job_id);
	snprintf(req_id, sizeof (req_id), "%s-%d", queue, job_id);
	free(queue);

	if (papiAttributeListGetString(svc->attributes, NULL, "user-name",
	    &user) == PAPI_OK) {
		REQUEST *r = getrequest(req_id);

		if ((r != NULL) && (r->user != NULL) &&
		    (strcmp(r->user, user) != 0))
			result = PAPI_NOT_AUTHORIZED;
		freerequest(r);
	}

	if (result == PAPI_OK) {
		short status = MOK;
		char *dest = printer_name_from_uri_id(destination, -1);

		if ((snd_msg(svc, S_MOVE_REQUEST, req_id, dest) < 0) ||
		    (rcv_msg(svc, R_MOVE_REQUEST, &status, &bits) < 0))
			status = MTRANSMITERR;

		free(dest);

		result = lpsched_status_to_papi_status(status);
	}

	return (result);
}

papi_status_t
papiJobCancel(papi_service_t handle, char *printer, int32_t job_id)
{
	papi_status_t result = PAPI_OK;
	service_t *svc = handle;
	char req_id[64];
	char *dest;
	char *user = NULL;

	if ((svc == NULL) || (printer == NULL) || (job_id < 0))
		return (PAPI_BAD_ARGUMENT);

	dest = printer_name_from_uri_id(printer, job_id);
	snprintf(req_id, sizeof (req_id), "%s-%d", dest, job_id);
	free(dest);

	if (papiAttributeListGetString(svc->attributes, NULL, "user-name",
	    &user) == PAPI_OK) {
		REQUEST *r = getrequest(req_id);

		if ((result = authorized(handle, job_id)) != PAPI_OK)
			result = PAPI_NOT_AUTHORIZED;

		if ((r != NULL) && (r->user != NULL) &&
		    (strcmp(r->user, user) != 0))
			result = PAPI_NOT_AUTHORIZED;
		freerequest(r);
	}

	if (result == PAPI_OK) {
		short status = MOK;

		if ((snd_msg(svc, S_CANCEL_REQUEST, req_id) < 0) ||
		    (rcv_msg(svc, R_CANCEL_REQUEST, &status) < 0))
			status = MTRANSMITERR;

		result = lpsched_status_to_papi_status(status);
	}

	return (result);
}

papi_status_t
hold_release_job(papi_service_t handle, char *printer,
		int32_t job_id, int flag)
{
	papi_status_t status;
	service_t *svc = handle;
	REQUEST *r = NULL;
	char *file;
	char *dest;

	if ((svc == NULL) || (printer == NULL) || (job_id < 0))
		return (PAPI_BAD_ARGUMENT);

	if ((status = authorized(svc, job_id)) != PAPI_OK)
		return (status);

	dest = printer_name_from_uri_id(printer, job_id);
	status = lpsched_start_change(svc, dest, job_id, &file);
	if (status != PAPI_OK)
		return (status);

	if ((r = getrequest(file)) != NULL) {
		r->actions &= ~ACT_RESUME;
		switch (flag) {
		case 0:
			r->actions |= ACT_HOLD;
			break;
		case 1:
			r->actions |= ACT_RESUME;
			break;
		case 2:
			r->actions |= ACT_IMMEDIATE;
			break;
		}
		if (putrequest(file, r) < 0) {
			detailed_error(svc,
			    gettext("failed to write job: %s: %s"),
			    file, strerror(errno));
			freerequest(r);
			return (PAPI_DEVICE_ERROR);
		}
		freerequest(r);
	} else {
		detailed_error(svc, gettext("failed to read job: %s: %s"),
		    file, strerror(errno));
		return (PAPI_DEVICE_ERROR);
	}

	status = lpsched_end_change(svc, dest, job_id);

	return (status);
}

papi_status_t
papiJobHold(papi_service_t handle, char *printer, int32_t job_id)
{
	return (hold_release_job(handle, printer, job_id, 0));
}

papi_status_t
papiJobRelease(papi_service_t handle, char *printer, int32_t job_id)
{
	return (hold_release_job(handle, printer, job_id, 1));
}

papi_status_t
papiJobPromote(papi_service_t handle, char *printer, int32_t job_id)
{
	return (hold_release_job(handle, printer, job_id, 2));
}

papi_status_t
papiJobModify(papi_service_t handle, char *printer, int32_t job_id,
		papi_attribute_t **attributes, papi_job_t *job)
{
	papi_status_t status;
	job_t *j = NULL;
	service_t *svc = handle;
	char *file = NULL;
	char *dest;
	REQUEST *r = NULL;
	char lpfile[BUFSIZ];

	if ((svc == NULL) || (printer == NULL) || (job_id < 0) ||
	    (attributes == NULL))
		return (PAPI_BAD_ARGUMENT);

	if ((*job = j = calloc(1, sizeof (*j))) == NULL)
		return (PAPI_TEMPORARY_ERROR);

	dest = printer_name_from_uri_id(printer, job_id);
	status = lpsched_start_change(svc, dest, job_id, &file);
	if (status != PAPI_OK)
		return (status);

	if ((r = getrequest(file)) != NULL) {
		job_attributes_to_lpsched_request(handle, r,
		    (papi_attribute_t **)attributes);
#ifdef LP_USE_PAPI_ATTR
		/*
		 * store the job attributes in the PAPI job attribute file
		 * that was created by the origonal job request. We need to
		 * modify the attributes in the file as per the new attributes
		 */
		snprintf(lpfile, sizeof (lpfile), "%s%d-%s",
		    "/var/spool/lp/temp/", job_id, LP_PAPIATTRNAME);
		status = psm_modifyAttrsFile(attributes, lpfile);
		if (status != PAPI_OK) {
			detailed_error(svc,
			    "unable to modify the attributes file: %s: %s",
			    lpfile, strerror(errno));
			return (PAPI_DEVICE_ERROR);
		}
#endif

		if (putrequest(file, r) < 0) {
			detailed_error(svc,
			    gettext("failed to write job: %s: %s"),
			    file, strerror(errno));
			freerequest(r);
			return (PAPI_DEVICE_ERROR);
		}
	} else {
		detailed_error(svc, gettext("failed to read job: %s: %s"),
		    file, strerror(errno));
		return (PAPI_DEVICE_ERROR);
	}

	status = lpsched_end_change(svc, dest, job_id);
	lpsched_request_to_job_attributes(r, j);

	papiAttributeListAddInteger(&j->attributes, PAPI_ATTR_REPLACE,
	    "job-id", job_id);

	freerequest(r);

	return (status);
}

/*
 * Extension to PAPI, a variation of this is slated for post-1.0
 */
#define	DUMMY_FILE	"/var/spool/lp/fifos/FIFO"

papi_status_t
papiJobCreate(papi_service_t handle, char *printer,
		papi_attribute_t **job_attributes,
		papi_job_ticket_t *job_ticket, papi_job_t *job)
{
	papi_status_t status;
	service_t *svc = handle;
	job_t *j = NULL;
	REQUEST *request;
	char *request_id = NULL;
	char *c;
	char *tmp = NULL;
	char metadata_file[MAXPATHLEN];

	if ((svc == NULL) || (printer == NULL) || (job == NULL))
		return (PAPI_BAD_ARGUMENT);

	if (job_ticket != NULL)
		return (PAPI_JOB_TICKET_NOT_SUPPORTED);

	if ((*job = j = calloc(1, sizeof (*j))) == NULL)
		return (PAPI_TEMPORARY_ERROR);

	/* 1 for the control file (-0) */
	status = lpsched_alloc_files(svc, 1, &request_id);
	if (status != PAPI_OK)
		return (status);

	/* convert the attributes to an lpsched REQUEST structure */
	request = create_request(svc, (char *)printer,
	    (papi_attribute_t **)job_attributes);
	if (request == NULL)
		return (PAPI_TEMPORARY_ERROR);
	addlist(&request->file_list, DUMMY_FILE);	/* add a dummy file */
	request->actions |= ACT_HOLD;			/* hold the job */

#ifdef LP_USE_PAPI_ATTR
	/*
	 * store the job attributes in the PAPI job attribute file that was
	 * created by lpsched_alloc_files(), the attributes will then pass
	 * through lpsched and be given to the slow-filters and the printer's
	 * interface script to process them
	 */
	snprintf(metadata_file, sizeof (metadata_file), "%s%s-%s",
	    "/var/spool/lp/temp/", request_id, LP_PAPIATTRNAME);
	status = psm_copy_attrsToFile(job_attributes, metadata_file);
	if (status != PAPI_OK) {
		detailed_error(svc, "unable to copy attributes to file: %s: %s",
		    metadata_file, strerror(errno));
		free(request_id);
		return (PAPI_DEVICE_ERROR);
	}
#endif

	/* store the REQUEST on disk */
	snprintf(metadata_file, sizeof (metadata_file), "%s-0", request_id);
	free(request_id);
	if (putrequest(metadata_file, request) < 0) {
		detailed_error(svc, gettext("unable to save request: %s: %s"),
		    metadata_file, strerror(errno));
		return (PAPI_DEVICE_ERROR);
	}

	status = lpsched_commit_job(svc, metadata_file, &tmp);
	if (status != PAPI_OK) {
		unlink(metadata_file);
		return (status);
	}

	lpsched_request_to_job_attributes(request, j);

	if ((c = strrchr(tmp, '-')) != NULL)
		c++;
	papiAttributeListAddInteger(&j->attributes, PAPI_ATTR_REPLACE,
	    "job-id", atoi(c));
	papiAttributeListAddString(&j->attributes, PAPI_ATTR_REPLACE,
	    "job-uri", tmp);

	return (PAPI_OK);
}

papi_status_t
papiJobCommit(papi_service_t handle, char *printer, int32_t id)
{
	papi_status_t status = PAPI_OK;
	service_t *svc = handle;
	REQUEST *r = NULL;
	char *metadata_file;
	char *dest;

	if ((svc == NULL) || (printer == NULL))
		return (PAPI_BAD_ARGUMENT);

	dest = printer_name_from_uri_id(printer, id);
	/* tell the scheduler that we want to change the job */
	status = lpsched_start_change(svc, dest, id, &metadata_file);
	if (status != PAPI_OK)
		return (status);

	if ((r = getrequest(metadata_file)) != NULL) {
		r->actions &= ~ACT_RESUME;
		r->actions |= ACT_RESUME;
		dellist(&r->file_list, DUMMY_FILE);

		if (putrequest(metadata_file, r) < 0) {
			detailed_error(svc,
			    gettext("failed to write job: %s: %s"),
			    metadata_file, strerror(errno));
			freerequest(r);
			return (PAPI_DEVICE_ERROR);
		}
	} else {
		detailed_error(svc, gettext("failed to read job: %s: %s"),
		    metadata_file, strerror(errno));
		return (PAPI_DEVICE_ERROR);
	}

	status = lpsched_end_change(svc, dest, id);
	freerequest(r);

	return (status);
}

papi_status_t
papiJobStreamAdd(papi_service_t handle, char *printer, int32_t id,
		papi_stream_t *stream)
{
	papi_status_t status;
	service_t *svc = handle;
	job_stream_t *s = NULL;
	char *metadata_file = NULL;
	char *dest;
	char path[MAXPATHLEN];

	/* allocate space for the stream */
	if ((*stream = s = calloc(1, sizeof (*s))) == NULL)
		return (PAPI_TEMPORARY_ERROR);

	dest = printer_name_from_uri_id(printer, id);
	/* create/open data file (only root or lp can really do this */
	snprintf(path, sizeof (path), "/var/spool/lp/temp/%d-XXXXXX", id);
	if ((s->fd = mkstemp(path)) < 0) {
		detailed_error(svc, gettext("unable to create sink (%s): %s"),
		    path, strerror(errno));
		free(s);
		return (PAPI_NOT_AUTHORIZED);
	}

	/* add data file to job */
	status = lpsched_start_change(svc, dest, id, &metadata_file);
	if (status != PAPI_OK) {
		close(s->fd);
		free(s);
		unlink(path);
		return (status);
	}

	if ((s->request = getrequest(metadata_file)) == NULL) {
		detailed_error(svc, gettext("unable to load request: %s: %s"),
		    metadata_file, strerror(errno));
		close(s->fd);
		free(s);
		unlink(path);
		return (PAPI_NOT_POSSIBLE);
	}

	addlist(&(s->request->file_list), path);

	if (putrequest(metadata_file, s->request) < 0) {
		detailed_error(svc, gettext("unable to save request: %s: %s"),
		    metadata_file, strerror(errno));
		close(s->fd);
		free(s);
		unlink(path);
		return (PAPI_NOT_POSSIBLE);
	}

	status = lpsched_end_change(svc, dest, id);

	if (status != PAPI_OK)
		return (status);

	return (PAPI_OK);
}