summaryrefslogtreecommitdiff
path: root/chromium/printing/backend/print_backend_cups.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/printing/backend/print_backend_cups.cc')
-rw-r--r--chromium/printing/backend/print_backend_cups.cc386
1 files changed, 386 insertions, 0 deletions
diff --git a/chromium/printing/backend/print_backend_cups.cc b/chromium/printing/backend/print_backend_cups.cc
new file mode 100644
index 00000000000..44c38bb96c3
--- /dev/null
+++ b/chromium/printing/backend/print_backend_cups.cc
@@ -0,0 +1,386 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "printing/backend/print_backend.h"
+
+#include "build/build_config.h"
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <pthread.h>
+
+#if defined(OS_MACOSX)
+#include <AvailabilityMacros.h>
+#else
+#include <gcrypt.h>
+#endif
+
+#include "base/debug/leak_annotations.h"
+#include "base/file_util.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/values.h"
+#include "printing/backend/cups_helper.h"
+#include "printing/backend/print_backend_consts.h"
+#include "url/gurl.h"
+
+#if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 4)
+const int CUPS_PRINTER_SCANNER = 0x2000000; // Scanner-only device
+#endif
+
+#if !defined(OS_MACOSX)
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+
+namespace {
+
+// Init GCrypt library (needed for CUPS) using pthreads.
+// There exists a bug in CUPS library, where it crashed with: "ath.c:184:
+// _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed."
+// It happened when multiple threads tried printing simultaneously.
+// Google search for 'gnutls thread safety' provided a solution that
+// initialized gcrypt and gnutls.
+
+// TODO(phajdan.jr): Remove this after https://bugs.g10code.com/gnupg/issue1197
+// gets fixed on all Linux distros we support (i.e. when they ship libgcrypt
+// with the fix).
+
+// Initially, we linked with -lgnutls and simply called gnutls_global_init(),
+// but this did not work well since we build one binary on Ubuntu Hardy and
+// expect it to run on many Linux distros. (See http://crbug.com/46954)
+// So instead we use dlopen() and dlsym() to dynamically load and call
+// gnutls_global_init().
+
+class GcryptInitializer {
+ public:
+ GcryptInitializer() {
+ Init();
+ }
+
+ private:
+ void Init() {
+ const char* kGnuTlsFiles[] = {
+ "libgnutls.so.28",
+ "libgnutls.so.26",
+ "libgnutls.so",
+ };
+ gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
+ for (size_t i = 0; i < arraysize(kGnuTlsFiles); ++i) {
+ void* gnutls_lib = dlopen(kGnuTlsFiles[i], RTLD_NOW);
+ if (!gnutls_lib) {
+ VLOG(1) << "Cannot load " << kGnuTlsFiles[i];
+ continue;
+ }
+ const char* kGnuTlsInitFuncName = "gnutls_global_init";
+ int (*pgnutls_global_init)(void) = reinterpret_cast<int(*)()>(
+ dlsym(gnutls_lib, kGnuTlsInitFuncName));
+ if (!pgnutls_global_init) {
+ VLOG(1) << "Could not find " << kGnuTlsInitFuncName
+ << " in " << kGnuTlsFiles[i];
+ continue;
+ }
+ {
+ // GnuTLS has a genuine small memory leak that is easier to annotate
+ // than suppress. See http://crbug.com/176888#c7
+ // TODO(earthdok): remove this once the leak is fixed.
+ ANNOTATE_SCOPED_MEMORY_LEAK;
+ if ((*pgnutls_global_init)() != 0)
+ LOG(ERROR) << "gnutls_global_init() failed";
+ }
+ return;
+ }
+ LOG(ERROR) << "Cannot find libgnutls";
+ }
+};
+
+base::LazyInstance<GcryptInitializer> g_gcrypt_initializer =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+#endif // !defined(OS_MACOSX)
+
+namespace printing {
+
+static const char kCUPSPrinterInfoOpt[] = "printer-info";
+static const char kCUPSPrinterStateOpt[] = "printer-state";
+static const char kCUPSPrinterTypeOpt[] = "printer-type";
+static const char kCUPSPrinterMakeModelOpt[] = "printer-make-and-model";
+
+class PrintBackendCUPS : public PrintBackend {
+ public:
+ PrintBackendCUPS(const GURL& print_server_url,
+ http_encryption_t encryption, bool blocking);
+
+ // PrintBackend implementation.
+ virtual bool EnumeratePrinters(PrinterList* printer_list) OVERRIDE;
+ virtual std::string GetDefaultPrinterName() OVERRIDE;
+ virtual bool GetPrinterSemanticCapsAndDefaults(
+ const std::string& printer_name,
+ PrinterSemanticCapsAndDefaults* printer_info) OVERRIDE;
+ virtual bool GetPrinterCapsAndDefaults(
+ const std::string& printer_name,
+ PrinterCapsAndDefaults* printer_info) OVERRIDE;
+ virtual std::string GetPrinterDriverInfo(
+ const std::string& printer_name) OVERRIDE;
+ virtual bool IsValidPrinter(const std::string& printer_name) OVERRIDE;
+
+ protected:
+ virtual ~PrintBackendCUPS() {}
+
+ private:
+ // Following functions are wrappers around corresponding CUPS functions.
+ // <functions>2() are called when print server is specified, and plain
+ // version in another case. There is an issue specifing CUPS_HTTP_DEFAULT
+ // in the <functions>2(), it does not work in CUPS prior to 1.4.
+ int GetDests(cups_dest_t** dests);
+ base::FilePath GetPPD(const char* name);
+
+ GURL print_server_url_;
+ http_encryption_t cups_encryption_;
+ bool blocking_;
+};
+
+PrintBackendCUPS::PrintBackendCUPS(const GURL& print_server_url,
+ http_encryption_t encryption,
+ bool blocking)
+ : print_server_url_(print_server_url),
+ cups_encryption_(encryption),
+ blocking_(blocking) {
+}
+
+bool PrintBackendCUPS::EnumeratePrinters(PrinterList* printer_list) {
+ DCHECK(printer_list);
+ printer_list->clear();
+
+ cups_dest_t* destinations = NULL;
+ int num_dests = GetDests(&destinations);
+ if ((num_dests == 0) && (cupsLastError() > IPP_OK_EVENTS_COMPLETE)) {
+ VLOG(1) << "CUPS: Error getting printers from CUPS server"
+ << ", server: " << print_server_url_
+ << ", error: " << static_cast<int>(cupsLastError());
+ return false;
+ }
+
+ for (int printer_index = 0; printer_index < num_dests; printer_index++) {
+ const cups_dest_t& printer = destinations[printer_index];
+
+ // CUPS can have 'printers' that are actually scanners. (not MFC)
+ // At least on Mac. Check for scanners and skip them.
+ const char* type_str = cupsGetOption(kCUPSPrinterTypeOpt,
+ printer.num_options, printer.options);
+ if (type_str != NULL) {
+ int type;
+ if (base::StringToInt(type_str, &type) && (type & CUPS_PRINTER_SCANNER))
+ continue;
+ }
+
+ PrinterBasicInfo printer_info;
+ printer_info.printer_name = printer.name;
+ printer_info.is_default = printer.is_default;
+
+ const char* info = cupsGetOption(kCUPSPrinterInfoOpt,
+ printer.num_options, printer.options);
+ if (info != NULL)
+ printer_info.printer_description = info;
+
+ const char* state = cupsGetOption(kCUPSPrinterStateOpt,
+ printer.num_options, printer.options);
+ if (state != NULL)
+ base::StringToInt(state, &printer_info.printer_status);
+
+ const char* drv_info = cupsGetOption(kCUPSPrinterMakeModelOpt,
+ printer.num_options,
+ printer.options);
+ if (drv_info)
+ printer_info.options[kDriverInfoTagName] = *drv_info;
+
+ // Store printer options.
+ for (int opt_index = 0; opt_index < printer.num_options; opt_index++) {
+ printer_info.options[printer.options[opt_index].name] =
+ printer.options[opt_index].value;
+ }
+
+ printer_list->push_back(printer_info);
+ }
+
+ cupsFreeDests(num_dests, destinations);
+
+ VLOG(1) << "CUPS: Enumerated printers"
+ << ", server: " << print_server_url_
+ << ", # of printers: " << printer_list->size();
+ return true;
+}
+
+std::string PrintBackendCUPS::GetDefaultPrinterName() {
+ // Not using cupsGetDefault() because it lies about the default printer.
+ cups_dest_t* dests;
+ int num_dests = GetDests(&dests);
+ cups_dest_t* dest = cupsGetDest(NULL, NULL, num_dests, dests);
+ std::string name = dest ? std::string(dest->name) : std::string();
+ cupsFreeDests(num_dests, dests);
+ return name;
+}
+
+bool PrintBackendCUPS::GetPrinterSemanticCapsAndDefaults(
+ const std::string& printer_name,
+ PrinterSemanticCapsAndDefaults* printer_info) {
+ PrinterCapsAndDefaults info;
+ if (!GetPrinterCapsAndDefaults(printer_name, &info) )
+ return false;
+
+ return parsePpdCapabilities(
+ printer_name, info.printer_capabilities, printer_info);
+}
+
+bool PrintBackendCUPS::GetPrinterCapsAndDefaults(
+ const std::string& printer_name,
+ PrinterCapsAndDefaults* printer_info) {
+ DCHECK(printer_info);
+
+ VLOG(1) << "CUPS: Getting caps and defaults"
+ << ", printer name: " << printer_name;
+
+ base::FilePath ppd_path(GetPPD(printer_name.c_str()));
+ // In some cases CUPS failed to get ppd file.
+ if (ppd_path.empty()) {
+ LOG(ERROR) << "CUPS: Failed to get PPD"
+ << ", printer name: " << printer_name;
+ return false;
+ }
+
+ std::string content;
+ bool res = file_util::ReadFileToString(ppd_path, &content);
+
+ base::DeleteFile(ppd_path, false);
+
+ if (res) {
+ printer_info->printer_capabilities.swap(content);
+ printer_info->caps_mime_type = "application/pagemaker";
+ // In CUPS, printer defaults is a part of PPD file. Nothing to upload here.
+ printer_info->printer_defaults.clear();
+ printer_info->defaults_mime_type.clear();
+ }
+
+ return res;
+}
+
+std::string PrintBackendCUPS::GetPrinterDriverInfo(
+ const std::string& printer_name) {
+ cups_dest_t* destinations = NULL;
+ int num_dests = GetDests(&destinations);
+ std::string result;
+ for (int printer_index = 0; printer_index < num_dests; printer_index++) {
+ const cups_dest_t& printer = destinations[printer_index];
+ if (printer_name == printer.name) {
+ const char* info = cupsGetOption(kCUPSPrinterMakeModelOpt,
+ printer.num_options,
+ printer.options);
+ if (info)
+ result = *info;
+ }
+ }
+
+ cupsFreeDests(num_dests, destinations);
+ return result;
+}
+
+bool PrintBackendCUPS::IsValidPrinter(const std::string& printer_name) {
+ // This is not very efficient way to get specific printer info. CUPS 1.4
+ // supports cupsGetNamedDest() function. However, CUPS 1.4 is not available
+ // everywhere (for example, it supported from Mac OS 10.6 only).
+ PrinterList printer_list;
+ EnumeratePrinters(&printer_list);
+
+ PrinterList::iterator it;
+ for (it = printer_list.begin(); it != printer_list.end(); ++it)
+ if (it->printer_name == printer_name)
+ return true;
+ return false;
+}
+
+scoped_refptr<PrintBackend> PrintBackend::CreateInstance(
+ const DictionaryValue* print_backend_settings) {
+#if !defined(OS_MACOSX)
+ // Initialize gcrypt library.
+ g_gcrypt_initializer.Get();
+#endif
+
+ std::string print_server_url_str, cups_blocking;
+ int encryption = HTTP_ENCRYPT_NEVER;
+ if (print_backend_settings) {
+ print_backend_settings->GetString(kCUPSPrintServerURL,
+ &print_server_url_str);
+
+ print_backend_settings->GetString(kCUPSBlocking,
+ &cups_blocking);
+
+ print_backend_settings->GetInteger(kCUPSEncryption, &encryption);
+ }
+ GURL print_server_url(print_server_url_str.c_str());
+ return new PrintBackendCUPS(print_server_url,
+ static_cast<http_encryption_t>(encryption),
+ cups_blocking == kValueTrue);
+}
+
+int PrintBackendCUPS::GetDests(cups_dest_t** dests) {
+ if (print_server_url_.is_empty()) { // Use default (local) print server.
+ return cupsGetDests(dests);
+ } else {
+ HttpConnectionCUPS http(print_server_url_, cups_encryption_);
+ http.SetBlocking(blocking_);
+ return cupsGetDests2(http.http(), dests);
+ }
+}
+
+base::FilePath PrintBackendCUPS::GetPPD(const char* name) {
+ // cupsGetPPD returns a filename stored in a static buffer in CUPS.
+ // Protect this code with lock.
+ CR_DEFINE_STATIC_LOCAL(base::Lock, ppd_lock, ());
+ base::AutoLock ppd_autolock(ppd_lock);
+ base::FilePath ppd_path;
+ const char* ppd_file_path = NULL;
+ if (print_server_url_.is_empty()) { // Use default (local) print server.
+ ppd_file_path = cupsGetPPD(name);
+ if (ppd_file_path)
+ ppd_path = base::FilePath(ppd_file_path);
+ } else {
+ // cupsGetPPD2 gets stuck sometimes in an infinite time due to network
+ // configuration/issues. To prevent that, use non-blocking http connection
+ // here.
+ // Note: After looking at CUPS sources, it looks like non-blocking
+ // connection will timeout after 10 seconds of no data period. And it will
+ // return the same way as if data was completely and sucessfully downloaded.
+ HttpConnectionCUPS http(print_server_url_, cups_encryption_);
+ http.SetBlocking(blocking_);
+ ppd_file_path = cupsGetPPD2(http.http(), name);
+ // Check if the get full PPD, since non-blocking call may simply return
+ // normally after timeout expired.
+ if (ppd_file_path) {
+ // There is no reliable way right now to detect full and complete PPD
+ // get downloaded. If we reach http timeout, it may simply return
+ // downloaded part as a full response. It might be good enough to check
+ // http->data_remaining or http->_data_remaining, unfortunately http_t
+ // is an internal structure and fields are not exposed in CUPS headers.
+ // httpGetLength or httpGetLength2 returning the full content size.
+ // Comparing file size against that content length might be unreliable
+ // since some http reponses are encoded and content_length > file size.
+ // Let's just check for the obvious CUPS and http errors here.
+ ppd_path = base::FilePath(ppd_file_path);
+ ipp_status_t error_code = cupsLastError();
+ int http_error = httpError(http.http());
+ if (error_code > IPP_OK_EVENTS_COMPLETE || http_error != 0) {
+ LOG(ERROR) << "Error downloading PPD file"
+ << ", name: " << name
+ << ", CUPS error: " << static_cast<int>(error_code)
+ << ", HTTP error: " << http_error;
+ base::DeleteFile(ppd_path, false);
+ ppd_path.clear();
+ }
+ }
+ }
+ return ppd_path;
+}
+
+} // namespace printing