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
39static void _init(const ifc_status_monitor_t *this_p, const wprint_connect_info_t *);
40
41static void _get_status(const ifc_status_monitor_t *this_p, printer_state_dyn_t *printer_state_dyn);
42
43static 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), void *param);
46
47static void _stop(const ifc_status_monitor_t *this_p);
48
49static status_t _cancel(const ifc_status_monitor_t *this_p, const char *requesting_user);
50
51static void _destroy(const ifc_status_monitor_t *this_p);
52
53static const ifc_status_monitor_t _status_ifc = {.init = _init, .get_status = _get_status,
54        .cancel = _cancel, .start = _start, .stop = _stop, .destroy = _destroy,};
55
56typedef struct {
57    unsigned char initialized;
58    http_t *http;
59    char printer_uri[1024];
60    char http_resource[1024];
61    unsigned char stop_monitor;
62    unsigned char monitor_running;
63    sem_t monitor_sem;
64    pthread_mutex_t mutex;
65    pthread_mutexattr_t mutexattr;
66    ifc_status_monitor_t ifc;
67} ipp_monitor_t;
68
69const ifc_status_monitor_t *ipp_status_get_monitor_ifc(const ifc_wprint_t *wprint_ifc) {
70    ipp_monitor_t *monitor = (ipp_monitor_t *) malloc(sizeof(ipp_monitor_t));
71
72    // setup the interface
73    monitor->initialized = 0;
74    monitor->http = NULL;
75    memcpy(&monitor->ifc, &_status_ifc, sizeof(ifc_status_monitor_t));
76    return &monitor->ifc;
77}
78
79static void _init(const ifc_status_monitor_t *this_p, const wprint_connect_info_t *connect_info) {
80    ipp_monitor_t *monitor;
81    LOGD("_init(): enter");
82    do {
83        if (this_p == NULL) {
84            continue;
85        }
86        monitor = IMPL(ipp_monitor_t, ifc, this_p);
87
88        if (monitor->initialized != 0) {
89            sem_post(&monitor->monitor_sem);
90            sem_destroy(&monitor->monitor_sem);
91
92            pthread_mutex_unlock(&monitor->mutex);
93            pthread_mutex_destroy(&monitor->mutex);
94        }
95
96        if (monitor->http != NULL) {
97            httpClose(monitor->http);
98        }
99
100        monitor->http = ipp_cups_connect(connect_info, monitor->printer_uri,
101                sizeof(monitor->printer_uri));
102        getResourceFromURI(monitor->printer_uri, monitor->http_resource, 1024);
103
104        monitor->monitor_running = 0;
105        monitor->stop_monitor = 0;
106
107        pthread_mutexattr_init(&monitor->mutexattr);
108        pthread_mutexattr_settype(&(monitor->mutexattr), PTHREAD_MUTEX_RECURSIVE_NP);
109        pthread_mutex_init(&monitor->mutex, &monitor->mutexattr);
110        sem_init(&monitor->monitor_sem, 0, 0);
111        monitor->initialized = 1;
112    } while (0);
113}
114
115static void _destroy(const ifc_status_monitor_t *this_p) {
116    ipp_monitor_t *monitor;
117    LOGD("_destroy(): enter");
118    do {
119        if (this_p == NULL) {
120            continue;
121        }
122
123        monitor = IMPL(ipp_monitor_t, ifc, this_p);
124        if (monitor->initialized) {
125            pthread_mutex_lock(&monitor->mutex);
126
127            sem_post(&monitor->monitor_sem);
128            sem_destroy(&monitor->monitor_sem);
129
130            pthread_mutex_unlock(&monitor->mutex);
131            pthread_mutex_destroy(&monitor->mutex);
132        }
133
134        if (monitor->http != NULL) {
135            httpClose(monitor->http);
136        }
137
138        free(monitor);
139    } while (0);
140}
141
142static void _get_status(const ifc_status_monitor_t *this_p,
143        printer_state_dyn_t *printer_state_dyn) {
144    int i;
145    ipp_monitor_t *monitor;
146    ipp_pstate_t printer_state;
147    ipp_status_t ipp_status;
148    LOGD("_get_status(): enter");
149    do {
150        if (printer_state_dyn == NULL) {
151            LOGD("_get_status(): printer_state_dyn is null!");
152            continue;
153        }
154
155        printer_state_dyn->printer_status = PRINT_STATUS_UNKNOWN;
156        printer_state_dyn->printer_reasons[0] = PRINT_STATUS_UNKNOWN;
157        for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) {
158            printer_state_dyn->printer_reasons[i] = PRINT_STATUS_MAX_STATE;
159        }
160
161        if (this_p == NULL) {
162            LOGE("_get_status(): this_p is null!");
163            continue;
164        }
165
166        monitor = IMPL(ipp_monitor_t, ifc, this_p);
167        if (!monitor->initialized) {
168            LOGE("_get_status(): Monitor is uninitialized");
169            continue;
170        }
171
172        if (monitor->http == NULL) {
173            LOGE("_get_status(): monitor->http is NULL, setting Unable to Connect");
174            printer_state_dyn->printer_reasons[0] = PRINT_STATUS_UNABLE_TO_CONNECT;
175            continue;
176        }
177
178        printer_state_dyn->printer_status = PRINT_STATUS_IDLE;
179        ipp_status = get_PrinterState(monitor->http, monitor->printer_uri, printer_state_dyn,
180                &printer_state);
181        LOGD("_get_status(): ipp_status=%d", ipp_status);
182        debuglist_printerStatus(printer_state_dyn);
183    } while (0);
184}
185
186static void _start(const ifc_status_monitor_t *this_p,
187        void (*status_cb)(const printer_state_dyn_t *new_status,
188                const printer_state_dyn_t *old_status, void *status_param),
189        void *param) {
190    int i;
191    printer_state_dyn_t last_status, curr_status;
192    ipp_monitor_t *monitor = NULL;
193
194    LOGD("_start(): enter");
195
196    // initialize our status structures
197    for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) {
198        curr_status.printer_reasons[i] = PRINT_STATUS_MAX_STATE;
199        last_status.printer_reasons[i] = PRINT_STATUS_MAX_STATE;
200    }
201
202    last_status.printer_status = PRINT_STATUS_UNKNOWN;
203    last_status.printer_reasons[0] = PRINT_STATUS_INITIALIZING;
204
205    curr_status.printer_status = PRINT_STATUS_UNKNOWN;
206    curr_status.printer_reasons[0] = PRINT_STATUS_INITIALIZING;
207
208    // send out the first callback
209    if (status_cb != NULL) {
210        (*status_cb)(&curr_status, &last_status, param);
211    }
212    do {
213        curr_status.printer_status = PRINT_STATUS_SVC_REQUEST;
214        curr_status.printer_reasons[0] = PRINT_STATUS_UNABLE_TO_CONNECT;
215
216        if (this_p == NULL) {
217            continue;
218        }
219
220        monitor = IMPL(ipp_monitor_t, ifc, this_p);
221        if (!monitor->initialized) {
222            continue;
223        }
224
225        if (monitor->monitor_running) {
226            continue;
227        }
228
229        monitor->stop_monitor = 0;
230        monitor->monitor_running = 1;
231        if (monitor->http == NULL) {
232            if (status_cb != NULL) {
233                (*status_cb)(&curr_status, &last_status, param);
234            }
235            sem_wait(&monitor->monitor_sem);
236
237            last_status.printer_status = PRINT_STATUS_UNKNOWN;
238            last_status.printer_reasons[0] = PRINT_STATUS_SHUTTING_DOWN;
239
240            curr_status.printer_status = PRINT_STATUS_UNKNOWN;
241            curr_status.printer_reasons[0] = PRINT_STATUS_SHUTTING_DOWN;
242        } else {
243            while (!monitor->stop_monitor) {
244                pthread_mutex_lock(&monitor->mutex);
245                _get_status(this_p, &curr_status);
246                pthread_mutex_unlock(&monitor->mutex);
247                if ((status_cb != NULL) &&
248                        (memcmp(&curr_status, &last_status, sizeof(printer_state_dyn_t)) != 0)) {
249                    (*status_cb)(&curr_status, &last_status, param);
250                    memcpy(&last_status, &curr_status, sizeof(printer_state_dyn_t));
251                }
252                sleep(1);
253            }
254        }
255        monitor->monitor_running = 0;
256    } while (0);
257
258    if (status_cb != NULL) {
259        (*status_cb)(&curr_status, &last_status, param);
260    }
261}
262
263static void _stop(const ifc_status_monitor_t *this_p) {
264    // request a stop and release the semaphore
265    ipp_monitor_t *monitor;
266    LOGD("_stop(): enter");
267    do {
268        if (this_p == NULL) {
269            continue;
270        }
271
272        monitor = IMPL(ipp_monitor_t, ifc, this_p);
273        if (!monitor->initialized) {
274            continue;
275        }
276
277        sem_post(&monitor->monitor_sem);
278        monitor->stop_monitor = 1;
279    } while (0);
280}
281
282static status_t _cancel(const ifc_status_monitor_t *this_p, const char *requesting_user) {
283    status_t return_value = ERROR;
284    int job_id = -1;
285    ipp_monitor_t *monitor = NULL;
286    ipp_t *request = NULL;
287    ipp_t *response = NULL;
288    ipp_attribute_t *attr;
289
290    LOGD("_cancel(): enter");
291
292    monitor = IMPL(ipp_monitor_t, ifc, this_p);
293    if (this_p != NULL && monitor != NULL && monitor->initialized) {
294        pthread_mutex_lock(&monitor->mutex);
295        do {
296            if (monitor->stop_monitor) {
297                break;
298            }
299
300            request = ippNewRequest(IPP_GET_JOBS);
301            if (request == NULL) {
302                break;
303            }
304
305            ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
306                    monitor->printer_uri);
307            ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1);
308            ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
309                    NULL, requesting_user);
310
311            // Requested printer attributes
312            static const char *pattrs[] = {"job-id", "job-state", "job-state-reasons"};
313
314            ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes",
315                    sizeof(pattrs) / sizeof(pattrs[1]), NULL, pattrs);
316
317            response = ipp_doCupsRequest(monitor->http, request, monitor->http_resource,
318                    monitor->printer_uri);
319            if (response == NULL) {
320                ipp_status_t ipp_status = cupsLastError();
321                LOGD("_cancel get job attributes: response is null, ipp_status %d: %s",
322                        ipp_status, ippErrorString(ipp_status));
323                return_value = ERROR;
324            } else {
325                attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER);
326                if (attr != NULL) {
327                    job_id = ippGetInteger(attr, 0);
328                    LOGD("_cancel got job-id: %d", job_id);
329                } else { // We need the job id to attempt a cancel
330                    break;
331                }
332
333                attr = ippFindAttribute(response, "job-state", IPP_TAG_ENUM);
334                if (attr != NULL) {
335                    ipp_jstate_t jobState = (ipp_jstate_t)ippGetInteger(attr, 0);
336                    LOGD("_cancel got job-state: %d", jobState);
337                }
338
339                attr = ippFindAttribute(response, "job-state-reasons", IPP_TAG_KEYWORD);
340                if (attr != NULL) {
341                    int idx;
342                    for (idx = 0; idx < ippGetCount(attr); idx++) {
343                        LOGD("before job-state-reason (%d): %s", idx,
344                                ippGetString(attr, idx, NULL));
345                    }
346                }
347            }
348        } while (0);
349
350        ippDelete(request);
351        request = NULL;
352        ippDelete(response);
353        response = NULL;
354
355        do {
356            if (job_id == -1) {
357                break;
358            }
359
360            request = ippNewRequest(IPP_CANCEL_JOB);
361            if (request == NULL) {
362                break;
363            }
364
365            ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
366                    monitor->printer_uri);
367            ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job_id);
368            ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
369                    "requesting-user-name", NULL, requesting_user);
370
371            if ((response = ipp_doCupsRequest(monitor->http, request, monitor->http_resource,
372                    monitor->printer_uri)) == NULL) {
373                ipp_status_t ipp_status = cupsLastError();
374                LOGD("cancel:  response is null:  ipp_status %d %s", ipp_status,
375                        ippErrorString(ipp_status));
376                return_value = ERROR;
377            } else {
378                ipp_status_t ipp_status = cupsLastError();
379                LOGE("IPP_Status for cancel request was %d %s", ipp_status,
380                        ippErrorString(ipp_status));
381                attr = ippFindAttribute(response, "job-state-reasons", IPP_TAG_KEYWORD);
382                if (attr != NULL) {
383                    int idx;
384                    for (idx = 0; ippGetCount(attr); idx++) {
385                        LOGD("job-state-reason (%d): %s", idx, ippGetString(attr, idx, NULL));
386                    }
387                }
388                return_value = OK;
389            }
390        } while (0);
391
392        ippDelete(request);
393        ippDelete(response);
394
395        if (monitor->initialized) {
396            pthread_mutex_unlock(&monitor->mutex);
397        }
398    }
399    return return_value;
400}