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