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 .
20 #include <sal/config.h>
25 #include <com/sun/star/io/BufferSizeExceededException.hpp>
26 #include <com/sun/star/io/NotConnectedException.hpp>
27 #include <com/sun/star/lang/IllegalArgumentException.hpp>
28 #include <unotools/tempfile.hxx>
29 #include <rtl/ustring.hxx>
30 #include <o3tl/safeint.hxx>
31 #include <o3tl/char16_t2wchar_t.hxx>
32 #include <osl/mutex.hxx>
33 #include <osl/detail/file.h>
34 #include <osl/file.hxx>
35 #include <tools/time.hxx>
36 #include <tools/debug.hxx>
37 #include <tools/Guid.hxx>
38 #include <comphelper/DirectoryHelper.hxx>
42 #elif defined( _WIN32 )
48 OUString gTempNameBase_Impl
;
50 OUString
ensureTrailingSlash(const OUString
& url
)
52 if (!url
.isEmpty() && !url
.endsWith("/"))
57 OUString
stripTrailingSlash(const OUString
& url
)
59 if (url
.endsWith("/"))
60 return url
.copy(0, url
.getLength() - 1);
64 bool okOrExists(osl::FileBase::RC ret
)
66 return ret
== osl::FileBase::E_None
|| ret
== osl::FileBase::E_EXIST
;
69 const OUString
& getTempNameBase_Impl()
71 if (gTempNameBase_Impl
.isEmpty())
73 OUString ustrTempDirURL
;
74 osl::FileBase::RC rc
= osl::File::getTempDirURL(ustrTempDirURL
);
75 if (rc
== osl::FileBase::E_None
)
77 gTempNameBase_Impl
= ensureTrailingSlash(ustrTempDirURL
);
78 osl::Directory::createPath(gTempNameBase_Impl
);
81 assert(gTempNameBase_Impl
.isEmpty() || gTempNameBase_Impl
.endsWith("/"));
82 DBG_ASSERT(!gTempNameBase_Impl
.isEmpty(), "No TempDir!");
83 return gTempNameBase_Impl
;
86 OUString
ConstructTempDir_Impl( const OUString
* pParent
, bool bCreateParentDirs
)
90 // Ignore pParent on iOS. We don't want to create any temp files
91 // in the same directory where the document being edited is.
93 if ( pParent
&& !pParent
->isEmpty() )
95 // test for valid filename
97 if ((osl::FileBase::getSystemPathFromFileURL(*pParent
, aRet
)
98 == osl::FileBase::E_None
)
99 && (osl::FileBase::getFileURLFromSystemPath(aRet
, aRet
)
100 == osl::FileBase::E_None
))
102 osl::DirectoryItem aItem
;
103 sal_Int32 i
= aRet
.getLength();
104 if ( aRet
[i
-1] == '/' )
107 if ( osl::DirectoryItem::get( aRet
.copy(0, i
), aItem
) == osl::FileBase::E_None
|| bCreateParentDirs
)
113 (void) bCreateParentDirs
;
116 if ( aName
.isEmpty() )
118 // if no parent or invalid parent : use default directory
119 aName
= getTempNameBase_Impl();
120 osl::Directory::createPath(aName
); // tdf#159769: always make sure it exists
123 // Make sure that directory ends with a separator
124 return ensureTrailingSlash(aName
);
129 virtual bool next(OUString
*) = 0;
132 virtual ~Tokens() {} // avoid warnings
135 class SequentialTokens
: public Tokens
{
137 explicit SequentialTokens(bool showZero
): m_value(0), m_show(showZero
) {}
139 bool next(OUString
* token
) override
{
140 assert(token
!= nullptr);
141 if (m_value
== SAL_MAX_UINT32
) {
144 *token
= m_show
? OUString::number(m_value
) : OUString();
155 class UniqueTokens
: public Tokens
{
157 UniqueTokens(): m_count(0) {}
159 bool next(OUString
* token
) override
{
160 assert(token
!= nullptr);
161 // Because of the shared globalValue, no single instance of UniqueTokens
162 // is guaranteed to exhaustively test all 36^6 possible values, but stop
163 // after that many attempts anyway:
164 sal_uInt32 radix
= 36;
165 sal_uInt32 max
= radix
* radix
* radix
* radix
* radix
* radix
;
166 // 36^6 == 2'176'782'336 < SAL_MAX_UINT32 == 4'294'967'295
167 if (m_count
== max
) {
172 osl::MutexGuard
g(osl::Mutex::getGlobalMutex());
174 = ((globalValue
== SAL_MAX_UINT32
175 ? tools::Time::GetSystemTicks() : globalValue
+ 1)
179 *token
= OUString::number(v
, radix
);
185 static sal_uInt32 globalValue
;
190 sal_uInt32
UniqueTokens::globalValue
= SAL_MAX_UINT32
;
192 class TempDirCreatedObserver
: public osl::DirectoryCreationObserver
195 virtual void DirectoryCreated(const OUString
& aDirectoryUrl
) override
197 osl::File::setAttributes( aDirectoryUrl
, osl_File_Attribute_OwnRead
|
198 osl_File_Attribute_OwnWrite
| osl_File_Attribute_OwnExe
);
202 OUString
lcl_createName(
203 std::u16string_view rLeadingChars
, Tokens
& tokens
, std::u16string_view pExtension
,
204 const OUString
* pParent
, bool bDirectory
, bool bKeep
, bool bLock
,
205 bool bCreateParentDirs
)
207 OUString aName
= ConstructTempDir_Impl( pParent
, bCreateParentDirs
);
208 if ( bCreateParentDirs
)
210 size_t nOffset
= rLeadingChars
.rfind(u
"/");
212 if (std::u16string_view::npos
!= nOffset
)
213 aDirName
= aName
+ rLeadingChars
.substr( 0, nOffset
);
216 TempDirCreatedObserver observer
;
217 if (!okOrExists(osl::Directory::createPath(aDirName
, &observer
)))
220 aName
+= rLeadingChars
;
223 while (tokens
.next(&token
))
225 OUString
aTmp( aName
+ token
);
226 if ( !pExtension
.empty() )
232 osl::FileBase::RC err
= osl::Directory::create(
234 (osl_File_OpenFlag_Read
| osl_File_OpenFlag_Write
235 | osl_File_OpenFlag_Private
));
236 if (err
== osl::FileBase::E_None
)
238 // !bKeep: only for creating a name, not a file or directory
239 if (bKeep
|| osl::Directory::remove(aTmp
) == osl::FileBase::E_None
)
244 else if (err
!= osl::FileBase::E_EXIST
)
245 // if f.e. name contains invalid chars stop trying to create dirs
250 DBG_ASSERT( bKeep
, "Too expensive, use directory for creating name!" );
251 osl::File
aFile(aTmp
);
252 osl::FileBase::RC err
= aFile
.open(
253 osl_File_OpenFlag_Create
| osl_File_OpenFlag_Private
254 | (bLock
? 0 : osl_File_OpenFlag_NoLock
));
255 if (err
== osl::FileBase::E_None
|| (bLock
&& err
== osl::FileBase::E_NOLCK
))
260 else if (err
!= osl::FileBase::E_EXIST
)
262 // if f.e. name contains invalid chars stop trying to create dirs
263 // but if there is a folder with such name proceed further
265 osl::DirectoryItem aTmpItem
;
266 osl::FileStatus
aTmpStatus(osl_FileStatus_Mask_Type
);
267 if (osl::DirectoryItem::get(aTmp
, aTmpItem
) != osl::FileBase::E_None
268 || aTmpItem
.getFileStatus(aTmpStatus
) != osl::FileBase::E_None
269 || aTmpStatus
.getFileType() != osl::FileStatus::Directory
)
277 OUString
createEyeCatcher()
279 OUString eyeCatcher
= u
"lu"_ustr
;
282 if (const char* eye
= getenv("LO_TESTNAME"))
283 eyeCatcher
= OUString(eye
, strlen(eye
), RTL_TEXTENCODING_ASCII_US
);
284 #elif defined(_WIN32)
285 if (const wchar_t* eye
= _wgetenv(L
"LO_TESTNAME"))
286 eyeCatcher
= OUString(o3tl::toU(eye
));
290 eyeCatcher
+= OUString::number(getpid());
291 #elif defined(_WIN32)
292 eyeCatcher
+= OUString::number(_getpid());
298 const OUString
& getEyeCatcher()
300 static const OUString sEyeCatcher
= createEyeCatcher();
304 OUString
CreateTempName_Impl( const OUString
* pParent
, bool bKeep
, bool bDir
= true )
307 return lcl_createName( getEyeCatcher(), t
, u
"", pParent
, bDir
, bKeep
,
311 OUString
CreateTempNameFast()
313 OUString aName
= getTempNameBase_Impl() + getEyeCatcher();
315 tools::Guid
aGuid(tools::Guid::Generate
);
317 return aName
+ aGuid
.getOUString() + ".tmp" ;
324 OUString
CreateTempName()
326 OUString
aName(CreateTempName_Impl( nullptr, false ));
328 // convert to file URL
330 if ( !aName
.isEmpty() )
331 osl::FileBase::getSystemPathFromFileURL(aName
, aTmp
);
335 TempFileFast::TempFileFast( )
339 TempFileFast::TempFileFast(TempFileFast
&& other
) noexcept
:
340 mxStream(std::move(other
.mxStream
))
344 TempFileFast::~TempFileFast()
349 SvStream
* TempFileFast::GetStream( StreamMode eMode
)
353 OUString aName
= CreateTempNameFast();
355 mxStream
.reset(new SvFileStream(aName
, eMode
| StreamMode::TEMPORARY
| StreamMode::DELETE_ON_CLOSE
));
357 mxStream
.reset(new SvFileStream(aName
, eMode
| StreamMode::TEMPORARY
));
360 return mxStream
.get();
363 void TempFileFast::CloseStream()
368 OUString aName
= mxStream
->GetFileName();
372 // On Windows, the file is opened with FILE_FLAG_DELETE_ON_CLOSE, so it will delete as soon as the handle closes.
373 // On other platforms, we need to explicitly delete it.
375 if (!aName
.isEmpty() && (osl::FileBase::getFileURLFromSystemPath(aName
, aName
) == osl::FileBase::E_None
))
376 osl::File::remove(aName
);
381 OUString
CreateTempURL( const OUString
* pParent
, bool bDirectory
)
383 return CreateTempName_Impl( pParent
, true, bDirectory
);
386 OUString
CreateTempURL( std::u16string_view rLeadingChars
, bool _bStartWithZero
,
387 std::u16string_view pExtension
, const OUString
* pParent
,
388 bool bCreateParentDirs
)
390 SequentialTokens
t(_bStartWithZero
);
391 return lcl_createName( rLeadingChars
, t
, pExtension
, pParent
, false,
392 true, true, bCreateParentDirs
);
395 TempFileNamed::TempFileNamed( const OUString
* pParent
, bool bDirectory
)
396 : bIsDirectory( bDirectory
)
397 , bKillingFileEnabled( false )
399 aName
= CreateTempName_Impl( pParent
, true, bDirectory
);
402 TempFileNamed::TempFileNamed( std::u16string_view rLeadingChars
, bool _bStartWithZero
,
403 std::u16string_view pExtension
, const OUString
* pParent
,
404 bool bCreateParentDirs
)
405 : bIsDirectory( false )
406 , bKillingFileEnabled( false )
408 SequentialTokens
t(_bStartWithZero
);
409 aName
= lcl_createName( rLeadingChars
, t
, pExtension
, pParent
, false,
410 true, true, bCreateParentDirs
);
413 TempFileNamed::TempFileNamed(TempFileNamed
&& other
) noexcept
:
414 aName(std::move(other
.aName
)), pStream(std::move(other
.pStream
)), bIsDirectory(other
.bIsDirectory
),
415 bKillingFileEnabled(other
.bKillingFileEnabled
)
417 other
.bKillingFileEnabled
= false;
420 TempFileNamed::~TempFileNamed()
422 if ( !bKillingFileEnabled
)
428 comphelper::DirectoryHelper::deleteDirRecursively(aName
);
432 osl::File::remove(aName
);
436 bool TempFileNamed::IsValid() const
438 return !aName
.isEmpty();
441 OUString
TempFileNamed::GetFileName() const
444 osl::FileBase::getSystemPathFromFileURL(aName
, aTmp
);
448 OUString
const & TempFileNamed::GetURL() const
450 // if you request the URL, then you presumably want to access this via UCB,
451 // and UCB will want to open the file via a separate file handle, which means
452 // we have to make this file data actually hit disk. We do this here (and not
453 // elsewhere) to make the other (normal) paths fast. Flushing to disk
454 // really slows temp files down.
460 SvStream
* TempFileNamed::GetStream( StreamMode eMode
)
464 if (!aName
.isEmpty())
465 pStream
.reset(new SvFileStream(aName
, eMode
| StreamMode::TEMPORARY
));
467 pStream
.reset(new SvMemoryStream
);
470 return pStream
.get();
473 void TempFileNamed::CloseStream()
478 OUString
SetTempNameBaseDirectory( const OUString
&rBaseName
)
480 if( rBaseName
.isEmpty() )
483 // remove trailing slash
484 OUString
aUnqPath(stripTrailingSlash(rBaseName
));
486 // try to create the directory
487 bool bRet
= okOrExists(osl::Directory::createPath(aUnqPath
));
489 // failure to create base directory means returning an empty string
493 // append own internal directory
494 gTempNameBase_Impl
= ensureTrailingSlash(rBaseName
);
496 TempFileNamed
aBase( {}, true );
497 if ( aBase
.IsValid() )
498 // use it in case of success
499 gTempNameBase_Impl
= ensureTrailingSlash(aBase
.GetURL());
501 // return system path of used directory
502 osl::FileBase::getSystemPathFromFileURL(gTempNameBase_Impl
, aTmp
);
508 OUString
GetTempNameBaseDirectory()
510 return ConstructTempDir_Impl(nullptr, false);
514 TempFileFastService::TempFileFastService()
515 : mbInClosed( false )
516 , mbOutClosed( false )
518 mpTempFile
.emplace();
519 mpStream
= mpTempFile
->GetStream(StreamMode::READWRITE
);
522 TempFileFastService::~TempFileFastService ()
528 sal_Int32 SAL_CALL
TempFileFastService::readBytes( css::uno::Sequence
< sal_Int8
>& aData
, sal_Int32 nBytesToRead
)
530 std::unique_lock
aGuard( maMutex
);
532 throw css::io::NotConnectedException ( OUString(), getXWeak() );
535 if (nBytesToRead
< 0)
536 throw css::io::BufferSizeExceededException( OUString(), getXWeak());
538 if (aData
.getLength() < nBytesToRead
)
539 aData
.realloc(nBytesToRead
);
541 sal_uInt32 nRead
= mpStream
->ReadBytes(static_cast<void*>(aData
.getArray()), nBytesToRead
);
544 if (nRead
< o3tl::make_unsigned(aData
.getLength()))
545 aData
.realloc( nRead
);
550 sal_Int32 SAL_CALL
TempFileFastService::readSomeBytes( css::uno::Sequence
< sal_Int8
>& aData
, sal_Int32 nMaxBytesToRead
)
553 std::unique_lock
aGuard( maMutex
);
555 throw css::io::NotConnectedException ( OUString(), getXWeak() );
560 if (nMaxBytesToRead
< 0)
561 throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
569 return readBytes(aData
, nMaxBytesToRead
);
572 // comphelper::ByteReader
573 sal_Int32
TempFileFastService::readSomeBytes( sal_Int8
* aData
, sal_Int32 nBytesToRead
)
575 std::unique_lock
aGuard( maMutex
);
577 throw css::io::NotConnectedException ( OUString(), getXWeak() );
582 if (nBytesToRead
< 0)
583 throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
588 sal_uInt32 nRead
= mpStream
->ReadBytes(aData
, nBytesToRead
);
594 void SAL_CALL
TempFileFastService::skipBytes( sal_Int32 nBytesToSkip
)
596 std::unique_lock
aGuard( maMutex
);
598 throw css::io::NotConnectedException ( OUString(), getXWeak() );
602 mpStream
->SeekRel(nBytesToSkip
);
606 sal_Int32 SAL_CALL
TempFileFastService::available()
608 std::unique_lock
aGuard( maMutex
);
610 throw css::io::NotConnectedException ( OUString(), getXWeak() );
614 sal_Int64 nAvailable
= mpStream
->remainingSize();
617 return std::min
<sal_Int64
>(SAL_MAX_INT32
, nAvailable
);
620 void SAL_CALL
TempFileFastService::closeInput()
622 std::unique_lock
aGuard( maMutex
);
624 throw css::io::NotConnectedException ( OUString(), getXWeak() );
630 // stream will be deleted by TempFile implementation
638 void SAL_CALL
TempFileFastService::writeBytes( const css::uno::Sequence
< sal_Int8
>& aData
)
640 std::unique_lock
aGuard( maMutex
);
642 throw css::io::NotConnectedException ( OUString(), getXWeak() );
645 sal_uInt32 nWritten
= mpStream
->WriteBytes(aData
.getConstArray(), aData
.getLength());
647 if ( nWritten
!= static_cast<sal_uInt32
>(aData
.getLength()))
648 throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
651 // comphelper::ByteWriter
653 void TempFileFastService::writeBytes( const sal_Int8
* aData
, sal_Int32 nBytesToWrite
)
655 std::unique_lock
aGuard( maMutex
);
657 throw css::io::NotConnectedException ( OUString(), getXWeak() );
660 sal_uInt32 nWritten
= mpStream
->WriteBytes(aData
, nBytesToWrite
);
662 if ( nWritten
!= o3tl::make_unsigned(nBytesToWrite
) )
663 throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
666 void SAL_CALL
TempFileFastService::flush()
668 std::unique_lock
aGuard( maMutex
);
670 throw css::io::NotConnectedException ( OUString(), getXWeak() );
677 void SAL_CALL
TempFileFastService::closeOutput()
679 std::unique_lock
aGuard( maMutex
);
681 throw css::io::NotConnectedException ( OUString(), getXWeak() );
686 // so that if you then open the InputStream, you can read the content
687 mpStream
->FlushBuffer();
693 // stream will be deleted by TempFile implementation
699 void TempFileFastService::checkError() const
701 if (!mpStream
|| mpStream
->SvStream::GetError () != ERRCODE_NONE
)
702 throw css::io::NotConnectedException ( OUString(), const_cast < TempFileFastService
* > (this)->getXWeak() );
705 void TempFileFastService::checkConnected()
708 throw css::io::NotConnectedException ( OUString(), getXWeak() );
713 void SAL_CALL
TempFileFastService::seek( sal_Int64 nLocation
)
715 std::unique_lock
aGuard( maMutex
);
719 throw css::lang::IllegalArgumentException();
721 sal_Int64 nNewLoc
= mpStream
->Seek(static_cast<sal_uInt32
>(nLocation
) );
722 if ( nNewLoc
!= nLocation
)
723 throw css::lang::IllegalArgumentException();
727 sal_Int64 SAL_CALL
TempFileFastService::getPosition()
729 std::unique_lock
aGuard( maMutex
);
732 sal_uInt64 nPos
= mpStream
->Tell();
734 return static_cast<sal_Int64
>(nPos
);
737 sal_Int64 SAL_CALL
TempFileFastService::getLength()
739 std::unique_lock
aGuard( maMutex
);
744 sal_Int64 nEndPos
= mpStream
->TellEnd();
751 css::uno::Reference
< css::io::XInputStream
> SAL_CALL
TempFileFastService::getInputStream()
756 css::uno::Reference
< css::io::XOutputStream
> SAL_CALL
TempFileFastService::getOutputStream()
763 void SAL_CALL
TempFileFastService::truncate()
765 std::unique_lock
aGuard( maMutex
);
767 // SetStreamSize() call does not change the position
769 mpStream
->SetStreamSize( 0 );
777 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */