services/healthreport/profile.jsm

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 #ifndef MERGED_COMPARTMENT
     9 this.EXPORTED_SYMBOLS = [
    10   "ProfileCreationTimeAccessor",
    11   "ProfileMetadataProvider",
    12 ];
    14 const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
    16 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
    18 Cu.import("resource://gre/modules/Metrics.jsm");
    20 #endif
    22 const DEFAULT_PROFILE_MEASUREMENT_NAME = "age";
    23 const REQUIRED_UINT32_TYPE = {type: "TYPE_UINT32"};
    25 Cu.import("resource://gre/modules/Promise.jsm");
    26 Cu.import("resource://gre/modules/osfile.jsm")
    27 Cu.import("resource://gre/modules/Task.jsm");
    28 Cu.import("resource://gre/modules/Log.jsm");
    29 Cu.import("resource://services-common/utils.js");
    31 // Profile creation time access.
    32 // This is separate from the provider to simplify testing and enable extraction
    33 // to a shared location in the future.
    34 this.ProfileCreationTimeAccessor = function(profile, log) {
    35   this.profilePath = profile || OS.Constants.Path.profileDir;
    36   if (!this.profilePath) {
    37     throw new Error("No profile directory.");
    38   }
    39   this._log = log || {"debug": function (s) { dump(s + "\n"); }};
    40 }
    41 this.ProfileCreationTimeAccessor.prototype = {
    42   /**
    43    * There are three ways we can get our creation time:
    44    *
    45    * 1. From our own saved value (to avoid redundant work).
    46    * 2. From the on-disk JSON file.
    47    * 3. By calculating it from the filesystem.
    48    *
    49    * If we have to calculate, we write out the file; if we have
    50    * to touch the file, we persist in-memory.
    51    *
    52    * @return a promise that resolves to the profile's creation time.
    53    */
    54   get created() {
    55     if (this._created) {
    56       return Promise.resolve(this._created);
    57     }
    59     function onSuccess(times) {
    60       if (times && times.created) {
    61         return this._created = times.created;
    62       }
    63       return onFailure.call(this, null, times);
    64     }
    66     function onFailure(err, times) {
    67       return this.computeAndPersistTimes(times)
    68                  .then(function onSuccess(created) {
    69                          return this._created = created;
    70                        }.bind(this));
    71     }
    73     return this.readTimes()
    74                .then(onSuccess.bind(this),
    75                      onFailure.bind(this));
    76   },
    78   /**
    79    * Explicitly make `file`, a filename, a full path
    80    * relative to our profile path.
    81    */
    82   getPath: function (file) {
    83     return OS.Path.join(this.profilePath, file);
    84   },
    86   /**
    87    * Return a promise which resolves to the JSON contents
    88    * of the time file in this accessor's profile.
    89    */
    90   readTimes: function (file="times.json") {
    91     return CommonUtils.readJSON(this.getPath(file));
    92   },
    94   /**
    95    * Return a promise representing the writing of `contents`
    96    * to `file` in the specified profile.
    97    */
    98   writeTimes: function (contents, file="times.json") {
    99     return CommonUtils.writeJSON(contents, this.getPath(file));
   100   },
   102   /**
   103    * Merge existing contents with a 'created' field, writing them
   104    * to the specified file. Promise, naturally.
   105    */
   106   computeAndPersistTimes: function (existingContents, file="times.json") {
   107     let path = this.getPath(file);
   108     function onOldest(oldest) {
   109       let contents = existingContents || {};
   110       contents.created = oldest;
   111       return this.writeTimes(contents, path)
   112                  .then(function onSuccess() {
   113                    return oldest;
   114                  });
   115     }
   117     return this.getOldestProfileTimestamp()
   118                .then(onOldest.bind(this));
   119   },
   121   /**
   122    * Traverse the contents of the profile directory, finding the oldest file
   123    * and returning its creation timestamp.
   124    */
   125   getOldestProfileTimestamp: function () {
   126     let self = this;
   127     let oldest = Date.now() + 1000;
   128     let iterator = new OS.File.DirectoryIterator(this.profilePath);
   129     self._log.debug("Iterating over profile " + this.profilePath);
   130     if (!iterator) {
   131       throw new Error("Unable to fetch oldest profile entry: no profile iterator.");
   132     }
   134     function onEntry(entry) {
   135       function onStatSuccess(info) {
   136         // OS.File doesn't seem to be behaving. See Bug 827148.
   137         // Let's do the best we can. This whole function is defensive.
   138         let date = info.winBirthDate || info.macBirthDate;
   139         if (!date || !date.getTime()) {
   140           // OS.File will only return file creation times of any kind on Mac
   141           // and Windows, where birthTime is defined.
   142           // That means we're unable to function on Linux, so we use mtime
   143           // instead.
   144           self._log.debug("No birth date. Using mtime.");
   145           date = info.lastModificationDate;
   146         }
   148         if (date) {
   149           let timestamp = date.getTime();
   150           self._log.debug("Using date: " + entry.path + " = " + date);
   151           if (timestamp < oldest) {
   152             oldest = timestamp;
   153           }
   154         }
   155       }
   157       function onStatFailure(e) {
   158         // Never mind.
   159         self._log.debug("Stat failure: " + CommonUtils.exceptionStr(e));
   160       }
   162       return OS.File.stat(entry.path)
   163                     .then(onStatSuccess, onStatFailure);
   164     }
   166     let promise = iterator.forEach(onEntry);
   168     function onSuccess() {
   169       iterator.close();
   170       return oldest;
   171     }
   173     function onFailure(reason) {
   174       iterator.close();
   175       throw new Error("Unable to fetch oldest profile entry: " + reason);
   176     }
   178     return promise.then(onSuccess, onFailure);
   179   },
   180 }
   182 /**
   183  * Measurements pertaining to the user's profile.
   184  */
   185 function ProfileMetadataMeasurement() {
   186   Metrics.Measurement.call(this);
   187 }
   188 ProfileMetadataMeasurement.prototype = {
   189   __proto__: Metrics.Measurement.prototype,
   191   name: DEFAULT_PROFILE_MEASUREMENT_NAME,
   192   version: 1,
   194   fields: {
   195     // Profile creation date. Number of days since Unix epoch.
   196     profileCreation: {type: Metrics.Storage.FIELD_LAST_NUMERIC},
   197   },
   198 };
   200 /**
   201  * Turn a millisecond timestamp into a day timestamp.
   202  *
   203  * @param msec a number of milliseconds since epoch.
   204  * @return the number of whole days denoted by the input.
   205  */
   206 function truncate(msec) {
   207   return Math.floor(msec / MILLISECONDS_PER_DAY);
   208 }
   210 /**
   211  * A Metrics.Provider for profile metadata, such as profile creation time.
   212  */
   213 this.ProfileMetadataProvider = function() {
   214   Metrics.Provider.call(this);
   215 }
   216 this.ProfileMetadataProvider.prototype = {
   217   __proto__: Metrics.Provider.prototype,
   219   name: "org.mozilla.profile",
   221   measurementTypes: [ProfileMetadataMeasurement],
   223   pullOnly: true,
   225   getProfileCreationDays: function () {
   226     let accessor = new ProfileCreationTimeAccessor(null, this._log);
   228     return accessor.created
   229                    .then(truncate);
   230   },
   232   collectConstantData: function () {
   233     let m = this.getMeasurement(DEFAULT_PROFILE_MEASUREMENT_NAME, 1);
   235     return Task.spawn(function collectConstant() {
   236       let createdDays = yield this.getProfileCreationDays();
   238       yield this.enqueueStorageOperation(function storeDays() {
   239         return m.setLastNumeric("profileCreation", createdDays);
   240       });
   241     }.bind(this));
   242   },
   243 };

mercurial