summaryrefslogtreecommitdiff
path: root/xpcom/io/nsAnonymousTemporaryFile.cpp
blob: 586e552c44ce5df3484fb2b5f3bd91e54be5ef38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "mozilla/dom/ContentChild.h"
#include "mozilla/SyncRunnable.h"
#include "nsAnonymousTemporaryFile.h"
#include "nsDirectoryServiceUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsXULAppAPI.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsAppDirectoryServiceDefs.h"
#include "prio.h"
#include "private/pprio.h"

#ifdef XP_WIN
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "mozilla/Services.h"
#include "nsIIdleService.h"
#include "nsISimpleEnumerator.h"
#include "nsIFile.h"
#include "nsAutoPtr.h"
#include "nsITimer.h"
#include "nsCRT.h"

#endif

using namespace mozilla;

// We store the temp files in the system temp dir.
//
// On Windows systems in particular we use a sub-directory of the temp
// directory, because:
//   1. DELETE_ON_CLOSE is unreliable on Windows, in particular if we power
//      cycle (and perhaps if we crash) the files are not deleted. We store
//      the temporary files in a known sub-dir so that we can find and delete
//      them easily and quickly.
//   2. On Windows NT the system temp dir is in the user's $HomeDir/AppData,
//      so we can be sure the user always has write privileges to that directory;
//      if the sub-dir for our temp files was in some shared location and
//      was created by a privileged user, it's possible that other users
//      wouldn't have write access to that sub-dir. (Non-Windows systems
//      don't store their temp files in a sub-dir, so this isn't an issue on
//      those platforms).
//   3. Content processes can access the system temp dir
//      (NS_GetSpecialDirectory fails on NS_APP_USER_PROFILE_LOCAL_50_DIR
//      for content process for example, which is where we previously stored
//      temp files on Windows). This argument applies to all platforms, not
//      just Windows.
static nsresult
GetTempDir(nsIFile** aTempDir)
{
  if (NS_WARN_IF(!aTempDir)) {
    return NS_ERROR_INVALID_ARG;
  }
  nsCOMPtr<nsIFile> tmpFile;
  nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

#ifdef XP_WIN
  // On windows DELETE_ON_CLOSE is unreliable, so we store temporary files
  // in a subdir of the temp dir and delete that in an idle service observer
  // to ensure it's been cleared.
  rv = tmpFile->AppendNative(nsDependentCString("mozilla-temp-files"));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
  if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
#endif

  tmpFile.forget(aTempDir);

  return NS_OK;
}

namespace {

class nsRemoteAnonymousTemporaryFileRunnable : public Runnable
{
public:
  dom::FileDescOrError *mResultPtr;
  explicit nsRemoteAnonymousTemporaryFileRunnable(dom::FileDescOrError *aResultPtr)
  : mResultPtr(aResultPtr)
  { }

protected:
  NS_IMETHOD Run() override {
    dom::ContentChild* child = dom::ContentChild::GetSingleton();
    MOZ_ASSERT(child);
    child->SendOpenAnonymousTemporaryFile(mResultPtr);
    return NS_OK;
  }
};

} // namespace

nsresult
NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc)
{
  if (NS_WARN_IF(!aOutFileDesc)) {
    return NS_ERROR_INVALID_ARG;
  }

  if (dom::ContentChild* child = dom::ContentChild::GetSingleton()) {
    dom::FileDescOrError fd = NS_OK;
    if (NS_IsMainThread()) {
      child->SendOpenAnonymousTemporaryFile(&fd);
    } else {
      nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
      MOZ_ASSERT(mainThread);
      SyncRunnable::DispatchToThread(mainThread,
        new nsRemoteAnonymousTemporaryFileRunnable(&fd));
    }
    if (fd.type() == dom::FileDescOrError::Tnsresult) {
      nsresult rv = fd.get_nsresult();
      MOZ_ASSERT(NS_FAILED(rv));
      return rv;
    }
    auto rawFD = fd.get_FileDescriptor().ClonePlatformHandle();
    *aOutFileDesc = PR_ImportFile(PROsfd(rawFD.release()));
    return NS_OK;
  }

  nsresult rv;
  nsCOMPtr<nsIFile> tmpFile;
  rv = GetTempDir(getter_AddRefs(tmpFile));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Give the temp file a name with a random element. CreateUnique will also
  // append a counter to the name if it encounters a name collision. Adding
  // a random element to the name reduces the likelihood of a name collision,
  // so that CreateUnique() doesn't end up trying a lot of name variants in
  // its "try appending an incrementing counter" loop, as file IO can be
  // expensive on some mobile flash drives.
  nsAutoCString name("mozilla-temp-");
  name.AppendInt(rand());

  rv = tmpFile->AppendNative(name);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsIFile::DELETE_ON_CLOSE,
                                 PR_IRWXU, aOutFileDesc);

  return rv;
}

#ifdef XP_WIN

// On Windows we have an idle service observer that runs some time after
// startup and deletes any stray anonymous temporary files...

