/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 | | |