1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <sal/config.h>
21
22 #include <cassert>
23 #include <utility>
24
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>
32 #include <comphelper/DirectoryHelper.hxx>
33
34 #ifdef UNX
35 #include <unistd.h>
36 #elif defined( _WIN32 )
37 #include <process.h>
38 #endif
39
40 using namespace osl;
41
42 namespace
43 {
44 struct TempNameBase_Impl
45 : public rtl::Static< OUString, TempNameBase_Impl > {};
46 }
47
48 namespace utl
49 {
50
getParentName(const OUString & aFileName)51 static OUString getParentName( const OUString& aFileName )
52 {
53 sal_Int32 lastIndex = aFileName.lastIndexOf( '/' );
54 OUString aParent;
55
56 if (lastIndex > -1)
57 {
58 aParent = aFileName.copy(0, lastIndex);
59
60 if (aParent.endsWith(":") && aParent.getLength() == 6)
61 aParent += "/";
62
63 if (aParent.equalsIgnoreAsciiCase("file://"))
64 aParent = "file:///";
65 }
66
67 return aParent;
68 }
69
ensuredir(const OUString & rUnqPath)70 static bool ensuredir( const OUString& rUnqPath )
71 {
72 OUString aPath;
73 if ( rUnqPath.isEmpty() )
74 return false;
75
76 // remove trailing slash
77 if ( rUnqPath.endsWith("/") )
78 aPath = rUnqPath.copy( 0, rUnqPath.getLength() - 1 );
79 else
80 aPath = rUnqPath;
81
82 // HACK: create directory on a mount point with nobrowse option
83 // returns ENOSYS in any case !!
84 osl::Directory aDirectory( aPath );
85 osl::FileBase::RC nError = aDirectory.open();
86 aDirectory.close();
87 if( nError == osl::File::E_None )
88 return true;
89
90 // try to create the directory
91 nError = osl::Directory::create( aPath );
92 bool bSuccess = ( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST );
93 if( !bSuccess )
94 {
95 // perhaps parent(s) don't exist
96 OUString aParentDir = getParentName( aPath );
97 if ( aParentDir != aPath )
98 {
99 bSuccess = ensuredir( getParentName( aPath ) );
100
101 // After parent directory structure exists try it one's more
102 if ( bSuccess )
103 {
104 // Parent directory exists, retry creation of directory
105 nError = osl::Directory::create( aPath );
106 bSuccess =( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST );
107 }
108 }
109 }
110
111 return bSuccess;
112 }
113
ConstructTempDir_Impl(const OUString * pParent,bool bCreateParentDirs)114 static OUString ConstructTempDir_Impl( const OUString* pParent, bool bCreateParentDirs )
115 {
116 OUString aName;
117
118 // Ignore pParent on iOS. We don't want to create any temp files
119 // in the same directory where the document being edited is.
120 #ifndef IOS
121 if ( pParent && !pParent->isEmpty() )
122 {
123 // test for valid filename
124 OUString aRet;
125 if ((osl::FileBase::getSystemPathFromFileURL(*pParent, aRet)
126 == osl::FileBase::E_None)
127 && (osl::FileBase::getFileURLFromSystemPath(aRet, aRet)
128 == osl::FileBase::E_None))
129 {
130 ::osl::DirectoryItem aItem;
131 sal_Int32 i = aRet.getLength();
132 if ( aRet[i-1] == '/' )
133 i--;
134
135 if ( DirectoryItem::get( aRet.copy(0, i), aItem ) == FileBase::E_None || bCreateParentDirs )
136 aName = aRet;
137 }
138 }
139 #else
140 (void) pParent;
141 (void) bCreateParentDirs;
142 #endif
143
144 if ( aName.isEmpty() )
145 {
146 OUString &rTempNameBase_Impl = TempNameBase_Impl::get();
147 if (rTempNameBase_Impl.isEmpty())
148 {
149 OUString ustrTempDirURL;
150 ::osl::FileBase::RC rc = ::osl::File::getTempDirURL(
151 ustrTempDirURL );
152 if (rc == ::osl::FileBase::E_None)
153 rTempNameBase_Impl = ustrTempDirURL;
154 }
155 // if no parent or invalid parent : use default directory
156 DBG_ASSERT( !rTempNameBase_Impl.isEmpty(), "No TempDir!" );
157 aName = rTempNameBase_Impl;
158 ensuredir( aName );
159 }
160
161 // Make sure that directory ends with a separator
162 if( !aName.isEmpty() && !aName.endsWith("/") )
163 aName += "/";
164
165 return aName;
166 }
167
168 namespace {
169
170 class Tokens {
171 public:
172 virtual bool next(OUString *) = 0;
173
174 protected:
~Tokens()175 virtual ~Tokens() {} // avoid warnings
176 };
177
178 class SequentialTokens: public Tokens {
179 public:
SequentialTokens(bool showZero)180 explicit SequentialTokens(bool showZero): m_value(0), m_show(showZero) {}
181
next(OUString * token)182 bool next(OUString * token) override {
183 assert(token != nullptr);
184 if (m_value == SAL_MAX_UINT32) {
185 return false;
186 }
187 *token = m_show ? OUString::number(m_value) : OUString();
188 ++m_value;
189 m_show = true;
190 return true;
191 }
192
193 private:
194 sal_uInt32 m_value;
195 bool m_show;
196 };
197
198 class UniqueTokens: public Tokens {
199 public:
UniqueTokens()200 UniqueTokens(): m_count(0) {}
201
next(OUString * token)202 bool next(OUString * token) override {
203 assert(token != nullptr);
204 // Because of the shared globalValue, no single instance of UniqueTokens
205 // is guaranteed to exhaustively test all 36^6 possible values, but stop
206 // after that many attempts anyway:
207 sal_uInt32 radix = 36;
208 sal_uInt32 max = radix * radix * radix * radix * radix * radix;
209 // 36^6 == 2'176'782'336 < SAL_MAX_UINT32 == 4'294'967'295
210 if (m_count == max) {
211 return false;
212 }
213 sal_uInt32 v;
214 {
215 osl::MutexGuard g(osl::Mutex::getGlobalMutex());
216 globalValue
217 = ((globalValue == SAL_MAX_UINT32
218 ? tools::Time::GetSystemTicks() : globalValue + 1)
219 % max);
220 v = globalValue;
221 }
222 *token = OUString::number(v, radix);
223 ++m_count;
224 return true;
225 }
226
227 private:
228 static sal_uInt32 globalValue;
229
230 sal_uInt32 m_count;
231 };
232
233 }
234
235 sal_uInt32 UniqueTokens::globalValue = SAL_MAX_UINT32;
236
237 namespace
238 {
239 class TempDirCreatedObserver : public DirectoryCreationObserver
240 {
241 public:
DirectoryCreated(const OUString & aDirectoryUrl)242 virtual void DirectoryCreated(const OUString& aDirectoryUrl) override
243 {
244 File::setAttributes( aDirectoryUrl, osl_File_Attribute_OwnRead |
245 osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnExe );
246 };
247 };
248 };
249
lcl_createName(const OUString & rLeadingChars,Tokens & tokens,const OUString * pExtension,const OUString * pParent,bool bDirectory,bool bKeep,bool bLock,bool bCreateParentDirs)250 static OUString lcl_createName(
251 const OUString& rLeadingChars, Tokens & tokens, const OUString* pExtension,
252 const OUString* pParent, bool bDirectory, bool bKeep, bool bLock,
253 bool bCreateParentDirs )
254 {
255 OUString aName = ConstructTempDir_Impl( pParent, bCreateParentDirs );
256 if ( bCreateParentDirs )
257 {
258 sal_Int32 nOffset = rLeadingChars.lastIndexOf("/");
259 OUString aDirName;
260 if (-1 != nOffset)
261 aDirName = aName + rLeadingChars.subView( 0, nOffset );
262 else
263 aDirName = aName;
264 TempDirCreatedObserver observer;
265 FileBase::RC err = Directory::createPath( aDirName, &observer );
266 if ( err != FileBase::E_None && err != FileBase::E_EXIST )
267 return OUString();
268 }
269 aName += rLeadingChars;
270
271 OUString token;
272 while (tokens.next(&token))
273 {
274 OUString aTmp( aName + token );
275 if ( pExtension )
276 aTmp += *pExtension;
277 else
278 aTmp += ".tmp";
279 if ( bDirectory )
280 {
281 FileBase::RC err = Directory::create(
282 aTmp,
283 (osl_File_OpenFlag_Read | osl_File_OpenFlag_Write
284 | osl_File_OpenFlag_Private));
285 if ( err == FileBase::E_None )
286 {
287 // !bKeep: only for creating a name, not a file or directory
288 if ( bKeep || Directory::remove( aTmp ) == FileBase::E_None )
289 return aTmp;
290 else
291 return OUString();
292 }
293 else if ( err != FileBase::E_EXIST )
294 // if f.e. name contains invalid chars stop trying to create dirs
295 return OUString();
296 }
297 else
298 {
299 DBG_ASSERT( bKeep, "Too expensive, use directory for creating name!" );
300 File aFile( aTmp );
301 FileBase::RC err = aFile.open(
302 osl_File_OpenFlag_Create | osl_File_OpenFlag_Private
303 | (bLock ? 0 : osl_File_OpenFlag_NoLock));
304 if ( err == FileBase::E_None || (bLock && err == FileBase::E_NOLCK) )
305 {
306 aFile.close();
307 return aTmp;
308 }
309 else if ( err != FileBase::E_EXIST )
310 {
311 // if f.e. name contains invalid chars stop trying to create dirs
312 // but if there is a folder with such name proceed further
313
314 DirectoryItem aTmpItem;
315 FileStatus aTmpStatus( osl_FileStatus_Mask_Type );
316 if ( DirectoryItem::get( aTmp, aTmpItem ) != FileBase::E_None
317 || aTmpItem.getFileStatus( aTmpStatus ) != FileBase::E_None
318 || aTmpStatus.getFileType() != FileStatus::Directory )
319 return OUString();
320 }
321 }
322 }
323 return OUString();
324 }
325
CreateTempName_Impl(const OUString * pParent,bool bKeep,bool bDir=true)326 static OUString CreateTempName_Impl( const OUString* pParent, bool bKeep, bool bDir = true )
327 {
328 OUString aEyeCatcher = "lu";
329 #ifdef UNX
330 #ifdef DBG_UTIL
331 const char* eye = getenv("LO_TESTNAME");
332 if(eye)
333 {
334 aEyeCatcher = OUString(eye, strlen(eye), RTL_TEXTENCODING_ASCII_US);
335 }
336 #else
337 static const pid_t pid = getpid();
338 static const OUString aPidString = OUString::number(pid);
339 aEyeCatcher += aPidString;
340 #endif
341 #elif defined(_WIN32)
342 static const int pid = _getpid();
343 static const OUString aPidString = OUString::number(pid);
344 aEyeCatcher += aPidString;
345 #endif
346 UniqueTokens t;
347 return lcl_createName( aEyeCatcher, t, nullptr, pParent, bDir, bKeep,
348 false, false);
349 }
350
CreateTempName()351 OUString TempFile::CreateTempName()
352 {
353 OUString aName(CreateTempName_Impl( nullptr, false ));
354
355 // convert to file URL
356 OUString aTmp;
357 if ( !aName.isEmpty() )
358 FileBase::getSystemPathFromFileURL( aName, aTmp );
359 return aTmp;
360 }
361
TempFile(const OUString * pParent,bool bDirectory)362 TempFile::TempFile( const OUString* pParent, bool bDirectory )
363 : bIsDirectory( bDirectory )
364 , bKillingFileEnabled( false )
365 {
366 aName = CreateTempName_Impl( pParent, true, bDirectory );
367 }
368
TempFile(const OUString & rLeadingChars,bool _bStartWithZero,const OUString * pExtension,const OUString * pParent,bool bCreateParentDirs)369 TempFile::TempFile( const OUString& rLeadingChars, bool _bStartWithZero,
370 const OUString* pExtension, const OUString* pParent,
371 bool bCreateParentDirs )
372 : bIsDirectory( false )
373 , bKillingFileEnabled( false )
374 {
375 SequentialTokens t(_bStartWithZero);
376 aName = lcl_createName( rLeadingChars, t, pExtension, pParent, false,
377 true, true, bCreateParentDirs );
378 }
379
TempFile(TempFile && other)380 TempFile::TempFile(TempFile && other) noexcept :
381 aName(std::move(other.aName)), pStream(std::move(other.pStream)), bIsDirectory(other.bIsDirectory),
382 bKillingFileEnabled(other.bKillingFileEnabled)
383 {
384 other.bKillingFileEnabled = false;
385 }
386
~TempFile()387 TempFile::~TempFile()
388 {
389 // if we're going to delete this file, no point in flushing it when closing
390 if (pStream && bKillingFileEnabled && !aName.isEmpty())
391 static_cast<SvFileStream*>(pStream.get())->SetDontFlushOnClose(true);
392 pStream.reset();
393 if ( bKillingFileEnabled )
394 {
395 if ( bIsDirectory )
396 {
397 comphelper::DirectoryHelper::deleteDirRecursively(aName);
398 }
399 else
400 {
401 File::remove( aName );
402 }
403 }
404 }
405
IsValid() const406 bool TempFile::IsValid() const
407 {
408 return !aName.isEmpty();
409 }
410
GetFileName() const411 OUString TempFile::GetFileName() const
412 {
413 OUString aTmp;
414 FileBase::getSystemPathFromFileURL(aName, aTmp);
415 return aTmp;
416 }
417
GetURL() const418 OUString const & TempFile::GetURL() const
419 {
420 return aName;
421 }
422
GetStream(StreamMode eMode)423 SvStream* TempFile::GetStream( StreamMode eMode )
424 {
425 if (!pStream)
426 {
427 if (!aName.isEmpty())
428 pStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY));
429 else
430 pStream.reset(new SvMemoryStream(nullptr, 0, eMode));
431 }
432
433 return pStream.get();
434 }
435
CloseStream()436 void TempFile::CloseStream()
437 {
438 pStream.reset();
439 }
440
SetTempNameBaseDirectory(const OUString & rBaseName)441 OUString TempFile::SetTempNameBaseDirectory( const OUString &rBaseName )
442 {
443 if( rBaseName.isEmpty() )
444 return OUString();
445
446 OUString aUnqPath( rBaseName );
447
448 // remove trailing slash
449 if ( rBaseName.endsWith("/") )
450 aUnqPath = rBaseName.copy( 0, rBaseName.getLength() - 1 );
451
452 // try to create the directory
453 bool bRet = false;
454 osl::FileBase::RC err = osl::Directory::create( aUnqPath );
455 if ( err != FileBase::E_None && err != FileBase::E_EXIST )
456 // perhaps parent(s) don't exist
457 bRet = ensuredir( aUnqPath );
458 else
459 bRet = true;
460
461 // failure to create base directory means returning an empty string
462 OUString aTmp;
463 if ( bRet )
464 {
465 // append own internal directory
466 OUString &rTempNameBase_Impl = TempNameBase_Impl::get();
467 rTempNameBase_Impl = rBaseName + "/";
468
469 TempFile aBase( nullptr, true );
470 if ( aBase.IsValid() )
471 // use it in case of success
472 rTempNameBase_Impl = aBase.aName;
473
474 // return system path of used directory
475 FileBase::getSystemPathFromFileURL( rTempNameBase_Impl, aTmp );
476 }
477
478 return aTmp;
479 }
480
GetTempNameBaseDirectory()481 OUString TempFile::GetTempNameBaseDirectory()
482 {
483 return ConstructTempDir_Impl(nullptr, false);
484 }
485
486 }
487
488 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
489