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     } while (0);
118 }
119 
_destroy(const ifc_status_monitor_t * this_p)120 static void _destroy(const ifc_status_monitor_t *this_p) {
121     ipp_monitor_t *monitor;
122     LOGD("_destroy(): enter");
123     do {
124         if (this_p == NULL) {
125             continue;
126         }
127 
128         monitor = IMPL(ipp_monitor_t, ifc, this_p);
129         if (monitor->initialized) {
130             pthread_mutex_lock(&monitor->mutex);
131 
132             sem_post(&monitor->monitor_sem);
133             sem_destroy(&monitor->monitor_sem);
134 
135             pthread_mutex_unlock(&monitor->mutex);
136             pthread_mutex_destroy(&monitor->mutex);
137             monitor->stop_monitor = 1;
138         }
139 
140         if (monitor->http != NULL) {
141             httpClose(monitor->http);
142         }
143 
144         free(monitor);
145     } while (0);
146 }
147 
_get_status(const ifc_status_monitor_t * this_p,printer_state_dyn_t * printer_state_dyn)148 static void _get_status(const ifc_status_monitor_t *this_p,
149         printer_state_dyn_t *printer_state_dyn) {
150     int i;
151     ipp_monitor_t *monitor;
152     ipp_pstate_t printer_state;
153     ipp_status_t ipp_status;
154     LOGD("_get_status(): enter");
155     do {
156         if (printer_state_dyn == NULL) {
157             LOGD("_get_status(): printer_state_dyn is null!");
158             continue;
159         }
160 
161         printer_state_dyn->printer_status = PRINT_STATUS_UNKNOWN;
162         printer_state_dyn->printer_reasons[0] = PRINT_STATUS_UNKNOWN;
163         for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) {
164             printer_state_dyn->printer_reasons[i] = PRINT_STATUS_MAX_STATE;
165         }
166 
167         if (this_p == NULL) {
168             LOGE("_get_status(): this_p is null!");
169             continue;
170         }
171 
172         monitor = IMPL(ipp_monitor_t, ifc, this_p);
173         if (!monitor->initialized) {
174             LOGE("_get_status(): Monitor is uninitialized");
175             continue;
176         }
177 
178         if (monitor->http == NULL) {
179             LOGE("_get_status(): monitor->http is NULL, setting Unable to Connect");
180             printer_state_dyn->printer_reasons[0] = PRINT_STATUS_UNABLE_TO_CONNECT;
181             continue;
182         }
183 
184         printer_state_dyn->printer_status = PRINT_STATUS_IDLE;
185         ipp_status = get_PrinterState(monitor->http, monitor->printer_uri, printer_state_dyn,
186                 &printer_state);
187         LOGD("_get_status(): ipp_status=%d", ipp_status);
188         debuglist_printerStatus(printer_state_dyn);
189     } while (0);
190 }
191 
192 // TODO (b/312004304): job_state_cb code removed due to crash.
_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)193 static void _start(const ifc_status_monitor_t *this_p,
194         void (*status_cb)(const printer_state_dyn_t *new_status,
195                 const printer_state_dyn_t *old_status, void *status_param),
196         void (*job_state_cb)(const job_state_dyn_t *new_state, void *param),
197         void *param) {
198     int i, job_id = -1;
199     printer_state_dyn_t last_status, curr_status;
200     job_state_dyn_t old_state, new_state;
201     ipp_monitor_t *monitor = NULL;
202 
203     LOGD("_start(): enter");
204 
205     // initialize our status structures
206     for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) {
207         curr_status.printer_reasons[i] = PRINT_STATUS_MAX_STATE;
208         last_status.printer_reasons[i] = PRINT_STATUS_MAX_STATE;
209     }
210 
211     last_status.printer_status = PRINT_STATUS_UNKNOWN;
212     last_status.printer_reasons[0] = PRINT_STATUS_INITIALIZING;
213     last_status.job_id = 0;
214 
215     curr_status.printer_status = PRINT_STATUS_UNKNOWN;
216     curr_status.printer_reasons[0] = PRINT_STATUS_INITIALIZING;
217     curr_status.job_id = 0;
218 
219     // send out the first callback
220     if (status_cb != NULL) {
221         (*status_cb)(&curr_status, &last_status, param);
222     }
223 
224     /* initialize job status structures */
225     for (i = 0; i <= IPP_JOB_STATE_REASON_MAX_VALUE; i++) {
226         new_state.job_state_reasons[i] = IPP_JOB_STATE_REASON_MAX_VALUE;
227         old_state.job_state_reasons[i] = IPP_JOB_STATE_REASON_MAX_VALUE;
228     }
229 
230     old_state.job_state = IPP_JOB_STATE_UNKNOWN;
231     old_state.job_state_reasons[0] = IPP_JOB_STATE_REASON_UNKNOWN;
232 
233     new_state.job_state = IPP_JOB_STATE_UNKNOWN;
234     new_state.job_state_reasons[0] = IPP_JOB_STATE_REASON_UNKNOWN;
235 
236     do {
237         curr_status.printer_status = PRINT_STATUS_SVC_REQUEST;
238         curr_status.printer_reasons[0] = PRINT_STATUS_UNABLE_TO_CONNECT;
239 
240         if (this_p == NULL) {
241             continue;
242         }
243 
244         monitor = IMPL(ipp_monitor_t, ifc, this_p);
245         if (!monitor->initialized) {
246             continue;
247         }
248 
249         if (monitor->monitor_running) {
250             continue;
251         }
252 
253         monitor->stop_monitor = 0;
254         monitor->monitor_running = 1;
255         if (monitor->http == NULL) {
256             if (status_cb != NULL) {
257                 (*status_cb)(&curr_status, &last_status, param);
258             }
259             sem_wait(&monitor->monitor_sem);
260 
261             last_status.printer_status = PRINT_STATUS_UNKNOWN;
262             last_status.printer_reasons[0] = PRINT_STATUS_SHUTTING_DOWN;
263 
264             curr_status.printer_status = PRINT_STATUS_UNKNOWN;
265             curr_status.printer_reasons[0] = PRINT_STATUS_SHUTTING_DOWN;
266         } else {
267             while (!monitor->stop_monitor) {
268                 pthread_mutex_lock(&monitor->mutex);
269                 curr_status.job_id = job_id;
270                 _get_status(this_p, &curr_status);
271                 pthread_mutex_unlock(&monitor->mutex);
272                 if ((status_cb != NULL) &&
273                         (memcmp(&curr_status, &last_status, sizeof(printer_state_dyn_t)) != 0)) {
274                     (*status_cb)(&curr_status, &last_status, param);
275                     memcpy(&last_status, &curr_status, sizeof(printer_state_dyn_t));
276                 }
277                 // TODO (b/312004304): Code removed due to crash. Will add it back with proper mitigation.
278                 sleep(1);
279             }
280         }
281         monitor->monitor_running = 0;
282     } while (0);
283 
284     if (status_cb != NULL) {
285         (*status_cb)(&curr_status, &last_status, param);
286     }
287 }
288 
_stop(const ifc_status_monitor_t * this_p)289 static void _stop(const ifc_status_monitor_t *this_p) {
290     // request a stop and release the semaphore
291     ipp_monitor_t *monitor;
292     LOGD("_stop(): enter");
293     do {
294         if (this_p == NULL) {
295             continue;
296         }
297 
298         monitor = IMPL(ipp_monitor_t, ifc, this_p);
299         if (!monitor->initialized) {
300             continue;
301         }
302 
303         sem_post(&monitor->monitor_sem);
304         monitor->stop_monitor = 1;
305     } while (0);
306 }
307 
_cancel(const ifc_status_monitor_t * this_p,const char * requesting_user)308 static status_t _cancel(const ifc_status_monitor_t *this_p, const char *requesting_user) {
309     status_t return_value = ERROR;
310     int job_id = -1;
311     ipp_monitor_t *monitor = NULL;
312     ipp_t *request = NULL;
313     ipp_t *response = NULL;
314     ipp_attribute_t *attr;
315 
316     LOGD("_cancel(): enter");
317 
318     monitor = IMPL(ipp_monitor_t, ifc, this_p);
319     if (this_p != NULL && monitor != NULL && monitor->initialized && !monitor->stop_monitor) {
320         pthread_mutex_lock(&monitor->mutex);
321         do {
322             if (monitor->stop_monitor) {
323                 break;
324             }
325 
326             request = ippNewRequest(IPP_GET_JOBS);
327             if (request == NULL) {
328                 break;
329             }
330 
331             ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
332                     monitor->printer_uri);
333             ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1);
334             ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
335                     NULL, requesting_user);
336 
337             // Requested printer attributes
338             static const char *pattrs[] = {"job-id", "job-state", "job-state-reasons"};
339 
340             ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes",
341                     sizeof(pattrs) / sizeof(pattrs[1]), NULL, pattrs);
342 
343             response = ipp_doCupsRequest(monitor->http, request, monitor->http_resource,
344                     monitor->printer_uri);
345             if (response == NULL) {
346                 ipp_status_t ipp_status = cupsLastError();
347                 LOGD("_cancel get job attributes: response is null, ipp_status %d: %s",
348                         ipp_status, ippErrorString(ipp_status));
349                 return_value = ERROR;
350             } else {
351                 attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER);
352                 if (attr != NULL) {
353                     job_id = ippGetInteger(attr, 0);
354                     LOGD("_cancel got job-id: %d", job_id);
355                 } else { // We need the job id to attempt a cancel
356                     break;
357                 }
358 
359                 attr = ippFindAttribute(response, "job-state", IPP_TAG_ENUM);
360                 if (attr != NULL) {
361                     ipp_jstate_t jobState = (ipp_jstate_t)ippGetInteger(attr, 0);
362                     LOGD("_cancel got job-state: %d", jobState);
363                 }
364 
365                 attr = ippFindAttribute(response, "job-state-reasons", IPP_TAG_KEYWORD);
366                 if (attr != NULL) {
367                     int idx;
368                     for (idx = 0; idx < ippGetCount(attr); idx++) {
369                         LOGD("before job-state-reason (%d): %s", idx,
370                                 ippGetString(attr, idx, NULL));
371                     }
372                 }
373             }
374         } while (0);
375 
376         ippDelete(request);
377         request = NULL;
378         ippDelete(response);
379         response = NULL;
380 
381         do {
382             if (job_id == -1) {
383                 break;
384             }
385 
386             request = ippNewRequest(IPP_CANCEL_JOB);
387             if (request == NULL) {
388                 break;
389             }
390 
391             ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
392                     monitor->printer_uri);
393             ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job_id);
394             ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
395                     "requesting-user-name", NULL, requesting_user);
396 
397             if ((response = ipp_doCupsRequest(monitor->http, request, monitor->http_resource,
398                     monitor->printer_uri)) == NULL) {
399                 ipp_status_t ipp_status = cupsLastError();
400                 LOGD("cancel:  response is null:  ipp_status %d %s", ipp_status,
401                         ippErrorString(ipp_status));
402                 return_value = ERROR;
403             } else {
404                 ipp_status_t ipp_status = cupsLastError();
405                 LOGE("IPP_Status for cancel request was %d %s", ipp_status,
406                         ippErrorString(ipp_status));
407                 attr = ippFindAttribute(response, "job-state-reasons", IPP_TAG_KEYWORD);
408                 if (attr != NULL) {
409                     int idx;
410                     for (idx = 0; ippGetCount(attr); idx++) {
411                         LOGD("job-state-reason (%d): %s", idx, ippGetString(attr, idx, NULL));
412                     }
413                 }
414                 return_value = OK;
415             }
416         } while (0);
417 
418         ippDelete(request);
419         ippDelete(response);
420 
421         if (monitor->initialized) {
422             pthread_mutex_unlock(&monitor->mutex);
423         }
424     }
425     return return_value;
426 }
427 
428 /*
429  * Get job state for the given job_id
430  */
_get_job_state(const ifc_status_monitor_t * this_p,job_state_dyn_t * job_state_dyn,int job_id)431 static void _get_job_state(const ifc_status_monitor_t *this_p, job_state_dyn_t *job_state_dyn,
432                            int job_id) {
433     if (job_id == -1) return;
434 
435     LOGD("_get_job_state(): enter");
436 
437     ipp_monitor_t *monitor = NULL;
438     monitor = IMPL(ipp_monitor_t, ifc, this_p);
439 
440     if (this_p != NULL && monitor != NULL && monitor->initialized && !monitor->stop_monitor) {
441         pthread_mutex_lock(&monitor->mutex);
442 
443         do {
444             if (monitor->stop_monitor)
445                 break;
446 
447             ipp_monitor_t *ipp_job;
448             ipp_job = IMPL(ipp_monitor_t, ifc, this_p);
449 
450             if (ipp_job->http == NULL)
451                 break;
452 
453             ipp_jstate_t job_ippstate;
454             ipp_status_t ipp_status = get_JobStatus(ipp_job->http, ipp_job->printer_uri, job_id,
455                                                     job_state_dyn, &job_ippstate,
456                                                     monitor->requesting_user);
457             LOGD("_get_job_state(): Print job State is %d", ipp_status);
458         } while (0);
459         pthread_mutex_unlock(&monitor->mutex);
460     }
461 }
462