Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/xpcom/io/nsAnonymousTemporaryFile.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
8
#include "nsAnonymousTemporaryFile.h"
9
#include "nsDirectoryServiceUtils.h"
10
#include "nsDirectoryServiceDefs.h"
11
#include "nsXULAppAPI.h"
12
#include "nsCOMPtr.h"
13
#include "nsString.h"
14
#include "nsAppDirectoryServiceDefs.h"
15
#include "prio.h"
16
17
#ifdef XP_WIN
18
#include "nsIObserver.h"
19
#include "nsIObserverService.h"
20
#include "mozilla/ResultExtensions.h"
21
#include "mozilla/Services.h"
22
#include "nsIIdleService.h"
23
#include "nsISimpleEnumerator.h"
24
#include "nsIFile.h"
25
#include "nsAutoPtr.h"
26
#include "nsITimer.h"
27
#include "nsCRT.h"
28
29
#endif
30
31
using namespace mozilla;
32
33
// We store the temp files in the system temp dir.
34
//
35
// On Windows systems in particular we use a sub-directory of the temp
36
// directory, because:
37
//   1. DELETE_ON_CLOSE is unreliable on Windows, in particular if we power
38
//      cycle (and perhaps if we crash) the files are not deleted. We store
39
//      the temporary files in a known sub-dir so that we can find and delete
40
//      them easily and quickly.
41
//   2. On Windows NT the system temp dir is in the user's $HomeDir/AppData,
42
//      so we can be sure the user always has write privileges to that directory;
43
//      if the sub-dir for our temp files was in some shared location and
44
//      was created by a privileged user, it's possible that other users
45
//      wouldn't have write access to that sub-dir. (Non-Windows systems
46
//      don't store their temp files in a sub-dir, so this isn't an issue on
47
//      those platforms).
48
//   3. Content processes can access the system temp dir
49
//      (NS_GetSpecialDirectory fails on NS_APP_USER_PROFILE_LOCAL_50_DIR
50
//      for content process for example, which is where we previously stored
51
//      temp files on Windows). This argument applies to all platforms, not
52
//      just Windows.
53
static nsresult
54
GetTempDir(nsIFile** aTempDir)
55
0
{
56
0
  if (NS_WARN_IF(!aTempDir)) {
57
0
    return NS_ERROR_INVALID_ARG;
58
0
  }
59
0
  nsCOMPtr<nsIFile> tmpFile;
60
0
  nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile));
61
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
62
0
    return rv;
63
0
  }
64
0
65
#ifdef XP_WIN
66
  // On windows DELETE_ON_CLOSE is unreliable, so we store temporary files
67
  // in a subdir of the temp dir and delete that in an idle service observer
68
  // to ensure it's been cleared.
69
  rv = tmpFile->AppendNative(nsDependentCString("mozilla-temp-files"));
70
  if (NS_WARN_IF(NS_FAILED(rv))) {
71
    return rv;
72
  }
73
  rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
74
  if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
75
    return rv;
76
  }
77
#endif
78
79
0
  tmpFile.forget(aTempDir);
80
0
81
0
  return NS_OK;
82
0
}
83
84
nsresult
85
NS_OpenAnonymousTemporaryNsIFile(nsIFile** aFile)
86
0
{
87
0
  MOZ_ASSERT(XRE_IsParentProcess());
88
0
89
0
  if (NS_WARN_IF(!aFile)) {
90
0
    return NS_ERROR_INVALID_ARG;
91
0
  }
92
0
93
0
  nsresult rv;
94
0
  nsCOMPtr<nsIFile> tmpFile;
95
0
  rv = GetTempDir(getter_AddRefs(tmpFile));
96
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
97
0
    return rv;
98
0
  }
99
0
100
0
  // Give the temp file a name with a random element. CreateUnique will also
101
0
  // append a counter to the name if it encounters a name collision. Adding
102
0
  // a random element to the name reduces the likelihood of a name collision,
103
0
  // so that CreateUnique() doesn't end up trying a lot of name variants in
104
0
  // its "try appending an incrementing counter" loop, as file IO can be
105
0
  // expensive on some mobile flash drives.
106
0
  nsAutoCString name("mozilla-temp-");
107
0
  name.AppendInt(rand());
108
0
109
0
  rv = tmpFile->AppendNative(name);
110
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
111
0
    return rv;
112
0
  }
113
0
114
0
  rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
115
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
116
0
    return rv;
117
0
  }
118
0
119
0
  tmpFile.forget(aFile);
120
0
  return NS_OK;
121
0
}
122
123
nsresult
124
NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc)
125
0
{
126
0
  nsCOMPtr<nsIFile> tmpFile;
127
0
  nsresult rv = NS_OpenAnonymousTemporaryNsIFile(getter_AddRefs(tmpFile));
128
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
129
0
    return rv;
130
0
  }
131
0
132
0
  rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsIFile::DELETE_ON_CLOSE,
133
0
                                 PR_IRWXU, aOutFileDesc);
134
0
135
0
  return rv;
