Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / unotools / source / ucbhelper / tempfile.cxx
blob4c6749226d776d0d067aa3d8ab0d9d882ed111c0
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
22 #include <cassert>
23 #include <utility>
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>
39 #ifdef UNX
40 #include <unistd.h>
41 #elif defined( _WIN32 )
42 #include <process.h>
43 #endif
45 using namespace osl;
47 namespace
49 OUString gTempNameBase_Impl;
52 namespace utl
55 static OUString getParentName( std::u16string_view aFileName )
57 size_t lastIndex = aFileName.rfind( '/' );
58 OUString aParent;
60 if (lastIndex != std::u16string_view::npos)
62 aParent = aFileName.substr(0, lastIndex);
64 if (aParent.endsWith(":") && aParent.getLength() == 6)
65 aParent += "/";
67 if (aParent.equalsIgnoreAsciiCase("file://"))
68 aParent = "file:///";
71 return aParent;
74 static bool ensuredir( const OUString& rUnqPath )
76 OUString aPath;
77 if ( rUnqPath.isEmpty() )
78 return false;
80 // remove trailing slash
81 if ( rUnqPath.endsWith("/") )
82 aPath = rUnqPath.copy( 0, rUnqPath.getLength() - 1 );
83 else
84 aPath = rUnqPath;
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();
90 aDirectory.close();
91 if( nError == osl::File::E_None )
92 return true;
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 );
97 if( !bSuccess )
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
106 if ( bSuccess )
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 );
115 return bSuccess;
118 static OUString ConstructTempDir_Impl( const OUString* pParent, bool bCreateParentDirs )
120 OUString aName;
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.
124 #ifndef IOS
125 if ( pParent && !pParent->isEmpty() )
127 // test for valid filename
128 OUString aRet;
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] == '/' )
137 i--;
139 if ( DirectoryItem::get( aRet.copy(0, i), aItem ) == FileBase::E_None || bCreateParentDirs )
140 aName = aRet;
143 #else
144 (void) pParent;
145 (void) bCreateParentDirs;
146 #endif
148 if ( aName.isEmpty() )
150 if (gTempNameBase_Impl.isEmpty())
152 OUString ustrTempDirURL;
153 ::osl::FileBase::RC rc = ::osl::File::getTempDirURL(
154 ustrTempDirURL );
155 if (rc == ::osl::FileBase::E_None)
156 gTempNameBase_Impl = ustrTempDirURL;
157 ensuredir( aName );
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("/") )
166 aName += "/";
168 return aName;
171 namespace {
173 class Tokens {
174 public:
175 virtual bool next(OUString *) = 0;
177 protected:
178 virtual ~Tokens() {} // avoid warnings
181 class SequentialTokens: public Tokens {
182 public:
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) {
188 return false;
190 *token = m_show ? OUString::number(m_value) : OUString();
191 ++m_value;
192 m_show = true;
193 return true;
196 private:
197 sal_uInt32 m_value;
198 bool m_show;
201 class UniqueTokens: public Tokens {
202 public:
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) {
214 return false;
216 sal_uInt32 v;
218 osl::MutexGuard g(osl::Mutex::getGlobalMutex());
219 globalValue
220 = ((globalValue == SAL_MAX_UINT32
221 ? tools::Time::GetSystemTicks() : globalValue + 1)
222 % max);
223 v = globalValue;
225 *token = OUString::number(v, radix);
226 ++m_count;
227 return true;
230 private:
231 static sal_uInt32 globalValue;
233 sal_uInt32 m_count;
238 sal_uInt32 UniqueTokens::globalValue = SAL_MAX_UINT32;
240 namespace
242 class TempDirCreatedObserver : public DirectoryCreationObserver
244 public:
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"/");
262 OUString aDirName;
263 if (std::u16string_view::npos != nOffset)
264 aDirName = aName + rLeadingChars.substr( 0, nOffset );
265 else
266 aDirName = aName;
267 TempDirCreatedObserver observer;
268 FileBase::RC err = Directory::createPath( aDirName, &observer );
269 if ( err != FileBase::E_None && err != FileBase::E_EXIST )
270 return OUString();
272 aName += rLeadingChars;
274 OUString token;
275 while (tokens.next(&token))
277 OUString aTmp( aName + token );
278 if ( !pExtension.empty() )
279 aTmp += pExtension;
280 else
281 aTmp += ".tmp";
282 if ( bDirectory )
284 FileBase::RC err = Directory::create(
285 aTmp,
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 )
292 return aTmp;
293 else
294 return OUString();
296 else if ( err != FileBase::E_EXIST )
297 // if f.e. name contains invalid chars stop trying to create dirs
298 return OUString();
300 else
302 DBG_ASSERT( bKeep, "Too expensive, use directory for creating name!" );
303 File aFile( aTmp );
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) )
309 aFile.close();
310 return aTmp;
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 )
322 return OUString();
326 return OUString();
329 static OUString CreateTempName_Impl( const OUString* pParent, bool bKeep, bool bDir = true )
331 OUString aEyeCatcher = "lu";
332 #ifdef UNX
333 #ifdef DBG_UTIL
334 const char* eye = getenv("LO_TESTNAME");
335 if(eye)
337 aEyeCatcher = OUString(eye, strlen(eye), RTL_TEXTENCODING_ASCII_US);
339 #else
340 static const pid_t pid = getpid();
341 static const OUString aPidString = OUString::number(pid);
342 aEyeCatcher += aPidString;
343 #endif
344 #elif defined(_WIN32)
345 static const int pid = _getpid();
346 static const OUString aPidString = OUString::number(pid);
347 aEyeCatcher += aPidString;
348 #endif
349 UniqueTokens t;
350 return lcl_createName( aEyeCatcher, t, u"", pParent, bDir, bKeep,
351 false, false);
354 static OUString CreateTempNameFast()
356 OUString aEyeCatcher = "lu";
357 #ifdef UNX
358 #ifdef DBG_UTIL
359 const char* eye = getenv("LO_TESTNAME");
360 if(eye)
362 aEyeCatcher = OUString(eye, strlen(eye), RTL_TEXTENCODING_ASCII_US);
364 #else
365 static const pid_t pid = getpid();
366 static const OUString aPidString = OUString::number(pid);
367 aEyeCatcher += aPidString;
368 #endif
369 #elif defined(_WIN32)
370 static const int pid = _getpid();
371 static const OUString aPidString = OUString::number(pid);
372 aEyeCatcher += aPidString;
373 #endif
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
387 OUString aTmp;
388 if ( !aName.isEmpty() )
389 FileBase::getSystemPathFromFileURL( aName, aTmp );
390 return aTmp;
393 TempFileFast::TempFileFast( )
397 TempFileFast::TempFileFast(TempFileFast && other) noexcept :
398 mxStream(std::move(other.mxStream))
402 TempFileFast::~TempFileFast()
404 CloseStream();
407 SvStream* TempFileFast::GetStream( StreamMode eMode )
409 if (!mxStream)
411 OUString aName = CreateTempNameFast();
412 #ifdef _WIN32
413 mxStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY | StreamMode::DELETE_ON_CLOSE));
414 #else
415 mxStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY));
416 #endif
418 return mxStream.get();
421 void TempFileFast::CloseStream()
423 if (mxStream)
425 #if !defined _WIN32
426 OUString aName = mxStream->GetFileName();
427 #endif
428 mxStream.reset();
429 #ifdef _WIN32
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.
432 #else
433 if (!aName.isEmpty() && (osl::FileBase::getFileURLFromSystemPath(aName, aName) == osl::FileBase::E_None))
434 File::remove(aName);
435 #endif
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 )
481 return;
483 pStream.reset();
484 if ( bIsDirectory )
486 comphelper::DirectoryHelper::deleteDirRecursively(aName);
488 else
490 File::remove( aName );
494 bool TempFileNamed::IsValid() const
496 return !aName.isEmpty();
499 OUString TempFileNamed::GetFileName() const
501 OUString aTmp;
502 FileBase::getSystemPathFromFileURL(aName, aTmp);
503 return 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.
513 if (pStream)
514 pStream->Flush();
515 return aName;
518 SvStream* TempFileNamed::GetStream( StreamMode eMode )
520 if (!pStream)
522 if (!aName.isEmpty())
523 pStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY));
524 else
525 pStream.reset(new SvMemoryStream);
528 return pStream.get();
531 void TempFileNamed::CloseStream()
533 pStream.reset();
536 OUString SetTempNameBaseDirectory( const OUString &rBaseName )
538 if( rBaseName.isEmpty() )
539 return OUString();
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
548 bool bRet = false;
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 );
553 else
554 bRet = true;
556 // failure to create base directory means returning an empty string
557 OUString aTmp;
558 if ( bRet )
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 );
573 return 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 ()
594 // XInputStream
596 sal_Int32 SAL_CALL TempFileFastService::readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead )
598 std::unique_lock aGuard( maMutex );
599 if ( mbInClosed )
600 throw css::io::NotConnectedException ( OUString(), getXWeak() );
602 checkConnected();
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);
610 checkError();
612 if (nRead < o3tl::make_unsigned(aData.getLength()))
613 aData.realloc( nRead );
615 return nRead;
618 sal_Int32 SAL_CALL TempFileFastService::readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead )
621 std::unique_lock aGuard( maMutex );
622 if ( mbInClosed )
623 throw css::io::NotConnectedException ( OUString(), getXWeak() );
625 checkConnected();
626 checkError();
628 if (nMaxBytesToRead < 0)
629 throw css::io::BufferSizeExceededException( OUString(), getXWeak() );
631 if (mpStream->eof())
633 aData.realloc(0);
634 return 0;
637 return readBytes(aData, nMaxBytesToRead);
640 void SAL_CALL TempFileFastService::skipBytes( sal_Int32 nBytesToSkip )
642 std::unique_lock aGuard( maMutex );
643 if ( mbInClosed )
644 throw css::io::NotConnectedException ( OUString(), getXWeak() );
646 checkConnected();
647 checkError();
648 mpStream->SeekRel(nBytesToSkip);
649 checkError();
652 sal_Int32 SAL_CALL TempFileFastService::available()
654 std::unique_lock aGuard( maMutex );
655 if ( mbInClosed )
656 throw css::io::NotConnectedException ( OUString(), getXWeak() );
658 checkConnected();
660 sal_Int64 nAvailable = mpStream->remainingSize();
661 checkError();
663 return std::min<sal_Int64>(SAL_MAX_INT32, nAvailable);
666 void SAL_CALL TempFileFastService::closeInput()
668 std::unique_lock aGuard( maMutex );
669 if ( mbInClosed )
670 throw css::io::NotConnectedException ( OUString(), getXWeak() );
672 mbInClosed = true;
674 if ( mbOutClosed )
676 // stream will be deleted by TempFile implementation
677 mpStream = nullptr;
678 mpTempFile.reset();
682 // XOutputStream
684 void SAL_CALL TempFileFastService::writeBytes( const css::uno::Sequence< sal_Int8 >& aData )
686 std::unique_lock aGuard( maMutex );
687 if ( mbOutClosed )
688 throw css::io::NotConnectedException ( OUString(), getXWeak() );
690 checkConnected();
691 sal_uInt32 nWritten = mpStream->WriteBytes(aData.getConstArray(), aData.getLength());
692 checkError();
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 );
700 if ( mbOutClosed )
701 throw css::io::NotConnectedException ( OUString(), getXWeak() );
703 checkConnected();
704 mpStream->Flush();
705 checkError();
708 void SAL_CALL TempFileFastService::closeOutput()
710 std::unique_lock aGuard( maMutex );
711 if ( mbOutClosed )
712 throw css::io::NotConnectedException ( OUString(), getXWeak() );
714 mbOutClosed = true;
715 if (mpStream)
717 // so that if you then open the InputStream, you can read the content
718 mpStream->FlushBuffer();
719 mpStream->Seek(0);
722 if ( mbInClosed )
724 // stream will be deleted by TempFile implementation
725 mpStream = nullptr;
726 mpTempFile.reset();
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()
738 if (!mpStream)
739 throw css::io::NotConnectedException ( OUString(), getXWeak() );
742 // XSeekable
744 void SAL_CALL TempFileFastService::seek( sal_Int64 nLocation )
746 std::unique_lock aGuard( maMutex );
747 checkConnected();
748 checkError();
749 if ( nLocation < 0 )
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();
755 checkError();
758 sal_Int64 SAL_CALL TempFileFastService::getPosition()
760 std::unique_lock aGuard( maMutex );
761 checkConnected();
763 sal_uInt32 nPos = mpStream->Tell();
764 checkError();
765 return static_cast<sal_Int64>(nPos);
768 sal_Int64 SAL_CALL TempFileFastService::getLength()
770 std::unique_lock aGuard( maMutex );
771 checkConnected();
773 checkError();
775 sal_Int64 nEndPos = mpStream->TellEnd();
777 return nEndPos;
780 // XStream
782 css::uno::Reference< css::io::XInputStream > SAL_CALL TempFileFastService::getInputStream()
784 return this;
787 css::uno::Reference< css::io::XOutputStream > SAL_CALL TempFileFastService::getOutputStream()
789 return this;
792 // XTruncate
794 void SAL_CALL TempFileFastService::truncate()
796 std::unique_lock aGuard( maMutex );
797 checkConnected();
798 // SetStreamSize() call does not change the position
799 mpStream->Seek( 0 );
800 mpStream->SetStreamSize( 0 );
801 checkError();
808 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */