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