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