// Duration of idle time before we'll get a callback whereupon we attempt to
// remove any stray and unused anonymous temp files.
#define TEMP_FILE_IDLE_TIME_S 30

// The nsAnonTempFileRemover is created in a timer, which sets an idle observer.
// This is expiration time (in ms) which initial timer is set for (3 minutes).
#define SCHEDULE_TIMEOUT_MS 3 * 60 * 1000

#define XPCOM_SHUTDOWN_TOPIC "xpcom-shutdown"

// This class adds itself as an idle observer. When the application has
// been idle for about 30 seconds we'll get a notification, whereupon we'll
// attempt to delete ${TempDir}/mozilla-temp-files/. This is to ensure all
// temp files that were supposed to be deleted on application exit were actually
// deleted, as they may not be if we previously crashed. See bugs 572579 and
// 785662. This is only needed on some versions of Windows,
// nsIFile::DELETE_ON_CLOSE works on other platforms.
// This class adds itself as a shutdown observer so that it can cancel the
// idle observer and its timer on shutdown. Note: the observer and idle
// services hold references to instances of this object, and those references
// are what keep this object alive.
class nsAnonTempFileRemover final : public nsIObserver
{
public:
  NS_DECL_ISUPPORTS

  nsAnonTempFileRemover() {}

  nsresult Init()
  {
    // We add the idle observer in a timer, so that the app has enough
    // time to start up before we add the idle observer. If we register the
    // idle observer too early, it will be registered before the fake idle
    // service is installed when running in xpcshell, and this interferes with
    // the fake idle service, causing xpcshell-test failures.
    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
    if (NS_WARN_IF(!mTimer)) {
      return NS_ERROR_FAILURE;
    }
    nsresult rv = mTimer->Init(this,
                               SCHEDULE_TIMEOUT_MS,
                               nsITimer::TYPE_ONE_SHOT);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    // Register shutdown observer so we can cancel the timer if we shutdown before
    // the timer runs.
    nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService();
    if (NS_WARN_IF(!obsSrv)) {
      return NS_ERROR_FAILURE;
    }
    return obsSrv->AddObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
  }

  void Cleanup()
  {
    // Cancel timer.
    if (mTimer) {
      mTimer->Cancel();
      mTimer = nullptr;
    }
    // Remove idle service observer.
    nsCOMPtr<nsIIdleService> idleSvc =
      do_GetService("@mozilla.org/widget/idleservice;1");
    if (idleSvc) {
      idleSvc->RemoveIdleObserver(this, TEMP_FILE_IDLE_TIME_S);
    }
    // Remove shutdown observer.
    nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService();
    if (obsSrv) {
      obsSrv->RemoveObserver(this, XPCOM_SHUTDOWN_TOPIC);
    }
  }

  NS_IMETHODIMP Observe(nsISupports* aSubject,
                        const char* aTopic,
                        const char16_t* aData)
  {
    if (nsCRT::strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0 &&
        NS_FAILED(RegisterIdleObserver())) {
      Cleanup();
    } else if (nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0) {
      // The user has been idle for a while, clean up the temp files.
      // The idle service will drop its reference to this object after
      // we exit, destroying this object.
      RemoveAnonTempFileFiles();
      Cleanup();
    } else if (nsCRT::strcmp(aTopic, XPCOM_SHUTDOWN_TOPIC) == 0) {
      Cleanup();
    }
    return NS_OK;
  }

  nsresult RegisterIdleObserver()
  {
    // Add this as an idle observer. When we've been idle for
    // TEMP_FILE_IDLE_TIME_S seconds, we'll get a notification, and we'll then
    // try to delete any stray temp files.
    nsCOMPtr<nsIIdleService> idleSvc =
      do_GetService("@mozilla.org/widget/idleservice;1");
    if (!idleSvc) {
      return NS_ERROR_FAILURE;
    }
    return idleSvc->AddIdleObserver(this, TEMP_FILE_IDLE_TIME_S);
  }

  void RemoveAnonTempFileFiles()
  {
    nsCOMPtr<nsIFile> tmpDir;
    nsresult rv = GetTempDir(getter_AddRefs(tmpDir));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return;
    }

    // Remove the directory recursively.
    tmpDir->Remove(true);
  }

private:
  ~nsAnonTempFileRemover() {}

  nsCOMPtr<nsITimer> mTimer;
};

NS_IMPL_ISUPPORTS(nsAnonTempFileRemover, nsIObserver)

nsresult
CreateAnonTempFileRemover()
{
  // Create a temp file remover. If Init() succeeds, the temp file remover is kept
  // alive by a reference held by the observer service, since the temp file remover
  // is a shutdown observer. We only create the temp file remover if we're running
  // in the main process; there's no point in doing the temp file removal multiple
  // times per startup.
  if (!XRE_IsParentProcess()) {
    return NS_OK;
  }
  RefPtr<nsAnonTempFileRemover> tempRemover = new nsAnonTempFileRemover();
  return tempRemover->Init();
}

#endif