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