4 * Copyright 2021 Huw Davies
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.
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.
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
30 #include <sys/types.h>
37 #ifdef HAVE_CUPS_CUPS_H
38 #include <cups/cups.h>
40 #ifdef HAVE_CUPS_PPD_H
44 #ifdef HAVE_APPLICATIONSERVICES_APPLICATIONSERVICES_H
45 #define GetCurrentProcess GetCurrentProcess_Mac
46 #define GetCurrentThread GetCurrentThread_Mac
47 #define LoadResource LoadResource_Mac
48 #define AnimatePalette AnimatePalette_Mac
49 #define EqualRgn EqualRgn_Mac
50 #define FillRgn FillRgn_Mac
51 #define FrameRgn FrameRgn_Mac
52 #define GetPixel GetPixel_Mac
53 #define InvertRgn InvertRgn_Mac
54 #define LineTo LineTo_Mac
55 #define OffsetRgn OffsetRgn_Mac
56 #define PaintRgn PaintRgn_Mac
57 #define Polygon Polygon_Mac
58 #define ResizePalette ResizePalette_Mac
59 #define SetRectRgn SetRectRgn_Mac
60 #define EqualRect EqualRect_Mac
61 #define FillRect FillRect_Mac
62 #define FrameRect FrameRect_Mac
63 #define GetCursor GetCursor_Mac
64 #define InvertRect InvertRect_Mac
65 #define OffsetRect OffsetRect_Mac
66 #define PtInRect PtInRect_Mac
67 #define SetCursor SetCursor_Mac
68 #define SetRect SetRect_Mac
69 #define ShowCursor ShowCursor_Mac
70 #define UnionRect UnionRect_Mac
71 #include <ApplicationServices/ApplicationServices.h>
72 #undef GetCurrentProcess
73 #undef GetCurrentThread
100 #include "ntstatus.h"
101 #define WIN32_NO_STATUS
104 #include "winternl.h"
106 #include "winerror.h"
109 #include "winspool.h"
110 #include "ddk/winsplp.h"
111 #include "wine/debug.h"
112 #include "wine/unixlib.h"
116 WINE_DEFAULT_DEBUG_CHANNEL(winspool
);
118 static const WCHAR CUPS_Port
[] = { 'C','U','P','S',':',0 };
119 static const WCHAR LPR_Port
[] = { 'L','P','R',':',0 };
121 #ifdef SONAME_LIBCUPS
123 static void *libcups_handle
;
126 DO_FUNC(cupsAddOption); \
127 DO_FUNC(cupsFreeDests); \
128 DO_FUNC(cupsFreeOptions); \
129 DO_FUNC(cupsGetDests); \
130 DO_FUNC(cupsGetOption); \
131 DO_FUNC(cupsParseOptions); \
132 DO_FUNC(cupsPrintFile)
133 #define CUPS_OPT_FUNCS \
134 DO_FUNC(cupsGetNamedDest); \
135 DO_FUNC(cupsGetPPD); \
136 DO_FUNC(cupsGetPPD3); \
137 DO_FUNC(cupsLastErrorString)
139 #define DO_FUNC(f) static typeof(f) *p##f
142 static cups_dest_t
* (*pcupsGetNamedDest
)(http_t
*, const char *, const char *);
143 static const char * (*pcupsGetPPD
)(const char *);
144 static http_status_t (*pcupsGetPPD3
)(http_t
*, const char *, time_t *, char *, size_t);
145 static const char * (*pcupsLastErrorString
)(void);
147 #endif /* SONAME_LIBCUPS */
149 static NTSTATUS
process_attach( void *args
)
151 #ifdef SONAME_LIBCUPS
152 libcups_handle
= dlopen( SONAME_LIBCUPS
, RTLD_NOW
);
153 TRACE( "%p: %s loaded\n", libcups_handle
, SONAME_LIBCUPS
);
154 if (!libcups_handle
) return STATUS_DLL_NOT_FOUND
;
157 p##x = dlsym( libcups_handle, #x ); \
160 ERR( "failed to load symbol %s\n", #x ); \
161 libcups_handle = NULL; \
162 return STATUS_ENTRYPOINT_NOT_FOUND; \
166 #define DO_FUNC(x) p##x = dlsym( libcups_handle, #x )
169 return STATUS_SUCCESS
;
170 #else /* SONAME_LIBCUPS */
171 return STATUS_NOT_SUPPORTED
;
172 #endif /* SONAME_LIBCUPS */
175 static BOOL
copy_file( const char *src
, const char *dst
)
177 int fds
[2] = { -1, -1 }, num
;
181 fds
[0] = open( src
, O_RDONLY
);
182 fds
[1] = open( dst
, O_CREAT
| O_TRUNC
| O_WRONLY
, 0666 );
183 if (fds
[0] == -1 || fds
[1] == -1) goto fail
;
185 while ((num
= read( fds
[0], buf
, sizeof(buf
) )) != 0)
187 if (num
== -1) goto fail
;
188 if (write( fds
[1], buf
, num
) != num
) goto fail
;
193 if (fds
[1] != -1) close( fds
[1] );
194 if (fds
[0] != -1) close( fds
[0] );
198 static char *get_unix_file_name( LPCWSTR path
)
200 UNICODE_STRING nt_name
;
201 OBJECT_ATTRIBUTES attr
;
206 nt_name
.Buffer
= (WCHAR
*)path
;
207 nt_name
.MaximumLength
= nt_name
.Length
= wcslen( path
) * sizeof(WCHAR
);
208 InitializeObjectAttributes( &attr
, &nt_name
, 0, 0, NULL
);
211 if (!(buffer
= malloc( size
))) return NULL
;
212 status
= wine_nt_to_unix_file_name( &attr
, buffer
, &size
, FILE_OPEN_IF
);
213 if (status
!= STATUS_BUFFER_TOO_SMALL
) break;
216 if (status
&& status
!= STATUS_NO_SUCH_FILE
)
225 #ifdef SONAME_LIBCUPS
226 static WCHAR
*cups_get_optionW( const char *opt_name
, int num_options
, cups_option_t
*options
)
232 value
= pcupsGetOption( opt_name
, num_options
, options
);
233 if (!value
) return NULL
;
235 len
= strlen( value
) + 1;
236 ret
= malloc( len
* sizeof(WCHAR
) );
237 if (ret
) ntdll_umbstowcs( value
, len
, ret
, len
);
242 static cups_ptype_t
cups_get_printer_type( const cups_dest_t
*dest
)
248 value
= pcupsGetOption( "printer-type", dest
->num_options
, dest
->options
);
249 if (!value
) return 0;
250 ret
= (cups_ptype_t
)strtoul( value
, &end
, 10 );
255 static BOOL
cups_is_scanner( cups_dest_t
*dest
)
257 return cups_get_printer_type( dest
) & 0x2000000 /* CUPS_PRINTER_SCANNER */;
260 static http_status_t
cupsGetPPD3_wrapper( http_t
*http
, const char *name
, time_t *modtime
,
261 char *buffer
, size_t bufsize
)
265 if (pcupsGetPPD3
) return pcupsGetPPD3( http
, name
, modtime
, buffer
, bufsize
);
266 if (!pcupsGetPPD
) return HTTP_NOT_FOUND
;
268 TRACE( "No cupsGetPPD3 implementation, so calling cupsGetPPD\n" );
271 ppd
= pcupsGetPPD( name
);
273 TRACE( "cupsGetPPD returns %s\n", debugstr_a(ppd
) );
275 if (!ppd
) return HTTP_NOT_FOUND
;
277 if (rename( ppd
, buffer
) == -1)
279 BOOL res
= copy_file( ppd
, buffer
);
281 if (!res
) return HTTP_NOT_FOUND
;
286 /*****************************************************************************
287 * get_cups_jobs_ticket_options
289 * Explicitly set CUPS options based on any %cupsJobTicket lines.
290 * The CUPS scheduler only looks for these in Print-File requests, and since
291 * cupsPrintFile uses Create-Job / Send-Document, the ticket lines don't get
294 static int get_cups_job_ticket_options( const char *file
, int num_options
, cups_option_t
**options
)
296 FILE *fp
= fopen( file
, "r" );
297 char buf
[257]; /* DSC max of 256 + '\0' */
298 const char *ps_adobe
= "%!PS-Adobe-";
299 const char *cups_job
= "%cupsJobTicket:";
301 if (!fp
) return num_options
;
302 if (!fgets( buf
, sizeof(buf
), fp
)) goto end
;
303 if (strncmp( buf
, ps_adobe
, strlen( ps_adobe
) )) goto end
;
304 while (fgets( buf
, sizeof(buf
), fp
))
306 if (strncmp( buf
, cups_job
, strlen( cups_job
) )) break;
307 num_options
= pcupsParseOptions( buf
+ strlen( cups_job
), num_options
, options
);
315 static int get_cups_default_options( const char *printer
, int num_options
, cups_option_t
**options
)
320 if (!pcupsGetNamedDest
) return num_options
;
322 dest
= pcupsGetNamedDest( NULL
, printer
, NULL
);
323 if (!dest
) return num_options
;
325 for (i
= 0; i
< dest
->num_options
; i
++)
327 if (!pcupsGetOption( dest
->options
[i
].name
, num_options
, *options
))
328 num_options
= pcupsAddOption( dest
->options
[i
].name
, dest
->options
[i
].value
,
329 num_options
, options
);
332 pcupsFreeDests( 1, dest
);
335 #endif /* SONAME_LIBCUPS */
337 static NTSTATUS
enum_printers( void *args
)
339 const struct enum_printers_params
*params
= args
;
340 #ifdef SONAME_LIBCUPS
341 unsigned int num
, i
, name_len
, comment_len
, location_len
, needed
;
342 WCHAR
*comment
, *location
, *ptr
;
343 struct printer_info
*info
;
347 if (!pcupsGetDests
) return STATUS_NOT_SUPPORTED
;
349 num
= pcupsGetDests( &dests
);
351 for (i
= 0; i
< num
; i
++)
353 if (cups_is_scanner( dests
+ i
))
355 TRACE( "Printer %d: %s - skipping scanner\n", i
, debugstr_a( dests
[i
].name
) );
358 TRACE( "Printer %d: %s\n", i
, debugstr_a( dests
[i
].name
) );
362 needed
= sizeof( *info
) * *params
->num
;
363 info
= params
->printers
;
364 ptr
= (WCHAR
*)(info
+ *params
->num
);
366 for (i
= 0; i
< num
; i
++)
368 if (cups_is_scanner( dests
+ i
)) continue;
370 comment
= cups_get_optionW( "printer-info", dests
[i
].num_options
, dests
[i
].options
);
371 location
= cups_get_optionW( "printer-location", dests
[i
].num_options
, dests
[i
].options
);
373 name_len
= strlen( dests
[i
].name
) + 1;
374 comment_len
= comment
? wcslen( comment
) + 1 : 0;
375 location_len
= location
? wcslen( location
) + 1 : 0;
376 needed
+= (name_len
+ comment_len
+ location_len
) * sizeof(WCHAR
);
378 if (needed
<= *params
->size
)
381 ntdll_umbstowcs( dests
[i
].name
, name_len
, info
->name
, name_len
);
382 info
->comment
= comment
? ptr
+ name_len
: NULL
;
383 memcpy( info
->comment
, comment
, comment_len
* sizeof(WCHAR
) );
384 info
->location
= location
? ptr
+ name_len
+ comment_len
: NULL
;
385 memcpy( info
->location
, location
, location_len
* sizeof(WCHAR
) );
386 info
->is_default
= dests
[i
].is_default
;
388 ptr
+= name_len
+ comment_len
+ location_len
;
393 pcupsFreeDests( num
, dests
);
395 if (needed
> *params
->size
)
397 *params
->size
= needed
;
398 return STATUS_BUFFER_OVERFLOW
;
400 return STATUS_SUCCESS
;
403 return STATUS_NOT_SUPPORTED
;
404 #endif /* SONAME_LIBCUPS */
407 static NTSTATUS
get_ppd( void *args
)
409 const struct get_ppd_params
*params
= args
;
410 char *unix_ppd
= get_unix_file_name( params
->ppd
);
411 NTSTATUS status
= STATUS_SUCCESS
;
413 TRACE( "(%s, %s)\n", debugstr_w( params
->printer
), debugstr_w( params
->ppd
) );
415 if (!unix_ppd
) return STATUS_NO_SUCH_FILE
;
417 if (!params
->printer
) /* unlink */
423 #ifdef SONAME_LIBCUPS
424 http_status_t http_status
;
429 len
= wcslen( params
->printer
);
430 printer_name
= malloc( len
* 3 + 1 );
431 ntdll_wcstoumbs( params
->printer
, len
+ 1, printer_name
, len
* 3 + 1, FALSE
);
433 http_status
= cupsGetPPD3_wrapper( 0, printer_name
, &modtime
, unix_ppd
, strlen( unix_ppd
) + 1 );
434 if (http_status
!= HTTP_OK
)
437 status
= STATUS_DEVICE_UNREACHABLE
;
439 free( printer_name
);
441 status
= STATUS_NOT_SUPPORTED
;
448 static NTSTATUS
get_default_page_size( void *args
)
450 #ifdef HAVE_APPLICATIONSERVICES_APPLICATIONSERVICES_H
451 const struct get_default_page_size_params
*params
= args
;
452 NTSTATUS status
= STATUS_UNSUCCESSFUL
;
453 PMPrintSession session
= NULL
;
454 PMPageFormat format
= NULL
;
455 CFStringRef paper_name
;
460 if (PMCreateSession( &session
)) goto end
;
461 if (PMCreatePageFormat( &format
)) goto end
;
462 if (PMSessionDefaultPageFormat( session
, format
)) goto end
;
463 if (PMGetPageFormatPaper( format
, &paper
)) goto end
;
464 if (PMPaperGetPPDPaperName( paper
, &paper_name
)) goto end
;
467 range
.length
= CFStringGetLength( paper_name
);
468 size
= (range
.length
+ 1) * sizeof(WCHAR
);
470 if (*params
->name_size
>= size
)
472 CFStringGetCharacters( paper_name
, range
, (UniChar
*)params
->name
);
473 params
->name
[range
.length
] = 0;
474 status
= STATUS_SUCCESS
;
477 status
= STATUS_BUFFER_OVERFLOW
;
478 *params
->name_size
= size
;
481 if (format
) PMRelease( format
);
482 if (session
) PMRelease( session
);
485 return STATUS_NOT_IMPLEMENTED
;
489 /*****************************************************************************
492 static BOOL
schedule_pipe( const WCHAR
*cmd
, const WCHAR
*filename
)
494 char *unixname
, *cmdA
;
496 int fds
[2] = { -1, -1 }, file_fd
= -1, no_read
;
502 if (!(unixname
= get_unix_file_name( filename
))) return FALSE
;
505 cmdA
= malloc( len
* 3 + 1);
506 ntdll_wcstoumbs( cmd
, len
+ 1, cmdA
, len
* 3 + 1, FALSE
);
508 TRACE( "printing with: %s\n", cmdA
);
510 if ((file_fd
= open( unixname
, O_RDONLY
)) == -1) goto end
;
514 ERR( "pipe() failed!\n" );
518 if ((pid
= fork()) == 0)
524 /* reset signals that we previously set to SIG_IGN */
525 signal( SIGPIPE
, SIG_DFL
);
527 execl( "/bin/sh", "/bin/sh", "-c", cmdA
, NULL
);
532 ERR( "fork() failed!\n" );
538 while ((no_read
= read( file_fd
, buf
, sizeof(buf
) )) > 0)
539 write( fds
[1], buf
, no_read
);
546 wret
= waitpid( pid
, &status
, 0 );
547 } while (wret
< 0 && errno
== EINTR
);
550 ERR( "waitpid() failed!\n" );
553 if (!WIFEXITED(status
) || WEXITSTATUS(status
))
555 ERR( "child process failed! %d\n", status
);
562 if (file_fd
!= -1) close( file_fd
);
563 if (fds
[0] != -1) close( fds
[0] );
564 if (fds
[1] != -1) close( fds
[1] );
571 /*****************************************************************************
574 static BOOL
schedule_unixfile( const WCHAR
*output
, const WCHAR
*filename
)
576 char *unixname
, *outputA
;
580 if (!(unixname
= get_unix_file_name( filename
))) return FALSE
;
582 len
= wcslen( output
);
583 outputA
= malloc( len
* 3 + 1);
584 ntdll_wcstoumbs( output
, len
+ 1, outputA
, len
* 3 + 1, FALSE
);
586 ret
= copy_file( unixname
, outputA
);
593 /*****************************************************************************
596 static BOOL
schedule_lpr( const WCHAR
*printer_name
, const WCHAR
*filename
)
598 static const WCHAR lpr
[] = { 'l','p','r',' ','-','P','\'' };
599 static const WCHAR quote
[] = { '\'',0 };
600 int printer_len
= wcslen( printer_name
);
604 cmd
= malloc( printer_len
* sizeof(WCHAR
) + sizeof(lpr
) + sizeof(quote
) );
605 memcpy( cmd
, lpr
, sizeof(lpr
) );
606 memcpy( cmd
+ ARRAY_SIZE(lpr
), printer_name
, printer_len
* sizeof(WCHAR
) );
607 memcpy( cmd
+ ARRAY_SIZE(lpr
) + printer_len
, quote
, sizeof(quote
) );
609 ret
= schedule_pipe( cmd
, filename
);
615 /*****************************************************************************
618 static BOOL
schedule_cups( const WCHAR
*printer_name
, const WCHAR
*filename
, const WCHAR
*document_title
)
620 #ifdef SONAME_LIBCUPS
623 char *unixname
, *queue
, *unix_doc_title
;
624 cups_option_t
*options
= NULL
;
625 int num_options
= 0, i
;
629 if (!(unixname
= get_unix_file_name( filename
))) return FALSE
;
630 len
= wcslen( printer_name
);
631 queue
= malloc( len
* 3 + 1);
632 ntdll_wcstoumbs( printer_name
, len
+ 1, queue
, len
* 3 + 1, FALSE
);
634 len
= wcslen( document_title
);
635 unix_doc_title
= malloc( len
* 3 + 1 );
636 ntdll_wcstoumbs( document_title
, len
+ 1, unix_doc_title
, len
+ 3 + 1, FALSE
);
638 num_options
= get_cups_job_ticket_options( unixname
, num_options
, &options
);
639 num_options
= get_cups_default_options( queue
, num_options
, &options
);
641 TRACE( "printing via cups with options:\n" );
642 for (i
= 0; i
< num_options
; i
++)
643 TRACE( "\t%d: %s = %s\n", i
, options
[i
].name
, options
[i
].value
);
645 ret
= pcupsPrintFile( queue
, unixname
, unix_doc_title
, num_options
, options
);
646 if (ret
== 0 && pcupsLastErrorString
)
647 WARN( "cupsPrintFile failed with error %s\n", debugstr_a( pcupsLastErrorString() ) );
649 pcupsFreeOptions( num_options
, options
);
651 free( unix_doc_title
);
659 return schedule_lpr( printer_name
, filename
);
663 static NTSTATUS
schedule_job( void *args
)
665 const struct schedule_job_params
*params
= args
;
667 if (params
->wine_port
[0] == '|')
668 return schedule_pipe( params
->wine_port
+ 1, params
->filename
);
670 if (params
->wine_port
[0])
671 return schedule_unixfile( params
->wine_port
, params
->filename
);
673 if (!wcsncmp( params
->port
, LPR_Port
, ARRAY_SIZE(LPR_Port
) - 1 ))
674 return schedule_lpr( params
->port
+ ARRAY_SIZE(LPR_Port
) - 1, params
->filename
);
676 if (!wcsncmp( params
->port
, CUPS_Port
, ARRAY_SIZE(CUPS_Port
) - 1 ))
677 return schedule_cups( params
->port
+ ARRAY_SIZE(CUPS_Port
) - 1, params
->filename
, params
->document_title
);
682 const unixlib_entry_t __wine_unix_call_funcs
[] =
686 get_default_page_size
,
695 struct printer_info32
703 static NTSTATUS
wow64_enum_printers( void *args
)
710 } const *params32
= args
;
714 struct enum_printers_params params
=
716 ULongToPtr( params32
->printers
),
717 ULongToPtr( params32
->size
),
718 ULongToPtr( params32
->num
)
721 if (!(status
= enum_printers( ¶ms
)))
723 /* convert structures in place */
724 struct printer_info
*info
= ULongToPtr( params32
->printers
);
725 struct printer_info32
*info32
= (struct printer_info32
*)info
;
726 unsigned int num
= *(unsigned int *)ULongToPtr( params32
->num
);
728 for (i
= 0; i
< num
; i
++)
730 info32
[i
].name
= PtrToUlong(info
[i
].name
);
731 info32
[i
].comment
= PtrToUlong(info
[i
].comment
);
732 info32
[i
].location
= PtrToUlong(info
[i
].location
);
733 info32
[i
].is_default
= info
[i
].is_default
;
739 static NTSTATUS
wow64_get_default_page_size( void *args
)
745 } const *params32
= args
;
747 struct get_default_page_size_params params
=
749 ULongToPtr( params32
->name
),
750 ULongToPtr( params32
->name_size
)
753 return get_default_page_size( ¶ms
);
756 static NTSTATUS
wow64_get_ppd( void *args
)
762 } const *params32
= args
;
764 struct get_ppd_params params
=
766 ULongToPtr( params32
->printer
),
767 ULongToPtr( params32
->ppd
)
770 return get_ppd( ¶ms
);
773 static NTSTATUS
wow64_schedule_job( void *args
)
779 PTR32 document_title
;
781 } const *params32
= args
;
783 struct schedule_job_params params
=
785 ULongToPtr( params32
->filename
),
786 ULongToPtr( params32
->port
),
787 ULongToPtr( params32
->document_title
),
788 ULongToPtr( params32
->wine_port
)
791 return schedule_job( ¶ms
);
794 const unixlib_entry_t __wine_unix_call_wow64_funcs
[] =
798 wow64_get_default_page_size
,