Version 6.4.0.3, tag libreoffice-6.4.0.3
[LibreOffice.git] / unotools / source / ucbhelper / tempfile.cxx
blobbce71120f4f926f942f17ad3c541c0843ce226ea
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 <unotools/tempfile.hxx>
26 #include <rtl/ustring.hxx>
27 #include <rtl/instance.hxx>
28 #include <osl/detail/file.h>
29 #include <osl/file.hxx>
30 #include <tools/time.hxx>
31 #include <tools/debug.hxx>
33 #ifdef UNX
34 #include <unistd.h>
35 #elif defined( WNT )
36 #include <process.h>
37 #endif
39 using namespace osl;
41 namespace
43 struct TempNameBase_Impl
44 : public rtl::Static< OUString, TempNameBase_Impl > {};
47 namespace utl
50 static OUString getParentName( const OUString& aFileName )
52 sal_Int32 lastIndex = aFileName.lastIndexOf( '/' );
53 OUString aParent;
55 if (lastIndex > -1)
57 aParent = aFileName.copy(0, lastIndex);
59 if (aParent.endsWith(":") && aParent.getLength() == 6)
60 aParent += "/";
62 if (aParent.equalsIgnoreAsciiCase("file://"))
63 aParent = "file:///";
66 return aParent;
69 static bool ensuredir( const OUString& rUnqPath )
71 OUString aPath;
72 if ( rUnqPath.isEmpty() )
73 return false;
75 // remove trailing slash
76 if ( rUnqPath.endsWith("/") )
77 aPath = rUnqPath.copy( 0, rUnqPath.getLength() - 1 );
78 else
79 aPath = rUnqPath;
81 // HACK: create directory on a mount point with nobrowse option
82 // returns ENOSYS in any case !!
83 osl::Directory aDirectory( aPath );
84 osl::FileBase::RC nError = aDirectory.open();
85 aDirectory.close();
86 if( nError == osl::File::E_None )
87 return true;
89 // try to create the directory
90 nError = osl::Directory::create( aPath );
91 bool bSuccess = ( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST );
92 if( !bSuccess )
94 // perhaps parent(s) don't exist
95 OUString aParentDir = getParentName( aPath );
96 if ( aParentDir != aPath )
98 bSuccess = ensuredir( getParentName( aPath ) );
100 // After parent directory structure exists try it one's more
101 if ( bSuccess )
103 // Parent directory exists, retry creation of directory
104 nError = osl::Directory::create( aPath );
105 bSuccess =( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST );
110 return bSuccess;
113 static OUString ConstructTempDir_Impl( const OUString* pParent )
115 OUString aName;
117 // Ignore pParent on iOS. We don't want to create any temp files
118 // in the same directory where the document being edited is.
119 #ifndef IOS
120 if ( pParent && !pParent->isEmpty() )
122 // test for valid filename
123 OUString aRet;
124 if ((osl::FileBase::getSystemPathFromFileURL(*pParent, aRet)
125 == osl::FileBase::E_None)
126 && (osl::FileBase::getFileURLFromSystemPath(aRet, aRet)
127 == osl::FileBase::E_None))
129 ::osl::DirectoryItem aItem;
130 sal_Int32 i = aRet.getLength();
131 if ( aRet[i-1] == '/' )
132 i--;
134 if ( DirectoryItem::get( aRet.copy(0, i), aItem ) == FileBase::E_None )
135 aName = aRet;
138 #else
139 (void) pParent;
140 #endif
142 if ( aName.isEmpty() )
144 OUString &rTempNameBase_Impl = TempNameBase_Impl::get();
145 if (rTempNameBase_Impl.isEmpty())
147 OUString ustrTempDirURL;
148 ::osl::FileBase::RC rc = ::osl::File::getTempDirURL(
149 ustrTempDirURL );
150 if (rc == ::osl::FileBase::E_None)
151 rTempNameBase_Impl = ustrTempDirURL;
153 // if no parent or invalid parent : use default directory
154 DBG_ASSERT( !rTempNameBase_Impl.isEmpty(), "No TempDir!" );
155 aName = rTempNameBase_Impl;
156 ensuredir( aName );
159 // Make sure that directory ends with a separator
160 if( !aName.isEmpty() && !aName.endsWith("/") )
161 aName += "/";
163 return aName;
166 class Tokens {
167 public:
168 virtual bool next(OUString *) = 0;
170 protected:
171 virtual ~Tokens() {} // avoid warnings
174 class SequentialTokens: public Tokens {
175 public:
176 explicit SequentialTokens(bool showZero): m_value(0), m_show(showZero) {}
178 bool next(OUString * token) override {
179 assert(token != nullptr);
180 if (m_value == SAL_MAX_UINT32) {
181 return false;
183 *token = m_show ? OUString::number(m_value) : OUString();
184 ++m_value;
185 m_show = true;
186 return true;
189 private:
190 sal_uInt32 m_value;
191 bool m_show;
194 class UniqueTokens: public Tokens {
195 public:
196 UniqueTokens(): m_count(0) {}
198 bool next(OUString * token) override {
199 assert(token != nullptr);
200 // Because of the shared globalValue, no single instance of UniqueTokens
201 // is guaranteed to exhaustively test all 36^6 possible values, but stop
202 // after that many attempts anyway:
203 sal_uInt32 radix = 36;
204 sal_uInt32 max = radix * radix * radix * radix * radix * radix;
205 // 36^6 == 2'176'782'336 < SAL_MAX_UINT32 == 4'294'967'295
206 if (m_count == max) {
207 return false;
209 sal_uInt32 v;
211 osl::MutexGuard g(osl::Mutex::getGlobalMutex());
212 globalValue
213 = ((globalValue == SAL_MAX_UINT32
214 ? tools::Time::GetSystemTicks() : globalValue + 1)
215 % max);
216 v = globalValue;
218 *token = OUString::number(v, radix);
219 ++m_count;
220 return true;
223 private:
224 static sal_uInt32 globalValue;
226 sal_uInt32 m_count;
229 sal_uInt32 UniqueTokens::globalValue = SAL_MAX_UINT32;
231 namespace
233 class TempDirCreatedObserver : public DirectoryCreationObserver
235 public:
236 virtual void DirectoryCreated(const OUString& aDirectoryUrl) override
238 File::setAttributes( aDirectoryUrl, osl_File_Attribute_OwnRead |
239 osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnExe );
244 static OUString lcl_createName(
245 const OUString& rLeadingChars, Tokens & tokens, const OUString* pExtension,
246 const OUString* pParent, bool bDirectory, bool bKeep, bool bLock,
247 bool bCreateParentDirs )
249 OUString aName = ConstructTempDir_Impl( pParent );
250 if ( bCreateParentDirs )
252 sal_Int32 nOffset = rLeadingChars.lastIndexOf("/");
253 if (-1 != nOffset)
255 OUString aDirName = aName + rLeadingChars.copy( 0, nOffset );
256 TempDirCreatedObserver observer;
257 FileBase::RC err = Directory::createPath( aDirName, &observer );
258 if ( err != FileBase::E_None && err != FileBase::E_EXIST )
259 return OUString();
262 aName += rLeadingChars;
264 OUString token;
265 while (tokens.next(&token))
267 OUString aTmp( aName + token );
268 if ( pExtension )
269 aTmp += *pExtension;
270 else
271 aTmp += ".tmp";
272 if ( bDirectory )
274 FileBase::RC err = Directory::create(
275 aTmp,
276 (osl_File_OpenFlag_Read | osl_File_OpenFlag_Write
277 | osl_File_OpenFlag_Private));
278 if ( err == FileBase::E_None )
280 // !bKeep: only for creating a name, not a file or directory
281 if ( bKeep || Directory::remove( aTmp ) == FileBase::E_None )
282 return aTmp;
283 else
284 return OUString();
286 else if ( err != FileBase::E_EXIST )
287 // if f.e. name contains invalid chars stop trying to create dirs
288 return OUString();
290 else
292 DBG_ASSERT( bKeep, "Too expensive, use directory for creating name!" );
293 File aFile( aTmp );
294 FileBase::RC err = aFile.open(
295 osl_File_OpenFlag_Create | osl_File_OpenFlag_Private
296 | (bLock ? 0 : osl_File_OpenFlag_NoLock));
297 if ( err == FileBase::E_None || (bLock && err == FileBase::E_NOLCK) )
299 aFile.close();
300 return aTmp;
302 else if ( err != FileBase::E_EXIST )
304 // if f.e. name contains invalid chars stop trying to create dirs
305 // but if there is a folder with such name proceed further
307 DirectoryItem aTmpItem;
308 FileStatus aTmpStatus( osl_FileStatus_Mask_Type );
309 if ( DirectoryItem::get( aTmp, aTmpItem ) != FileBase::E_None
310 || aTmpItem.getFileStatus( aTmpStatus ) != FileBase::E_None
311 || aTmpStatus.getFileType() != FileStatus::Directory )
312 return OUString();
316 return OUString();
319 static OUString CreateTempName_Impl( const OUString* pParent, bool bKeep, bool bDir = true )
321 OUString aEyeCatcher = "lu";
322 #ifdef UNX
323 #ifdef DBG_UTIL
324 const char* eye = getenv("LO_TESTNAME");
325 if(eye)
327 aEyeCatcher = OUString(eye, strlen(eye), RTL_TEXTENCODING_ASCII_US);
329 #else
330 static const pid_t pid = getpid();
331 static const OUString aPidString = OUString::number(pid);
332 aEyeCatcher += aPidString;
333 #endif
334 #elif defined(WNT)
335 static const int pid = _getpid();
336 static const OUString aPidString = OUString::number(pid);
337 aEyeCatcher += aPidString;
338 #endif
339 UniqueTokens t;
340 return lcl_createName( aEyeCatcher, t, nullptr, pParent, bDir, bKeep,
341 false, false);
344 OUString TempFile::CreateTempName()
346 OUString aName(CreateTempName_Impl( nullptr, false ));
348 // convert to file URL
349 OUString aTmp;
350 if ( !aName.isEmpty() )
351 FileBase::getSystemPathFromFileURL( aName, aTmp );
352 return aTmp;
355 TempFile::TempFile( const OUString* pParent, bool bDirectory )
356 : bIsDirectory( bDirectory )
357 , bKillingFileEnabled( false )
359 aName = CreateTempName_Impl( pParent, true, bDirectory );
362 TempFile::TempFile( const OUString& rLeadingChars, bool _bStartWithZero,
363 const OUString* pExtension, const OUString* pParent,
364 bool bCreateParentDirs )
365 : bIsDirectory( false )
366 , bKillingFileEnabled( false )
368 SequentialTokens t(_bStartWithZero);
369 aName = lcl_createName( rLeadingChars, t, pExtension, pParent, false,
370 true, true, bCreateParentDirs );
373 TempFile::TempFile(TempFile && other) noexcept :
374 aName(std::move(other.aName)), pStream(std::move(other.pStream)), bIsDirectory(other.bIsDirectory),
375 bKillingFileEnabled(other.bKillingFileEnabled)
377 other.bKillingFileEnabled = false;
380 TempFile::~TempFile()
382 pStream.reset();
383 if ( bKillingFileEnabled )
385 if ( bIsDirectory )
387 // at the moment no recursiv algorithm present
388 Directory::remove( aName );
390 else
392 File::remove( aName );
397 bool TempFile::IsValid() const
399 return !aName.isEmpty();
402 OUString TempFile::GetFileName() const
404 OUString aTmp;
405 FileBase::getSystemPathFromFileURL(aName, aTmp);
406 return aTmp;
409 OUString const & TempFile::GetURL() const
411 return aName;
414 SvStream* TempFile::GetStream( StreamMode eMode )
416 if (!pStream)
418 if (!aName.isEmpty())
419 pStream.reset(new SvFileStream(aName, eMode));
420 else
421 pStream.reset(new SvMemoryStream(nullptr, 0, eMode));
424 return pStream.get();
427 void TempFile::CloseStream()
429 pStream.reset();
432 OUString TempFile::SetTempNameBaseDirectory( const OUString &rBaseName )
434 if( rBaseName.isEmpty() )
435 return OUString();
437 OUString aUnqPath( rBaseName );
439 // remove trailing slash
440 if ( rBaseName.endsWith("/") )
441 aUnqPath = rBaseName.copy( 0, rBaseName.getLength() - 1 );
443 // try to create the directory
444 bool bRet = false;
445 osl::FileBase::RC err = osl::Directory::create( aUnqPath );
446 if ( err != FileBase::E_None && err != FileBase::E_EXIST )
447 // perhaps parent(s) don't exist
448 bRet = ensuredir( aUnqPath );
449 else
450 bRet = true;
452 // failure to create base directory means returning an empty string
453 OUString aTmp;
454 if ( bRet )
456 // append own internal directory
457 OUString &rTempNameBase_Impl = TempNameBase_Impl::get();
458 rTempNameBase_Impl = rBaseName + "/";
460 TempFile aBase( nullptr, true );
461 if ( aBase.IsValid() )
462 // use it in case of success
463 rTempNameBase_Impl = aBase.aName;
465 // return system path of used directory
466 FileBase::getSystemPathFromFileURL( rTempNameBase_Impl, aTmp );
469 return aTmp;
472 OUString TempFile::GetTempNameBaseDirectory()
474 return ConstructTempDir_Impl(nullptr);
479 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */