mcicda: Exclude unused headers.
[wine] / dlls / localspl / localmon.c
1 /*
2  * Implementation of the Local Printmonitor
3  *
4  * Copyright 2006 Detlef Riekenberg
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include <stdarg.h>
22
23 #define COBJMACROS
24 #define NONAMELESSUNION
25
26 #include "windef.h"
27 #include "winbase.h"
28 #include "wingdi.h"
29 #include "winuser.h"
30 #include "winreg.h"
31 #include "winver.h"
32 #include "winnls.h"
33
34 #include "winspool.h"
35 #include "ddk/winsplp.h"
36 #include "localspl_private.h"
37
38 #include "wine/debug.h"
39 #include "wine/list.h"
40 #include "wine/unicode.h"
41
42
43 WINE_DEFAULT_DEBUG_CHANNEL(localspl);
44
45 /*****************************************************/
46
47 static CRITICAL_SECTION xcv_handles_cs;
48 static CRITICAL_SECTION_DEBUG xcv_handles_cs_debug =
49 {
50     0, 0, &xcv_handles_cs,
51     { &xcv_handles_cs_debug.ProcessLocksList, &xcv_handles_cs_debug.ProcessLocksList },
52       0, 0, { (DWORD_PTR)(__FILE__ ": xcv_handles_cs") }
53 };
54 static CRITICAL_SECTION xcv_handles_cs = { &xcv_handles_cs_debug, -1, 0, 0, 0, 0 };
55
56 /* ############################### */
57
58 typedef struct {
59     struct list entry;
60     ACCESS_MASK GrantedAccess;
61     WCHAR       nameW[1];
62 } xcv_t;
63
64 static struct list xcv_handles = LIST_INIT( xcv_handles );
65
66 /* ############################### */
67
68 static const WCHAR cmd_AddPortW[] = {'A','d','d','P','o','r','t',0};
69 static const WCHAR cmd_DeletePortW[] = {'D','e','l','e','t','e','P','o','r','t',0};
70 static const WCHAR cmd_ConfigureLPTPortCommandOKW[] = {'C','o','n','f','i','g','u','r','e',
71                                     'L','P','T','P','o','r','t',
72                                     'C','o','m','m','a','n','d','O','K',0};
73
74 static const WCHAR cmd_GetDefaultCommConfigW[] = {'G','e','t',
75                                     'D','e','f','a','u','l','t',
76                                     'C','o','m','m','C','o','n','f','i','g',0};
77
78 static const WCHAR cmd_GetTransmissionRetryTimeoutW[] = {'G','e','t',
79                                     'T','r','a','n','s','m','i','s','s','i','o','n',
80                                     'R','e','t','r','y','T','i','m','e','o','u','t',0};
81
82 static const WCHAR cmd_MonitorUIW[] = {'M','o','n','i','t','o','r','U','I',0};
83 static const WCHAR cmd_PortIsValidW[] = {'P','o','r','t','I','s','V','a','l','i','d',0};
84 static const WCHAR cmd_SetDefaultCommConfigW[] = {'S','e','t',
85                                     'D','e','f','a','u','l','t',
86                                     'C','o','m','m','C','o','n','f','i','g',0};
87
88 static const WCHAR dllnameuiW[] = {'l','o','c','a','l','u','i','.','d','l','l',0};
89 static const WCHAR emptyW[] = {0};
90
91 static const WCHAR portname_LPT[]  = {'L','P','T',0};
92 static const WCHAR portname_COM[]  = {'C','O','M',0};
93 static const WCHAR portname_FILE[] = {'F','I','L','E',':',0};
94 static const WCHAR portname_CUPS[] = {'C','U','P','S',':',0};
95 static const WCHAR portname_LPR[]  = {'L','P','R',':',0};
96
97 static const WCHAR TransmissionRetryTimeoutW[] = {'T','r','a','n','s','m','i','s','s','i','o','n',
98                                     'R','e','t','r','y','T','i','m','e','o','u','t',0};
99
100 static const WCHAR WinNT_CV_PortsW[] = {'S','o','f','t','w','a','r','e','\\',
101                                         'M','i','c','r','o','s','o','f','t','\\',
102                                         'W','i','n','d','o','w','s',' ','N','T','\\',
103                                         'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
104                                         'P','o','r','t','s',0};
105
106 static const WCHAR WinNT_CV_WindowsW[] = {'S','o','f','t','w','a','r','e','\\',
107                                         'M','i','c','r','o','s','o','f','t','\\',
108                                         'W','i','n','d','o','w','s',' ','N','T','\\',
109                                         'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
110                                         'W','i','n','d','o','w','s',0};
111
112
113 /******************************************************************
114  * does_port_exist (internal)
115  *
116  * returns TRUE, when the Port already exists
117  *
118  */
119 static BOOL does_port_exist(LPCWSTR myname)
120 {
121
122     LPPORT_INFO_1W  pi;
123     DWORD   needed = 0;
124     DWORD   returned;
125     DWORD   id;
126
127     TRACE("(%s)\n", debugstr_w(myname));
128
129     id = EnumPortsW(NULL, 1, NULL, 0, &needed, &returned);
130     pi = spl_alloc(needed);
131     returned = 0;
132     if (pi)
133         id = EnumPortsW(NULL, 1, (LPBYTE) pi, needed, &needed, &returned);
134
135     if (id && returned > 0) {
136         /* we got a number of valid names. */
137         for (id = 0; id < returned; id++)
138         {
139             if (lstrcmpiW(myname, pi[id].pName) == 0) {
140                 TRACE("(%u) found %s\n", id, debugstr_w(pi[id].pName));
141                 spl_free(pi);
142                 return TRUE;
143             }
144         }
145     }
146
147     spl_free(pi);
148     return FALSE;
149 }
150
151 /******************************************************************
152  * enumerate the local Ports from the Registry (internal)  
153  *
154  * See localmon_EnumPortsW.
155  *
156  * NOTES
157  *  returns the needed size (in bytes) for pPorts
158  *  and *lpreturned is set to number of entries returned in pPorts
159  *
160  */
161
162 static DWORD get_ports_from_reg(DWORD level, LPBYTE pPorts, DWORD cbBuf, LPDWORD lpreturned)
163 {
164     HKEY    hroot = 0;
165     LPWSTR  ptr;
166     LPPORT_INFO_2W out;
167     WCHAR   portname[MAX_PATH];
168     WCHAR   res_PortW[IDS_LOCALPORT_MAXLEN];
169     WCHAR   res_MonitorW[IDS_LOCALMONITOR_MAXLEN];
170     INT     reslen_PortW;
171     INT     reslen_MonitorW;
172     DWORD   len;
173     DWORD   res;
174     DWORD   needed = 0;
175     DWORD   numentries;
176     DWORD   entrysize;
177     DWORD   id = 0;
178
179     TRACE("(%d, %p, %d, %p)\n", level, pPorts, cbBuf, lpreturned);
180
181     entrysize = (level == 1) ? sizeof(PORT_INFO_1W) : sizeof(PORT_INFO_2W);
182
183     numentries = *lpreturned;           /* this is 0, when we scan the registry */
184     needed = entrysize * numentries;
185     ptr = (LPWSTR) &pPorts[needed];
186
187     if (needed > cbBuf) pPorts = NULL;  /* No buffer for the structs */
188
189     numentries = 0;
190     needed = 0;
191
192     /* we do not check more parameters as done in windows */
193     if ((level < 1) || (level > 2)) {
194         goto getports_cleanup;
195     }
196
197     /* "+1" for '\0' */
198     reslen_MonitorW = LoadStringW(LOCALSPL_hInstance, IDS_LOCALMONITOR, res_MonitorW, IDS_LOCALMONITOR_MAXLEN) + 1;  
199     reslen_PortW = LoadStringW(LOCALSPL_hInstance, IDS_LOCALPORT, res_PortW, IDS_LOCALPORT_MAXLEN) + 1;  
200
201     res = RegOpenKeyW(HKEY_LOCAL_MACHINE, WinNT_CV_PortsW, &hroot);
202     if (res == ERROR_SUCCESS) {
203
204         /* Scan all Port-Names */
205         while (res == ERROR_SUCCESS) {
206             len = MAX_PATH;
207             portname[0] = '\0';
208             res = RegEnumValueW(hroot, id, portname, &len, NULL, NULL, NULL, NULL);
209
210             if ((res == ERROR_SUCCESS) && (portname[0])) {
211                 numentries++;
212                 /* calsulate the required size */
213                 needed += entrysize;
214                 needed += (len + 1) * sizeof(WCHAR);
215                 if (level > 1) {
216                     needed += (reslen_MonitorW + reslen_PortW) * sizeof(WCHAR);
217                 }
218
219                 /* Now fill the user-buffer, if available */
220                 if (pPorts && (cbBuf >= needed)){
221                     out = (LPPORT_INFO_2W) pPorts;
222                     pPorts += entrysize;
223                     TRACE("%p: writing PORT_INFO_%dW #%d (%s)\n", out, level, numentries, debugstr_w(portname));
224                     out->pPortName = ptr;
225                     lstrcpyW(ptr, portname);            /* Name of the Port */
226                     ptr += (len + 1);
227                     if (level > 1) {
228                         out->pMonitorName = ptr;
229                         lstrcpyW(ptr, res_MonitorW);    /* Name of the Monitor */
230                         ptr += reslen_MonitorW;
231
232                         out->pDescription = ptr;
233                         lstrcpyW(ptr, res_PortW);       /* Port Description */
234                         ptr += reslen_PortW;
235
236                         out->fPortType = PORT_TYPE_WRITE;
237                         out->Reserved = 0;
238                     }
239                 }
240                 id++;
241             }
242         }
243         RegCloseKey(hroot);
244     }
245     else
246     {
247         ERR("failed with %d for %s\n", res, debugstr_w(WinNT_CV_PortsW));
248         SetLastError(res);
249     }
250
251 getports_cleanup:
252     *lpreturned = numentries;
253     TRACE("need %d byte for %d entries (%d)\n", needed, numentries, GetLastError());
254     return needed;
255 }
256
257 /*****************************************************
258  * get_type_from_name (internal)
259  * 
260  */
261
262 static DWORD get_type_from_name(LPCWSTR name)
263 {
264     HANDLE  hfile;
265
266     if (!strncmpW(name, portname_LPT, sizeof(portname_LPT) / sizeof(WCHAR) -1))
267         return PORT_IS_LPT;
268
269     if (!strncmpW(name, portname_COM, sizeof(portname_COM) / sizeof(WCHAR) -1))
270         return PORT_IS_COM;
271
272     if (!strcmpW(name, portname_FILE))
273         return PORT_IS_FILE;
274
275     if (name[0] == '/')
276         return PORT_IS_UNIXNAME;
277
278     if (name[0] == '|')
279         return PORT_IS_PIPE;
280
281     if (!strncmpW(name, portname_CUPS, sizeof(portname_CUPS) / sizeof(WCHAR) -1))
282         return PORT_IS_CUPS;
283
284     if (!strncmpW(name, portname_LPR, sizeof(portname_LPR) / sizeof(WCHAR) -1))
285         return PORT_IS_LPR;
286
287     /* Must be a file or a directory. Does the file exist ? */
288     hfile = CreateFileW(name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
289     TRACE("%p for OPEN_EXISTING on %s\n", hfile, debugstr_w(name));
290     if (hfile == INVALID_HANDLE_VALUE) {
291         /* Can we create the file? */
292         hfile = CreateFileW(name, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL);
293         TRACE("%p for OPEN_ALWAYS\n", hfile);
294     }
295     if (hfile != INVALID_HANDLE_VALUE) {
296         CloseHandle(hfile);
297         return PORT_IS_FILENAME;
298     }
299     /* We can't use the name. use GetLastError() for the reason */
300     return PORT_IS_UNKNOWN;
301 }
302
303 /*****************************************************
304  *   localmon_EnumPortsW [exported through MONITOREX]
305  *
306  * Enumerate all local Ports
307  *
308  * PARAMS
309  *  pName       [I] Servername (ignored)
310  *  level       [I] Structure-Level (1 or 2)
311  *  pPorts      [O] PTR to Buffer that receives the Result
312  *  cbBuf       [I] Size of Buffer at pPorts
313  *  pcbNeeded   [O] PTR to DWORD that receives the size in Bytes used / required for pPorts
314  *  pcReturned  [O] PTR to DWORD that receives the number of Ports in pPorts
315  *
316  * RETURNS
317  *  Success: TRUE
318  *  Failure: FALSE and in pcbNeeded the Bytes required for pPorts, if cbBuf is too small
319  *
320  * NOTES
321  *|  Windows ignores pName
322  *|  Windows crash the app, when pPorts, pcbNeeded or pcReturned are NULL
323  *|  Windows >NT4.0 does not check for illegal levels (TRUE is returned)
324  *
325  * ToDo
326  *   "HCU\Software\Wine\Spooler\<portname>" - redirection
327  *
328  */
329 BOOL WINAPI localmon_EnumPortsW(LPWSTR pName, DWORD level, LPBYTE pPorts,
330                             DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
331 {
332     BOOL    res = FALSE;
333     DWORD   needed;
334     DWORD   numentries;
335
336     TRACE("(%s, %d, %p, %d, %p, %p)\n",
337           debugstr_w(pName), level, pPorts, cbBuf, pcbNeeded, pcReturned);
338
339     numentries = 0;
340     needed = get_ports_from_reg(level, NULL, 0, &numentries);
341     /* we calculated the needed buffersize. now do the error-checks */
342     if (cbBuf < needed) {
343         SetLastError(ERROR_INSUFFICIENT_BUFFER);
344         goto cleanup;
345     }
346
347     /* fill the buffer with the Port-Names */
348     needed = get_ports_from_reg(level, pPorts, cbBuf, &numentries);
349     res = TRUE;
350
351     if (pcReturned) *pcReturned = numentries;
352
353 cleanup:
354     if (pcbNeeded)  *pcbNeeded = needed;
355
356     TRACE("returning %d with %d (%d byte for %d entries)\n", 
357             res, GetLastError(), needed, numentries);
358
359     return (res);
360 }
361
362 /*****************************************************
363  * localmon_XcvClosePort [exported through MONITOREX]
364  *
365  * Close a Communication-Channel
366  *
367  * PARAMS
368  *  hXcv  [i] The Handle to close
369  *
370  * RETURNS
371  *  Success: TRUE
372  *  Failure: FALSE
373  *
374  */
375 BOOL WINAPI localmon_XcvClosePort(HANDLE hXcv)
376 {
377     xcv_t * xcv = (xcv_t *) hXcv;
378
379     TRACE("(%p)\n", xcv);
380     /* No checks are done in Windows */
381     EnterCriticalSection(&xcv_handles_cs);
382     list_remove(&xcv->entry);
383     LeaveCriticalSection(&xcv_handles_cs);
384     spl_free(xcv);
385     return TRUE;
386 }
387
388 /*****************************************************
389  * localmon_XcvDataPort [exported through MONITOREX]
390  *
391  * Execute command through a Communication-Channel
392  *
393  * PARAMS
394  *  hXcv            [i] The Handle to work with
395  *  pszDataName     [i] Name of the command to execute
396  *  pInputData      [i] Buffer for extra Input Data (needed only for some commands)
397  *  cbInputData     [i] Size in Bytes of Buffer at pInputData
398  *  pOutputData     [o] Buffer to receive additional Data (needed only for some commands)
399  *  cbOutputData    [i] Size in Bytes of Buffer at pOutputData
400  *  pcbOutputNeeded [o] PTR to receive the minimal Size in Bytes of the Buffer at pOutputData
401  *
402  * RETURNS
403  *  Success: ERROR_SUCCESS
404  *  Failure: win32 error code
405  *
406  * NOTES
407  *
408  *  Minimal List of commands, that every Printmonitor DLL should support:
409  *
410  *| "MonitorUI" : Return the Name of the Userinterface-DLL as WSTR in pOutputData
411  *| "AddPort"   : Add a Port (Name as WSTR in pInputData)
412  *| "DeletePort": Delete a Port (Name as WSTR in pInputData)
413  *
414  *
415  */
416 DWORD WINAPI localmon_XcvDataPort(HANDLE hXcv, LPCWSTR pszDataName, PBYTE pInputData, DWORD cbInputData,
417                 PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
418 {
419     WCHAR   buffer[16];     /* buffer for a decimal number */
420     LPWSTR  ptr;
421     DWORD   res;
422     DWORD   needed;
423     HKEY    hroot;
424
425     TRACE("(%p, %s, %p, %d, %p, %d, %p)\n", hXcv, debugstr_w(pszDataName),
426           pInputData, cbInputData, pOutputData, cbOutputData, pcbOutputNeeded);
427
428     if (!lstrcmpW(pszDataName, cmd_AddPortW)) {
429         TRACE("InputData (%d): %s\n", cbInputData, debugstr_w( (LPWSTR) pInputData));
430         res = RegOpenKeyW(HKEY_LOCAL_MACHINE, WinNT_CV_PortsW, &hroot);
431         if (res == ERROR_SUCCESS) {
432             if (does_port_exist((LPWSTR) pInputData)) {
433                 RegCloseKey(hroot);
434                 TRACE("=> %u\n", ERROR_ALREADY_EXISTS);
435                 return ERROR_ALREADY_EXISTS;
436             }
437             res = RegSetValueExW(hroot, (LPWSTR) pInputData, 0, REG_SZ, (const BYTE *) emptyW, sizeof(emptyW));
438             RegCloseKey(hroot);
439         }
440         TRACE("=> %u\n", res);
441         return res;
442     }
443
444
445     if (!lstrcmpW(pszDataName, cmd_ConfigureLPTPortCommandOKW)) {
446         TRACE("InputData (%d): %s\n", cbInputData, debugstr_w( (LPWSTR) pInputData));
447         res = RegCreateKeyW(HKEY_LOCAL_MACHINE, WinNT_CV_WindowsW, &hroot);
448         if (res == ERROR_SUCCESS) {
449             res = RegSetValueExW(hroot, TransmissionRetryTimeoutW, 0, REG_SZ, pInputData, cbInputData);
450             RegCloseKey(hroot);
451         }
452         return res;
453     }
454
455     if (!lstrcmpW(pszDataName, cmd_DeletePortW)) {
456         TRACE("InputData (%d): %s\n", cbInputData, debugstr_w( (LPWSTR) pInputData));
457         res = RegOpenKeyW(HKEY_LOCAL_MACHINE, WinNT_CV_PortsW, &hroot);
458         if (res == ERROR_SUCCESS) {
459             res = RegDeleteValueW(hroot, (LPWSTR) pInputData);
460             RegCloseKey(hroot);
461             TRACE("=> %u with %u\n", res, GetLastError() );
462             return res;
463         }
464         return ERROR_FILE_NOT_FOUND;
465     }
466
467     if (!lstrcmpW(pszDataName, cmd_GetDefaultCommConfigW)) {
468         TRACE("InputData (%d): %s\n", cbInputData, debugstr_w( (LPWSTR) pInputData));
469         *pcbOutputNeeded = cbOutputData;
470         res = GetDefaultCommConfigW((LPWSTR) pInputData, (LPCOMMCONFIG) pOutputData, pcbOutputNeeded);
471         TRACE("got %u with %u\n", res, GetLastError() );
472         return res ? ERROR_SUCCESS : GetLastError();
473     }
474
475     if (!lstrcmpW(pszDataName, cmd_GetTransmissionRetryTimeoutW)) {
476         * pcbOutputNeeded = sizeof(DWORD);
477         if (cbOutputData >= sizeof(DWORD)) {
478             /* the w2k resource kit documented a default of 90, but that's wrong */
479             *((LPDWORD) pOutputData) = 45;
480
481             res = RegOpenKeyW(HKEY_LOCAL_MACHINE, WinNT_CV_WindowsW, &hroot);
482             if (res == ERROR_SUCCESS) {
483                 needed = sizeof(buffer) - sizeof(WCHAR);
484                 res = RegQueryValueExW(hroot, TransmissionRetryTimeoutW, NULL, NULL, (LPBYTE) buffer, &needed);
485                 if ((res == ERROR_SUCCESS) && (buffer[0])) {
486                     *((LPDWORD) pOutputData) = strtoulW(buffer, NULL, 0);
487                 }
488                 RegCloseKey(hroot);
489             }
490             return ERROR_SUCCESS;
491         }
492         return ERROR_INSUFFICIENT_BUFFER;
493     }
494
495
496     if (!lstrcmpW(pszDataName, cmd_MonitorUIW)) {
497         * pcbOutputNeeded = sizeof(dllnameuiW);
498         if (cbOutputData >= sizeof(dllnameuiW)) {
499             memcpy(pOutputData, dllnameuiW, sizeof(dllnameuiW));
500             return ERROR_SUCCESS;
501         }
502         return ERROR_INSUFFICIENT_BUFFER;
503     }
504
505     if (!lstrcmpW(pszDataName, cmd_PortIsValidW)) {
506         TRACE("InputData (%d): %s\n", cbInputData, debugstr_w( (LPWSTR) pInputData));
507         res = get_type_from_name((LPCWSTR) pInputData);
508         TRACE("detected as %u\n",  res);
509         /* names, that we have recognized, are valid */
510         if (res) return ERROR_SUCCESS;
511
512         /* ERROR_ACCESS_DENIED, ERROR_PATH_NOT_FOUND or something else */
513         TRACE("=> %u\n", GetLastError());
514         return GetLastError();
515     }
516
517     if (!lstrcmpW(pszDataName, cmd_SetDefaultCommConfigW)) {
518         /* get the portname from the Handle */
519         ptr =  strchrW(((xcv_t *)hXcv)->nameW, ' ');
520         if (ptr) {
521             ptr++;  /* skip the space */
522         }
523         else
524         {
525             ptr =  ((xcv_t *)hXcv)->nameW;
526         }
527         lstrcpynW(buffer, ptr, sizeof(buffer)/sizeof(WCHAR));
528         if (buffer[0]) buffer[lstrlenW(buffer)-1] = '\0';  /* remove the ':' */
529         res = SetDefaultCommConfigW(buffer, (LPCOMMCONFIG) pInputData, cbInputData);
530         TRACE("got %u with %u\n", res, GetLastError() );
531         return res ? ERROR_SUCCESS : GetLastError();
532     }
533
534     FIXME("command not supported: %s\n", debugstr_w(pszDataName));
535     return ERROR_INVALID_PARAMETER;
536 }
537
538 /*****************************************************
539  * localmon_XcvOpenPort [exported through MONITOREX]
540  *
541  * Open a Communication-Channel
542  *
543  * PARAMS
544  *  pName         [i] Name of selected Object
545  *  GrantedAccess [i] Access-Rights to use
546  *  phXcv         [o] The resulting Handle is stored here
547  *
548  * RETURNS
549  *  Success: TRUE
550  *  Failure: FALSE
551  *
552  */
553 BOOL WINAPI localmon_XcvOpenPort(LPCWSTR pName, ACCESS_MASK GrantedAccess, PHANDLE phXcv)
554 {
555     DWORD   len;
556     xcv_t * xcv;
557
558     TRACE("%s, 0x%x, %p)\n", debugstr_w(pName), GrantedAccess, phXcv);
559     /* No checks for any field is done in Windows */
560     len = (lstrlenW(pName) + 1) * sizeof(WCHAR);
561     xcv = spl_alloc( sizeof(xcv_t) + len);
562     if (xcv) {
563         xcv->GrantedAccess = GrantedAccess;
564         memcpy(&xcv->nameW, pName, len);
565         *phXcv = (HANDLE) xcv;
566         EnterCriticalSection(&xcv_handles_cs);
567         list_add_tail(&xcv_handles, &xcv->entry);
568         LeaveCriticalSection(&xcv_handles_cs);
569         TRACE("=> %p\n", xcv);
570         return TRUE;
571     }
572     else
573     {
574         *phXcv = NULL;
575         return FALSE;
576     }
577 }
578
579 /*****************************************************
580  *      InitializePrintMonitor  (LOCALSPL.@)
581  *
582  * Initialize the Monitor for the Local Ports
583  *
584  * PARAMS
585  *  regroot [I] Registry-Path, where the settings are stored
586  *
587  * RETURNS
588  *  Success: Pointer to a MONITOREX Structure
589  *  Failure: NULL
590  *
591  * NOTES
592  *  The fixed location "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Ports"
593  *  is used to store the Ports (IniFileMapping from "win.ini", Section "Ports").
594  *  Native localspl.dll fails, when no valid Port-Entry is present.
595  *
596  */
597
598 LPMONITOREX WINAPI InitializePrintMonitor(LPWSTR regroot)
599 {
600     static MONITOREX mymonitorex =
601     {
602         sizeof(MONITOREX) - sizeof(DWORD),
603         {
604             localmon_EnumPortsW,
605             NULL,       /* localmon_OpenPortW */ 
606             NULL,       /* localmon_OpenPortExW */ 
607             NULL,       /* localmon_StartDocPortW */
608             NULL,       /* localmon_WritePortW */
609             NULL,       /* localmon_ReadPortW */
610             NULL,       /* localmon_EndDocPortW */
611             NULL,       /* localmon_ClosePortW */
612             NULL,       /* localmon_AddPortW */
613             NULL,       /* localmon_AddPortExW */
614             NULL,       /* Use ConfigurePortUI in localui.dll */
615             NULL,       /* Use DeletePortUI in localui.dll */
616             NULL,       /* localmon_GetPrinterDataFromPort */
617             NULL,       /* localmon_SetPortTimeOuts */
618             localmon_XcvOpenPort,
619             localmon_XcvDataPort,
620             localmon_XcvClosePort
621         }
622     };
623
624     TRACE("(%s)\n", debugstr_w(regroot));
625     /* Parameter "regroot" is ignored on NT4.0 (localmon.dll) */
626     if (!regroot || !regroot[0]) {
627         SetLastError(ERROR_INVALID_PARAMETER);
628         return NULL;
629     }
630     TRACE("=> %p\n", &mymonitorex);
631     /* Native windows returns always the same pointer on success */
632     return &mymonitorex;
633 }