xref: /AOO41X/main/desktop/source/deployment/dp_persmap.cxx (revision 54628ca40d27d15cc98fe861da7fff7e60c2f7d6)
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 // MARKER(update_precomp.py): autogen include statement, do not remove
23 #include "precompiled_desktop.hxx"
24 
25 #include "dp_misc.h"
26 #include "dp_persmap.h"
27 #include "rtl/strbuf.hxx"
28 
29 #ifndef DISABLE_BDB2PMAP
30 #include <vector>
31 #endif
32 
33 using namespace ::rtl;
34 
35 // the persistent map is used to manage a handful of key-value string pairs
36 // this implementation replaces a rather heavy-weight berkeleydb integration
37 
38 // the file backing up a persistent map consists of line pairs with
39 // - a key string   (encoded with chars 0x00..0x0F being escaped)
40 // - a value string (encoded with chars 0x00..0x0F being escaped)
41 
42 namespace dp_misc
43 {
44 
45 static const char PmapMagic[4] = {'P','m','p','1'};
46 
47 //______________________________________________________________________________
48 PersistentMap::PersistentMap( OUString const & url_, bool readOnly )
49 :   m_MapFile( expandUnoRcUrl(url_) )
50 ,   m_bReadOnly( readOnly)
51 ,   m_bIsOpen( false)
52 ,   m_bToBeCreated( !readOnly)
53 ,   m_bIsDirty( false)
54 {
55 #ifndef DISABLE_BDB2PMAP
56     m_MapFileName = expandUnoRcUrl( url_);
57 #endif
58 
59     open();
60 }
61 
62 //______________________________________________________________________________
63 PersistentMap::PersistentMap()
64 :   m_MapFile( OUString())
65 ,   m_bReadOnly( false)
66 ,   m_bIsOpen( false)
67 ,   m_bToBeCreated( false)
68 ,   m_bIsDirty( false)
69 {}
70 
71 //______________________________________________________________________________
72 PersistentMap::~PersistentMap()
73 {
74     if( m_bIsDirty)
75         flush();
76     if( m_bIsOpen)
77         m_MapFile.close();
78 }
79 
80 //______________________________________________________________________________
81 
82 // replace 0x00..0x0F with "%0".."%F"
83 // replace "%" with "%%"
84 static OString encodeString( const OString& rStr)
85 {
86     const sal_Char* pChar = rStr.getStr();
87     const sal_Int32 nLen = rStr.getLength();
88     sal_Int32 i = nLen;
89     // short circuit for the simple non-encoded case
90     while( --i >= 0)
91     {
92         const sal_Char c = *(pChar++);
93         if( (0x00 <= c) && (c <= 0x0F))
94             break;
95         if( c == '%')
96             break;
97     }
98     if( i < 0)
99         return rStr;
100 
101     // escape chars 0x00..0x0F with "%0".."%F"
102     OStringBuffer aEncStr( nLen + 32);
103     aEncStr.append( pChar - (nLen-i), nLen - i);
104     while( --i >= 0)
105     {
106         sal_Char c = *(pChar++);
107         if( (0x00 <= c) && (c <= 0x0F))
108         {
109             aEncStr.append( '%');
110             c += (c <= 0x09) ? '0' : 'A'-10;
111         } else if( c == '%')
112             aEncStr.append( '%');
113         aEncStr.append( c);
114     }
115 
116     return aEncStr.makeStringAndClear();
117 }
118 
119 //______________________________________________________________________________
120 
121 // replace "%0".."%F" with 0x00..0x0F
122 // replace "%%" with "%"
123 static OString decodeString( const sal_Char* pEncChars, int nLen)
124 {
125     const char* pChar = pEncChars;
126     sal_Int32 i = nLen;
127     // short circuit for the simple non-encoded case
128     while( --i >= 0)
129         if( *(pChar++) == '%')
130             break;
131     if( i < 0)
132         return OString( pEncChars, nLen);
133 
134     // replace escaped chars with their decoded counterparts
135     OStringBuffer aDecStr( nLen);
136     pChar = pEncChars;
137     for( i = nLen; --i >= 0;)
138     {
139         sal_Char c = *(pChar++);
140         // handle escaped character
141         if( c == '%')
142         {
143             --i;
144             OSL_ASSERT( i >= 0);
145             c = *(pChar++);
146             if( ('0' <= c) && (c <= '9'))
147                 c -= '0';
148             else
149             {
150                 OSL_ASSERT( ('A' <= c) && (c <= 'F'));
151                 c -= ('A'-10);
152             }
153         }
154         aDecStr.append( c);
155     }
156 
157     return aDecStr.makeStringAndClear();
158 }
159 
160 //______________________________________________________________________________
161 bool PersistentMap::open()
162 {
163     // open the existing file
164     sal_uInt32 nOpenFlags = osl_File_OpenFlag_Read;
165     if( !m_bReadOnly)
166         nOpenFlags |= osl_File_OpenFlag_Write;
167 
168     const osl::File::RC rcOpen = m_MapFile.open( nOpenFlags);
169     m_bIsOpen = (rcOpen == osl::File::E_None);
170 
171     // or create later if needed
172     m_bToBeCreated &= (rcOpen == osl::File::E_NOENT) && !m_bIsOpen;
173 
174 #ifndef DISABLE_BDB2PMAP
175     if( m_bToBeCreated)
176         importFromBDB();
177 #endif // DISABLE_BDB2PMAP
178 
179     if( !m_bIsOpen)
180         return m_bToBeCreated;
181 
182     const bool readOK = readAll();
183     return readOK;
184 }
185 
186 //______________________________________________________________________________
187 bool PersistentMap::readAll()
188 {
189     // prepare for re-reading the map-file
190     m_MapFile.setPos( osl_Pos_Absolut, 0);
191     m_entries.clear();
192 
193     // read header and check magic
194     char aHeaderBytes[ sizeof(PmapMagic)];
195     sal_uInt64 nBytesRead = 0;
196     m_MapFile.read( aHeaderBytes, sizeof(aHeaderBytes), nBytesRead);
197     OSL_ASSERT( nBytesRead == sizeof(aHeaderBytes));
198     if( nBytesRead != sizeof(aHeaderBytes))
199         return false;
200     // check header magic
201     for( int i = 0; i < (int)sizeof(PmapMagic); ++i)
202         if( aHeaderBytes[i] != PmapMagic[i])
203             return false;
204 
205     // read key value pairs and add them to the map
206     ByteSequence aKeyLine;
207     ByteSequence aValLine;
208     for(;;)
209     {
210         // read key-value line pair
211         // an empty key name indicates the end of the line pairs
212         if( m_MapFile.readLine( aKeyLine) != osl::File::E_None)
213             return false;
214         if( !aKeyLine.getLength())
215             break;
216         if( m_MapFile.readLine( aValLine) != osl::File::E_None)
217             return false;
218         // decode key and value strings
219         const OString aKeyName = decodeString( (sal_Char*)aKeyLine.getConstArray(), aKeyLine.getLength());
220         const OString aValName = decodeString( (sal_Char*)aValLine.getConstArray(), aValLine.getLength());
221         // insert key-value pair into map
222         add( aKeyName, aValName);
223         // check end-of-file status
224         sal_Bool bIsEOF = true;
225         if( m_MapFile.isEndOfFile( &bIsEOF) != osl::File::E_None)
226             return false;
227         if( bIsEOF)
228             break;
229     }
230 
231     m_bIsDirty = false;
232     return true;
233 }
234 
235 //______________________________________________________________________________
236 void PersistentMap::flush( void)
237 {
238     if( !m_bIsDirty)
239         return;
240     OSL_ASSERT( !m_bReadOnly);
241     if( m_bToBeCreated && !m_entries.empty())
242     {
243         const sal_uInt32 nOpenFlags = osl_File_OpenFlag_Read | osl_File_OpenFlag_Write | osl_File_OpenFlag_Create;
244         const osl::File::RC rcOpen = m_MapFile.open( nOpenFlags);
245         m_bIsOpen = (rcOpen == osl::File::E_None);
246         m_bToBeCreated = !m_bIsOpen;
247     }
248     if( !m_bIsOpen)
249         return;
250 
251     // write header magic
252     m_MapFile.setPos( osl_Pos_Absolut, 0);
253     sal_uInt64 nBytesWritten = 0;
254     m_MapFile.write( PmapMagic, sizeof(PmapMagic), nBytesWritten);
255 
256     // write key value pairs
257     t_string2string_map::const_iterator it = m_entries.begin();
258     for(; it != m_entries.end(); ++it) {
259         // write line for key
260         const OString aKeyString = encodeString( (*it).first);
261         const sal_Int32 nKeyLen = aKeyString.getLength();
262         m_MapFile.write( aKeyString.getStr(), nKeyLen, nBytesWritten);
263         OSL_ASSERT( nKeyLen == (sal_Int32)nBytesWritten);
264         m_MapFile.write( "\n", 1, nBytesWritten);
265         // write line for value
266         const OString& rValString = encodeString( (*it).second);
267         const sal_Int32 nValLen = rValString.getLength();
268         m_MapFile.write( rValString.getStr(), nValLen, nBytesWritten);
269         OSL_ASSERT( nValLen == (sal_Int32)nBytesWritten);
270         m_MapFile.write( "\n", 1, nBytesWritten);
271     }
272 
273     // write a file delimiter (an empty key-string)
274     m_MapFile.write( "\n", 1, nBytesWritten);
275     // truncate file here
276     sal_uInt64 nNewFileSize;
277     if( m_MapFile.getPos( nNewFileSize) == osl::File::E_None)
278         m_MapFile.setSize( nNewFileSize);
279     // flush to disk
280     m_MapFile.sync();
281     // the in-memory map now matches to the file on disk
282     m_bIsDirty = false;
283 }
284 
285 //______________________________________________________________________________
286 bool PersistentMap::has( OString const & key ) const
287 {
288     return get( NULL, key );
289 }
290 
291 //______________________________________________________________________________
292 bool PersistentMap::get( OString * value, OString const & key ) const
293 {
294     t_string2string_map::const_iterator it = m_entries.find( key);
295     if( it == m_entries.end())
296         return false;
297     if( value)
298         *value = it->second;
299     return true;
300 }
301 
302 //______________________________________________________________________________
303 void PersistentMap::add( OString const & key, OString const & value )
304 {
305     if( m_bReadOnly)
306         return;
307     typedef std::pair<t_string2string_map::iterator,bool> InsertRC;
308     InsertRC r = m_entries.insert( t_string2string_map::value_type(key,value));
309     m_bIsDirty = r.second;
310 }
311 
312 //______________________________________________________________________________
313 void PersistentMap::put( OString const & key, OString const & value )
314 {
315     add( key, value);
316     // HACK: flush now as the extension manager does not seem
317     //       to properly destruct this object in some situations
318     if( m_bIsDirty)
319         flush();
320 }
321 
322 //______________________________________________________________________________
323 bool PersistentMap::erase( OString const & key, bool flush_immediately )
324 {
325     if( m_bReadOnly)
326         return false;
327     size_t nCount = m_entries.erase( key);
328     if( !nCount)
329         return false;
330     m_bIsDirty = true;
331     if( flush_immediately)
332         flush();
333     return true;
334 }
335 
336 //______________________________________________________________________________
337 t_string2string_map PersistentMap::getEntries() const
338 {
339     // TODO: return by const reference instead?
340     return m_entries;
341 }
342 
343 //______________________________________________________________________________
344 #ifndef DISABLE_BDB2PMAP
345 bool PersistentMap::importFromBDB()
346 {
347     if( m_bReadOnly)
348         return false;
349 
350     // get the name of its BDB counterpart
351     rtl::OUString aDBName = m_MapFileName;
352     if( !aDBName.endsWithAsciiL( ".pmap", 5))
353         return false;
354     aDBName = aDBName.replaceAt( aDBName.getLength()-5, 5, OUSTR(".db"));
355 
356     // open the corresponding BDB file for reading
357     osl::File aDBFile( aDBName);
358     osl::File::RC rc = aDBFile.open( osl_File_OpenFlag_Read);
359     if( rc != osl::File::E_None)
360         return false;
361     sal_uInt64 nFileSize = 0;
362     if( aDBFile.getSize( nFileSize) != osl::File::E_None)
363         return false;
364 
365     // read the BDB file
366     std::vector<sal_uInt8> aRawBDB( nFileSize);
367     for( sal_uInt64 nOfs = 0; nOfs < nFileSize;) {
368         sal_uInt64 nBytesRead = 0;
369         rc = aDBFile.read( (void*)&aRawBDB[nOfs], nFileSize - nOfs, nBytesRead);
370         if( (rc != osl::File::E_None) || !nBytesRead)
371             return false;
372         nOfs += nBytesRead;
373     }
374 
375     // check BDB file header for non_encrypted Hash format v4..9
376     if( nFileSize < 0x0100)
377         return false;
378     if( aRawBDB[24] != 0) // only not-encrypted migration
379         return false;
380     if( aRawBDB[25] != 8) // we expect a P_HASHMETA page
381         return false;
382     const bool bLE = (aRawBDB[12]==0x61 && aRawBDB[13]==0x15 && aRawBDB[14]==0x06);
383     const bool bBE = (aRawBDB[15]==0x61 && aRawBDB[14]==0x15 && aRawBDB[13]==0x06);
384     if( bBE == bLE)
385         return false;
386     if( (aRawBDB[16] < 4) || (9 < aRawBDB[16])) // version
387         return false;
388     const sal_uInt64 nPgSize = bLE
389     ?   (aRawBDB[20] + (aRawBDB[21]<<8) + (aRawBDB[22]<<16) + (aRawBDB[23]<<24))
390     :   (aRawBDB[23] + (aRawBDB[22]<<8) + (aRawBDB[21]<<16) + (aRawBDB[20]<<24));
391     const int nPgCount = nFileSize / nPgSize;
392     if( nPgCount * nPgSize != nFileSize)
393         return false;
394 
395     // find PackageManager's new_style entries
396     // using a simple heuristic for BDB_Hash pages
397     int nEntryCount = 0;
398     for( int nPgNo = 1; nPgNo < nPgCount; ++nPgNo) {
399         // parse the next _db_page
400         const sal_uInt8* const pPage = &aRawBDB[ nPgNo * nPgSize];
401         const sal_uInt8* const pEnd = pPage + nPgSize;
402         const int nHfOffset = bLE ? (pPage[22] + (pPage[23]<<8)) : (pPage[23] + (pPage[22]<<8));
403         if( nHfOffset <= 0)
404             continue;
405         const sal_uInt8* pCur = pPage + nHfOffset;
406         // iterate through the entries
407         for(; pCur < pEnd; ++pCur) {
408             if( pCur[0] != 0x01)
409                 continue;
410             // get the value-candidate
411             const sal_uInt8* pVal = pCur + 1;
412             while( ++pCur < pEnd)
413                 if( (*pCur < ' ') || ((*pCur > 0x7F) && (*pCur != 0xFF)))
414                     break;
415             if( pCur >= pEnd)
416                 break;
417             if( (pCur[0] != 0x01) || (pCur[1] != 0xFF))
418                 continue;
419             const OString aVal( (sal_Char*)pVal, pCur - pVal);
420             // get the key-candidate
421             const sal_uInt8* pKey = pCur + 1;
422             while( ++pCur < pEnd)
423                 if( (*pCur < ' ') || ((*pCur > 0x7F) && (*pCur != 0xFF)))
424                     break;
425             if( (pCur < pEnd) && (*pCur > 0x01))
426                 continue;
427             const OString aKey( (sal_Char*)pKey, pCur - pKey);
428             --pCur; // prepare for next round by rewinding to end of key-string
429 
430             // add the key/value pair
431             add( aKey, aVal);
432             ++nEntryCount;
433         }
434     }
435 
436     return (nEntryCount > 0);
437 }
438 #endif // DISABLE_BDB2PMAP
439 
440 }
441 
442