136
0
}
137
138
#ifdef XP_WIN
139
140
// On Windows we have an idle service observer that runs some time after
141
// startup and deletes any stray anonymous temporary files...
142
143
// Duration of idle time before we'll get a callback whereupon we attempt to
144
// remove any stray and unused anonymous temp files.
145
#define TEMP_FILE_IDLE_TIME_S 30
146
147
// The nsAnonTempFileRemover is created in a timer, which sets an idle observer.
148
// This is expiration time (in ms) which initial timer is set for (3 minutes).
149
#define SCHEDULE_TIMEOUT_MS 3 * 60 * 1000
150
151
#define XPCOM_SHUTDOWN_TOPIC "xpcom-shutdown"
152
153
// This class adds itself as an idle observer. When the application has
154
// been idle for about 30 seconds we'll get a notification, whereupon we'll
155
// attempt to delete ${TempDir}/mozilla-temp-files/. This is to ensure all
156
// temp files that were supposed to be deleted on application exit were actually
157
// deleted, as they may not be if we previously crashed. See bugs 572579 and
158
// 785662. This is only needed on some versions of Windows,
159
// nsIFile::DELETE_ON_CLOSE works on other platforms.
160
// This class adds itself as a shutdown observer so that it can cancel the
161
// idle observer and its timer on shutdown. Note: the observer and idle
162
// services hold references to instances of this object, and those references
163
// are what keep this object alive.
164
class nsAnonTempFileRemover final : public nsIObserver
165
{
166
public:
167
  NS_DECL_ISUPPORTS
168
169
  nsAnonTempFileRemover() {}
170
171
  nsresult Init()
172
  {
173
    // We add the idle observer in a timer, so that the app has enough
174
    // time to start up before we add the idle observer. If we register the
175
    // idle observer too early, it will be registered before the fake idle
176
    // service is installed when running in xpcshell, and this interferes with
177
    // the fake idle service, causing xpcshell-test failures.
178
    MOZ_TRY_VAR(mTimer, NS_NewTimerWithObserver(this,
179
                                                SCHEDULE_TIMEOUT_MS,
180
                                                nsITimer::TYPE_ONE_SHOT));
181
182
    // Register shutdown observer so we can cancel the timer if we shutdown before
183
    // the timer runs.
184
    nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService();
185
    if (NS_WARN_IF(!obsSrv)) {
186
      return NS_ERROR_FAILURE;
187
    }
188
    return obsSrv->AddObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
189
  }
190
191
  void Cleanup()
192
  {
193
    // Cancel timer.
194
    if (mTimer) {
195
      mTimer->Cancel();
196
      mTimer = nullptr;
197
    }
198
    // Remove idle service observer.
199
    nsCOMPtr<nsIIdleService> idleSvc =
200
      do_GetService("@mozilla.org/widget/idleservice;1");
201
    if (idleSvc) {
202
      idleSvc->RemoveIdleObserver(this, TEMP_FILE_IDLE_TIME_S);
203
    }
204
    // Remove shutdown observer.
205
    nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService();
206
    if (obsSrv) {
207
      obsSrv->RemoveObserver(this, XPCOM_SHUTDOWN_TOPIC);
208
    }
209
  }
210
211
  NS_IMETHODIMP Observe(nsISupports* aSubject,
212
                        const char* aTopic,
213
                        const char16_t* aData)
214
  {
215
    if (nsCRT::strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0 &&
216
        NS_FAILED(RegisterIdleObserver())) {
217
      Cleanup();
218
    } else if (nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0) {
219
      // The user has been idle for a while, clean up the temp files.
220
      // The idle service will drop its reference to this object after
221
      // we exit, destroying this object.
222
      RemoveAnonTempFileFiles();
223
      Cleanup();
224
    } else if (nsCRT::strcmp(aTopic, XPCOM_SHUTDOWN_TOPIC) == 0) {
225
      Cleanup();
226
    }
227
    return NS_OK;
228
  }
229
230
  nsresult RegisterIdleObserver()
231
  {
232
    // Add this as an idle observer. When we've been idle for
233
    // TEMP_FILE_IDLE_TIME_S seconds, we'll get a notification, and we'll then
234
    // try to delete any stray temp files.
235
    nsCOMPtr<nsIIdleService> idleSvc =
236
      do_GetService("@mozilla.org/widget/idleservice;1");
237
    if (!idleSvc) {
238
      return NS_ERROR_FAILURE;
239
    }
240
    return idleSvc->AddIdleObserver(this, TEMP_FILE_IDLE_TIME_S);
241
  }
242
243
  void RemoveAnonTempFileFiles()
244
  {
245
    nsCOMPtr<nsIFile> tmpDir;
246
    nsresult rv = GetTempDir(getter_AddRefs(tmpDir));
247
    if (NS_WARN_IF(NS_FAILED(rv))) {
248
      return;
249
    }
250
251
    // Remove the directory recursively.
252
    tmpDir->Remove(true);
253
  }
254
255
private:
256
  ~nsAnonTempFileRemover() {}
257
258
  nsCOMPtr<nsITimer> mTimer;
259
};
260
261
NS_IMPL_ISUPPORTS(nsAnonTempFileRemover, nsIObserver)
262
263
nsresult
264
CreateAnonTempFileRemover()
265
{
266
  // Create a temp file remover. If Init() succeeds, the temp file remover is kept
267
  // alive by a reference held by the observer service, since the temp file remover
268
  // is a shutdown observer. We only create the temp file remover if we're running
269
  // in the main process; there's no point in doing the temp file removal multiple
270
  // times per startup.
271
  if (!XRE_IsParentProcess()) {
272
    return NS_OK;
273
  }
274
  RefPtr<nsAnonTempFileRemover> tempRemover = new nsAnonTempFileRemover();
275
  return tempRemover->Init();
276
}
277
278
#endif
279