xref: /aoo41x/main/vcl/unx/generic/printer/cupsmgr.cxx (revision c82f2877)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_vcl.hxx"
26 
27 #ifdef ENABLE_CUPS
28 #include <cups/cups.h>
29 #include <cups/ppd.h>
30 
31 #else // !ENABLE_CUPS
32 typedef void ppd_file_t;
33 typedef void cups_dest_t;
34 typedef void cups_option_t;
35 #endif
36 
37 #include <unistd.h>
38 
39 #include "cupsmgr.hxx"
40 
41 #include "osl/thread.h"
42 #include "osl/diagnose.h"
43 #include "osl/conditn.hxx"
44 
45 #include "rtl/ustrbuf.hxx"
46 
47 #include <algorithm>
48 #include <setjmp.h>
49 #include <signal.h>
50 
51 #define CUPS_LIB_NAME "libcups.so.2"
52 
53 namespace psp
54 {
55 class CUPSWrapper
56 {
57     oslModule		m_pLib;
58     osl::Mutex		m_aGetPPDMutex;
59     bool            m_bPPDThreadRunning;
60 
61     int				(*m_pcupsPrintFile)(const char*, const char*, const char*, int, cups_option_t*);
62     int				(*m_pcupsGetDests)(cups_dest_t**);
63     void			(*m_pcupsSetDests)(int,cups_dest_t*);
64     void			(*m_pcupsFreeDests)(int,cups_dest_t*);
65     const char*		(*m_pcupsGetPPD)(const char*);
66     int				(*m_pcupsMarkOptions)(ppd_file_t*,int,cups_option_t*);
67     int				(*m_pcupsAddOption)(const char*,const char*,int,cups_option_t**);
68     void			(*m_pcupsFreeOptions)(int,cups_option_t*);
69     ppd_file_t*		(*m_pppdOpenFile)(const char* pFile);
70     void			(*m_pppdClose)(ppd_file_t*);
71     const char*		(*m_pcupsServer)();
72     void			(*m_pcupsSetPasswordCB)(const char*(cb)(const char*));
73     const char*		(*m_pcupsUser)();
74     void			(*m_pcupsSetUser)(const char*);
75     const char*     (*m_pcupsGetOption)(const char*,int,cups_option_t*);
76 
77     oslGenericFunction loadSymbol( const char* );
78 public:
79     CUPSWrapper();
80     ~CUPSWrapper();
81 
82     bool isValid();
83 
84     int cupsGetDests(cups_dest_t** pDests)
85     { return m_pcupsGetDests(pDests); }
86 
87     void cupsSetDests( int nDests, cups_dest_t* pDests )
88     { m_pcupsSetDests( nDests, pDests ); }
89 
90     void cupsFreeDests(int nDests, cups_dest_t* pDests)
91     { m_pcupsFreeDests(nDests, pDests); }
92 
93     int cupsPrintFile( const char* pPrinter,
94                        const char* pFileName,
95                        const char* pTitle,
96                        int nOptions,
97                    cups_option_t* pOptions )
98     { return m_pcupsPrintFile( pPrinter, pFileName, pTitle, nOptions, pOptions ); }
99 
100     rtl::OString cupsGetPPD( const char* pPrinter );
101 
102     int cupsMarkOptions(ppd_file_t* pPPD, int nOptions, cups_option_t* pOptions )
103     { return m_pcupsMarkOptions(pPPD, nOptions, pOptions); }
104 
105     int cupsAddOption( const char* pName, const char* pValue, int nOptions, cups_option_t** pOptions )
106     { return m_pcupsAddOption( pName, pValue, nOptions, pOptions ); }
107 
108     void cupsFreeOptions( int nOptions, cups_option_t* pOptions )
109     { m_pcupsFreeOptions( nOptions, pOptions ); }
110 
111     ppd_file_t* ppdOpenFile( const char* pFileName )
112     { return m_pppdOpenFile( pFileName ); }
113 
114     void ppdClose( ppd_file_t* pPPD )
115     { m_pppdClose( pPPD ); }
116 
117     const char	*cupsServer(void)
118     { return m_pcupsServer(); }
119 
120     const char	*cupsUser(void)
121     { return m_pcupsUser(); }
122 
123     void cupsSetPasswordCB(const char *(*cb)(const char *))
124     { m_pcupsSetPasswordCB( cb ); }
125 
126     void cupsSetUser(const char *user)
127     { m_pcupsSetUser( user ); }
128 
129     const char* cupsGetOption(const char* name, int num_options, cups_option_t* options)
130     { return m_pcupsGetOption( name, num_options, options ); }
131 
132 };
133 }
134 
135 using namespace psp;
136 using namespace osl;
137 using namespace rtl;
138 
139 /*
140  *  CUPSWrapper class
141  */
142 
143 oslGenericFunction CUPSWrapper::loadSymbol( const char* pSymbol )
144 {
145     OUString aSym( OUString::createFromAscii( pSymbol ) );
146     oslGenericFunction pSym = osl_getFunctionSymbol( m_pLib, aSym.pData );
147 #if OSL_DEBUG_LEVEL > 1
148     fprintf( stderr, "%s %s\n", pSymbol, pSym ? "found" : "not found" );
149 #endif
150     return pSym;
151 }
152 
153 CUPSWrapper::CUPSWrapper()
154         : m_pLib( NULL ),
155           m_bPPDThreadRunning( false )
156 {
157 #ifdef ENABLE_CUPS
158     OUString aLib( RTL_CONSTASCII_USTRINGPARAM( CUPS_LIB_NAME ) );
159     m_pLib = osl_loadModule( aLib.pData, SAL_LOADMODULE_LAZY );
160     if( ! m_pLib )
161     {
162         aLib = OUString( RTL_CONSTASCII_USTRINGPARAM( SAL_MODULENAME( "cups" ) ) );
163         m_pLib = osl_loadModule( aLib.pData, SAL_LOADMODULE_LAZY );
164     }
165 #endif
166 
167     if( ! m_pLib )
168     {
169 #if OSL_DEBUG_LEVEL > 1
170         fprintf( stderr, "no cups library found\n" );
171 #endif
172         return;
173     }
174 
175     m_pcupsPrintFile	 	= (int(*)(const char*,const char*,const char*,int,cups_option_t*))
176         loadSymbol( "cupsPrintFile" );
177     m_pcupsGetDests			= (int(*)(cups_dest_t**))
178         loadSymbol( "cupsGetDests" );
179     m_pcupsSetDests			= (void(*)(int,cups_dest_t*))
180         loadSymbol( "cupsSetDests" );
181     m_pcupsFreeDests		= (void(*)(int,cups_dest_t*))
182         loadSymbol( "cupsFreeDests" );
183     m_pcupsGetPPD			= (const char*(*)(const char*))
184         loadSymbol( "cupsGetPPD" );
185     m_pcupsMarkOptions		= (int(*)(ppd_file_t*,int,cups_option_t*))
186         loadSymbol( "cupsMarkOptions" );
187     m_pcupsAddOption		= (int(*)(const char*,const char*,int,cups_option_t**))
188         loadSymbol( "cupsAddOption" );
189     m_pcupsFreeOptions		= (void(*)(int,cups_option_t*))
190         loadSymbol( "cupsFreeOptions" );
191     m_pppdOpenFile			= (ppd_file_t*(*)(const char*))
192         loadSymbol( "ppdOpenFile" );
193     m_pppdClose				= (void(*)(ppd_file_t*))
194         loadSymbol( "ppdClose" );
195     m_pcupsServer			= (const char*(*)())
196         loadSymbol( "cupsServer" );
197     m_pcupsUser				= (const char*(*)())
198         loadSymbol( "cupsUser" );
199     m_pcupsSetPasswordCB	= (void(*)(const char*(*)(const char*)))
200         loadSymbol( "cupsSetPasswordCB" );
201     m_pcupsSetUser			= (void(*)(const char*))
202         loadSymbol( "cupsSetUser" );
203     m_pcupsGetOption        = (const char*(*)(const char*,int,cups_option_t*))
204         loadSymbol( "cupsGetOption" );
205 
206     if( ! (
207            m_pcupsPrintFile					&&
208            m_pcupsGetDests					&&
209            m_pcupsSetDests					&&
210            m_pcupsFreeDests					&&
211            m_pcupsGetPPD					&&
212            m_pcupsMarkOptions				&&
213            m_pcupsAddOption					&&
214            m_pcupsServer					&&
215            m_pcupsUser						&&
216            m_pcupsSetPasswordCB				&&
217            m_pcupsSetUser					&&
218            m_pcupsFreeOptions				&&
219            m_pppdOpenFile					&&
220            m_pppdClose                      &&
221            m_pcupsGetOption
222            ) )
223     {
224         osl_unloadModule( m_pLib );
225         m_pLib = NULL;
226     }
227 }
228 
229 CUPSWrapper::~CUPSWrapper()
230 {
231     if( m_pLib )
232         osl_unloadModule( m_pLib );
233 }
234 
235 bool CUPSWrapper::isValid()
236 {
237     return m_pLib != NULL;
238 }
239 
240 typedef const char*(*PPDFunction)(const char*);
241 struct GetPPDAttribs
242 {
243     PPDFunction         m_pFunction;
244     osl::Condition		m_aCondition;
245     OString			    m_aParameter;
246     OString			    m_aResult;
247     oslThread			m_aThread;
248     int                 m_nRefs;
249     bool*               m_pResetRunning;
250     osl::Mutex*         m_pSyncMutex;
251 
252     GetPPDAttribs( PPDFunction pFn, const char * m_pParameter,
253                    bool* pResetRunning, osl::Mutex* pSyncMutex )
254             : m_pFunction( pFn ),
255               m_aParameter( m_pParameter ),
256               m_pResetRunning( pResetRunning ),
257               m_pSyncMutex( pSyncMutex )
258     {
259         m_nRefs = 2;
260         m_aCondition.reset();
261     }
262 
263     ~GetPPDAttribs()
264     {
265         if( m_aResult.getLength() )
266             unlink( m_aResult.getStr() );
267     }
268 
269     void unref()
270     {
271         if( --m_nRefs == 0 )
272         {
273             *m_pResetRunning = false;
274             delete this;
275         }
276     }
277 
278     void executeCall()
279     {
280         // This CUPS method is not at all thread-safe we need
281         // to dup the pointer to a static buffer it returns ASAP
282         OString aResult = m_pFunction( m_aParameter );
283         MutexGuard aGuard( *m_pSyncMutex );
284         m_aResult = aResult;
285         m_aCondition.set();
286         unref();
287     }
288 
289     OString waitResult( TimeValue *pDelay )
290     {
291         m_pSyncMutex->release();
292 
293         if (m_aCondition.wait( pDelay ) != Condition::result_ok
294             )
295         {
296             #if OSL_DEBUG_LEVEL > 1
297             fprintf( stderr, "cupsGetPPD %s timed out\n",
298             (const sal_Char *) m_aParameter
299             );
300             #endif
301         }
302         m_pSyncMutex->acquire();
303 
304         OString aRetval = m_aResult;
305         m_aResult = OString();
306         unref();
307 
308         return aRetval;
309     }
310 };
311 
312 extern "C" {
313     static void getPPDWorker(void* pData)
314     {
315         GetPPDAttribs* pAttribs = (GetPPDAttribs*)pData;
316         pAttribs->executeCall();
317     }
318 }
319 
320 OString CUPSWrapper::cupsGetPPD( const char* pPrinter )
321 {
322     OString aResult;
323 
324     m_aGetPPDMutex.acquire();
325     // if one thread hangs in cupsGetPPD already, don't start another
326     if( ! m_bPPDThreadRunning )
327     {
328         m_bPPDThreadRunning = true;
329         GetPPDAttribs* pAttribs = new GetPPDAttribs( m_pcupsGetPPD,
330                                                      pPrinter,
331                                                      &m_bPPDThreadRunning,
332                                                      &m_aGetPPDMutex );
333 
334         oslThread aThread = osl_createThread( getPPDWorker, pAttribs );
335 
336         TimeValue aValue;
337         aValue.Seconds = 5;
338         aValue.Nanosec = 0;
339 
340         // NOTE: waitResult release and acquires the GetPPD mutex
341         aResult = pAttribs->waitResult( &aValue );
342         osl_destroyThread( aThread );
343     }
344     m_aGetPPDMutex.release();
345 
346     return aResult;
347 }
348 
349 #ifdef ENABLE_CUPS
350 static const char* setPasswordCallback( const char* pIn )
351 {
352     const char* pRet = NULL;
353 
354     PrinterInfoManager& rMgr = PrinterInfoManager::get();
355     if( rMgr.getType() == PrinterInfoManager::CUPS ) // sanity check
356         pRet = static_cast<CUPSManager&>(rMgr).authenticateUser( pIn );
357     return pRet;
358 }
359 #endif
360 
361 /*
362  *  CUPSManager class
363  */
364 
365 CUPSManager* CUPSManager::tryLoadCUPS()
366 {
367     CUPSManager* pManager = NULL;
368 #ifdef ENABLE_CUPS
369     static const char* pEnv = getenv( "SAL_DISABLE_CUPS" );
370 
371     if( ! pEnv || ! *pEnv )
372     {
373         // try to load CUPS
374         CUPSWrapper* pWrapper = new CUPSWrapper();
375         if( pWrapper->isValid() )
376             pManager = new CUPSManager( pWrapper );
377         else
378             delete pWrapper;
379     }
380 #endif
381     return pManager;
382 }
383 
384 extern "C"
385 {
386 static void run_dest_thread_stub( void* pThis )
387 {
388     CUPSManager::runDestThread( pThis );
389 }
390 }
391 
392 CUPSManager::CUPSManager( CUPSWrapper* pWrapper ) :
393         PrinterInfoManager( CUPS ),
394         m_pCUPSWrapper( pWrapper ),
395         m_nDests( 0 ),
396         m_pDests( NULL ),
397         m_bNewDests( false )
398 {
399     m_aDestThread = osl_createThread( run_dest_thread_stub, this );
400 }
401 
402 CUPSManager::~CUPSManager()
403 {
404     if( m_aDestThread )
405     {
406         // if the thread is still running here, then
407         // cupsGetDests is hung; terminate the thread instead of joining
408         osl_terminateThread( m_aDestThread );
409         osl_destroyThread( m_aDestThread );
410     }
411 
412     if( m_nDests && m_pDests )
413         m_pCUPSWrapper->cupsFreeDests( m_nDests, (cups_dest_t*)m_pDests );
414     delete m_pCUPSWrapper;
415 }
416 
417 void CUPSManager::runDestThread( void* pThis )
418 {
419     ((CUPSManager*)pThis)->runDests();
420 }
421 
422 static sigjmp_buf aViolationBuffer;
423 
424 extern "C"
425 {
426     static void lcl_signal_action(int nSignal)
427     {
428         fprintf( stderr, "Signal %d during fontconfig initialization called, ignoring fontconfig\n", nSignal );
429         siglongjmp( aViolationBuffer, 1 );
430     }
431 }
432 
433 void CUPSManager::runDests()
434 {
435 #if OSL_DEBUG_LEVEL > 1
436     fprintf( stderr, "starting cupsGetDests\n" );
437 #endif
438     int nDests = 0;
439     cups_dest_t* pDests = NULL;
440 
441     // #i86306# prepare against really broken CUPS installations / missing servers
442 
443     // install signal handler for SEGV, BUS and ABRT
444     struct sigaction act;
445 	struct sigaction oact[3];
446 
447     act.sa_handler = lcl_signal_action;
448     act.sa_flags   = 0;
449 	sigemptyset(&(act.sa_mask));
450 
451     int nSegvSignalInstalled = sigaction(SIGSEGV, &act, &oact[0]);
452     int nBusSignalInstalled = sigaction(SIGBUS, &act, &oact[1]);
453     int nAbortSignalInstalled = sigaction(SIGABRT, &act, &oact[2]);
454 
455     // prepare against a signal during FcInit or FcConfigGetCurrent
456     if( sigsetjmp( aViolationBuffer, ~0 ) == 0 )
457     {
458         nDests = m_pCUPSWrapper->cupsGetDests( &pDests );
459         #if OSL_DEBUG_LEVEL > 1
460         fprintf( stderr, "came out of cupsGetDests\n" );
461         #endif
462 
463         osl::MutexGuard aGuard( m_aCUPSMutex );
464         m_nDests = nDests;
465         m_pDests = pDests;
466         m_bNewDests = true;
467         #if OSL_DEBUG_LEVEL > 1
468         fprintf( stderr, "finished cupsGetDests\n" );
469         #endif
470     }
471     else
472     {
473         #if OSL_DEBUG_LEVEL > 1
474         fprintf( stderr, "cupsGetDests crashed, not using CUPS\n" );
475         #endif
476     }
477 
478     // restore old signal handlers
479     if( nSegvSignalInstalled == 0 )
480         sigaction( SIGSEGV, &oact[0], NULL );
481     if( nBusSignalInstalled == 0 )
482         sigaction( SIGBUS, &oact[1], NULL );
483     if( nAbortSignalInstalled == 0 )
484         sigaction( SIGABRT, &oact[2], NULL );
485 }
486 
487 void CUPSManager::initialize()
488 {
489     // get normal printers, clear printer list
490     PrinterInfoManager::initialize();
491 
492 #ifdef ENABLE_CUPS
493     // check whether thread has completed
494     // if not behave like old printing system
495     osl::MutexGuard aGuard( m_aCUPSMutex );
496 
497     if( ! m_bNewDests )
498         return;
499 
500     // dest thread has run, clean up
501     if( m_aDestThread )
502     {
503         osl_joinWithThread( m_aDestThread );
504         osl_destroyThread( m_aDestThread );
505         m_aDestThread = NULL;
506     }
507     m_bNewDests = false;
508 
509     // clear old stuff
510     m_aCUPSDestMap.clear();
511 
512     if( ! (m_nDests && m_pDests ) )
513         return;
514 
515     if( isCUPSDisabled() )
516         return;
517 
518     // check for CUPS server(?) > 1.2
519     // since there is no API to query, check for options that were
520     // introduced in dests with 1.2
521     // this is needed to check for %%IncludeFeature support
522     // (#i65684#, #i65491#)
523     bool bUsePDF = false;
524     cups_dest_t* pDest = ((cups_dest_t*)m_pDests);
525     const char* pOpt = m_pCUPSWrapper->cupsGetOption( "printer-info",
526                                                       pDest->num_options,
527                                                       pDest->options );
528     if( pOpt )
529     {
530         m_bUseIncludeFeature = true;
531         bUsePDF = true;
532         if( m_aGlobalDefaults.m_nPSLevel == 0 && m_aGlobalDefaults.m_nPDFDevice == 0 )
533             m_aGlobalDefaults.m_nPDFDevice = 1;
534     }
535     // do not send include JobPatch; CUPS will insert that itself
536     // TODO: currently unknwon which versions of CUPS insert JobPatches
537     // so currently it is assumed CUPS = don't insert JobPatch files
538     m_bUseJobPatch = false;
539 
540     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
541     int nPrinter = m_nDests;
542 
543     // reset global default PPD options; these are queried on demand from CUPS
544     m_aGlobalDefaults.m_pParser = NULL;
545     m_aGlobalDefaults.m_aContext = PPDContext();
546 
547     // add CUPS printers, should there be a printer
548     // with the same name as a CUPS printer, overwrite it
549     while( nPrinter-- )
550     {
551         pDest = ((cups_dest_t*)m_pDests)+nPrinter;
552         OUString aPrinterName = OStringToOUString( pDest->name, aEncoding );
553         if( pDest->instance && *pDest->instance )
554         {
555             OUStringBuffer aBuf( 256 );
556             aBuf.append( aPrinterName );
557             aBuf.append( sal_Unicode( '/' ) );
558             aBuf.append( OStringToOUString( pDest->instance, aEncoding ) );
559             aPrinterName = aBuf.makeStringAndClear();
560         }
561 
562         // initialize printer with possible configuration from psprint.conf
563         bool bSetToGlobalDefaults = m_aPrinters.find( aPrinterName ) == m_aPrinters.end();
564         Printer aPrinter = m_aPrinters[ aPrinterName ];
565         if( bSetToGlobalDefaults )
566             aPrinter.m_aInfo = m_aGlobalDefaults;
567         aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
568         if( pDest->is_default )
569             m_aDefaultPrinter = aPrinterName;
570 
571         for( int k = 0; k < pDest->num_options; k++ )
572         {
573             if(!strcmp(pDest->options[k].name, "printer-info"))
574                 aPrinter.m_aInfo.m_aComment=OStringToOUString(pDest->options[k].value, aEncoding);
575             if(!strcmp(pDest->options[k].name, "printer-location"))
576                 aPrinter.m_aInfo.m_aLocation=OStringToOUString(pDest->options[k].value, aEncoding);
577         }
578 
579 
580         OUStringBuffer aBuf( 256 );
581         aBuf.appendAscii( "CUPS:" );
582         aBuf.append( aPrinterName );
583         // note: the parser that goes with the PrinterInfo
584         // is created implicitly by the JobData::operator=()
585         // when it detects the NULL ptr m_pParser.
586         // if we wanted to fill in the parser here this
587         // would mean we'd have to download PPDs for each and
588         // every printer - which would be really bad runtime
589         // behaviour
590         aPrinter.m_aInfo.m_pParser = NULL;
591         aPrinter.m_aInfo.m_aContext.setParser( NULL );
592         std::hash_map< OUString, PPDContext, OUStringHash >::const_iterator c_it = m_aDefaultContexts.find( aPrinterName );
593         if( c_it != m_aDefaultContexts.end() )
594         {
595             aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
596             aPrinter.m_aInfo.m_aContext = c_it->second;
597         }
598         if( bUsePDF && aPrinter.m_aInfo.m_nPSLevel == 0 && aPrinter.m_aInfo.m_nPDFDevice == 0 )
599             aPrinter.m_aInfo.m_nPDFDevice = 1;
600         aPrinter.m_aInfo.m_aDriverName = aBuf.makeStringAndClear();
601         aPrinter.m_bModified = false;
602 
603         m_aPrinters[ aPrinter.m_aInfo.m_aPrinterName ] = aPrinter;
604         m_aCUPSDestMap[ aPrinter.m_aInfo.m_aPrinterName ] = nPrinter;
605     }
606 
607     // remove everything that is not a CUPS printer and not
608     // a special purpose printer (PDF, Fax)
609     std::list< OUString > aRemovePrinters;
610     for( std::hash_map< OUString, Printer, OUStringHash >::iterator it = m_aPrinters.begin();
611          it != m_aPrinters.end(); ++it )
612     {
613         if( m_aCUPSDestMap.find( it->first ) != m_aCUPSDestMap.end() )
614             continue;
615 
616         if( it->second.m_aInfo.m_aFeatures.getLength() > 0 )
617             continue;
618         aRemovePrinters.push_back( it->first );
619     }
620     while( aRemovePrinters.begin() != aRemovePrinters.end() )
621     {
622         m_aPrinters.erase( aRemovePrinters.front() );
623         aRemovePrinters.pop_front();
624     }
625 
626     m_pCUPSWrapper->cupsSetPasswordCB( setPasswordCallback );
627 #endif // ENABLE_CUPS
628 }
629 
630 #ifdef ENABLE_CUPS
631 static void updatePrinterContextInfo( ppd_group_t* pPPDGroup, PPDContext& rContext )
632 {
633     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
634     for( int i = 0; i < pPPDGroup->num_options; i++ )
635     {
636         ppd_option_t* pOption = pPPDGroup->options + i;
637         for( int n = 0; n < pOption->num_choices; n++ )
638         {
639             ppd_choice_t* pChoice = pOption->choices + n;
640             if( pChoice->marked )
641             {
642                 const PPDKey* pKey = rContext.getParser()->getKey( OStringToOUString( pOption->keyword, aEncoding ) );
643                 if( pKey )
644                 {
645                     const PPDValue* pValue = pKey->getValue( OStringToOUString( pChoice->choice, aEncoding ) );
646                     if( pValue )
647                     {
648                         if( pValue != pKey->getDefaultValue() )
649                         {
650                             rContext.setValue( pKey, pValue, true );
651 #if OSL_DEBUG_LEVEL > 1
652                             fprintf( stderr, "key %s is set to %s\n", pOption->keyword, pChoice->choice );
653 #endif
654 
655                         }
656 #if OSL_DEBUG_LEVEL > 1
657                         else
658                             fprintf( stderr, "key %s is defaulted to %s\n", pOption->keyword, pChoice->choice );
659 #endif
660                     }
661 #if OSL_DEBUG_LEVEL > 1
662                     else
663                         fprintf( stderr, "caution: value %s not found in key %s\n", pChoice->choice, pOption->keyword );
664 #endif
665                 }
666 #if OSL_DEBUG_LEVEL > 1
667                 else
668                     fprintf( stderr, "caution: key %s not found in parser\n", pOption->keyword );
669 #endif
670             }
671         }
672     }
673 
674     // recurse through subgroups
675     for( int g = 0; g < pPPDGroup->num_subgroups; g++ )
676     {
677         updatePrinterContextInfo( pPPDGroup->subgroups + g, rContext );
678     }
679 }
680 #endif // ENABLE_CUPS
681 
682 const PPDParser* CUPSManager::createCUPSParser( const OUString& rPrinter )
683 {
684     const PPDParser* pNewParser = NULL;
685     OUString aPrinter;
686 
687     if( rPrinter.compareToAscii( "CUPS:", 5 ) == 0 )
688         aPrinter = rPrinter.copy( 5 );
689     else
690         aPrinter = rPrinter;
691 
692 #ifdef ENABLE_CUPS
693     if( m_aCUPSMutex.tryToAcquire() )
694     {
695         if( m_nDests && m_pDests && ! isCUPSDisabled() )
696         {
697             std::hash_map< OUString, int, OUStringHash >::iterator dest_it =
698             m_aCUPSDestMap.find( aPrinter );
699             if( dest_it != m_aCUPSDestMap.end() )
700             {
701                 cups_dest_t* pDest = ((cups_dest_t*)m_pDests) + dest_it->second;
702                 OString aPPDFile = m_pCUPSWrapper->cupsGetPPD( pDest->name );
703                 #if OSL_DEBUG_LEVEL > 1
704                 fprintf( stderr, "PPD for %s is %s\n", OUStringToOString( aPrinter, osl_getThreadTextEncoding() ).getStr(), aPPDFile.getStr() );
705                 #endif
706                 if( aPPDFile.getLength() )
707                 {
708                     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
709                     OUString aFileName( OStringToOUString( aPPDFile, aEncoding ) );
710                     // update the printer info with context information
711                     ppd_file_t* pPPD = m_pCUPSWrapper->ppdOpenFile( aPPDFile.getStr() );
712                     if( pPPD )
713                     {
714                         // create the new parser
715                         PPDParser* pCUPSParser = new PPDParser( aFileName );
716                         pCUPSParser->m_aFile = rPrinter;
717                         pNewParser = pCUPSParser;
718 
719                         /*int nConflicts =*/ m_pCUPSWrapper->cupsMarkOptions( pPPD, pDest->num_options, pDest->options );
720                         #if OSL_DEBUG_LEVEL > 1
721                         fprintf( stderr, "processing the following options for printer %s (instance %s):\n",
722                         pDest->name, pDest->instance );
723                         for( int k = 0; k < pDest->num_options; k++ )
724                             fprintf( stderr, "   \"%s\" = \"%s\"\n",
725                         pDest->options[k].name,
726                         pDest->options[k].value );
727                         #endif
728                         PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
729 
730                         // remember the default context for later use
731                         PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
732                         rContext.setParser( pNewParser );
733                         // set system default paper; printer CUPS PPD options
734                         // may overwrite it
735                         setDefaultPaper( rContext );
736                         for( int i = 0; i < pPPD->num_groups; i++ )
737                             updatePrinterContextInfo( pPPD->groups + i, rContext );
738 
739                         rInfo.m_pParser = pNewParser;
740                         rInfo.m_aContext = rContext;
741 
742                         // clean up the mess
743                         m_pCUPSWrapper->ppdClose( pPPD );
744                     }
745                     #if OSL_DEBUG_LEVEL > 1
746                     else
747                         fprintf( stderr, "ppdOpenFile failed, falling back to generic driver\n" );
748                     #endif
749 
750                     // remove temporary PPD file
751                     unlink( aPPDFile.getStr() );
752                 }
753                 #if OSL_DEBUG_LEVEL > 1
754                 else
755                     fprintf( stderr, "cupsGetPPD failed, falling back to generic driver\n" );
756                 #endif
757             }
758             #if OSL_DEBUG_LEVEL > 1
759             else
760                 fprintf( stderr, "no dest found for printer %s\n", OUStringToOString( aPrinter, osl_getThreadTextEncoding() ).getStr() );
761             #endif
762         }
763         m_aCUPSMutex.release();
764     }
765     #if OSL_DEBUG_LEVEL >1
766     else
767         fprintf( stderr, "could not acquire CUPS mutex !!!\n" );
768     #endif
769     #endif // ENABLE_CUPS
770 
771     if( ! pNewParser )
772     {
773         // get the default PPD
774         pNewParser = PPDParser::getParser( String( RTL_CONSTASCII_USTRINGPARAM( "SGENPRT" ) ) );
775 
776         PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
777 
778         rInfo.m_pParser = pNewParser;
779         rInfo.m_aContext.setParser( pNewParser );
780     }
781 
782     return pNewParser;
783 }
784 
785 void CUPSManager::setupJobContextData(
786     JobData&
787 #ifdef ENABLE_CUPS
788     rData
789 #endif
790 )
791 {
792 #ifdef ENABLE_CUPS
793     std::hash_map< OUString, int, OUStringHash >::iterator dest_it =
794         m_aCUPSDestMap.find( rData.m_aPrinterName );
795 
796     if( dest_it == m_aCUPSDestMap.end() )
797         return PrinterInfoManager::setupJobContextData( rData );
798 
799     std::hash_map< OUString, Printer, OUStringHash >::iterator p_it =
800         m_aPrinters.find( rData.m_aPrinterName );
801     if( p_it == m_aPrinters.end() ) // huh ?
802     {
803 #if OSL_DEBUG_LEVEL > 1
804         fprintf( stderr, "CUPS printer list in disorder, no dest for printer %s !\n", OUStringToOString( rData.m_aPrinterName, osl_getThreadTextEncoding() ).getStr() );
805 #endif
806         return;
807     }
808 
809     if( p_it->second.m_aInfo.m_pParser == NULL )
810     {
811         // in turn calls createCUPSParser
812         // which updates the printer info
813         p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
814     }
815     if( p_it->second.m_aInfo.m_aContext.getParser() == NULL )
816     {
817         OUString aPrinter;
818         if( p_it->second.m_aInfo.m_aDriverName.compareToAscii( "CUPS:", 5 ) == 0 )
819             aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 5 );
820         else
821             aPrinter = p_it->second.m_aInfo.m_aDriverName;
822 
823         p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
824     }
825 
826     rData.m_pParser		= p_it->second.m_aInfo.m_pParser;
827     rData.m_aContext	= p_it->second.m_aInfo.m_aContext;
828 #endif
829 }
830 
831 FILE* CUPSManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
832 {
833     OSL_TRACE( "endSpool: %s, %s",
834                rtl::OUStringToOString( rPrintername, RTL_TEXTENCODING_UTF8 ).getStr(),
835               bQuickCommand ? "true" : "false" );
836 
837     if( m_aCUPSDestMap.find( rPrintername ) == m_aCUPSDestMap.end() )
838     {
839         OSL_TRACE( "defer to PrinterInfoManager::startSpool" );
840         return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
841     }
842 
843 #ifdef ENABLE_CUPS
844     OUString aTmpURL, aTmpFile;
845     osl_createTempFile( NULL, NULL, &aTmpURL.pData );
846     osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
847     OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
848     FILE* fp = fopen( aSysFile.getStr(), "w" );
849     if( fp )
850         m_aSpoolFiles[fp] = aSysFile;
851 
852     return fp;
853 #else
854     return NULL;
855 #endif
856 }
857 
858 struct less_ppd_key : public ::std::binary_function<double, double, bool>
859 {
860     bool operator()(const PPDKey* left, const PPDKey* right)
861     { return left->getOrderDependency() < right->getOrderDependency(); }
862 };
863 
864 void CUPSManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, int& rNumOptions, void** rOptions ) const
865 {
866     rNumOptions = 0;
867     *rOptions = NULL;
868     int i;
869 
870     // emit features ordered to OrderDependency
871     // ignore features that are set to default
872 
873     // sanity check
874     if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser )
875     {
876         int nKeys = rJob.m_aContext.countValuesModified();
877         ::std::vector< const PPDKey* > aKeys( nKeys );
878         for(  i = 0; i < nKeys; i++ )
879             aKeys[i] = rJob.m_aContext.getModifiedKey( i );
880         ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() );
881 
882         for( i = 0; i < nKeys; i++ )
883         {
884             const PPDKey* pKey = aKeys[i];
885             const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
886             if(pValue && pValue->m_eType == eInvocation && pValue->m_aValue.Len() )
887             {
888                 OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
889                 OString aValue = OUStringToOString( pValue->m_aOption, RTL_TEXTENCODING_ASCII_US );
890                 rNumOptions = m_pCUPSWrapper->cupsAddOption( aKey.getStr(), aValue.getStr(), rNumOptions, (cups_option_t**)rOptions );
891             }
892         }
893     }
894 
895     if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 )
896     {
897         rtl::OString aVal( rtl::OString::valueOf( sal_Int32( rJob.m_nCopies ) ) );
898         rNumOptions = m_pCUPSWrapper->cupsAddOption( "copies", aVal.getStr(), rNumOptions, (cups_option_t**)rOptions );
899     }
900     if( ! bBanner )
901     {
902         rNumOptions = m_pCUPSWrapper->cupsAddOption( "job-sheets", "none", rNumOptions, (cups_option_t**)rOptions );
903     }
904 }
905 
906 int CUPSManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner )
907 {
908     OSL_TRACE( "endSpool: %s, %s, copy count = %d",
909                rtl::OUStringToOString( rPrintername, RTL_TEXTENCODING_UTF8 ).getStr(),
910                rtl::OUStringToOString( rJobTitle, RTL_TEXTENCODING_UTF8 ).getStr(),
911                rDocumentJobData.m_nCopies
912                );
913 
914     int nJobID = 0;
915 
916     osl::MutexGuard aGuard( m_aCUPSMutex );
917 
918     std::hash_map< OUString, int, OUStringHash >::iterator dest_it =
919         m_aCUPSDestMap.find( rPrintername );
920     if( dest_it == m_aCUPSDestMap.end() )
921     {
922         OSL_TRACE( "defer to PrinterInfoManager::endSpool" );
923         return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner );
924     }
925 
926     #ifdef ENABLE_CUPS
927     std::hash_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
928     if( it != m_aSpoolFiles.end() )
929     {
930         fclose( pFile );
931         rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
932 
933         // setup cups options
934         int nNumOptions = 0;
935         cups_option_t* pOptions = NULL;
936         getOptionsFromDocumentSetup( rDocumentJobData, bBanner, nNumOptions, (void**)&pOptions );
937 
938         cups_dest_t* pDest = ((cups_dest_t*)m_pDests) + dest_it->second;
939         nJobID = m_pCUPSWrapper->cupsPrintFile( pDest->name,
940         it->second.getStr(),
941         OUStringToOString( rJobTitle, aEnc ).getStr(),
942         nNumOptions, pOptions );
943 #if OSL_DEBUG_LEVEL > 1
944         fprintf( stderr, "cupsPrintFile( %s, %s, %s, %d, %p ) returns %d\n",
945                     pDest->name,
946                     it->second.getStr(),
947                     OUStringToOString( rJobTitle, aEnc ).getStr(),
948                     nNumOptions,
949                     pOptions,
950                     nJobID
951                     );
952         for( int n = 0; n < nNumOptions; n++ )
953             fprintf( stderr, "    option %s=%s\n", pOptions[n].name, pOptions[n].value );
954         OString aCmd( "cp " );
955         aCmd = aCmd + it->second;
956         aCmd = aCmd + OString( " $HOME/cupsprint.ps" );
957         system( aCmd.getStr() );
958 #endif
959 
960         unlink( it->second.getStr() );
961         m_aSpoolFiles.erase( pFile );
962         if( pOptions )
963             m_pCUPSWrapper->cupsFreeOptions( nNumOptions, pOptions );
964     }
965 #endif // ENABLE_CUPS
966 
967     return nJobID;
968 }
969 
970 
971 void CUPSManager::changePrinterInfo( const OUString& rPrinter, const PrinterInfo& rNewInfo )
972 {
973     PrinterInfoManager::changePrinterInfo( rPrinter, rNewInfo );
974 }
975 
976 bool CUPSManager::checkPrintersChanged( bool bWait )
977 {
978     bool bChanged = false;
979     if( bWait )
980     {
981         if(  m_aDestThread )
982         {
983             // initial asynchronous detection still running
984             #if OSL_DEBUG_LEVEL > 1
985             fprintf( stderr, "syncing cups discovery thread\n" );
986             #endif
987             osl_joinWithThread( m_aDestThread );
988             osl_destroyThread( m_aDestThread );
989             m_aDestThread = NULL;
990             #if OSL_DEBUG_LEVEL > 1
991             fprintf( stderr, "done: syncing cups discovery thread\n" );
992             #endif
993         }
994         else
995         {
996             // #i82321# check for cups printer updates
997             // with this change the whole asynchronous detection in a thread is
998             // almost useless. The only relevance left is for some stalled systems
999             // where the user can set SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION
1000             // (see vcl/unx/source/gdi/salprnpsp.cxx)
1001             // so that checkPrintersChanged( true ) will never be called
1002 
1003             // there is no way to query CUPS whether the printer list has changed
1004             // so get the dest list anew
1005             if( m_nDests && m_pDests )
1006                 m_pCUPSWrapper->cupsFreeDests( m_nDests, (cups_dest_t*)m_pDests );
1007             m_nDests = 0;
1008             m_pDests = NULL;
1009             runDests();
1010         }
1011     }
1012     if( m_aCUPSMutex.tryToAcquire() )
1013     {
1014         bChanged = m_bNewDests;
1015         m_aCUPSMutex.release();
1016     }
1017 
1018     if( ! bChanged )
1019     {
1020         bChanged = PrinterInfoManager::checkPrintersChanged( bWait );
1021         // #i54375# ensure new merging with CUPS list in :initialize
1022         if( bChanged )
1023             m_bNewDests = true;
1024     }
1025 
1026     if( bChanged )
1027         initialize();
1028 
1029     return bChanged;
1030 }
1031 
1032 bool CUPSManager::addPrinter( const OUString& rName, const OUString& rDriver )
1033 {
1034     // don't touch the CUPS printers
1035     if( m_aCUPSDestMap.find( rName ) != m_aCUPSDestMap.end() ||
1036         rDriver.compareToAscii( "CUPS:", 5 ) == 0
1037         )
1038         return false;
1039     return PrinterInfoManager::addPrinter( rName, rDriver );
1040 }
1041 
1042 bool CUPSManager::removePrinter( const OUString& rName, bool bCheck )
1043 {
1044     // don't touch the CUPS printers
1045     if( m_aCUPSDestMap.find( rName ) != m_aCUPSDestMap.end() )
1046         return false;
1047     return PrinterInfoManager::removePrinter( rName, bCheck );
1048 }
1049 
1050 bool CUPSManager::setDefaultPrinter( const OUString& rName )
1051 {
1052     bool bSuccess = false;
1053 #ifdef ENABLE_CUPS
1054     std::hash_map< OUString, int, OUStringHash >::iterator nit =
1055         m_aCUPSDestMap.find( rName );
1056     if( nit != m_aCUPSDestMap.end() && m_aCUPSMutex.tryToAcquire() )
1057     {
1058         cups_dest_t* pDests = (cups_dest_t*)m_pDests;
1059         for( int i = 0; i < m_nDests; i++ )
1060             pDests[i].is_default = 0;
1061         pDests[ nit->second ].is_default = 1;
1062         m_pCUPSWrapper->cupsSetDests( m_nDests, (cups_dest_t*)m_pDests );
1063         m_aDefaultPrinter = rName;
1064         m_aCUPSMutex.release();
1065         bSuccess = true;
1066     }
1067     else
1068 #endif
1069         bSuccess = PrinterInfoManager::setDefaultPrinter( rName );
1070 
1071     return bSuccess;
1072 }
1073 
1074 bool CUPSManager::writePrinterConfig()
1075 {
1076 #ifdef ENABLE_CUPS
1077     bool bDestModified = false;
1078     rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
1079 
1080     for( std::hash_map< OUString, Printer, OUStringHash >::iterator prt =
1081              m_aPrinters.begin(); prt != m_aPrinters.end(); ++prt )
1082     {
1083         std::hash_map< OUString, int, OUStringHash >::iterator nit =
1084             m_aCUPSDestMap.find( prt->first );
1085         if( nit == m_aCUPSDestMap.end() )
1086             continue;
1087 
1088         if( ! prt->second.m_bModified )
1089             continue;
1090 
1091         if( m_aCUPSMutex.tryToAcquire() )
1092         {
1093             bDestModified = true;
1094             cups_dest_t* pDest = ((cups_dest_t*)m_pDests) + nit->second;
1095             PrinterInfo& rInfo = prt->second.m_aInfo;
1096 
1097             // create new option list
1098             int nNewOptions = 0;
1099             cups_option_t* pNewOptions = NULL;
1100             int nValues = rInfo.m_aContext.countValuesModified();
1101             for( int i = 0; i < nValues; i++ )
1102             {
1103                 const PPDKey* pKey = rInfo.m_aContext.getModifiedKey( i );
1104                 const PPDValue* pValue = rInfo.m_aContext.getValue( pKey );
1105                 if( pKey && pValue ) // sanity check
1106                 {
1107                     OString aName = OUStringToOString( pKey->getKey(), aEncoding );
1108                     OString aValue = OUStringToOString( pValue->m_aOption, aEncoding );
1109                     nNewOptions = m_pCUPSWrapper->cupsAddOption( aName.getStr(), aValue.getStr(), nNewOptions, &pNewOptions );
1110                 }
1111             }
1112             // set PPD options on CUPS dest
1113             m_pCUPSWrapper->cupsFreeOptions( pDest->num_options, pDest->options );
1114             pDest->num_options = nNewOptions;
1115             pDest->options = pNewOptions;
1116             m_aCUPSMutex.release();
1117         }
1118     }
1119     if( bDestModified && m_aCUPSMutex.tryToAcquire() )
1120     {
1121         m_pCUPSWrapper->cupsSetDests( m_nDests, (cups_dest_t*)m_pDests );
1122         m_aCUPSMutex.release();
1123     }
1124 #endif // ENABLE_CUPS
1125 
1126     return PrinterInfoManager::writePrinterConfig();
1127 }
1128 
1129 bool CUPSManager::addOrRemovePossible() const
1130 {
1131     return (m_nDests && m_pDests && ! isCUPSDisabled())? false : PrinterInfoManager::addOrRemovePossible();
1132 }
1133 
1134 const char* CUPSManager::authenticateUser( const char* /*pIn*/ )
1135 {
1136     const char* pRet = NULL;
1137 
1138 #ifdef ENABLE_CUPS
1139     OUString aLib = OUString::createFromAscii( _XSALSET_LIBNAME );
1140     oslModule pLib = osl_loadModule( aLib.pData, SAL_LOADMODULE_LAZY );
1141     if( pLib )
1142     {
1143         OUString aSym( RTL_CONSTASCII_USTRINGPARAM( "Sal_authenticateQuery" ) );
1144         bool (*getpw)( const OString& rServer, OString& rUser, OString& rPw) =
1145             (bool(*)(const OString&,OString&,OString&))osl_getFunctionSymbol( pLib, aSym.pData );
1146         if( getpw )
1147         {
1148             osl::MutexGuard aGuard( m_aCUPSMutex );
1149 
1150             OString aUser = m_pCUPSWrapper->cupsUser();
1151             OString aServer = m_pCUPSWrapper->cupsServer();
1152             OString aPassword;
1153             if( getpw( aServer, aUser, aPassword ) )
1154             {
1155                 m_aPassword = aPassword;
1156                 m_aUser = aUser;
1157                 m_pCUPSWrapper->cupsSetUser( m_aUser.getStr() );
1158                 pRet = m_aPassword.getStr();
1159             }
1160         }
1161         osl_unloadModule( pLib );
1162     }
1163 #if OSL_DEBUG_LEVEL > 1
1164     else fprintf( stderr, "loading of module %s failed\n", OUStringToOString( aLib, osl_getThreadTextEncoding() ).getStr() );
1165 #endif
1166 #endif // ENABLE_CUPS
1167 
1168     return pRet;
1169 }
1170