print_backend_cups.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "printing/backend/print_backend.h"
6
7#include "build/build_config.h"
8
9#include <dlfcn.h>
10#include <errno.h>
11#include <pthread.h>
12
13#if defined(OS_MACOSX)
14#include <AvailabilityMacros.h>
15#else
16#include <gcrypt.h>
17#endif
18
19#include "base/file_util.h"
20#include "base/lazy_instance.h"
21#include "base/logging.h"
22#include "base/strings/string_number_conversions.h"
23#include "base/synchronization/lock.h"
24#include "base/values.h"
25#include "googleurl/src/gurl.h"
26#include "printing/backend/cups_helper.h"
27#include "printing/backend/print_backend_consts.h"
28
29#if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 4)
30const int CUPS_PRINTER_SCANNER = 0x2000000;  // Scanner-only device
31#endif
32
33#if !defined(OS_MACOSX)
34GCRY_THREAD_OPTION_PTHREAD_IMPL;
35
36namespace {
37
38// Init GCrypt library (needed for CUPS) using pthreads.
39// There exists a bug in CUPS library, where it crashed with: "ath.c:184:
40// _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed."
41// It happened when multiple threads tried printing simultaneously.
42// Google search for 'gnutls thread safety' provided a solution that
43// initialized gcrypt and gnutls.
44
45// TODO(phajdan.jr): Remove this after https://bugs.g10code.com/gnupg/issue1197
46// gets fixed on all Linux distros we support (i.e. when they ship libgcrypt
47// with the fix).
48
49// Initially, we linked with -lgnutls and simply called gnutls_global_init(),
50// but this did not work well since we build one binary on Ubuntu Hardy and
51// expect it to run on many Linux distros. (See http://crbug.com/46954)
52// So instead we use dlopen() and dlsym() to dynamically load and call
53// gnutls_global_init().
54
55class GcryptInitializer {
56 public:
57  GcryptInitializer() {
58    Init();
59  }
60
61 private:
62  void Init() {
63    const char* kGnuTlsFiles[] = {
64      "libgnutls.so.28",
65      "libgnutls.so.26",
66      "libgnutls.so",
67    };
68    gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
69    for (size_t i = 0; i < arraysize(kGnuTlsFiles); ++i) {
70      void* gnutls_lib = dlopen(kGnuTlsFiles[i], RTLD_NOW);
71      if (!gnutls_lib) {
72        VLOG(1) << "Cannot load " << kGnuTlsFiles[i];
73        continue;
74      }
75      const char* kGnuTlsInitFuncName = "gnutls_global_init";
76      int (*pgnutls_global_init)(void) = reinterpret_cast<int(*)()>(
77          dlsym(gnutls_lib, kGnuTlsInitFuncName));
78      if (!pgnutls_global_init) {
79        VLOG(1) << "Could not find " << kGnuTlsInitFuncName
80                << " in " << kGnuTlsFiles[i];
81        continue;
82      }
83      if ((*pgnutls_global_init)() != 0)
84        LOG(ERROR) << "gnutls_global_init() failed";
85      return;
86    }
87    LOG(ERROR) << "Cannot find libgnutls";
88  }
89};
90
91base::LazyInstance<GcryptInitializer> g_gcrypt_initializer =
92    LAZY_INSTANCE_INITIALIZER;
93
94}  // namespace
95#endif  // !defined(OS_MACOSX)
96
97namespace printing {
98
99static const char kCUPSPrinterInfoOpt[] = "printer-info";
100static const char kCUPSPrinterStateOpt[] = "printer-state";
101static const char kCUPSPrinterTypeOpt[] = "printer-type";
102static const char kCUPSPrinterMakeModelOpt[] = "printer-make-and-model";
103
104class PrintBackendCUPS : public PrintBackend {
105 public:
106  PrintBackendCUPS(const GURL& print_server_url,
107                   http_encryption_t encryption, bool blocking);
108
109  // PrintBackend implementation.
110  virtual bool EnumeratePrinters(PrinterList* printer_list) OVERRIDE;
111  virtual std::string GetDefaultPrinterName() OVERRIDE;
112  virtual bool GetPrinterSemanticCapsAndDefaults(
113      const std::string& printer_name,
114      PrinterSemanticCapsAndDefaults* printer_info) OVERRIDE;
115  virtual bool GetPrinterCapsAndDefaults(
116      const std::string& printer_name,
117      PrinterCapsAndDefaults* printer_info) OVERRIDE;
118  virtual std::string GetPrinterDriverInfo(
119      const std::string& printer_name) OVERRIDE;
120  virtual bool IsValidPrinter(const std::string& printer_name) OVERRIDE;
121
122 protected:
123  virtual ~PrintBackendCUPS() {}
124
125 private:
126  // Following functions are wrappers around corresponding CUPS functions.
127  // <functions>2()  are called when print server is specified, and plain
128  // version in another case. There is an issue specifing CUPS_HTTP_DEFAULT
129  // in the <functions>2(), it does not work in CUPS prior to 1.4.
130  int GetDests(cups_dest_t** dests);
131  base::FilePath GetPPD(const char* name);
132
133  GURL print_server_url_;
134  http_encryption_t cups_encryption_;
135  bool blocking_;
136};
137
138PrintBackendCUPS::PrintBackendCUPS(const GURL& print_server_url,
139                                   http_encryption_t encryption,
140                                   bool blocking)
141    : print_server_url_(print_server_url),
142      cups_encryption_(encryption),
143      blocking_(blocking) {
144}
145
146bool PrintBackendCUPS::EnumeratePrinters(PrinterList* printer_list) {
147  DCHECK(printer_list);
148  printer_list->clear();
149
150  cups_dest_t* destinations = NULL;
151  int num_dests = GetDests(&destinations);
152  if ((num_dests == 0) && (cupsLastError() > IPP_OK_EVENTS_COMPLETE)) {
153    VLOG(1) << "CUPS: Error getting printers from CUPS server"
154            << ", server: " << print_server_url_
155            << ", error: " << static_cast<int>(cupsLastError());
156    return false;
157  }
158
159  for (int printer_index = 0; printer_index < num_dests; printer_index++) {
160    const cups_dest_t& printer = destinations[printer_index];
161
162    // CUPS can have 'printers' that are actually scanners. (not MFC)
163    // At least on Mac. Check for scanners and skip them.
164    const char* type_str = cupsGetOption(kCUPSPrinterTypeOpt,
165        printer.num_options, printer.options);
166    if (type_str != NULL) {
167      int type;
168      if (base::StringToInt(type_str, &type) && (type & CUPS_PRINTER_SCANNER))
169        continue;
170    }
171
172    PrinterBasicInfo printer_info;
173    printer_info.printer_name = printer.name;
174    printer_info.is_default = printer.is_default;
175
176    const char* info = cupsGetOption(kCUPSPrinterInfoOpt,
177        printer.num_options, printer.options);
178    if (info != NULL)
179      printer_info.printer_description = info;
180
181    const char* state = cupsGetOption(kCUPSPrinterStateOpt,
182        printer.num_options, printer.options);
183    if (state != NULL)
184      base::StringToInt(state, &printer_info.printer_status);
185
186    const char* drv_info = cupsGetOption(kCUPSPrinterMakeModelOpt,
187                                         printer.num_options,
188                                         printer.options);
189    if (drv_info)
190      printer_info.options[kDriverInfoTagName] = *drv_info;
191
192    // Store printer options.
193    for (int opt_index = 0; opt_index < printer.num_options; opt_index++) {
194      printer_info.options[printer.options[opt_index].name] =
195          printer.options[opt_index].value;
196    }
197
198    printer_list->push_back(printer_info);
199  }
200
201  cupsFreeDests(num_dests, destinations);
202
203  VLOG(1) << "CUPS: Enumerated printers"
204          << ", server: " << print_server_url_
205          << ", # of printers: " << printer_list->size();
206  return true;
207}
208
209std::string PrintBackendCUPS::GetDefaultPrinterName() {
210  // Not using cupsGetDefault() because it lies about the default printer.
211  cups_dest_t* dests;
212  int num_dests = GetDests(&dests);
213  cups_dest_t* dest = cupsGetDest(NULL, NULL, num_dests, dests);
214  std::string name = dest ? std::string(dest->name) : std::string();
215  cupsFreeDests(num_dests, dests);
216  return name;
217}
218
219bool PrintBackendCUPS::GetPrinterSemanticCapsAndDefaults(
220    const std::string& printer_name,
221    PrinterSemanticCapsAndDefaults* printer_info) {
222  PrinterCapsAndDefaults info;
223  if (!GetPrinterCapsAndDefaults(printer_name, &info) )
224    return false;
225
226  return parsePpdCapabilities(
227      printer_name, info.printer_capabilities, printer_info);
228}
229
230bool PrintBackendCUPS::GetPrinterCapsAndDefaults(
231    const std::string& printer_name,
232    PrinterCapsAndDefaults* printer_info) {
233  DCHECK(printer_info);
234
235  VLOG(1) << "CUPS: Getting caps and defaults"
236          << ", printer name: " << printer_name;
237
238  base::FilePath ppd_path(GetPPD(printer_name.c_str()));
239  // In some cases CUPS failed to get ppd file.
240  if (ppd_path.empty()) {
241    LOG(ERROR) << "CUPS: Failed to get PPD"
242               << ", printer name: " << printer_name;
243    return false;
244  }
245
246  std::string content;
247  bool res = file_util::ReadFileToString(ppd_path, &content);
248
249  file_util::Delete(ppd_path, false);
250
251  if (res) {
252    printer_info->printer_capabilities.swap(content);
253    printer_info->caps_mime_type = "application/pagemaker";
254    // In CUPS, printer defaults is a part of PPD file. Nothing to upload here.
255    printer_info->printer_defaults.clear();
256    printer_info->defaults_mime_type.clear();
257  }
258
259  return res;
260}
261
262std::string PrintBackendCUPS::GetPrinterDriverInfo(
263    const std::string& printer_name) {
264  cups_dest_t* destinations = NULL;
265  int num_dests = GetDests(&destinations);
266  std::string result;
267  for (int printer_index = 0; printer_index < num_dests; printer_index++) {
268    const cups_dest_t& printer = destinations[printer_index];
269    if (printer_name == printer.name) {
270      const char* info = cupsGetOption(kCUPSPrinterMakeModelOpt,
271                                       printer.num_options,
272                                       printer.options);
273      if (info)
274        result = *info;
275    }
276  }
277
278  cupsFreeDests(num_dests, destinations);
279  return result;
280}
281
282bool PrintBackendCUPS::IsValidPrinter(const std::string& printer_name) {
283  // This is not very efficient way to get specific printer info. CUPS 1.4
284  // supports cupsGetNamedDest() function. However, CUPS 1.4 is not available
285  // everywhere (for example, it supported from Mac OS 10.6 only).
286  PrinterList printer_list;
287  EnumeratePrinters(&printer_list);
288
289  PrinterList::iterator it;
290  for (it = printer_list.begin(); it != printer_list.end(); ++it)
291    if (it->printer_name == printer_name)
292      return true;
293  return false;
294}
295
296scoped_refptr<PrintBackend> PrintBackend::CreateInstance(
297    const DictionaryValue* print_backend_settings) {
298#if !defined(OS_MACOSX)
299  // Initialize gcrypt library.
300  g_gcrypt_initializer.Get();
301#endif
302
303  std::string print_server_url_str, cups_blocking;
304  int encryption = HTTP_ENCRYPT_NEVER;
305  if (print_backend_settings) {
306    print_backend_settings->GetString(kCUPSPrintServerURL,
307                                      &print_server_url_str);
308
309    print_backend_settings->GetString(kCUPSBlocking,
310                                      &cups_blocking);
311
312    print_backend_settings->GetInteger(kCUPSEncryption, &encryption);
313  }
314  GURL print_server_url(print_server_url_str.c_str());
315  return new PrintBackendCUPS(print_server_url,
316                              static_cast<http_encryption_t>(encryption),
317                              cups_blocking == kValueTrue);
318}
319
320int PrintBackendCUPS::GetDests(cups_dest_t** dests) {
321  if (print_server_url_.is_empty()) {  // Use default (local) print server.
322    return cupsGetDests(dests);
323  } else {
324    HttpConnectionCUPS http(print_server_url_, cups_encryption_);
325    http.SetBlocking(blocking_);
326    return cupsGetDests2(http.http(), dests);
327  }
328}
329
330base::FilePath PrintBackendCUPS::GetPPD(const char* name) {
331  // cupsGetPPD returns a filename stored in a static buffer in CUPS.
332  // Protect this code with lock.
333  CR_DEFINE_STATIC_LOCAL(base::Lock, ppd_lock, ());
334  base::AutoLock ppd_autolock(ppd_lock);
335  base::FilePath ppd_path;
336  const char* ppd_file_path = NULL;
337  if (print_server_url_.is_empty()) {  // Use default (local) print server.
338    ppd_file_path = cupsGetPPD(name);
339    if (ppd_file_path)
340      ppd_path = base::FilePath(ppd_file_path);
341  } else {
342    // cupsGetPPD2 gets stuck sometimes in an infinite time due to network
343    // configuration/issues. To prevent that, use non-blocking http connection
344    // here.
345    // Note: After looking at CUPS sources, it looks like non-blocking
346    // connection will timeout after 10 seconds of no data period. And it will
347    // return the same way as if data was completely and sucessfully downloaded.
348    HttpConnectionCUPS http(print_server_url_, cups_encryption_);
349    http.SetBlocking(blocking_);
350    ppd_file_path = cupsGetPPD2(http.http(), name);
351    // Check if the get full PPD, since non-blocking call may simply return
352    // normally after timeout expired.
353    if (ppd_file_path) {
354      // There is no reliable way right now to detect full and complete PPD
355      // get downloaded. If we reach http timeout, it may simply return
356      // downloaded part as a full response. It might be good enough to check
357      // http->data_remaining or http->_data_remaining, unfortunately http_t
358      // is an internal structure and fields are not exposed in CUPS headers.
359      // httpGetLength or httpGetLength2 returning the full content size.
360      // Comparing file size against that content length might be unreliable
361      // since some http reponses are encoded and content_length > file size.
362      // Let's just check for the obvious CUPS and http errors here.
363      ppd_path = base::FilePath(ppd_file_path);
364      ipp_status_t error_code = cupsLastError();
365      int http_error = httpError(http.http());
366      if (error_code > IPP_OK_EVENTS_COMPLETE || http_error != 0) {
367        LOG(ERROR) << "Error downloading PPD file"
368                   << ", name: " << name
369                   << ", CUPS error: " << static_cast<int>(error_code)
370                   << ", HTTP error: " << http_error;
371        file_util::Delete(ppd_path, false);
372        ppd_path.clear();
373      }
374    }
375  }
376  return ppd_path;
377}
378
379}  // namespace printing
380