1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
28 #include <osl/file.hxx>
29 #include <tools/config.hxx>
30 #include <sal/log.hxx>
46 ImplGroupData
* mpNext
;
47 ImplKeyData
* mpFirstKey
;
49 sal_uInt16 mnEmptyLines
;
54 ImplGroupData
* mpFirstGroup
;
56 sal_uInt32 mnDataUpdateId
;
57 sal_uInt32 mnTimeStamp
;
63 static OUString
toUncPath( const OUString
& rPath
)
67 // check if rFileName is already a URL; if not make it so
68 if( rPath
.startsWith( "file://"))
72 else if( ::osl::FileBase::getFileURLFromSystemPath( rPath
, aFileURL
) != ::osl::FileBase::E_None
)
79 static sal_uInt32
ImplSysGetConfigTimeStamp( const OUString
& rFileName
)
81 sal_uInt32 nTimeStamp
= 0;
82 ::osl::DirectoryItem aItem
;
83 ::osl::FileStatus
aStatus( osl_FileStatus_Mask_ModifyTime
);
85 if( ::osl::DirectoryItem::get( rFileName
, aItem
) == ::osl::FileBase::E_None
&&
86 aItem
.getFileStatus( aStatus
) == ::osl::FileBase::E_None
)
88 nTimeStamp
= aStatus
.getModifyTime().Seconds
;
94 static std::unique_ptr
<sal_uInt8
[]> ImplSysReadConfig( const OUString
& rFileName
,
95 sal_uInt64
& rRead
, bool& rbRead
, bool& rbIsUTF8BOM
, sal_uInt32
& rTimeStamp
)
97 std::unique_ptr
<sal_uInt8
[]> pBuf
;
98 ::osl::File
aFile( rFileName
);
100 if( aFile
.open( osl_File_OpenFlag_Read
) == ::osl::FileBase::E_None
)
103 if( aFile
.getSize( nPos
) == ::osl::FileBase::E_None
)
105 if (nPos
> SAL_MAX_SIZE
) {
109 pBuf
.reset(new sal_uInt8
[static_cast< std::size_t >(nPos
)]);
110 sal_uInt64 nRead
= 0;
111 if( aFile
.read( pBuf
.get(), nPos
, nRead
) == ::osl::FileBase::E_None
&& nRead
== nPos
)
113 //skip the byte-order-mark 0xEF 0xBB 0xBF, if it was UTF8 files
114 unsigned char const BOM
[3] = {0xEF, 0xBB, 0xBF};
115 if (nRead
> 2 && memcmp(pBuf
.get(), BOM
, 3) == 0)
118 memmove(pBuf
.get(), pBuf
.get() + 3, sal::static_int_cast
<std::size_t>(nRead
* sizeof(sal_uInt8
)) );
122 rTimeStamp
= ImplSysGetConfigTimeStamp( rFileName
);
137 static bool ImplSysWriteConfig( const OUString
& rFileName
,
138 const sal_uInt8
* pBuf
, sal_uInt32 nBufLen
, bool rbIsUTF8BOM
, sal_uInt32
& rTimeStamp
)
140 bool bSuccess
= false;
141 bool bUTF8BOMSuccess
= false;
143 ::osl::File
aFile( rFileName
);
144 ::osl::FileBase::RC eError
= aFile
.open( osl_File_OpenFlag_Write
| osl_File_OpenFlag_Create
);
145 if( eError
!= ::osl::FileBase::E_None
)
146 eError
= aFile
.open( osl_File_OpenFlag_Write
);
147 if( eError
== ::osl::FileBase::E_None
)
153 //write the byte-order-mark 0xEF 0xBB 0xBF first , if it was UTF8 files
156 unsigned char const BOM
[3] = {0xEF, 0xBB, 0xBF};
157 sal_uInt64 nUTF8BOMWritten
;
158 if( aFile
.write( BOM
, 3, nUTF8BOMWritten
) == ::osl::FileBase::E_None
&& 3 == nUTF8BOMWritten
)
160 bUTF8BOMSuccess
= true;
164 if( aFile
.write( pBuf
, nBufLen
, nWritten
) == ::osl::FileBase::E_None
&& nWritten
== nBufLen
)
168 if ( rbIsUTF8BOM
? bSuccess
&& bUTF8BOMSuccess
: bSuccess
)
170 rTimeStamp
= ImplSysGetConfigTimeStamp( rFileName
);
174 return rbIsUTF8BOM
? bSuccess
&& bUTF8BOMSuccess
: bSuccess
;
178 OString
makeOString(const sal_uInt8
* p
, sal_uInt64 n
)
180 if (n
> SAL_MAX_INT32
)
185 ::std::abort(); //TODO: handle this gracefully
189 reinterpret_cast< char const * >(p
),
190 sal::static_int_cast
< sal_Int32
>(n
));
194 static void ImplMakeConfigList( ImplConfigData
* pData
,
195 const sal_uInt8
* pBuf
, sal_uInt64 nLen
)
200 // Parse buffer and build config list
206 const sal_uInt8
* pLine
;
207 ImplKeyData
* pPrevKey
= nullptr;
209 ImplGroupData
* pPrevGroup
= nullptr;
210 ImplGroupData
* pGroup
= nullptr;
215 if ( pBuf
[i
] == 0x1A )
218 // Remove spaces and tabs
219 while ( (pBuf
[i
] == ' ') || (pBuf
[i
] == '\t') )
222 // remember line-starts
226 // search line-endings
227 while ( (i
< nLen
) && pBuf
[i
] && (pBuf
[i
] != '\r') && (pBuf
[i
] != '\n') &&
233 // if Line-ending is found, continue once
235 (pBuf
[i
] != pBuf
[i
+1]) &&
236 ((pBuf
[i
+1] == '\r') || (pBuf
[i
+1] == '\n')) )
243 pGroup
= new ImplGroupData
;
244 pGroup
->mpNext
= nullptr;
245 pGroup
->mpFirstKey
= nullptr;
246 pGroup
->mnEmptyLines
= 0;
248 pPrevGroup
->mpNext
= pGroup
;
250 pData
->mpFirstGroup
= pGroup
;
255 // filter group names
258 // remove spaces and tabs
259 while ( (*pLine
== ' ') || (*pLine
== '\t') )
265 while ( (nNameLen
< nLineLen
) && (pLine
[nNameLen
] != ']') )
269 while ( (pLine
[nNameLen
-1] == ' ') || (pLine
[nNameLen
-1] == '\t') )
272 pGroup
->maGroupName
= makeOString(pLine
, nNameLen
);
278 // If no group exists yet, add to default
281 pGroup
= new ImplGroupData
;
282 pGroup
->mpNext
= nullptr;
283 pGroup
->mpFirstKey
= nullptr;
284 pGroup
->mnEmptyLines
= 0;
285 pData
->mpFirstGroup
= pGroup
;
290 // if empty line, append it
293 while ( pGroup
->mnEmptyLines
)
295 pKey
= new ImplKeyData
;
296 pKey
->mbIsComment
= true;
297 pPrevKey
->mpNext
= pKey
;
299 pGroup
->mnEmptyLines
--;
304 pKey
= new ImplKeyData
;
305 pKey
->mpNext
= nullptr;
307 pPrevKey
->mpNext
= pKey
;
309 pGroup
->mpFirstKey
= pKey
;
311 if ( pLine
[0] == ';' )
313 pKey
->maValue
= makeOString(pLine
, nLineLen
);
314 pKey
->mbIsComment
= true;
318 pKey
->mbIsComment
= false;
320 while ( (nNameLen
< nLineLen
) && (pLine
[nNameLen
] != '=') )
323 // Remove spaces and tabs
326 while ( (pLine
[nNameLen
-1] == ' ') || (pLine
[nNameLen
-1] == '\t') )
329 pKey
->maKey
= makeOString(pLine
, nNameLen
);
331 if ( nKeyLen
< nLineLen
)
335 // Remove spaces and tabs
336 while ( (*pLine
== ' ') || (*pLine
== '\t') )
343 while ( (pLine
[nLineLen
-1] == ' ') || (pLine
[nLineLen
-1] == '\t') )
345 pKey
->maValue
= makeOString(pLine
, nLineLen
);
352 // Spaces are counted and appended only after key generation,
353 // as we want to store spaces even after adding new keys
355 pGroup
->mnEmptyLines
++;
361 static std::unique_ptr
<sal_uInt8
[]> ImplGetConfigBuffer( const ImplConfigData
* pData
, sal_uInt32
& rLen
)
363 std::unique_ptr
<sal_uInt8
[]> pWriteBuf
;
365 sal_uInt8 aLineEndBuf
[2] = {0, 0};
367 ImplGroupData
* pGroup
;
369 sal_uInt32 nValueLen
;
371 sal_uInt32 nLineEndLen
;
373 aLineEndBuf
[0] = '\r';
374 aLineEndBuf
[1] = '\n';
378 pGroup
= pData
->mpFirstGroup
;
381 // Don't write empty groups
382 if ( pGroup
->mpFirstKey
)
384 nBufLen
+= pGroup
->maGroupName
.getLength() + nLineEndLen
+ 2;
385 pKey
= pGroup
->mpFirstKey
;
388 nValueLen
= pKey
->maValue
.getLength();
389 if ( pKey
->mbIsComment
)
390 nBufLen
+= nValueLen
+ nLineEndLen
;
392 nBufLen
+= pKey
->maKey
.getLength() + nValueLen
+ nLineEndLen
+ 1;
397 // Write empty lines after each group
398 if ( !pGroup
->mnEmptyLines
)
399 pGroup
->mnEmptyLines
= 1;
400 nBufLen
+= nLineEndLen
* pGroup
->mnEmptyLines
;
403 pGroup
= pGroup
->mpNext
;
406 // Output buffer length
410 pWriteBuf
.reset(new sal_uInt8
[nLineEndLen
]);
411 pWriteBuf
[0] = aLineEndBuf
[0];
412 if ( nLineEndLen
== 2 )
413 pWriteBuf
[1] = aLineEndBuf
[1];
417 // Allocate new write buffer (caller frees it)
418 pWriteBuf
.reset(new sal_uInt8
[nBufLen
]);
421 pBuf
= pWriteBuf
.get();
422 pGroup
= pData
->mpFirstGroup
;
425 // Don't write empty groups
426 if ( pGroup
->mpFirstKey
)
429 memcpy( pBuf
, pGroup
->maGroupName
.getStr(), pGroup
->maGroupName
.getLength() );
430 pBuf
+= pGroup
->maGroupName
.getLength();
432 *pBuf
= aLineEndBuf
[0]; pBuf
++;
433 if ( nLineEndLen
== 2 )
435 *pBuf
= aLineEndBuf
[1]; pBuf
++;
437 pKey
= pGroup
->mpFirstKey
;
440 nValueLen
= pKey
->maValue
.getLength();
441 if ( pKey
->mbIsComment
)
445 memcpy( pBuf
, pKey
->maValue
.getStr(), nValueLen
);
448 *pBuf
= aLineEndBuf
[0]; pBuf
++;
449 if ( nLineEndLen
== 2 )
451 *pBuf
= aLineEndBuf
[1]; pBuf
++;
456 nKeyLen
= pKey
->maKey
.getLength();
457 memcpy( pBuf
, pKey
->maKey
.getStr(), nKeyLen
);
460 memcpy( pBuf
, pKey
->maValue
.getStr(), nValueLen
);
462 *pBuf
= aLineEndBuf
[0]; pBuf
++;
463 if ( nLineEndLen
== 2 )
465 *pBuf
= aLineEndBuf
[1]; pBuf
++;
472 // Store empty line after each group
473 sal_uInt16 nEmptyLines
= pGroup
->mnEmptyLines
;
474 while ( nEmptyLines
)
476 *pBuf
= aLineEndBuf
[0]; pBuf
++;
477 if ( nLineEndLen
== 2 )
479 *pBuf
= aLineEndBuf
[1]; pBuf
++;
485 pGroup
= pGroup
->mpNext
;
491 static void ImplReadConfig( ImplConfigData
* pData
)
493 sal_uInt32 nTimeStamp
= 0;
494 sal_uInt64 nRead
= 0;
496 bool bIsUTF8BOM
= false;
497 std::unique_ptr
<sal_uInt8
[]> pBuf
= ImplSysReadConfig( pData
->maFileName
, nRead
, bRead
, bIsUTF8BOM
, nTimeStamp
);
499 // Read config list from buffer
502 ImplMakeConfigList( pData
, pBuf
.get(), nRead
);
505 pData
->mnTimeStamp
= nTimeStamp
;
506 pData
->mbModified
= false;
508 pData
->mbRead
= true;
510 pData
->mbIsUTF8BOM
= true;
513 static void ImplWriteConfig( ImplConfigData
* pData
)
515 SAL_WARN_IF( pData
->mnTimeStamp
!= ImplSysGetConfigTimeStamp( pData
->maFileName
),
516 "tools.generic", "Config overwrites modified configfile: " << pData
->maFileName
);
518 // Read config list from buffer
520 std::unique_ptr
<sal_uInt8
[]> pBuf
= ImplGetConfigBuffer( pData
, nBufLen
);
523 if ( ImplSysWriteConfig( pData
->maFileName
, pBuf
.get(), nBufLen
, pData
->mbIsUTF8BOM
, pData
->mnTimeStamp
) )
524 pData
->mbModified
= false;
527 pData
->mbModified
= false;
530 static void ImplDeleteConfigData( ImplConfigData
* pData
)
532 ImplKeyData
* pTempKey
;
534 ImplGroupData
* pTempGroup
;
535 ImplGroupData
* pGroup
= pData
->mpFirstGroup
;
538 pTempGroup
= pGroup
->mpNext
;
541 pKey
= pGroup
->mpFirstKey
;
544 pTempKey
= pKey
->mpNext
;
549 // remove group and continue
554 pData
->mpFirstGroup
= nullptr;
557 static std::unique_ptr
<ImplConfigData
> ImplGetConfigData( const OUString
& rFileName
)
559 std::unique_ptr
<ImplConfigData
> pData(new ImplConfigData
);
560 pData
->maFileName
= rFileName
;
561 pData
->mpFirstGroup
= nullptr;
562 pData
->mnDataUpdateId
= 0;
563 pData
->mbRead
= false;
564 pData
->mbIsUTF8BOM
= false;
565 ImplReadConfig( pData
.get() );
570 bool Config::ImplUpdateConfig() const
572 // Re-read file if timestamp differs
573 if ( mpData
->mnTimeStamp
!= ImplSysGetConfigTimeStamp( maFileName
) )
575 ImplDeleteConfigData( mpData
.get() );
576 ImplReadConfig( mpData
.get() );
577 mpData
->mnDataUpdateId
++;
584 ImplGroupData
* Config::ImplGetGroup() const
586 if ( !mpActGroup
|| (mnDataUpdateId
!= mpData
->mnDataUpdateId
) )
588 ImplGroupData
* pPrevGroup
= nullptr;
589 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
592 if ( pGroup
->maGroupName
.equalsIgnoreAsciiCase(maGroupName
) )
596 pGroup
= pGroup
->mpNext
;
599 // Add group if not exists
602 pGroup
= new ImplGroupData
;
603 pGroup
->mpNext
= nullptr;
604 pGroup
->mpFirstKey
= nullptr;
605 pGroup
->mnEmptyLines
= 1;
607 pPrevGroup
->mpNext
= pGroup
;
609 mpData
->mpFirstGroup
= pGroup
;
612 // Always inherit group names and update cache members
613 pGroup
->maGroupName
= maGroupName
;
614 const_cast<Config
*>(this)->mnDataUpdateId
= mpData
->mnDataUpdateId
;
615 const_cast<Config
*>(this)->mpActGroup
= pGroup
;
621 Config::Config( const OUString
& rFileName
)
623 // Initialize config data
624 maFileName
= toUncPath( rFileName
);
625 mpData
= ImplGetConfigData( maFileName
);
626 mpActGroup
= nullptr;
629 SAL_INFO("tools.generic", "Config::Config( " << maFileName
<< " )");
634 SAL_INFO("tools.generic", "Config::~Config()" );
637 ImplDeleteConfigData( mpData
.get() );
640 void Config::SetGroup(const OString
& rGroup
)
642 // If group is to be reset, it needs to be updated on next call
643 if ( maGroupName
!= rGroup
)
645 maGroupName
= rGroup
;
646 mnDataUpdateId
= mpData
->mnDataUpdateId
-1;
650 void Config::DeleteGroup(std::string_view rGroup
)
652 // Update config data if necessary
653 if ( !mpData
->mbRead
)
656 mpData
->mbRead
= true;
659 ImplGroupData
* pPrevGroup
= nullptr;
660 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
663 if ( pGroup
->maGroupName
.equalsIgnoreAsciiCase(rGroup
) )
667 pGroup
= pGroup
->mpNext
;
674 ImplKeyData
* pTempKey
;
675 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
678 pTempKey
= pKey
->mpNext
;
683 // Rewire pointers and remove group
685 pPrevGroup
->mpNext
= pGroup
->mpNext
;
687 mpData
->mpFirstGroup
= pGroup
->mpNext
;
690 // Rewrite config data
691 mpData
->mbModified
= true;
693 mnDataUpdateId
= mpData
->mnDataUpdateId
;
694 mpData
->mnDataUpdateId
++;
697 OString
Config::GetGroupName(sal_uInt16 nGroup
) const
699 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
700 sal_uInt16 nGroupCount
= 0;
704 if ( nGroup
== nGroupCount
)
706 aGroupName
= pGroup
->maGroupName
;
711 pGroup
= pGroup
->mpNext
;
717 sal_uInt16
Config::GetGroupCount() const
719 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
720 sal_uInt16 nGroupCount
= 0;
724 pGroup
= pGroup
->mpNext
;
730 bool Config::HasGroup(std::string_view rGroup
) const
732 ImplGroupData
* pGroup
= mpData
->mpFirstGroup
;
737 if( pGroup
->maGroupName
.equalsIgnoreAsciiCase(rGroup
) )
743 pGroup
= pGroup
->mpNext
;
749 OString
Config::ReadKey(const OString
& rKey
) const
751 return ReadKey(rKey
, OString());
754 OString
Config::ReadKey(const OString
& rKey
, const OString
& rDefault
) const
756 SAL_INFO("tools.generic", "Config::ReadKey( " << rKey
<< " ) from " << GetGroup()
757 << " in " << maFileName
);
759 // Search key, return value if found
760 ImplGroupData
* pGroup
= ImplGetGroup();
763 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
766 if ( !pKey
->mbIsComment
&& pKey
->maKey
.equalsIgnoreAsciiCase(rKey
) )
767 return pKey
->maValue
;
776 void Config::WriteKey(const OString
& rKey
, const OString
& rStr
)
778 SAL_INFO("tools.generic", "Config::WriteKey( " << rKey
<< ", " << rStr
<< " ) to "
779 << GetGroup() << " in " << maFileName
);
781 // Update config data if necessary
782 if ( !mpData
->mbRead
)
785 mpData
->mbRead
= true;
788 // Search key and update value if found
789 ImplGroupData
* pGroup
= ImplGetGroup();
793 ImplKeyData
* pPrevKey
= nullptr;
794 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
797 if ( !pKey
->mbIsComment
&& pKey
->maKey
.equalsIgnoreAsciiCase(rKey
) )
807 pKey
= new ImplKeyData
;
808 pKey
->mpNext
= nullptr;
810 pKey
->mbIsComment
= false;
812 pPrevKey
->mpNext
= pKey
;
814 pGroup
->mpFirstKey
= pKey
;
818 bNewValue
= pKey
->maValue
!= rStr
;
822 pKey
->maValue
= rStr
;
824 mpData
->mbModified
= true;
828 void Config::DeleteKey(std::string_view rKey
)
830 // Update config data if necessary
831 if ( !mpData
->mbRead
)
834 mpData
->mbRead
= true;
837 // Search key and update value
838 ImplGroupData
* pGroup
= ImplGetGroup();
842 ImplKeyData
* pPrevKey
= nullptr;
843 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
846 if ( !pKey
->mbIsComment
&& pKey
->maKey
.equalsIgnoreAsciiCase(rKey
) )
855 // Rewire group pointers and delete
857 pPrevKey
->mpNext
= pKey
->mpNext
;
859 pGroup
->mpFirstKey
= pKey
->mpNext
;
862 mpData
->mbModified
= true;
866 sal_uInt16
Config::GetKeyCount() const
868 SAL_INFO("tools.generic", "Config::GetKeyCount() from " << GetGroup() << " in " << maFileName
);
870 // Search key and update value
871 sal_uInt16 nCount
= 0;
872 ImplGroupData
* pGroup
= ImplGetGroup();
875 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
878 if ( !pKey
->mbIsComment
)
888 OString
Config::GetKeyName(sal_uInt16 nKey
) const
890 SAL_INFO("tools.generic", "Config::GetKeyName( " << OString::number(static_cast<sal_Int32
>(nKey
))
891 << " ) from " << GetGroup() << " in " << maFileName
);
893 // search key and return name if found
894 ImplGroupData
* pGroup
= ImplGetGroup();
897 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
900 if ( !pKey
->mbIsComment
)
914 OString
Config::ReadKey(sal_uInt16 nKey
) const
916 SAL_INFO("tools.generic", "Config::ReadKey( " << OString::number(static_cast<sal_Int32
>(nKey
))
917 << " ) from " << GetGroup() << " in " << maFileName
);
919 // Search key and return value if found
920 ImplGroupData
* pGroup
= ImplGetGroup();
923 ImplKeyData
* pKey
= pGroup
->mpFirstKey
;
926 if ( !pKey
->mbIsComment
)
929 return pKey
->maValue
;
942 if ( mpData
->mbModified
)
943 ImplWriteConfig( mpData
.get() );
946 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */