1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  * Copyright (C) 2016 Mopria Alliance, Inc.
4  * Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 #ifndef _GNU_SOURCE
20 #define _GNU_SOURCE
21 #endif
22 
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <semaphore.h>
26 #include <fcntl.h>
27 
28 #include "lib_wprint.h"
29 #include "ippstatus_monitor.h"
30 #include "ipphelper.h"
31 
32 #include "cups.h"
33 #include "http-private.h"
34 #include <pthread.h>
35 #include "wprint_debug.h"
36 
37 #define TAG "ippstatus_monitor"
38 
39 static void _init(const ifc_status_monitor_t *this_p, const wprint_connect_info_t *);
40 
41 static void _get_status(const ifc_status_monitor_t *this_p, printer_state_dyn_t *printer_state_dyn);
42 
43 static void _start(const ifc_status_monitor_t *this_p, void (*status_cb)(
44         const printer_state_dyn_t *new_status, const printer_state_dyn_t *old_status,
45                 void *status_param),
46                 void (*job_state_cb)(const job_state_dyn_t *new_state, void *param), void *param);
47 
48 static void _stop(const ifc_status_monitor_t *this_p);
49 
50 static status_t _cancel(const ifc_status_monitor_t *this_p, const char *requesting_user);
51 
52 static void _destroy(const ifc_status_monitor_t *this_p);
53 
54 static void _get_job_state(const ifc_status_monitor_t *this_p, job_state_dyn_t *job_state_dyn,
55         int job_id);
56 
57 static const ifc_status_monitor_t _status_ifc = {.init = _init, .get_status = _get_status,
58         .cancel = _cancel, .start = _start, .stop = _stop, .destroy = _destroy,};
59 
60 typedef struct {
61     unsigned char initialized;
62     http_t *http;
63     char printer_uri[1024];
64     char http_resource[1024];
65     volatile unsigned char stop_monitor;
66     unsigned char monitor_running;
67     sem_t monitor_sem;
68     pthread_mutex_t mutex;
69     pthread_mutexattr_t mutexattr;
70     ifc_status_monitor_t ifc;
71     char requesting_user[1024];
72 } ipp_monitor_t;
73 
ipp_status_get_monitor_ifc(const ifc_wprint_t * wprint_ifc)74 const ifc_status_monitor_t *ipp_status_get_monitor_ifc(const ifc_wprint_t *wprint_ifc) {
75     ipp_monitor_t *monitor = (ipp_monitor_t *) malloc(sizeof(ipp_monitor_t));
76 
77     // setup the interface
78     monitor->initialized = 0;
79     monitor->http = NULL;
80     memcpy(&monitor->ifc, &_status_ifc, sizeof(ifc_status_monitor_t));
81     return &monitor->ifc;
82 }
83 
_init(const ifc_status_monitor_t * this_p,const wprint_connect_info_t * connect_info)84 static void _init(const ifc_status_monitor_t *this_p, const wprint_connect_info_t *connect_info) {
85     ipp_monitor_t *monitor;
86     LOGD("_init(): enter");
87     do {
88         if (this_p == NULL) {
89             continue;
90         }
91         monitor = IMPL(ipp_monitor_t, ifc, this_p);
92 
93         if (monitor->initialized != 0) {
94             sem_post(&monitor->monitor_sem);
95             sem_destroy(&monitor->monitor_sem);
96 
97             pthread_mutex_unlock(&monitor->mutex);
98             pthread_mutex_destroy(&monitor->mutex);
99         }
100 
101         if (monitor->http != NULL) {
102             httpClose(monitor->http);
103         }
104 
105         monitor->http = ipp_cups_connect(connect_info, monitor->printer_uri,
106                 sizeof(monitor->printer_uri));
107         getResourceFromURI(monitor->printer_uri, monitor->http_resource, 1024);
108 
109         monitor->monitor_running = 0;
110         monitor->stop_monitor = 0;
111 
112         pthread_mutexattr_init(&monitor->mutexattr);
113         pthread_mutexattr_settype(&(monitor->mutexattr), PTHREAD_MUTEX_RECURSIVE_NP);
114         pthread_mutex_init(&monitor->mutex, &monitor->mutexattr);
115         sem_init(&monitor->monitor_sem, 0, 0);
116         monitor->initialized = 1;
117         if (connect_info->requesting_user_name) {
118             strlcpy(monitor->requesting_user, connect_info->requesting_user_name,
119                     sizeof(monitor->requesting_user));
120         }
121     } while (0);
122 }
123 
_destroy(const ifc_status_monitor_t * this_p)124 static void _destroy(const ifc_status_monitor_t *this_p) {
125     ipp_monitor_t *monitor;
126     LOGD("_destroy(): enter");
127     do {
128         if (this_p == NULL) {
129             continue;
130         }
131 
132         monitor = IMPL(ipp_monitor_t, ifc, this_p);
133         if (monitor->initialized) {
134             pthread_mutex_lock(&monitor->mutex);
135 
136             sem_post(&monitor->monitor_sem);
137             sem_destroy(&monitor->monitor_sem);
138 
139             pthread_mutex_unlock(&monitor->mutex);
140             pthread_mutex_destroy(&monitor->mutex);
141             monitor->stop_monitor = 1;
142         }
143 
144         if (monitor->http != NULL) {
145             httpClose(monitor->http);
146         }
147 
148         free(monitor);
149     } while (0);
150 }
151 
_get_status(const ifc_status_monitor_t * this_p,printer_state_dyn_t * printer_state_dyn)152 static void _get_status(const ifc_status_monitor_t *this_p,
153         printer_state_dyn_t *printer_state_dyn) {
154     int i;
155     ipp_monitor_t *monitor;
156     ipp_pstate_t printer_state;
157     ipp_status_t ipp_status;
158     LOGD("_get_status(): enter");
159     do {
160         if (printer_state_dyn == NULL) {
161             LOGD("_get_status(): printer_state_dyn is null!");
162             continue;
163         }
164 
165         printer_state_dyn->printer_status = PRINT_STATUS_UNKNOWN;
166         printer_state_dyn->printer_reasons[0] = PRINT_STATUS_UNKNOWN;
167         for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) {
168             printer_state_dyn->printer_reasons[i] = PRINT_STATUS_MAX_STATE;
169         }
170 
171         if (this_p == NULL) {
172             LOGE("_get_status(): this_p is null!");
173             continue;
174         }
175 
176         monitor = IMPL(ipp_monitor_t, ifc, this_p);
177         if (!monitor->initialized) {
178             LOGE("_get_status(): Monitor is uninitialized");
179             continue;
180         }
181 
182         if (monitor->http == NULL) {
183             LOGE("_get_status(): monitor->http is NULL, setting Unable to Connect");
184             printer_state_dyn->printer_reasons[0] = PRINT_STATUS_UNABLE_TO_CONNECT;
185             continue;
186         }
187 
188         printer_state_dyn->printer_status = PRINT_STATUS_IDLE;
189         ipp_status = get_PrinterState(monitor->http, monitor->printer_uri, printer_state_dyn,
190                 &printer_state);
191         LOGD("_get_status(): ipp_status=%d", ipp_status);
192         debuglist_printerStatus(printer_state_dyn);
193     } while (0);
194 }
195 
_start(const ifc_status_monitor_t * this_p,void (* status_cb)(const printer_state_dyn_t * new_status,const printer_state_dyn_t * old_status,void * status_param),void (* job_state_cb)(const job_state_dyn_t * new_state,void * param),void * param)196 static void _start(const ifc_status_monitor_t *this_p,
197         void (*status_cb)(const printer_state_dyn_t *new_status,
198                 const printer_state_dyn_t *old_status, void *status_param),
199         void (*job_state_cb)(const job_state_dyn_t *new_state, void *param),
200         void *param) {
201     int i, job_id = -1;
202     printer_state_dyn_t last_status, curr_status;
203     job_state_dyn_t old_state, new_state;
204     ipp_monitor_t *monitor = NULL;
205 
206     LOGD("_start(): enter");
207 
208     // initialize our status structures
209     for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) {
210         curr_status.printer_reasons[i] = PRINT_STATUS_MAX_STATE;
211         last_status.printer_reasons[i] = PRINT_STATUS_MAX_STATE;
212     }
213 
214     last_status.printer_status = PRINT_STATUS_UNKNOWN;
215     last_status.printer_reasons[0] = PRINT_STATUS_INITIALIZING;
216     last_status.job_id = 0;
217 
218     curr_status.printer_status = PRINT_STATUS_UNKNOWN;
219     curr_status.printer_reasons[0] = PRINT_STATUS_INITIALIZING;
220     curr_status.job_id = 0;
221 
222     // send out the first callback
223     if (status_cb != NULL) {
224         (*status_cb)(&curr_status, &last_status, param);
225     }
226 
227     /* initialize job status structures */
228     for (i = 0; i <= IPP_JOB_STATE_REASON_MAX_VALUE; i++) {
229         new_state.job_state_reasons[i] = IPP_JOB_STATE_REASON_MAX_VALUE;
230         old_state.job_state_reasons[i] = IPP_JOB_STATE_REASON_MAX_VALUE;
231     }
232 
233     old_state.job_state = IPP_JOB_STATE_UNKNOWN;
234     old_state.job_state_reasons[0] = IPP_JOB_STATE_REASON_UNKNOWN;
235 
236     new_state.job_state = IPP_JOB_STATE_UNKNOWN;
237     new_state.job_state_reasons[0] = IPP_JOB_STATE_REASON_UNKNOWN;
238 
239     do {
240         curr_status.printer_status = PRINT_STATUS_SVC_REQUEST;
241         curr_status.printer_reasons[0] = PRINT_STATUS_UNABLE_TO_CONNECT;
242 
243         if (this_p == NULL) {
244             continue;
245         }
246 
247         monitor = IMPL(ipp_monitor_t, ifc, this_p);
248         if (!monitor->initialized) {
249             continue;
250         }
251 
252         if (monitor->monitor_running) {
253             continue;
254         }
255 
256         monitor->stop_monitor = 0;
257         monitor->monitor_running = 1;
258         if (monitor->http == NULL) {
259             if (status_cb != NULL) {
260                 (*status_cb)(&curr_status, &last_status, param);
261             }
262             sem_wait(&monitor->monitor_sem);
263 
264             last_status.printer_status = PRINT_STATUS_UNKNOWN;
265             last_status.printer_reasons[0] = PRINT_STATUS_SHUTTING_DOWN;
266 
267             curr_status.printer_status = PRINT_STATUS_UNKNOWN;
268             curr_status.printer_reasons[0] = PRINT_STATUS_SHUTTING_DOWN;
269         } else {
270             while (!monitor->stop_monitor) {
271                 pthread_mutex_lock(&monitor->mutex);
272                 curr_status.job_id = job_id;
273                 _get_status(this_p, &curr_status);
274                 pthread_mutex_unlock(&monitor->mutex);
275                 if ((status_cb != NULL) &&
276                         (memcmp(&curr_status, &last_status, sizeof(printer_state_dyn_t)) != 0)) {
277                     (*status_cb)(&curr_status, &last_status, param);
278                     memcpy(&last_status, &curr_status, sizeof(printer_state_dyn_t));
279                 }
280 
281                 // Do not call for job state if thread has been stopped
282                 if (job_state_cb != NULL && !monitor->stop_monitor) {
283                     pthread_mutex_lock(&monitor->mutex);
284                     if (job_id == -1) {
285                         job_id = getJobId(monitor->http, monitor->http_resource,
286                                           monitor->printer_uri, &new_state,
287                                           monitor->requesting_user);
288                     }
289                     _get_job_state(this_p, &new_state, job_id);
290                     pthread_mutex_unlock(&monitor->mutex);
291 
292                     if (memcmp(&new_state, &old_state, sizeof(job_state_dyn_t)) != 0) {
293                         (*job_state_cb)(&new_state, param);
294                         memcpy(&old_state, &new_state, sizeof(job_state_dyn_t));
295                     }
296                 }
297                 sleep(1);
298             }
299         }
300         monitor->monitor_running = 0;
301     } while (0);
302 
303     if (status_cb != NULL) {
304         (*status_cb)(&curr_status, &last_status, param);
305     }
306 }
307 
_stop(const ifc_status_monitor_t * this_p)308 static void _stop(const ifc_status_monitor_t *this_p) {
309     // request a stop and release the semaphore
310     ipp_monitor_t *monitor;
311     LOGD("_stop(): enter");
312     do {
313         if (this_p == NULL) {
314             continue;
315         }
316 
317         monitor = IMPL(ipp_monitor_t, ifc, this_p);
318         if (!monitor->initialized) {
319             continue;
320         }
321 
322         sem_post(&monitor->monitor_sem);
323         monitor->stop_monitor = 1;
324     } while (0);
325 }
326 
_cancel(const ifc_status_monitor_t * this_p,const char * requesting_user)327 static status_t _cancel(const ifc_status_monitor_t *this_p, const char *requesting_user) {
328     status_t return_value = ERROR;
329     int job_id = -1;
330     ipp_monitor_t *monitor = NULL;
331     ipp_t *request = NULL;
332     ipp_t *response = NULL;
333     ipp_attribute_t *attr;
334 
335     LOGD("_cancel(): enter");
336 
337     monitor = IMPL(ipp_monitor_t, ifc, this_p);
338     if (this_p != NULL && monitor != NULL && monitor->initialized && !monitor->stop_monitor) {
339         pthread_mutex_lock(&monitor->mutex);
340         do {
341             if (monitor->stop_monitor) {
342                 break;
343             }
344 
345             request = ippNewRequest(IPP_GET_JOBS);
346             if (request == NULL) {
347                 break;
348             }
349 
350             ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
351                     monitor->printer_uri);
352             ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1);
353             ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
354                     NULL, requesting_user);
355 
356             // Requested printer attributes
357             static const char *pattrs[] = {"job-id", "job-state", "job-state-reasons"};
358 
359             ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes",
360                     sizeof(pattrs) / sizeof(pattrs[1]), NULL, pattrs);
361 
362             response = ipp_doCupsRequest(monitor->http, request, monitor->http_resource,
363                     monitor->printer_uri);
364             if (response == NULL) {
365                 ipp_status_t ipp_status = cupsLastError();
366                 LOGD("_cancel get job attributes: response is null, ipp_status %d: %s",
367                         ipp_status, ippErrorString(ipp_status));
368                 return_value = ERROR;
369             } else {
370                 attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER);
371                 if (attr != NULL) {
372                     job_id = ippGetInteger(attr, 0);
373                     LOGD("_cancel got job-id: %d", job_id);
374                 } else { // We need the job id to attempt a cancel
375                     break;
376                 }
377 
378                 attr = ippFindAttribute(response, "job-state", IPP_TAG_ENUM);
379                 if (attr != NULL) {
380                     ipp_jstate_t jobState = (ipp_jstate_t)ippGetInteger(attr, 0);
381                     LOGD("_cancel got job-state: %d", jobState);
382                 }
383 
384                 attr = ippFindAttribute(response, "job-state-reasons", IPP_TAG_KEYWORD);
385                 if (attr != NULL) {
386                     int idx;
387                     for (idx = 0; idx < ippGetCount(attr); idx++) {
388                         LOGD("before job-state-reason (%d): %s", idx,
389                                 ippGetString(attr, idx, NULL));
390                     }
391                 }
392             }
393         } while (0);
394 
395         ippDelete(request);
396         request = NULL;
397         ippDelete(response);
398         response = NULL;
399 
400         do {
401             if (job_id == -1) {
402                 break;
403             }
404 
405             request = ippNewRequest(IPP_CANCEL_JOB);
406             if (request == NULL) {
407                 break;
408             }
409 
410             ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
411                     monitor->printer_uri);
412             ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job_id);
413             ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
414                     "requesting-user-name", NULL, requesting_user);
415 
416             if ((response = ipp_doCupsRequest(monitor->http, request, monitor->http_resource,
417                     monitor->printer_uri)) == NULL) {
418                 ipp_status_t ipp_status = cupsLastError();
419                 LOGD("cancel:  response is null:  ipp_status %d %s", ipp_status,
420                         ippErrorString(ipp_status));
421                 return_value = ERROR;
422             } else {
423                 ipp_status_t ipp_status = cupsLastError();
424                 LOGE("IPP_Status for cancel request was %d %s", ipp_status,
425                         ippErrorString(ipp_status));
426                 attr = ippFindAttribute(response, "job-state-reasons", IPP_TAG_KEYWORD);
427                 if (attr != NULL) {
428                     int idx;
429                     for (idx = 0; ippGetCount(attr); idx++) {
430                         LOGD("job-state-reason (%d): %s", idx, ippGetString(attr, idx, NULL));
431                     }
432                 }
433                 return_value = OK;
434             }
435         } while (0);
436 
437         ippDelete(request);
438         ippDelete(response);
439 
440         if (monitor->initialized) {
441             pthread_mutex_unlock(&monitor->mutex);
442         }
443     }
444     return return_value;
445 }
446 
447 /*
448  * Get job state for the given job_id
449  */
_get_job_state(const ifc_status_monitor_t * this_p,job_state_dyn_t * job_state_dyn,int job_id)450 static void _get_job_state(const ifc_status_monitor_t *this_p, job_state_dyn_t *job_state_dyn,
451                            int job_id) {
452     if (job_id == -1) return;
453 
454     LOGD("_get_job_state(): enter");
455 
456     ipp_monitor_t *monitor = NULL;
457     monitor = IMPL(ipp_monitor_t, ifc, this_p);
458 
459     if (this_p != NULL && monitor != NULL && monitor->initialized && !monitor->stop_monitor) {
460         pthread_mutex_lock(&monitor->mutex);
461 
462         do {
463             if (monitor->stop_monitor)
464                 break;
465 
466             ipp_monitor_t *ipp_job;
467             ipp_job = IMPL(ipp_monitor_t, ifc, this_p);
468 
469             if (ipp_job->http == NULL)
470                 break;
471 
472             ipp_jstate_t job_ippstate;
473             ipp_status_t ipp_status = get_JobStatus(ipp_job->http, ipp_job->printer_uri, job_id,
474                                                     job_state_dyn, &job_ippstate,
475                                                     monitor->requesting_user);
476             LOGD("_get_job_state(): Print job State is %d", ipp_status);
477         } while (0);
478         pthread_mutex_unlock(&monitor->mutex);
479     }
480 }
481