1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 *
26 */
27
28 /* $Id: job.c 148 2006-04-25 16:54:17Z njacobs $ */
29
30
31 /*LINTLIBRARY*/
32
33 #include <stdlib.h>
34 #include <errno.h>
35 #include <string.h>
36 #include <papi_impl.h>
37 #include <fcntl.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <libintl.h>
41
42 #ifndef OPID_CUPS_MOVE_JOB
43 #define OPID_CUPS_MOVE_JOB 0x400D
44 #endif
45
46 void
papiJobFree(papi_job_t job)47 papiJobFree(papi_job_t job)
48 {
49 job_t *tmp = (job_t *)job;
50
51 if (tmp != NULL) {
52 if (tmp->attributes != NULL)
53 papiAttributeListFree(tmp->attributes);
54 free(tmp);
55 }
56 }
57
58 void
papiJobListFree(papi_job_t * jobs)59 papiJobListFree(papi_job_t *jobs)
60 {
61 if (jobs != NULL) {
62 int i;
63
64 for (i = 0; jobs[i] != NULL; i++)
65 papiJobFree(jobs[i]);
66 free(jobs);
67 }
68 }
69
70 papi_attribute_t **
papiJobGetAttributeList(papi_job_t job)71 papiJobGetAttributeList(papi_job_t job)
72 {
73 papi_attribute_t **result = NULL;
74 job_t *j = job;
75
76 if (j != NULL)
77 result = j->attributes;
78
79 return (result);
80 }
81
82 char *
papiJobGetPrinterName(papi_job_t job)83 papiJobGetPrinterName(papi_job_t job)
84 {
85 char *result = NULL;
86 job_t *j = job;
87
88 if (j != NULL)
89 (void) papiAttributeListGetString(j->attributes, NULL,
90 "printer-name", &result);
91
92 return (result);
93 }
94
95 int32_t
papiJobGetId(papi_job_t job)96 papiJobGetId(papi_job_t job)
97 {
98 int32_t result = -1;
99 job_t *j = job;
100
101 if (j != NULL)
102 (void) papiAttributeListGetInteger(j->attributes, NULL,
103 "job-id", &result);
104
105 return (result);
106 }
107
108 papi_job_ticket_t *
papiJobGetJobTicket(papi_job_t job)109 papiJobGetJobTicket(papi_job_t job)
110 {
111 papi_job_ticket_t *result = NULL;
112
113 return (result);
114 }
115
116 static void
populate_job_request(service_t * svc,papi_attribute_t *** request,papi_attribute_t ** attributes,char * printer,uint16_t type)117 populate_job_request(service_t *svc, papi_attribute_t ***request,
118 papi_attribute_t **attributes, char *printer, uint16_t type)
119 {
120 papi_attribute_t **operational = NULL, **job = NULL;
121 static char *operational_names[] = {
122 "job-name", "ipp-attribute-fidelity", "document-name",
123 "compression", "document-format", "document-natural-language",
124 "job-k-octets", "job-impressions", "job-media-sheets", NULL
125 };
126
127 /* create the base IPP request */
128 ipp_initialize_request(svc, request, type);
129
130 /* create an operational attributes group */
131 ipp_initialize_operational_attributes(svc, &operational, printer, -1);
132
133 /* split up the attributes into operational and job attributes */
134 split_and_copy_attributes(operational_names, attributes,
135 &operational, &job);
136
137 /* add the operational attributes group to the request */
138 papiAttributeListAddCollection(request, PAPI_ATTR_REPLACE,
139 "operational-attributes-group", operational);
140 papiAttributeListFree(operational);
141
142 /* add the job attributes group to the request */
143 if (job != NULL) {
144 /*
145 * Add job-originating-host-name to attributes
146 * if not already set.
147 */
148 char *hostname = NULL;
149
150 papiAttributeListGetString(job, NULL,
151 "job-originating-host-name", &hostname);
152
153 if (hostname == NULL) {
154 char host[BUFSIZ];
155
156 if (gethostname(host, sizeof (host)) == 0)
157 papiAttributeListAddString(&job, PAPI_ATTR_EXCL,
158 "job-originating-host-name", host);
159 }
160
161 papiAttributeListAddCollection(request, PAPI_ATTR_REPLACE,
162 "job-attributes-group", job);
163 papiAttributeListFree(job);
164 }
165 }
166
167 static papi_status_t
send_document_uri(service_t * svc,char * file,papi_attribute_t ** attributes,char * printer,int32_t id,char last,uint16_t type)168 send_document_uri(service_t *svc, char *file, papi_attribute_t **attributes,
169 char *printer, int32_t id, char last, uint16_t type)
170 {
171 papi_status_t result = PAPI_INTERNAL_ERROR;
172 papi_attribute_t **request = NULL, **op = NULL, **response = NULL;
173
174 /* create the base IPP request */
175 ipp_initialize_request(svc, &request, type);
176
177 /* create an operational attributes group */
178 ipp_initialize_operational_attributes(svc, &op, printer, id);
179
180 papiAttributeListAddString(&op, PAPI_ATTR_REPLACE, "document-name",
181 file);
182 papiAttributeListAddBoolean(&op, PAPI_ATTR_REPLACE, "last-document",
183 (last ? PAPI_TRUE : PAPI_FALSE));
184 papiAttributeListAddCollection(&request, PAPI_ATTR_REPLACE,
185 "operational-attributes-group", op);
186 papiAttributeListFree(op);
187
188 /* send the IPP request to the server */
189 result = ipp_send_request_with_file(svc, request, &response, file);
190 papiAttributeListFree(request);
191 papiAttributeListFree(response);
192
193 return (result);
194 }
195
196 typedef enum {_WITH_DATA, _BY_REFERENCE, _VALIDATE} call_type_t;
197
198 papi_status_t
internal_job_submit(papi_service_t handle,char * printer,papi_attribute_t ** job_attributes,papi_job_ticket_t * job_ticket,char ** files,papi_job_t * job,call_type_t call_type)199 internal_job_submit(papi_service_t handle, char *printer,
200 papi_attribute_t **job_attributes,
201 papi_job_ticket_t *job_ticket,
202 char **files, papi_job_t *job,
203 call_type_t call_type)
204 {
205 papi_status_t result = PAPI_INTERNAL_ERROR;
206 service_t *svc = handle;
207 struct stat statbuf;
208 job_t *j = NULL;
209 int i;
210 uint16_t req_type = OPID_PRINT_JOB;
211 uint16_t data_type = OPID_SEND_DOCUMENT;
212 papi_attribute_t **request = NULL, **response = NULL;
213
214 if ((svc == NULL) || (printer == NULL) || (job == NULL))
215 return (PAPI_BAD_ARGUMENT);
216
217 switch (call_type) {
218 case _BY_REFERENCE:
219 #ifdef SOME_DAY_WE_WILL_BE_ABLE_TO_USE_URIS_FOR_JOB_DATA
220 /*
221 * For the time being, this is disabled. There are a number
222 * of issues to be dealt with before we can send a URI
223 * across the network to the server. For example, the file
224 * name(s) passed in are most likely relative to the current
225 * hosts filesystem. They also most likely will require some
226 * form of authentication information to be passed with the
227 * URI.
228 */
229 req_type = OPID_PRINT_URI;
230 req_type = OPID_SEND_URI;
231 #endif
232 /* fall-through */
233 case _WITH_DATA:
234 if ((files == NULL) || (files[0] == NULL))
235 return (PAPI_BAD_ARGUMENT);
236
237 if (files[1] != NULL) /* more than 1 file */
238 req_type = OPID_CREATE_JOB;
239
240 break;
241 case _VALIDATE:
242 req_type = OPID_VALIDATE_JOB;
243 /* if we have files, validate access to them */
244 if (files != NULL) {
245 for (i = 0; files[i] != NULL; i++) {
246 if (access(files[i], R_OK) < 0) {
247 detailed_error(svc, "%s: %s", files[i],
248 strerror(errno));
249 return (PAPI_DOCUMENT_ACCESS_ERROR);
250 }
251
252 if (strcmp("standard input", files[i]) != 0) {
253 if (stat(files[i], &statbuf) < 0) {
254 detailed_error(svc, gettext(
255 "Cannot access file: %s:"
256 " %s"), files[i],
257 strerror(errno));
258 return (
259 PAPI_DOCUMENT_ACCESS_ERROR);
260 }
261 if (statbuf.st_size == 0) {
262 detailed_error(svc,
263 "Zero byte (empty) file: "
264 "%s",
265 files[i]);
266 return (PAPI_BAD_ARGUMENT);
267 }
268 }
269 }
270 files = NULL;
271 }
272 break;
273 }
274
275 /* if we are already connected, use that connection. */
276 if (svc->connection == NULL)
277 if ((result = service_connect(svc, printer)) != PAPI_OK)
278 return (result);
279
280 if ((*job = j = calloc(1, sizeof (*j))) == NULL)
281 return (PAPI_TEMPORARY_ERROR);
282
283 /*
284 * before creating IPP request
285 * add the job-name
286 */
287 if ((files != NULL) && (files[0] != NULL))
288 papiAttributeListAddString(&job_attributes, PAPI_ATTR_EXCL,
289 "job-name", files[0]);
290
291 /* create IPP request */
292 populate_job_request(svc, &request, job_attributes, printer, req_type);
293
294 switch (req_type) {
295 case OPID_PRINT_JOB:
296 result = ipp_send_request_with_file(svc, request, &response,
297 files[0]);
298 break;
299 case OPID_CREATE_JOB:
300 case OPID_VALIDATE_JOB:
301 case OPID_PRINT_URI:
302 result = ipp_send_request(svc, request, &response);
303 break;
304 }
305 papiAttributeListFree(request);
306
307 if (result == PAPI_OK) {
308 papi_attribute_t **op = NULL;
309
310 /* retrieve the job attributes */
311 papiAttributeListGetCollection(response, NULL,
312 "job-attributes-group", &op);
313 copy_attributes(&j->attributes, op);
314
315 if (req_type == OPID_CREATE_JOB) {
316 int32_t id = 0;
317
318 papiAttributeListGetInteger(j->attributes, NULL,
319 "job-id", &id);
320 /* send each document */
321 for (i = 0; ((result == PAPI_OK) && (files[i] != NULL));
322 i++)
323 result = send_document_uri(svc, files[i],
324 job_attributes,
325 printer, id, (files[i+1]?0:1),
326 data_type);
327 }
328 }
329 papiAttributeListFree(response);
330
331 return (result);
332 }
333
334 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)335 papiJobSubmit(papi_service_t handle, char *printer,
336 papi_attribute_t **job_attributes,
337 papi_job_ticket_t *job_ticket, char **files, papi_job_t *job)
338 {
339 return (internal_job_submit(handle, printer, job_attributes,
340 job_ticket, files, job, _WITH_DATA));
341 }
342
343 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)344 papiJobSubmitByReference(papi_service_t handle, char *printer,
345 papi_attribute_t **job_attributes,
346 papi_job_ticket_t *job_ticket, char **files, papi_job_t *job)
347 {
348 return (internal_job_submit(handle, printer, job_attributes,
349 job_ticket, files, job, _BY_REFERENCE));
350 }
351
352 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)353 papiJobValidate(papi_service_t handle, char *printer,
354 papi_attribute_t **job_attributes,
355 papi_job_ticket_t *job_ticket, char **files, papi_job_t *job)
356 {
357 return (internal_job_submit(handle, printer, job_attributes,
358 job_ticket, files, job, _VALIDATE));
359 }
360
361 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)362 papiJobStreamOpen(papi_service_t handle, char *printer,
363 papi_attribute_t **job_attributes,
364 papi_job_ticket_t *job_ticket, papi_stream_t *stream)
365 {
366 papi_status_t result = PAPI_INTERNAL_ERROR;
367 papi_attribute_t **request = NULL;
368 service_t *svc = handle;
369
370 if ((svc == NULL) || (printer == NULL) || (stream == NULL))
371 return (PAPI_BAD_ARGUMENT);
372
373 /* if we are already connected, use that connection. */
374 if (svc->connection == NULL)
375 if ((result = service_connect(svc, printer)) != PAPI_OK)
376 return (result);
377
378 papiAttributeListAddString(&job_attributes, PAPI_ATTR_EXCL,
379 "job-name", "standard input");
380
381 /* create job request */
382 populate_job_request(svc, &request, job_attributes, printer,
383 OPID_PRINT_JOB);
384
385 *stream = svc->connection;
386
387 result = ipp_send_initial_request_block(svc, request, 0);
388 papiAttributeListFree(request);
389
390 return (result);
391 }
392
393 papi_status_t
papiJobStreamWrite(papi_service_t handle,papi_stream_t stream,void * buffer,size_t buflen)394 papiJobStreamWrite(papi_service_t handle,
395 papi_stream_t stream, void *buffer, size_t buflen)
396 {
397 papi_status_t result = PAPI_OK;
398 service_t *svc = handle;
399 size_t rc;
400
401 #ifdef DEBUG
402 printf("papiJobStreamWrite(0x%8.8x, 0x%8.8x, 0x%8.8x, %d)\n",
403 handle, stream, buffer, buflen);
404 httpDumpData(stdout, "papiJobStreamWrite:", buffer, buflen);
405 #endif
406
407 if ((svc == NULL) || (stream == NULL) || (buffer == NULL) ||
408 (buflen == 0))
409 return (PAPI_BAD_ARGUMENT);
410
411 while ((result == PAPI_OK) && (buflen > 0)) {
412 rc = ipp_request_write(svc, buffer, buflen);
413 if (rc < 0)
414 result = PAPI_TEMPORARY_ERROR;
415 else {
416 buffer = (char *)buffer + rc;
417 buflen -= rc;
418 }
419 }
420
421 #ifdef DEBUG
422 printf("papiJobStreamWrite(): %s\n", papiStatusString(result));
423 #endif
424
425 return (result);
426 }
427
428 papi_status_t
papiJobStreamClose(papi_service_t handle,papi_stream_t stream,papi_job_t * job)429 papiJobStreamClose(papi_service_t handle,
430 papi_stream_t stream, papi_job_t *job)
431 {
432 papi_status_t result = PAPI_INTERNAL_ERROR;
433 http_status_t status = HTTP_CONTINUE;
434 service_t *svc = handle;
435 papi_attribute_t **response = NULL;
436 job_t *j = NULL;
437
438 if ((svc == NULL) || (stream == NULL) || (job == NULL))
439 return (PAPI_BAD_ARGUMENT);
440
441 if ((*job = j = calloc(1, sizeof (*j))) == NULL)
442 return (PAPI_TEMPORARY_ERROR);
443
444 (void) ipp_request_write(svc, "", 0);
445
446 /* update our connection info */
447 while (status == HTTP_CONTINUE)
448 status = httpUpdate(svc->connection);
449
450 if (status != HTTP_OK)
451 return (http_to_papi_status(status));
452 httpWait(svc->connection, 1000);
453
454 /* read the IPP response */
455 result = ipp_read_message(&ipp_request_read, svc, &response,
456 IPP_TYPE_RESPONSE);
457 if (result == PAPI_OK)
458 result = ipp_status_info(svc, response);
459
460 if (result == PAPI_OK) {
461 papi_attribute_t **op = NULL;
462
463 papiAttributeListGetCollection(response, NULL,
464 "job-attributes-group", &op);
465 copy_attributes(&j->attributes, op);
466 }
467 papiAttributeListFree(response);
468
469 return (result);
470 }
471
472 papi_status_t
papiJobQuery(papi_service_t handle,char * printer,int32_t job_id,char ** requested_attrs,papi_job_t * job)473 papiJobQuery(papi_service_t handle, char *printer, int32_t job_id,
474 char **requested_attrs,
475 papi_job_t *job)
476 {
477 papi_status_t result = PAPI_INTERNAL_ERROR;
478 service_t *svc = handle;
479 job_t *j = NULL;
480 papi_attribute_t **request = NULL, **op = NULL, **response = NULL;
481
482 if ((svc == NULL) || (printer == NULL))
483 return (PAPI_BAD_ARGUMENT);
484
485 /* if we are already connected, use that connection. */
486 if (svc->connection == NULL)
487 if ((result = service_connect(svc, printer)) != PAPI_OK)
488 return (result);
489
490 if ((*job = j = calloc(1, sizeof (*j))) == NULL)
491 return (PAPI_TEMPORARY_ERROR);
492
493 ipp_initialize_request(svc, &request, OPID_GET_JOB_ATTRIBUTES);
494
495 ipp_initialize_operational_attributes(svc, &op, printer, job_id);
496
497 if (requested_attrs != NULL) {
498 int i;
499
500 for (i = 0; requested_attrs[i] != NULL; i++)
501 papiAttributeListAddString(&op, PAPI_ATTR_APPEND,
502 "requested-attributes", requested_attrs[i]);
503 }
504
505 papiAttributeListAddCollection(&request, PAPI_ATTR_REPLACE,
506 "operational-attributes-group", op);
507 papiAttributeListFree(op);
508 result = ipp_send_request(svc, request, &response);
509 papiAttributeListFree(request);
510
511 op = NULL;
512 papiAttributeListGetCollection(response, NULL,
513 "job-attributes-group", &op);
514 copy_attributes(&j->attributes, op);
515 papiAttributeListFree(response);
516
517 return (result);
518 }
519
520 /* papiJob{Cancel|Hold|Release|Restart|Promote} are all the same */
521 static papi_status_t
_job_cancel_hold_release_restart_promote(papi_service_t handle,char * printer,int32_t job_id,uint16_t type)522 _job_cancel_hold_release_restart_promote(papi_service_t handle,
523 char *printer, int32_t job_id, uint16_t type)
524 {
525 papi_status_t result = PAPI_INTERNAL_ERROR;
526 service_t *svc = handle;
527 papi_attribute_t **request = NULL, **op = NULL, **response = NULL;
528
529 if ((svc == NULL) || (printer == NULL) || (job_id < 0))
530 return (PAPI_BAD_ARGUMENT);
531
532 /* if we are already connected, use that connection. */
533 if (svc->connection == NULL)
534 if ((result = service_connect(svc, printer)) != PAPI_OK)
535 return (result);
536
537 ipp_initialize_request(svc, &request, type);
538
539 ipp_initialize_operational_attributes(svc, &op, printer, job_id);
540
541 papiAttributeListAddCollection(&request, PAPI_ATTR_REPLACE,
542 "operational-attributes-group", op);
543 papiAttributeListFree(op);
544 result = ipp_send_request(svc, request, &response);
545 papiAttributeListFree(request);
546 papiAttributeListFree(response);
547
548 return (result);
549 }
550
551 papi_status_t
papiJobCancel(papi_service_t handle,char * printer,int32_t job_id)552 papiJobCancel(papi_service_t handle, char *printer, int32_t job_id)
553 {
554 return (_job_cancel_hold_release_restart_promote(handle, printer,
555 job_id, OPID_CANCEL_JOB));
556 }
557
558
559 papi_status_t
papiJobHold(papi_service_t handle,char * printer,int32_t job_id)560 papiJobHold(papi_service_t handle, char *printer, int32_t job_id)
561 {
562 return (_job_cancel_hold_release_restart_promote(handle, printer,
563 job_id, OPID_HOLD_JOB));
564 }
565
566 papi_status_t
papiJobRelease(papi_service_t handle,char * printer,int32_t job_id)567 papiJobRelease(papi_service_t handle, char *printer, int32_t job_id)
568 {
569 return (_job_cancel_hold_release_restart_promote(handle, printer,
570 job_id, OPID_RELEASE_JOB));
571 }
572
573 papi_status_t
papiJobRestart(papi_service_t handle,char * printer,int32_t job_id)574 papiJobRestart(papi_service_t handle, char *printer, int32_t job_id)
575 {
576 return (_job_cancel_hold_release_restart_promote(handle, printer,
577 job_id, OPID_RESTART_JOB));
578 }
579
580 papi_status_t
papiJobPromote(papi_service_t handle,char * printer,int32_t job_id)581 papiJobPromote(papi_service_t handle, char *printer, int32_t job_id)
582 {
583 return (_job_cancel_hold_release_restart_promote(handle, printer,
584 job_id, OPID_PROMOTE_JOB));
585 }
586
587 papi_status_t
papiJobMove(papi_service_t handle,char * printer,int32_t job_id,char * destination)588 papiJobMove(papi_service_t handle, char *printer, int32_t job_id,
589 char *destination)
590 {
591 papi_status_t result = PAPI_INTERNAL_ERROR;
592 service_t *svc = handle;
593 papi_attribute_t **request = NULL, **op = NULL, **response = NULL;
594
595 if ((svc == NULL) || (printer == NULL) || (job_id < 0) ||
596 (destination == NULL))
597 return (PAPI_BAD_ARGUMENT);
598
599 /* if we are already connected, use that connection. */
600 if (svc->connection == NULL)
601 if ((result = service_connect(svc, printer)) != PAPI_OK)
602 return (result);
603
604 ipp_initialize_request(svc, &request, OPID_CUPS_MOVE_JOB);
605
606 ipp_initialize_operational_attributes(svc, &op, printer, job_id);
607
608 papiAttributeListAddCollection(&request, PAPI_ATTR_REPLACE,
609 "operational-attributes-group", op);
610 papiAttributeListFree(op);
611
612 op = NULL;
613 papiAttributeListAddString(&op, PAPI_ATTR_EXCL,
614 "job-printer-uri", destination);
615 papiAttributeListAddCollection(&request, PAPI_ATTR_REPLACE,
616 "job-attributes-group", op);
617 papiAttributeListFree(op);
618
619 result = ipp_send_request(svc, request, &response);
620 papiAttributeListFree(request);
621 papiAttributeListFree(response);
622
623 return (result);
624 }
625
626 papi_status_t
papiJobModify(papi_service_t handle,char * printer,int32_t job_id,papi_attribute_t ** attributes,papi_job_t * job)627 papiJobModify(papi_service_t handle, char *printer, int32_t job_id,
628 papi_attribute_t **attributes, papi_job_t *job)
629 {
630 papi_status_t result = PAPI_INTERNAL_ERROR;
631 service_t *svc = handle;
632 papi_attribute_t **request = NULL, **op = NULL, **response = NULL;
633 job_t *j = NULL;
634
635 if ((svc == NULL) || (printer == NULL) || (job_id < 0) ||
636 (attributes == NULL))
637 return (PAPI_BAD_ARGUMENT);
638
639 if ((*job = j = calloc(1, sizeof (*j))) == NULL)
640 return (PAPI_TEMPORARY_ERROR);
641
642 /* if we are already connected, use that connection. */
643 if (svc->connection == NULL)
644 if ((result = service_connect(svc, printer)) != PAPI_OK)
645 return (result);
646
647 ipp_initialize_request(svc, &request, OPID_SET_JOB_ATTRIBUTES);
648
649 ipp_initialize_operational_attributes(svc, &op, printer, job_id);
650
651 papiAttributeListAddCollection(&request, PAPI_ATTR_REPLACE,
652 "operational-attributes-group", op);
653 papiAttributeListFree(op);
654 papiAttributeListAddCollection(&request, PAPI_ATTR_REPLACE,
655 "job-attributes-group", attributes);
656 result = ipp_send_request(svc, request, &response);
657 papiAttributeListFree(request);
658
659 op = NULL;
660 papiAttributeListGetCollection(response, NULL,
661 "job-attributes-group", &op);
662 copy_attributes(&j->attributes, op);
663 papiAttributeListFree(response);
664
665 return (result);
666 }
667