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