sbMetadataCrashTracker.cpp
Go to the documentation of this file.
1 /* vim: set sw=2 :miv */
2 /*
3  *=BEGIN SONGBIRD GPL
4  *
5  * This file is part of the Songbird web player.
6  *
7  * Copyright(c) 2005-2010 POTI, Inc.
8  * http://www.songbirdnest.com
9  *
10  * This file may be licensed under the terms of of the
11  * GNU General Public License Version 2 (the ``GPL'').
12  *
13  * Software distributed under the License is distributed
14  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
15  * express or implied. See the GPL for the specific language
16  * governing rights and limitations.
17  *
18  * You should have received a copy of the GPL along with this
19  * program. If not, go to http://www.gnu.org/licenses/gpl.html
20  * or write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  *
23  *=END SONGBIRD GPL
24  */
25 
31 // INCLUDES ===================================================================
32 
33 // TODO clean up
34 #include <nspr.h>
35 #include <nscore.h>
36 #include <nsAutoLock.h>
37 #include <nsServiceManagerUtils.h>
38 #include <nsCRTGlue.h>
39 #include <nsIFile.h>
40 #include <nsIFileURL.h>
41 #include <nsAutoPtr.h>
42 #include <nsThreadUtils.h>
43 #include <nsNetUtil.h>
44 #include <nsIURI.h>
45 #include <nsIIOService.h>
46 #include <nsIPrefService.h>
47 #include <nsIPrefBranch2.h>
48 #include <nsIInputStream.h>
49 #include <nsILineInputStream.h>
50 
51 #include "sbMetadataCrashTracker.h"
52 
53 // DEFINES ====================================================================
54 
55 // Initial hashtable capacity for mURLToIndexMap
56 #define DEFAULT_MAP_SIZE 20
57 
58 #include "prlog.h"
59 #ifdef PR_LOGGING
60 extern PRLogModuleInfo* gMetadataLog;
61 #define TRACE(args) PR_LOG(gMetadataLog, PR_LOG_DEBUG, args)
62 #define LOG(args) PR_LOG(gMetadataLog, PR_LOG_WARN, args)
63 #else
64 #define TRACE(args) /* nothing */
65 #define LOG(args) /* nothing */
66 #endif
67 
68 // CLASSES ====================================================================
69 
71 
73  mBlacklistFile(nsnull),
74  mCounter(0),
75  mLogFile(nsnull),
76  mOutputStream(nsnull),
77  mLock(nsnull)
78 {
79  TRACE(("sbMetadataCrashTracker[0x%.8x] - ctor", this));
80  MOZ_COUNT_CTOR(sbMetadataCrashTracker);
81 }
82 
83 
85 {
86  TRACE(("sbMetadataCrashTracker[0x%.8x] - dtor", this));
87  MOZ_COUNT_DTOR(sbMetadataCrashTracker);
88  ResetLog();
89  if (mLock) {
90  nsAutoLock::DestroyLock(mLock);
91  }
92 }
93 
94 
96 {
97  NS_ASSERTION(NS_IsMainThread(),
98  "sbMetadataCrashTracker::Init: MUST NOT INIT OFF OF MAIN THREAD!");
99  if (mLock) {
100  return NS_ERROR_ALREADY_INITIALIZED;
101  }
102  nsresult rv = NS_OK;
103 
104  mLock = nsAutoLock::NewLock(
105  "sbMetadataCrashTracker file lock");
106  NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
107 
108  // Set up a map to track file URLs while logging
109  PRBool success = mURLToIndexMap.Init(DEFAULT_MAP_SIZE);
110  NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
111 
112  // Set up the list of scary crashy URLs
113  success = mURLBlacklist.Init(DEFAULT_MAP_SIZE);
114  NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
115 
116  rv = GetProfileFile(NS_LITERAL_STRING("metadata-url-io.blacklist"),
117  getter_AddRefs(mBlacklistFile));
118  NS_ENSURE_SUCCESS(rv, rv);
119 
120  // Load the existing URL blacklist if we have one
121  rv = ReadBlacklist();
122  if (NS_FAILED(rv)) {
123  NS_ERROR("sbMetadataCrashTracker::Init failed to load blacklist!");
124  }
125 
126  // Get the file we will use for logging
127  nsAutoLock lock(mLock);
128 
129  rv = GetProfileFile(NS_LITERAL_STRING("metadata-io.log"),
130  getter_AddRefs(mLogFile));
131  NS_ENSURE_SUCCESS(rv, rv);
132 
133  // See if we crashed during the last run, and add to
134  // the blacklist if needed
135  rv = ProcessExistingLog();
136  if (NS_FAILED(rv)) {
137  NS_ERROR("sbMetadataCrashTracker::Init failed to process existing log!");
138  }
139 
140  // Allow the user to set a file name in a pref that, if encountered,
141  // will result in an immediate crash
142  nsCOMPtr<nsIPrefBranch> prefService =
143  do_GetService("@mozilla.org/preferences-service;1", &rv);
144  NS_ENSURE_SUCCESS( rv, rv);
145  // Get the value. We don't care if this fails.
146  prefService->GetCharPref("songbird.metadata.simulate.crash.url",
147  getter_Copies(mSimulateCrashURL));
148  return NS_OK;
149 }
150 
151 
153 {
154  NS_ENSURE_STATE(mLogFile);
155  if (mOutputStream) {
156  ResetLog();
157  }
158  nsresult rv = NS_OK;
159 
160  // Open a new log file
161  nsAutoLock lock(mLock);
162 
163  nsCOMPtr<nsIFileOutputStream> fileStream =
164  do_CreateInstance("@mozilla.org/network/file-output-stream;1", &rv);
165  NS_ENSURE_SUCCESS(rv, rv);
166 
167  PRInt32 ioFlags = PR_TRUNCATE | PR_CREATE_FILE | PR_WRONLY;
168  rv = fileStream->Init(mLogFile, ioFlags, -1, 0);
169  NS_ENSURE_SUCCESS(rv, rv);
170 
171  mOutputStream = do_QueryInterface(fileStream, &rv);
172  NS_ENSURE_SUCCESS(rv, rv);
173 
174  return rv;
175 }
176 
177 
179 {
180  nsresult rv = NS_OK;
181 
182  nsAutoLock lock(mLock);
183 
184  if (mOutputStream) {
185  mOutputStream->Close();
186  mOutputStream = nsnull;
187  mLogFile->Remove(PR_FALSE);
188  }
189 
190  mURLToIndexMap.Clear();
191 
192  return rv;
193 }
194 
195 
196 nsresult
197 sbMetadataCrashTracker::LogURLBegin(const nsACString& aURL)
198 {
199  nsresult rv = NS_OK;
200 
201  // Make sure we have a log
202  if (!mOutputStream) {
203  rv = StartLog();
204  NS_ENSURE_SUCCESS(rv, rv);
205  }
206 
207  nsAutoLock lock(mLock);
208 
209  // Rather than log the URL for both begin and end we
210  // assign each URL a number.
211  // This cuts the log file size in half.
212  PRUint32 index = mCounter++;
213  mURLToIndexMap.Put(aURL, index);
214 
215  // Record that we are Beginning this URL, and save the
216  // index so that we can match up the End record.
217  nsCString output("B");
218  output.AppendInt(index);
219  output.Append(" ");
220  output.Append(aURL);
221  output.Append("\n");
222 
223  PRUint32 bytesWritten;
224  rv = mOutputStream->Write(output.BeginReading(),
225  output.Length(),
226  &bytesWritten);
227  NS_ENSURE_SUCCESS(rv, rv);
228 
229  // For testing it is convenient to be able to force a crash.
230  // Check to see if this URL is supposed to crash us.
231  if (!mSimulateCrashURL.IsEmpty()) {
232  if (output.Find(mSimulateCrashURL, PR_TRUE) != -1) {
233  LOG(("LogURLBegin forcing a crash for %s", output.BeginReading()));
234  PRBool* crash;
235  crash = nsnull;
236  *crash = PR_TRUE;
237  }
238  }
239 
240  return rv;
241 }
242 
243 
244 nsresult
245 sbMetadataCrashTracker::LogURLEnd(const nsACString& aURL)
246 {
247  NS_ENSURE_STATE(mOutputStream);
248  nsresult rv = NS_OK;
249 
250  nsAutoLock lock(mLock);
251 
252  // Look up the index of this URL
253  PRUint32 index = 0;
254  PRBool success = mURLToIndexMap.Get(aURL, &index);
255  NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
256  mURLToIndexMap.Remove(aURL);
257 
258  // Write an End record
259  nsCString output("E");
260  output.AppendInt(index);
261  output.Append("\n");
262 
263  PRUint32 bytesWritten;
264  rv = mOutputStream->Write(output.BeginReading(),
265  output.Length(),
266  &bytesWritten);
267  NS_ENSURE_SUCCESS(rv, rv);
268 
269  return rv;
270 }
271 
272 
273 nsresult
275  PRBool* aIsBlackListed)
276 {
277  // Look up the URL in the hash table.
278  // No need to lock, since we only update mURLBlacklist on Init.
279  *aIsBlackListed = mURLBlacklist.Get(aURL, nsnull);
280  return NS_OK;
281 }
282 
283 nsresult
285 {
291  NS_ASSERTION(NS_IsMainThread(),
292  "sbMetadataCrashTracker::AddBlacklistURL"
293  " Unexpectedly called off main thread");
294  mURLBlacklist.Put(aURL, PR_TRUE);
295  return NS_OK;
296 }
297 
298 
299 // -- Private --
300 
301 nsresult
302 sbMetadataCrashTracker::ProcessExistingLog()
303 {
304  NS_ENSURE_STATE(mLogFile);
305  nsresult rv = NS_OK;
306 
307  // Did we crash on last run?
308  PRBool exists = PR_FALSE;
309  rv = mLogFile->Exists(&exists);
310  NS_ENSURE_SUCCESS(rv, rv);
311 
312  // No file, no crash
313  if (!exists) {
314  return NS_OK;
315  }
316 
317  // Read the log file and find any URLs that didn't complete.
318  nsCOMPtr<nsIInputStream> inputStream;
319  rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mLogFile);
320  NS_ENSURE_SUCCESS(rv, rv);
321  nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(inputStream, &rv));
322  NS_ENSURE_SUCCESS(rv, rv);
323 
324  nsDataHashtable<nsCStringHashKey, nsCString> indexToURLMap;
325  indexToURLMap.Init(DEFAULT_MAP_SIZE);
326 
327  PRBool more = PR_TRUE;
328  nsCString line;
329  nsCString url;
330  PRBool hashSuccess = PR_FALSE;
331  do {
332  rv = lineStream->ReadLine(line, &more);
333  if (NS_SUCCEEDED(rv) && line.Length() >= 2) {
334  switch (line.First()) {
335  // Handle Begin records
336  case 'B': {
337  PRInt32 separatorIndex = line.FindChar(' ', 1);
338  if (separatorIndex > 0 && separatorIndex < (PRInt32)(line.Length() - 1)) {
339  // Get the URL
340  url = Substring(line, separatorIndex + 1);
341  // Get the index
342  line = Substring(line, 1, separatorIndex - 1);
343  // Save in the hash, so that we can look for a matching End record
344  indexToURLMap.Put(line, url);
345 
346  TRACE(("sbMetadataCrashTracker::ProcessExistingLog " \
347  "- found Begin record '%s', '%s'",
348  line.BeginReading(), url.BeginReading()));
349  } else {
350  NS_ERROR("Found Begin record without URL");
351  }
352  break;
353  }
354 
355  // Handle End records
356  case 'E': {
357  // Chop off the E. Rest is the index.
358  line.Cut(0,1);
359 
360  // Remove the index from the map.
361  // This URL was completed correctly.
362  hashSuccess = indexToURLMap.Get(line, nsnull);
363  if (hashSuccess) {
364  TRACE(("sbMetadataCrashTracker::ProcessExistingLog " \
365  "- found End record '%s'", line.BeginReading()));
366  indexToURLMap.Remove(line);
367  } else {
368  NS_ERROR("Found End record without matching Begin");
369  }
370  break;
371  }
372 
373  default:
374  NS_ERROR("Invalid sbMetadataCrashTracker log file");
375  }
376  }
377  } while (NS_SUCCEEDED(rv) && more);
378  inputStream->Close();
379 
380  // Anything left over in the map must have been running during
381  // the crash. Add to the blacklist!
382  if (indexToURLMap.Count() > 0) {
383  indexToURLMap.EnumerateRead(AddURLsToBlacklist, &mURLBlacklist);
384  rv = WriteBlacklist();
385  NS_ENSURE_SUCCESS(rv, rv);
386  TRACE(("sbMetadataCrashTracker::ProcessExistingLog - " \
387  " found %d suspect URLs", indexToURLMap.Count()));
388  } else {
389  TRACE(("sbMetadataCrashTracker::ProcessExistingLog - no suspect URLs"));
390  }
391 
392  // Ok, we've processed the log file, so throw it away.
393  mLogFile->Remove(PR_FALSE);
394 
395  return rv;
396 }
397 
398 /* static */ PLDHashOperator PR_CALLBACK
399 sbMetadataCrashTracker::AddURLsToBlacklist(nsCStringHashKey::KeyType aKey,
400  nsCString aEntry,
401  void* aUserData)
402 {
403  if (aEntry.IsEmpty()) {
404  NS_ERROR("Attempted to add an empty string to the blacklist");
405  return PL_DHASH_NEXT;
406  }
407  nsDataHashtable<nsCStringHashKey, PRBool>* blacklistHash =
408  static_cast<nsDataHashtable<nsCStringHashKey, PRBool>* >(aUserData);
409  NS_ENSURE_TRUE(blacklistHash, PL_DHASH_STOP);
410 
411  blacklistHash->Put(aEntry, PR_TRUE);
412  return PL_DHASH_NEXT;
413 }
414 
415 /* static */ PLDHashOperator PR_CALLBACK
416 sbMetadataCrashTracker::WriteBlacklistURLToFile(nsCStringHashKey::KeyType aKey,
417  PRBool aEntry,
418  void* aUserData)
419 {
420  nsresult rv = NS_OK;
421  if (aKey.IsEmpty()) {
422  NS_ERROR("Attempted to add an empty string to the blacklist file");
423  return PL_DHASH_NEXT;
424  }
425  nsIOutputStream* outputStream =
426  static_cast<nsIOutputStream* >(aUserData);
427  NS_ENSURE_TRUE(outputStream, PL_DHASH_STOP);
428 
429  nsCString output(aKey);
430  output.AppendLiteral("\n");
431 
432  PRUint32 bytesWritten;
433  rv = outputStream->Write(output.BeginReading(),
434  output.Length(),
435  &bytesWritten);
436  NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP);
437 
438  return PL_DHASH_NEXT;
439 }
440 
441 nsresult
442 sbMetadataCrashTracker::ReadBlacklist()
443 {
444  NS_ENSURE_STATE(mBlacklistFile);
445  nsresult rv = NS_OK;
446 
447  // If no blacklist, don't bother reading
448  PRBool exists = PR_FALSE;
449  rv = mBlacklistFile->Exists(&exists);
450  NS_ENSURE_SUCCESS(rv, rv);
451  if (!exists) {
452  return NS_OK;
453  }
454 
455  // Open the blacklist
456  nsCOMPtr<nsIInputStream> inputStream;
457  rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mBlacklistFile);
458  NS_ENSURE_SUCCESS(rv, rv);
459  nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(inputStream, &rv));
460  NS_ENSURE_SUCCESS(rv, rv);
461 
462  PRBool more = PR_TRUE;
463  nsCString line;
464 
465  // Skip the first line, as it should be a text description
466  rv = lineStream->ReadLine(line, &more);
467  NS_ENSURE_SUCCESS(rv, rv);
468  NS_ENSURE_TRUE(more, NS_ERROR_FAILURE);
469  NS_ENSURE_TRUE(line.First() == '#', NS_ERROR_UNEXPECTED);
470 
471  // Read the URLs from the blacklist file into mURLBlacklist
472  do {
473  rv = lineStream->ReadLine(line, &more);
474  if (NS_SUCCEEDED(rv) && !line.IsEmpty()) {
475  PRBool blacklisted = PR_TRUE;
476  mURLBlacklist.Put(line, blacklisted);
477  LOG(("sbMetadataCrashTracker::ReadBlacklist() - found %s",
478  line.BeginReading()));
479  }
480  } while (NS_SUCCEEDED(rv) && more);
481 
482  inputStream->Close();
483 
484  return rv;
485 }
486 
487 nsresult
488 sbMetadataCrashTracker::WriteBlacklist()
489 {
490  NS_ENSURE_STATE(mBlacklistFile);
491  nsresult rv = NS_OK;
492 
493  // Open/Create file
494  nsCOMPtr<nsIFileOutputStream> fileStream =
495  do_CreateInstance("@mozilla.org/network/file-output-stream;1", &rv);
496  NS_ENSURE_SUCCESS(rv, rv);
497 
498  PRInt32 ioFlags = PR_TRUNCATE | PR_CREATE_FILE | PR_WRONLY;
499  rv = fileStream->Init(mBlacklistFile, ioFlags, -1, 0);
500  NS_ENSURE_SUCCESS(rv, rv);
501 
502  nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(fileStream, &rv);
503  NS_ENSURE_SUCCESS(rv, rv);
504 
505  // Add an explanatory message on the first line
506  nsCString output("# URLs listed in this file are suspected of " \
507  "crashing Songbird, and will be ignored.\n");
508 
509  PRUint32 bytesWritten;
510  rv = outputStream->Write(output.BeginReading(),
511  output.Length(),
512  &bytesWritten);
513  NS_ENSURE_SUCCESS(rv, rv);
514 
515  // Now add all blacklisted URLs, one per line.
516  mURLBlacklist.EnumerateRead(WriteBlacklistURLToFile, outputStream);
517 
518  outputStream->Close();
519  return rv;
520 }
521 
522 nsresult
523 sbMetadataCrashTracker::GetProfileFile(const nsAString& aName, nsIFile** aFile)
524 {
525  NS_ENSURE_ARG_POINTER(aFile);
526  nsresult rv = NS_OK;
527 
528  // Get the directory service
529  nsCOMPtr<nsIProperties> directoryService(
530  do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
531  NS_ENSURE_SUCCESS(rv, rv);
532 
533  // Get the user's profile directory
534  nsCOMPtr<nsIFile> file;
535  rv = directoryService->Get("ProfD", NS_GET_IID(nsIFile),
536  getter_AddRefs(file));
537  NS_ENSURE_SUCCESS(rv, rv);
538 
539  // Add the requested filename
540  rv = file->Append(aName);
541  NS_ENSURE_SUCCESS(rv, rv);
542 
543  file.forget(aFile);
544  return NS_OK;
545 }
NS_IMPL_THREADSAFE_ISUPPORTS0(sbMetadataCrashTracker)
#define DEFAULT_MAP_SIZE
return NS_OK
#define LOG(args)
var PR_CREATE_FILE
nsresult IsURLBlacklisted(const nsACString &aURL, PRBool *aIsBlackListed)
nsresult LogURLEnd(const nsACString &aURL)
nsresult AddBlacklistURL(const nsACString &aURL)
#define TRACE(args)
Logs file access and maintain a blacklist of crash-causing files.
var PR_TRUNCATE
_updateCookies aName
function url(spec)
var PR_WRONLY
nsresult LogURLBegin(const nsACString &aURL)
NS_DECL_ISUPPORTS sbMetadataCrashTracker()
var file