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 <osl/mutex.hxx>
32 #include <osl/detail/file.h>
33 #include <osl/file.hxx>
34 #include <tools/time.hxx>
35 #include <tools/debug.hxx>
36 #include <tools/Guid.hxx>
37 #include <comphelper/DirectoryHelper.hxx>
41 #elif defined( _WIN32 )
49 OUString gTempNameBase_Impl
;
55 static OUString
getParentName( std::u16string_view aFileName
)
57 size_t lastIndex
= aFileName
.rfind( '/' );
60 if (lastIndex
!= std::u16string_view::npos
)
62 aParent
= aFileName
.substr(0, lastIndex
);
64 if (aParent
.endsWith(":") && aParent
.getLength() == 6)
67 if (aParent
.equalsIgnoreAsciiCase("file://"))
74 static bool ensuredir( const OUString
& rUnqPath
)
77 if ( rUnqPath
.isEmpty() )
80 // remove trailing slash
81 if ( rUnqPath
.endsWith("/") )
82 aPath
= rUnqPath
.copy( 0, rUnqPath
.getLength() - 1 );
86 // HACK: create directory on a mount point with nobrowse option
87 // returns ENOSYS in any case !!
88 osl::Directory
aDirectory( aPath
);
89 osl::FileBase::RC nError
= aDirectory
.open();
91 if( nError
== osl::File::E_None
)
94 // try to create the directory
95 nError
= osl::Directory::create( aPath
);
96 bool bSuccess
= ( nError
== osl::File::E_None
|| nError
== osl::FileBase::E_EXIST
);
99 // perhaps parent(s) don't exist
100 OUString aParentDir
= getParentName( aPath
);
101 if ( aParentDir
!= aPath
)
103 bSuccess
= ensuredir( getParentName( aPath
) );
105 // After parent directory structure exists try it one's more
108 // Parent directory exists, retry creation of directory
109 nError
= osl::Directory::create( aPath
);
110 bSuccess
=( nError
== osl::File::E_None
|| nError
== osl::FileBase::E_EXIST
);
118 static OUString
ConstructTempDir_Impl( const OUString
* pParent
, bool bCreateParentDirs
)
122 // Ignore pParent on iOS. We don't want to create any temp files
123 // in the same directory where the document being edited is.
125 if ( pParent
&& !pParent
->isEmpty() )
127 // test for valid filename
129 if ((osl::FileBase::getSystemPathFromFileURL(*pParent
, aRet
)
130 == osl::FileBase::E_None
)
131 && (osl::FileBase::getFileURLFromSystemPath(aRet
, aRet
)
132 == osl::FileBase::E_None
))
134 ::osl::DirectoryItem aItem
;
135 sal_Int32 i
= aRet
.getLength();
136 if ( aRet
[i
-1] == '/' )
139 if ( DirectoryItem::get( aRet
.copy(0, i
), aItem
) == FileBase::E_None
|| bCreateParentDirs
)
145 (void) bCreateParentDirs
;
148 if ( aName
.isEmpty() )
150 if (gTempNameBase_Impl
.isEmpty())
152 OUString ustrTempDirURL
;
153 ::osl::FileBase::RC rc
= ::osl::File::getTempDirURL(
155 if (rc
== ::osl::FileBase::E_None
)
156 gTempNameBase_Impl
= ustrTempDirURL
;
159 // if no parent or invalid parent : use default directory
160 DBG_ASSERT( !gTempNameBase_Impl
.isEmpty(), "No TempDir!" );
161 aName
= gTempNameBase_Impl
;
164 // Make sure that directory ends with a separator
165 if( !aName
.isEmpty() && !aName
.endsWith("/") )
175 virtual bool next(OUString
*) = 0;
178 virtual ~Tokens() {} // avoid warnings
181 class SequentialTokens
: public Tokens
{
183 explicit SequentialTokens(bool showZero
): m_value(0), m_show(showZero
) {}
185 bool next(OUString
* token
) override
{
186 assert(token
!= nullptr);
187 if (m_value
== SAL_MAX_UINT32
) {
190 *token
= m_show
? OUString::number(m_value
) : OUString();
201 class UniqueTokens
: public Tokens
{
203 UniqueTokens(): m_count(0) {}
205 bool next(OUString
* token
) override
{
206 assert(token
!= nullptr);
207 // Because of the shared globalValue, no single instance of UniqueTokens
208 // is guaranteed to exhaustively test all 36^6 possible values, but stop
209 // after that many attempts anyway:
210 sal_uInt32 radix
= 36;
211 sal_uInt32 max
= radix
* radix
* radix
* radix
* radix
* radix
;
212 // 36^6 == 2'176'782'336 < SAL_MAX_UINT32 == 4'294'967'295
213 if (m_count
== max
) {
218 osl::MutexGuard
g(osl::Mutex::getGlobalMutex());
220 = ((globalValue
== SAL_MAX_UINT32
221 ? tools::Time::GetSystemTicks() : globalValue
+ 1)
225 *token
= OUString::number(v
, radix
);
231 static sal_uInt32 globalValue
;
238 sal_uInt32
UniqueTokens::globalValue
= SAL_MAX_UINT32
;
242 class TempDirCreatedObserver
: public DirectoryCreationObserver
245 virtual void DirectoryCreated(const OUString
& aDirectoryUrl
) override
247 File::setAttributes( aDirectoryUrl
, osl_File_Attribute_OwnRead
|
248 osl_File_Attribute_OwnWrite
| osl_File_Attribute_OwnExe
);
253 static OUString
lcl_createName(
254 std::u16string_view rLeadingChars
, Tokens
& tokens
, std::u16string_view pExtension
,
255 const OUString
* pParent
, bool bDirectory
, bool bKeep
, bool bLock
,
256 bool bCreateParentDirs
)
258 OUString aName
= ConstructTempDir_Impl( pParent
, bCreateParentDirs
);
259 if ( bCreateParentDirs
)
261 size_t nOffset
= rLeadingChars
.rfind(u
"/");
263 if (std::u16string_view::npos
!= nOffset
)
264 aDirName
= aName
+ rLeadingChars
.substr( 0, nOffset
);
267 TempDirCreatedObserver observer
;
268 FileBase::RC err
= Directory::createPath( aDirName
, &observer
);
269 if ( err
!= FileBase::E_None
&& err
!= FileBase::E_EXIST
)
272 aName
+= rLeadingChars
;
275 while (tokens
.next(&token
))
277 OUString
aTmp( aName
+ token
);
278 if ( !pExtension
.empty() )
284 FileBase::RC err
= Directory::create(
286 (osl_File_OpenFlag_Read
| osl_File_OpenFlag_Write
287 | osl_File_OpenFlag_Private
));
288 if ( err
== FileBase::E_None
)
290 // !bKeep: only for creating a name, not a file or directory
291 if ( bKeep
|| Directory::remove( aTmp
) == FileBase::E_None
)
296 else if ( err
!= FileBase::E_EXIST
)
297 // if f.e. name contains invalid chars stop trying to create dirs
302 DBG_ASSERT( bKeep
, "Too expensive, use directory for creating name!" );
304 FileBase::RC err
= aFile
.open(
305 osl_File_OpenFlag_Create
| osl_File_OpenFlag_Private
306 | (bLock
? 0 : osl_File_OpenFlag_NoLock
));
307 if ( err
== FileBase::E_None
|| (bLock
&& err
== FileBase::E_NOLCK
) )
312 else if ( err
!= FileBase::E_EXIST
)
314 // if f.e. name contains invalid chars stop trying to create dirs
315 // but if there is a folder with such name proceed further
317 DirectoryItem aTmpItem
;
318 FileStatus
aTmpStatus( osl_FileStatus_Mask_Type
);
319 if ( DirectoryItem::get( aTmp
, aTmpItem
) != FileBase::E_None
320 || aTmpItem
.getFileStatus( aTmpStatus
) != FileBase::E_None
321 || aTmpStatus
.getFileType() != FileStatus::Directory
)
329 static OUString
CreateTempName_Impl( const OUString
* pParent
, bool bKeep
, bool bDir
= true )
331 OUString aEyeCatcher
= "lu";
334 const char* eye
= getenv("LO_TESTNAME");
337 aEyeCatcher
= OUString(eye
, strlen(eye
), RTL_TEXTENCODING_ASCII_US
);
340 static const pid_t pid
= getpid();
341 static const OUString aPidString
= OUString::number(pid
);
342 aEyeCatcher
+= aPidString
;
344 #elif defined(_WIN32)
345 static const int pid
= _getpid();
346 static const OUString aPidString
= OUString::number(pid
);
347 aEyeCatcher
+= aPidString
;
350 return lcl_createName( aEyeCatcher
, t
, u
"", pParent
, bDir
, bKeep
,
354 static OUString
CreateTempNameFast()
356 OUString aEyeCatcher
= "lu";
359 const char* eye
= getenv("LO_TESTNAME");
362 aEyeCatcher
= OUString(eye
, strlen(eye
), RTL_TEXTENCODING_ASCII_US
);
365 static const pid_t pid
= getpid();
366 static const OUString aPidString
= OUString::number(pid
);
367 aEyeCatcher
+= aPidString
;
369 #elif defined(_WIN32)
370 static const int pid
= _getpid();
371 static const OUString aPidString
= OUString::number(pid
);
372 aEyeCatcher
+= aPidString
;
375 OUString aName
= ConstructTempDir_Impl( /*pParent*/nullptr, /*bCreateParentDirs*/false ) + aEyeCatcher
;
377 tools::Guid
aGuid(tools::Guid::Generate
);
379 return aName
+ aGuid
.getOUString() + ".tmp" ;
382 OUString
CreateTempName()
384 OUString
aName(CreateTempName_Impl( nullptr, false ));
386 // convert to file URL
388 if ( !aName
.isEmpty() )
389 FileBase::getSystemPathFromFileURL( aName
, aTmp
);
393 TempFileFast::TempFileFast( )
397 TempFileFast::TempFileFast(TempFileFast
&& other
) noexcept
:
398 mxStream(std::move(other
.mxStream
))
402 TempFileFast::~TempFileFast()
407 SvStream
* TempFileFast::GetStream( StreamMode eMode
)
411 OUString aName
= CreateTempNameFast();
413 mxStream
.reset(new SvFileStream(aName
, eMode
| StreamMode::TEMPORARY
| StreamMode::DELETE_ON_CLOSE
));
415 mxStream
.reset(new SvFileStream(aName
, eMode
| StreamMode::TEMPORARY
));
418 return mxStream
.get();
421 void TempFileFast::CloseStream()
426 OUString aName
= mxStream
->GetFileName();
430 // On Windows, the file is opened with FILE_FLAG_DELETE_ON_CLOSE, so it will delete as soon as the handle closes.
431 // On other platforms, we need to explicitly delete it.
433 if (!aName
.isEmpty() && (osl::FileBase::getFileURLFromSystemPath(aName
, aName
) == osl::FileBase::E_None
))
439 OUString
CreateTempURL( const OUString
* pParent
, bool bDirectory
)
441 return CreateTempName_Impl( pParent
, true, bDirectory
);
444 OUString
CreateTempURL( std::u16string_view rLeadingChars
, bool _bStartWithZero
,
445 std::u16string_view pExtension
, const OUString
* pParent
,
446 bool bCreateParentDirs
)
448 SequentialTokens
t(_bStartWithZero
);
449 return lcl_createName( rLeadingChars
, t
, pExtension
, pParent
, false,
450 true, true, bCreateParentDirs
);
453 TempFileNamed::TempFileNamed( const OUString
* pParent
, bool bDirectory
)
454 : bIsDirectory( bDirectory
)
455 , bKillingFileEnabled( false )
457 aName
= CreateTempName_Impl( pParent
, true, bDirectory
);
460 TempFileNamed::TempFileNamed( std::u16string_view rLeadingChars
, bool _bStartWithZero
,
461 std::u16string_view pExtension
, const OUString
* pParent
,
462 bool bCreateParentDirs
)
463 : bIsDirectory( false )
464 , bKillingFileEnabled( false )
466 SequentialTokens
t(_bStartWithZero
);
467 aName
= lcl_createName( rLeadingChars
, t
, pExtension
, pParent
, false,
468 true, true, bCreateParentDirs
);
471 TempFileNamed::TempFileNamed(TempFileNamed
&& other
) noexcept
:
472 aName(std::move(other
.aName
)), pStream(std::move(other
.pStream
)), bIsDirectory(other
.bIsDirectory
),
473 bKillingFileEnabled(other
.bKillingFileEnabled
)
475 other
.bKillingFileEnabled
= false;
478 TempFileNamed::~TempFileNamed()
480 if ( !bKillingFileEnabled
)
486 comphelper::DirectoryHelper::deleteDirRecursively(aName
);
490 File::remove( aName
);
494 bool TempFileNamed::IsValid() const
496 return !aName
.isEmpty();
499 OUString
TempFileNamed::GetFileName() const
502 FileBase::getSystemPathFromFileURL(aName
, aTmp
);
506 OUString
const & TempFileNamed::GetURL() const
508 // if you request the URL, then you presumably want to access this via UCB,
509 // and UCB will want to open the file via a separate file handle, which means
510 // we have to make this file data actually hit disk. We do this here (and not
511 // elsewhere) to make the other (normal) paths fast. Flushing to disk
512 // really slows temp files down.
518 SvStream
* TempFileNamed::GetStream( StreamMode eMode
)
522 if (!aName
.isEmpty())
523 pStream
.reset(new SvFileStream(aName
, eMode
| StreamMode::TEMPORARY
));
525 pStream
.reset(new SvMemoryStream
);
528 return pStream
.get();
531 void TempFileNamed::CloseStream()
536 OUString
SetTempNameBaseDirectory( const OUString
&rBaseName
)
538 if( rBaseName
.isEmpty() )
541 OUString
aUnqPath( rBaseName
);
543 // remove trailing slash
544 if ( rBaseName
.endsWith("/") )
545 aUnqPath
= rBaseName
.copy( 0, rBaseName
.getLength() - 1 );
547 // try to create the directory
549 osl::FileBase::RC err
= osl::Directory::create( aUnqPath
);
550 if ( err
!= FileBase::E_None
&& err
!= FileBase::E_EXIST
)
551 // perhaps parent(s) don't exist
552 bRet
= ensuredir( aUnqPath
);
556 // failure to create base directory means returning an empty string
560 // append own internal directory
561 OUString
&rTempNameBase_Impl
= gTempNameBase_Impl
;
562 rTempNameBase_Impl
= rBaseName
+ "/";
564 TempFileNamed
aBase( {}, true );
565 if ( aBase
.IsValid() )
566 // use it in case of success
567 rTempNameBase_Impl
= aBase
.aName
;
569 // return system path of used directory
570 FileBase::getSystemPathFromFileURL( rTempNameBase_Impl
, aTmp
);
576 OUString
GetTempNameBaseDirectory()
578 return ConstructTempDir_Impl(nullptr, false);
582 TempFileFastService::TempFileFastService()
583 : mbInClosed( false )
584 , mbOutClosed( false )
586 mpTempFile
.emplace();
587 mpStream
= mpTempFile
->GetStream(StreamMode::READWRITE
);
590 TempFileFastService::~TempFileFastService ()
596 sal_Int32 SAL_CALL
TempFileFastService::readBytes( css::uno::Sequence
< sal_Int8
>& aData
, sal_Int32 nBytesToRead
)
598 std::unique_lock
aGuard( maMutex
);
600 throw css::io::NotConnectedException ( OUString(), getXWeak() );
603 if (nBytesToRead
< 0)
604 throw css::io::BufferSizeExceededException( OUString(), getXWeak());
606 if (aData
.getLength() < nBytesToRead
)
607 aData
.realloc(nBytesToRead
);
609 sal_uInt32 nRead
= mpStream
->ReadBytes(static_cast<void*>(aData
.getArray()), nBytesToRead
);
612 if (nRead
< o3tl::make_unsigned(aData
.getLength()))
613 aData
.realloc( nRead
);
618 sal_Int32 SAL_CALL
TempFileFastService::readSomeBytes( css::uno::Sequence
< sal_Int8
>& aData
, sal_Int32 nMaxBytesToRead
)
621 std::unique_lock
aGuard( maMutex
);
623 throw css::io::NotConnectedException ( OUString(), getXWeak() );
628 if (nMaxBytesToRead
< 0)
629 throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
637 return readBytes(aData
, nMaxBytesToRead
);
640 void SAL_CALL
TempFileFastService::skipBytes( sal_Int32 nBytesToSkip
)
642 std::unique_lock
aGuard( maMutex
);
644 throw css::io::NotConnectedException ( OUString(), getXWeak() );
648 mpStream
->SeekRel(nBytesToSkip
);
652 sal_Int32 SAL_CALL
TempFileFastService::available()
654 std::unique_lock
aGuard( maMutex
);
656 throw css::io::NotConnectedException ( OUString(), getXWeak() );
660 sal_Int64 nAvailable
= mpStream
->remainingSize();
663 return std::min
<sal_Int64
>(SAL_MAX_INT32
, nAvailable
);
666 void SAL_CALL
TempFileFastService::closeInput()
668 std::unique_lock
aGuard( maMutex
);
670 throw css::io::NotConnectedException ( OUString(), getXWeak() );
676 // stream will be deleted by TempFile implementation
684 void SAL_CALL
TempFileFastService::writeBytes( const css::uno::Sequence
< sal_Int8
>& aData
)
686 std::unique_lock
aGuard( maMutex
);
688 throw css::io::NotConnectedException ( OUString(), getXWeak() );
691 sal_uInt32 nWritten
= mpStream
->WriteBytes(aData
.getConstArray(), aData
.getLength());
693 if ( nWritten
!= static_cast<sal_uInt32
>(aData
.getLength()))
694 throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
697 void SAL_CALL
TempFileFastService::flush()
699 std::unique_lock
aGuard( maMutex
);
701 throw css::io::NotConnectedException ( OUString(), getXWeak() );
708 void SAL_CALL
TempFileFastService::closeOutput()
710 std::unique_lock
aGuard( maMutex
);
712 throw css::io::NotConnectedException ( OUString(), getXWeak() );
717 // so that if you then open the InputStream, you can read the content
718 mpStream
->FlushBuffer();
724 // stream will be deleted by TempFile implementation
730 void TempFileFastService::checkError() const
732 if (!mpStream
|| mpStream
->SvStream::GetError () != ERRCODE_NONE
)
733 throw css::io::NotConnectedException ( OUString(), const_cast < TempFileFastService
* > (this)->getXWeak() );
736 void TempFileFastService::checkConnected()
739 throw css::io::NotConnectedException ( OUString(), getXWeak() );
744 void SAL_CALL
TempFileFastService::seek( sal_Int64 nLocation
)
746 std::unique_lock
aGuard( maMutex
);
750 throw css::lang::IllegalArgumentException();
752 sal_Int64 nNewLoc
= mpStream
->Seek(static_cast<sal_uInt32
>(nLocation
) );
753 if ( nNewLoc
!= nLocation
)
754 throw css::lang::IllegalArgumentException();
758 sal_Int64 SAL_CALL
TempFileFastService::getPosition()
760 std::unique_lock
aGuard( maMutex
);
763 sal_uInt32 nPos
= mpStream
->Tell();
765 return static_cast<sal_Int64
>(nPos
);
768 sal_Int64 SAL_CALL
TempFileFastService::getLength()
770 std::unique_lock
aGuard( maMutex
);
775 sal_Int64 nEndPos
= mpStream
->TellEnd();
782 css::uno::Reference
< css::io::XInputStream
> SAL_CALL
TempFileFastService::getInputStream()
787 css::uno::Reference
< css::io::XOutputStream
> SAL_CALL
TempFileFastService::getOutputStream()
794 void SAL_CALL
TempFileFastService::truncate()
796 std::unique_lock
aGuard( maMutex
);
798 // SetStreamSize() call does not change the position
800 mpStream
->SetStreamSize( 0 );
808 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */