Home | History | Annotate | Download | only in port_monitor
      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 "cloud_print/virtual_driver/win/port_monitor/port_monitor.h"
      6 
      7 #include <lmcons.h>
      8 #include <shellapi.h>
      9 #include <shlobj.h>
     10 #include <strsafe.h>
     11 #include <userenv.h>
     12 #include <windows.h>
     13 #include <winspool.h>
     14 
     15 #include "base/at_exit.h"
     16 #include "base/command_line.h"
     17 #include "base/file_util.h"
     18 #include "base/files/file_enumerator.h"
     19 #include "base/logging.h"
     20 #include "base/path_service.h"
     21 #include "base/process/process.h"
     22 #include "base/process/launch.h"
     23 #include "base/strings/string16.h"
     24 #include "base/win/registry.h"
     25 #include "base/win/scoped_handle.h"
     26 #include "base/win/windows_version.h"
     27 #include "chrome/common/chrome_switches.h"
     28 #include "chrome/installer/launcher_support/chrome_launcher_support.h"
     29 #include "cloud_print/common/win/cloud_print_utils.h"
     30 #include "cloud_print/virtual_driver/win/port_monitor/spooler_win.h"
     31 #include "cloud_print/virtual_driver/win/virtual_driver_consts.h"
     32 #include "cloud_print/virtual_driver/win/virtual_driver_helpers.h"
     33 
     34 namespace cloud_print {
     35 
     36 namespace {
     37 
     38 const wchar_t kIePath[] = L"Internet Explorer\\iexplore.exe";
     39 
     40 const char kChromeInstallUrl[] =
     41     "http://google.com/cloudprint/learn/chrome.html";
     42 
     43 const wchar_t kCloudPrintRegKey[] = L"Software\\Google\\CloudPrint";
     44 
     45 const wchar_t kXpsMimeType[] = L"application/vnd.ms-xpsdocument";
     46 
     47 const wchar_t kAppDataDir[] = L"Google\\Cloud Printer";
     48 
     49 struct MonitorData {
     50   scoped_ptr<base::AtExitManager> at_exit_manager;
     51 };
     52 
     53 struct PortData {
     54   PortData() : job_id(0), printer_handle(NULL), file(0) {
     55   }
     56   ~PortData() {
     57     Close();
     58   }
     59   void Close() {
     60     if (printer_handle) {
     61       ClosePrinter(printer_handle);
     62       printer_handle = NULL;
     63     }
     64     if (file) {
     65       file_util::CloseFile(file);
     66       file = NULL;
     67     }
     68   }
     69   DWORD job_id;
     70   HANDLE printer_handle;
     71   FILE* file;
     72   base::FilePath file_path;
     73 };
     74 
     75 typedef struct {
     76   ACCESS_MASK granted_access;
     77 } XcvUiData;
     78 
     79 
     80 MONITORUI g_monitor_ui = {
     81   sizeof(MONITORUI),
     82   MonitorUiAddPortUi,
     83   MonitorUiConfigureOrDeletePortUI,
     84   MonitorUiConfigureOrDeletePortUI
     85 };
     86 
     87 MONITOR2 g_monitor_2 = {
     88   sizeof(MONITOR2),
     89   Monitor2EnumPorts,
     90   Monitor2OpenPort,
     91   NULL,           // OpenPortEx is not supported.
     92   Monitor2StartDocPort,
     93   Monitor2WritePort,
     94   Monitor2ReadPort,
     95   Monitor2EndDocPort,
     96   Monitor2ClosePort,
     97   NULL,           // AddPort is not supported.
     98   NULL,           // AddPortEx is not supported.
     99   NULL,           // ConfigurePort is not supported.
    100   NULL,           // DeletePort is not supported.
    101   NULL,
    102   NULL,           // SetPortTimeOuts is not supported.
    103   Monitor2XcvOpenPort,
    104   Monitor2XcvDataPort,
    105   Monitor2XcvClosePort,
    106   Monitor2Shutdown
    107 };
    108 
    109 base::FilePath GetAppDataDir() {
    110   base::FilePath file_path;
    111   base::win::Version version = base::win::GetVersion();
    112   int path_id = (version >= base::win::VERSION_VISTA) ?
    113                 base::DIR_LOCAL_APP_DATA_LOW : base::DIR_LOCAL_APP_DATA;
    114   if (!PathService::Get(path_id, &file_path)) {
    115     LOG(ERROR) << "Can't get DIR_LOCAL_APP_DATA";
    116     return base::FilePath();
    117   }
    118   return file_path.Append(kAppDataDir);
    119 }
    120 
    121 // Delete files which where not deleted by chrome.
    122 void DeleteLeakedFiles(const base::FilePath& dir) {
    123   base::Time delete_before = base::Time::Now() - base::TimeDelta::FromDays(1);
    124   base::FileEnumerator enumerator(dir, false, base::FileEnumerator::FILES);
    125   for (base::FilePath file_path = enumerator.Next(); !file_path.empty();
    126        file_path = enumerator.Next()) {
    127     if (enumerator.GetInfo().GetLastModifiedTime() < delete_before)
    128       base::DeleteFile(file_path, false);
    129   }
    130 }
    131 
    132 // Attempts to retrieve the title of the specified print job.
    133 // On success returns TRUE and the first title_chars characters of the job title
    134 // are copied into title.
    135 // On failure returns FALSE and title is unmodified.
    136 bool GetJobTitle(HANDLE printer_handle,
    137                  DWORD job_id,
    138                  string16 *title) {
    139   DCHECK(printer_handle != NULL);
    140   DCHECK(title != NULL);
    141   DWORD bytes_needed = 0;
    142   GetJob(printer_handle, job_id, 1, NULL, 0, &bytes_needed);
    143   if (bytes_needed == 0) {
    144     LOG(ERROR) << "Unable to get bytes needed for job info.";
    145     return false;
    146   }
    147   scoped_ptr<BYTE[]> buffer(new BYTE[bytes_needed]);
    148   if (!GetJob(printer_handle,
    149               job_id,
    150               1,
    151               buffer.get(),
    152               bytes_needed,
    153               &bytes_needed)) {
    154     LOG(ERROR) << "Unable to get job info.";
    155     return false;
    156   }
    157   JOB_INFO_1* job_info = reinterpret_cast<JOB_INFO_1*>(buffer.get());
    158   *title = job_info->pDocument;
    159   return true;
    160 }
    161 
    162 // Handler for the UI functions exported by the port monitor.
    163 // Verifies that a valid parent Window exists and then just displays an
    164 // error message to let the user know that there is no interactive
    165 // configuration.
    166 void HandlePortUi(HWND hwnd, const string16& caption) {
    167   if (hwnd != NULL && IsWindow(hwnd)) {
    168     DisplayWindowsMessage(hwnd, CO_E_NOT_SUPPORTED, cloud_print::kPortName);
    169   }
    170 }
    171 
    172 // Gets the primary token for the user that submitted the print job.
    173 bool GetUserToken(HANDLE* primary_token) {
    174   HANDLE token = NULL;
    175   if (!OpenThreadToken(GetCurrentThread(),
    176                       TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
    177                       FALSE,
    178                       &token)) {
    179     LOG(ERROR) << "Unable to get thread token.";
    180     return false;
    181   }
    182   base::win::ScopedHandle token_scoped(token);
    183   if (!DuplicateTokenEx(token,
    184                         TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
    185                         NULL,
    186                         SecurityImpersonation,
    187                         TokenPrimary,
    188                         primary_token)) {
    189     LOG(ERROR) << "Unable to get primary thread token.";
    190     return false;
    191   }
    192   return true;
    193 }
    194 
    195 // Launches the Cloud Print dialog in Chrome.
    196 // xps_path references a file to print.
    197 // job_title is the title to be used for the resulting print job.
    198 bool LaunchPrintDialog(const base::FilePath& xps_path,
    199                        const string16& job_title) {
    200   HANDLE token = NULL;
    201   if (!GetUserToken(&token)) {
    202     LOG(ERROR) << "Unable to get user token.";
    203     return false;
    204   }
    205   base::win::ScopedHandle primary_token_scoped(token);
    206 
    207   base::FilePath chrome_path = GetChromeExePath();
    208   if (chrome_path.empty()) {
    209     LOG(ERROR) << "Unable to get chrome exe path.";
    210     return false;
    211   }
    212 
    213   CommandLine command_line(chrome_path);
    214 
    215   base::FilePath chrome_profile = GetChromeProfilePath();
    216   if (!chrome_profile.empty()) {
    217     command_line.AppendSwitchPath(switches::kUserDataDir, chrome_profile);
    218   }
    219 
    220   command_line.AppendSwitchPath(switches::kCloudPrintFile,
    221                                 xps_path);
    222   command_line.AppendSwitchNative(switches::kCloudPrintFileType,
    223                                   kXpsMimeType);
    224   command_line.AppendSwitchNative(switches::kCloudPrintJobTitle,
    225                                   job_title);
    226   command_line.AppendSwitch(switches::kCloudPrintDeleteFile);
    227   base::LaunchOptions options;
    228   options.as_user = primary_token_scoped;
    229   base::LaunchProcess(command_line, options, NULL);
    230   return true;
    231 }
    232 
    233 // Launches a page to allow the user to download chrome.
    234 // TODO(abodenha (at) chromium.org) Point to a custom page explaining what's wrong
    235 // rather than the generic chrome download page.  See
    236 // http://code.google.com/p/chromium/issues/detail?id=112019
    237 void LaunchChromeDownloadPage() {
    238   if (kIsUnittest)
    239     return;
    240   HANDLE token = NULL;
    241   if (!GetUserToken(&token)) {
    242     LOG(ERROR) << "Unable to get user token.";
    243     return;
    244   }
    245   base::win::ScopedHandle token_scoped(token);
    246 
    247   base::FilePath ie_path;
    248   PathService::Get(base::DIR_PROGRAM_FILESX86, &ie_path);
    249   ie_path = ie_path.Append(kIePath);
    250   CommandLine command_line(ie_path);
    251   command_line.AppendArg(kChromeInstallUrl);
    252 
    253   base::LaunchOptions options;
    254   options.as_user = token_scoped;
    255   base::LaunchProcess(command_line, options, NULL);
    256 }
    257 
    258 // Returns false if the print job is being run in a context
    259 // that shouldn't be launching Chrome.
    260 bool ValidateCurrentUser() {
    261   HANDLE token = NULL;
    262   if (!GetUserToken(&token)) {
    263     // If we can't get the token we're probably not impersonating
    264     // the user, so validation should fail.
    265     return false;
    266   }
    267   base::win::ScopedHandle token_scoped(token);
    268 
    269   if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
    270     DWORD session_id = 0;
    271     DWORD dummy;
    272     if (!GetTokenInformation(token_scoped,
    273                              TokenSessionId,
    274                              reinterpret_cast<void *>(&session_id),
    275                              sizeof(DWORD),
    276                              &dummy)) {
    277       return false;
    278     }
    279     if (session_id == 0) {
    280       return false;
    281     }
    282   }
    283   return true;
    284 }
    285 }  // namespace
    286 
    287 base::FilePath ReadPathFromRegistry(HKEY root, const wchar_t* path_name) {
    288   base::win::RegKey gcp_key(HKEY_CURRENT_USER, kCloudPrintRegKey, KEY_READ);
    289   string16 data;
    290   if (SUCCEEDED(gcp_key.ReadValue(path_name, &data)) &&
    291       base::PathExists(base::FilePath(data))) {
    292     return base::FilePath(data);
    293   }
    294   return base::FilePath();
    295 }
    296 
    297 base::FilePath ReadPathFromAnyRegistry(const wchar_t* path_name) {
    298   base::FilePath result = ReadPathFromRegistry(HKEY_CURRENT_USER, path_name);
    299   if (!result.empty())
    300     return result;
    301   return ReadPathFromRegistry(HKEY_LOCAL_MACHINE, path_name);
    302 }
    303 
    304 base::FilePath GetChromeExePath() {
    305   base::FilePath path = ReadPathFromAnyRegistry(kChromeExePathRegValue);
    306   if (!path.empty())
    307     return path;
    308   return chrome_launcher_support::GetAnyChromePath();
    309 }
    310 
    311 base::FilePath GetChromeProfilePath() {
    312   base::FilePath path = ReadPathFromAnyRegistry(kChromeProfilePathRegValue);
    313   if (!path.empty() && base::DirectoryExists(path))
    314     return path;
    315   return base::FilePath();
    316 }
    317 
    318 BOOL WINAPI Monitor2EnumPorts(HANDLE,
    319                               wchar_t*,
    320                               DWORD level,
    321                               BYTE*  ports,
    322                               DWORD   ports_size,
    323                               DWORD* needed_bytes,
    324                               DWORD* returned) {
    325   if (needed_bytes == NULL) {
    326     LOG(ERROR) << "needed_bytes should not be NULL.";
    327     SetLastError(ERROR_INVALID_PARAMETER);
    328     return FALSE;
    329   }
    330   if (level == 1) {
    331     *needed_bytes = sizeof(PORT_INFO_1);
    332   } else if (level == 2) {
    333     *needed_bytes = sizeof(PORT_INFO_2);
    334   } else {
    335     LOG(ERROR) << "Level "  << level << "is not supported.";
    336     SetLastError(ERROR_INVALID_LEVEL);
    337     return FALSE;
    338   }
    339   *needed_bytes += static_cast<DWORD>(cloud_print::kPortNameSize);
    340   if (ports_size < *needed_bytes) {
    341     LOG(WARNING) << *needed_bytes << " bytes are required.  Only "
    342                  << ports_size << " were allocated.";
    343     SetLastError(ERROR_INSUFFICIENT_BUFFER);
    344     return FALSE;
    345   }
    346   if (ports == NULL) {
    347     LOG(ERROR) << "ports should not be NULL.";
    348     SetLastError(ERROR_INVALID_PARAMETER);
    349     return FALSE;
    350   }
    351   if (returned == NULL) {
    352     LOG(ERROR) << "returned should not be NULL.";
    353     SetLastError(ERROR_INVALID_PARAMETER);
    354     return FALSE;
    355   }
    356 
    357   // Windows expects any strings refernced by PORT_INFO_X structures to
    358   // appear at the END of the buffer referenced by ports.  Placing
    359   // strings immediately after the PORT_INFO_X structure will cause
    360   // EnumPorts to fail until the spooler is restarted.
    361   // This is NOT mentioned in the documentation.
    362   wchar_t* string_target =
    363       reinterpret_cast<wchar_t*>(ports + ports_size -
    364       cloud_print::kPortNameSize);
    365   if (level == 1) {
    366     PORT_INFO_1* port_info = reinterpret_cast<PORT_INFO_1*>(ports);
    367     port_info->pName = string_target;
    368     StringCbCopy(port_info->pName,
    369                  cloud_print::kPortNameSize,
    370                  cloud_print::kPortName);
    371   } else {
    372     PORT_INFO_2* port_info = reinterpret_cast<PORT_INFO_2*>(ports);
    373     port_info->pPortName = string_target;
    374     StringCbCopy(port_info->pPortName,
    375                  cloud_print::kPortNameSize,
    376                  cloud_print::kPortName);
    377     port_info->pMonitorName = NULL;
    378     port_info->pDescription = NULL;
    379     port_info->fPortType = PORT_TYPE_WRITE;
    380     port_info->Reserved = 0;
    381   }
    382   *returned = 1;
    383   return TRUE;
    384 }
    385 
    386 BOOL WINAPI Monitor2OpenPort(HANDLE, wchar_t*, HANDLE* handle) {
    387   PortData* port_data = new PortData();
    388   if (port_data == NULL) {
    389     LOG(ERROR) << "Unable to allocate memory for internal structures.";
    390     SetLastError(E_OUTOFMEMORY);
    391     return FALSE;
    392   }
    393   if (handle == NULL) {
    394     LOG(ERROR) << "handle should not be NULL.";
    395     SetLastError(ERROR_INVALID_PARAMETER);
    396     return FALSE;
    397   }
    398   *handle = (HANDLE)port_data;
    399   return TRUE;
    400 }
    401 
    402 BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle,
    403                                  wchar_t* printer_name,
    404                                  DWORD job_id,
    405                                  DWORD,
    406                                  BYTE*) {
    407   SetGoogleUpdateUsage(kGoogleUpdateProductId);
    408   if (port_handle == NULL) {
    409     LOG(ERROR) << "port_handle should not be NULL.";
    410     SetLastError(ERROR_INVALID_PARAMETER);
    411     return FALSE;
    412   }
    413   if (printer_name == NULL) {
    414     LOG(ERROR) << "printer_name should not be NULL.";
    415     SetLastError(ERROR_INVALID_PARAMETER);
    416     return FALSE;
    417   }
    418   if (!ValidateCurrentUser()) {
    419     // TODO(abodenha (at) chromium.org) Abort the print job.
    420     return FALSE;
    421   }
    422   PortData* port_data = reinterpret_cast<PortData*>(port_handle);
    423   port_data->job_id = job_id;
    424   if (!OpenPrinter(printer_name, &(port_data->printer_handle), NULL)) {
    425     LOG(WARNING) << "Unable to open printer " << printer_name << ".";
    426     // We can continue without a handle to the printer.
    427     // It just means we can't get the job title or tell the spooler that
    428     // the print job is complete.
    429     // This is the normal flow during a unit test.
    430     port_data->printer_handle = NULL;
    431   }
    432   base::FilePath& file_path = port_data->file_path;
    433   base::FilePath app_data_dir = GetAppDataDir();
    434   if (app_data_dir.empty())
    435     return FALSE;
    436   DeleteLeakedFiles(app_data_dir);
    437   if (!file_util::CreateDirectory(app_data_dir) ||
    438       !file_util::CreateTemporaryFileInDir(app_data_dir, &file_path)) {
    439     LOG(ERROR) << "Can't create temporary file in " << app_data_dir.value();
    440     return FALSE;
    441   }
    442   port_data->file = file_util::OpenFile(file_path, "wb+");
    443   if (port_data->file == NULL) {
    444     LOG(ERROR) << "Error opening file " << file_path.value() << ".";
    445     return FALSE;
    446   }
    447   return TRUE;
    448 }
    449 
    450 BOOL WINAPI Monitor2WritePort(HANDLE port_handle,
    451                               BYTE* buffer,
    452                               DWORD buffer_size,
    453                               DWORD* bytes_written) {
    454   PortData* port_data = reinterpret_cast<PortData*>(port_handle);
    455   if (!ValidateCurrentUser()) {
    456     // TODO(abodenha (at) chromium.org) Abort the print job.
    457     return FALSE;
    458   }
    459   *bytes_written =
    460       static_cast<DWORD>(fwrite(buffer, 1, buffer_size, port_data->file));
    461   if (*bytes_written > 0) {
    462     return TRUE;
    463   } else {
    464     return FALSE;
    465   }
    466 }
    467 
    468 BOOL WINAPI Monitor2ReadPort(HANDLE, BYTE*, DWORD, DWORD* read_bytes) {
    469   LOG(ERROR) << "Read is not supported.";
    470   *read_bytes = 0;
    471   SetLastError(ERROR_NOT_SUPPORTED);
    472   return FALSE;
    473 }
    474 
    475 BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle) {
    476   if (!ValidateCurrentUser()) {
    477     // TODO(abodenha (at) chromium.org) Abort the print job.
    478     return FALSE;
    479   }
    480   PortData* port_data = reinterpret_cast<PortData*>(port_handle);
    481   if (port_data == NULL) {
    482     SetLastError(ERROR_INVALID_PARAMETER);
    483     return FALSE;
    484   }
    485 
    486   if (port_data->file != NULL) {
    487     file_util::CloseFile(port_data->file);
    488     port_data->file = NULL;
    489     bool delete_file = true;
    490     int64 file_size = 0;
    491     file_util::GetFileSize(port_data->file_path, &file_size);
    492     if (file_size > 0) {
    493       string16 job_title;
    494       if (port_data->printer_handle != NULL) {
    495         GetJobTitle(port_data->printer_handle,
    496                     port_data->job_id,
    497                     &job_title);
    498       }
    499       if (!LaunchPrintDialog(port_data->file_path, job_title)) {
    500         LaunchChromeDownloadPage();
    501       } else {
    502         delete_file = false;
    503       }
    504     }
    505     if (delete_file)
    506       base::DeleteFile(port_data->file_path, false);
    507   }
    508   if (port_data->printer_handle != NULL) {
    509     // Tell the spooler that the job is complete.
    510     SetJob(port_data->printer_handle,
    511            port_data->job_id,
    512            0,
    513            NULL,
    514            JOB_CONTROL_SENT_TO_PRINTER);
    515   }
    516   port_data->Close();
    517   // Return success even if we can't display the dialog.
    518   // TODO(abodenha (at) chromium.org) Come up with a better way of handling
    519   // this situation.
    520   return TRUE;
    521 }
    522 
    523 BOOL WINAPI Monitor2ClosePort(HANDLE port_handle) {
    524   if (port_handle == NULL) {
    525     LOG(ERROR) << "port_handle should not be NULL.";
    526     SetLastError(ERROR_INVALID_PARAMETER);
    527     return FALSE;
    528   }
    529   PortData* port_data = reinterpret_cast<PortData*>(port_handle);
    530   delete port_data;
    531   return TRUE;
    532 }
    533 
    534 VOID WINAPI Monitor2Shutdown(HANDLE monitor_handle) {
    535   if (monitor_handle != NULL) {
    536     MonitorData* monitor_data =
    537       reinterpret_cast<MonitorData*>(monitor_handle);
    538     delete monitor_handle;
    539   }
    540 }
    541 
    542 BOOL WINAPI Monitor2XcvOpenPort(HANDLE,
    543                                 const wchar_t*,
    544                                 ACCESS_MASK granted_access,
    545                                 HANDLE* handle) {
    546   if (handle == NULL) {
    547     LOG(ERROR) << "handle should not be NULL.";
    548     SetLastError(ERROR_INVALID_PARAMETER);
    549     return FALSE;
    550   }
    551   XcvUiData* xcv_data = new XcvUiData();
    552   if (xcv_data == NULL) {
    553     LOG(ERROR) << "Unable to allocate memory for internal structures.";
    554     SetLastError(E_OUTOFMEMORY);
    555     return FALSE;
    556   }
    557   xcv_data->granted_access = granted_access;
    558   *handle = (HANDLE)xcv_data;
    559   return TRUE;
    560 }
    561 
    562 DWORD WINAPI Monitor2XcvDataPort(HANDLE xcv_handle,
    563                                  const wchar_t* data_name,
    564                                  BYTE*,
    565                                  DWORD,
    566                                  BYTE* output_data,
    567                                  DWORD output_data_bytes,
    568                                  DWORD* output_data_bytes_needed) {
    569   XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(xcv_handle);
    570   DWORD ret_val = ERROR_SUCCESS;
    571   if ((xcv_data->granted_access & SERVER_ACCESS_ADMINISTER) == 0) {
    572     return ERROR_ACCESS_DENIED;
    573   }
    574   if (output_data == NULL || output_data_bytes == 0) {
    575     return ERROR_INVALID_PARAMETER;
    576   }
    577   // We don't handle AddPort or DeletePort since we don't support
    578   // dynamic creation of ports.
    579   if (lstrcmp(L"MonitorUI", data_name) == 0) {
    580     DWORD dll_path_len = 0;
    581     base::FilePath dll_path(GetPortMonitorDllName());
    582     dll_path_len = static_cast<DWORD>(dll_path.value().length());
    583     if (output_data_bytes_needed != NULL) {
    584       *output_data_bytes_needed = dll_path_len;
    585     }
    586     if (output_data_bytes < dll_path_len) {
    587         return ERROR_INSUFFICIENT_BUFFER;
    588     } else {
    589         ret_val = StringCbCopy(reinterpret_cast<wchar_t*>(output_data),
    590                                output_data_bytes,
    591                                dll_path.value().c_str());
    592     }
    593   } else {
    594     return ERROR_INVALID_PARAMETER;
    595   }
    596   return ret_val;
    597 }
    598 
    599 BOOL WINAPI Monitor2XcvClosePort(HANDLE handle) {
    600   XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(handle);
    601   delete xcv_data;
    602   return TRUE;
    603 }
    604 
    605 BOOL WINAPI MonitorUiAddPortUi(const wchar_t*,
    606                                HWND hwnd,
    607                                const wchar_t* monitor_name,
    608                                wchar_t**) {
    609   HandlePortUi(hwnd, monitor_name);
    610   return TRUE;
    611 }
    612 
    613 BOOL WINAPI MonitorUiConfigureOrDeletePortUI(const wchar_t*,
    614                                              HWND hwnd,
    615                                              const wchar_t* port_name) {
    616   HandlePortUi(hwnd, port_name);
    617   return TRUE;
    618 }
    619 
    620 }   // namespace cloud_print
    621 
    622 MONITOR2* WINAPI InitializePrintMonitor2(MONITORINIT*,
    623                                          HANDLE* handle) {
    624   cloud_print::MonitorData* monitor_data = new cloud_print::MonitorData;
    625   if (monitor_data == NULL) {
    626     return NULL;
    627   }
    628   if (handle != NULL) {
    629     *handle = (HANDLE)monitor_data;
    630     if (!cloud_print::kIsUnittest) {
    631       // Unit tests set up their own AtExitManager
    632       monitor_data->at_exit_manager.reset(new base::AtExitManager());
    633       // Single spooler.exe handles verbose users.
    634       PathService::DisableCache();
    635     }
    636   } else {
    637     SetLastError(ERROR_INVALID_PARAMETER);
    638     return NULL;
    639   }
    640   return &cloud_print::g_monitor_2;
    641 }
    642 
    643 MONITORUI* WINAPI InitializePrintMonitorUI(void) {
    644   return &cloud_print::g_monitor_ui;
    645 }
    646 
    647