browser_backup_recovery.js (9796B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 // This tests are for a sessionstore.js atomic backup. 5 // Each test will wait for a write to the Session Store 6 // before executing. 7 8 const PREF_SS_INTERVAL = "browser.sessionstore.interval"; 9 const Paths = SessionFile.Paths; 10 11 // Global variables that contain sessionstore.jsonlz4 and sessionstore.baklz4 data for 12 // comparison between tests. 13 var gSSData; 14 var gSSBakData; 15 16 function promiseRead(path) { 17 return IOUtils.readUTF8(path, { decompress: true }); 18 } 19 20 async function reInitSessionFile() { 21 await SessionFile.wipe(); 22 await SessionFile.read(); 23 } 24 25 add_setup(async function () { 26 // Make sure that we are not racing with SessionSaver's time based 27 // saves. 28 Services.prefs.setIntPref(PREF_SS_INTERVAL, 10000000); 29 registerCleanupFunction(() => Services.prefs.clearUserPref(PREF_SS_INTERVAL)); 30 }); 31 32 add_task(async function test_creation() { 33 // Cancel all pending session saves so they won't get in our way. 34 SessionSaver.cancel(); 35 36 // Create dummy sessionstore backups 37 let OLD_BACKUP = PathUtils.join(PathUtils.profileDir, "sessionstore.baklz4"); 38 let OLD_UPGRADE_BACKUP = PathUtils.join( 39 PathUtils.profileDir, 40 "sessionstore.baklz4-0000000" 41 ); 42 43 await IOUtils.writeUTF8(OLD_BACKUP, "sessionstore.bak"); 44 await IOUtils.writeUTF8(OLD_UPGRADE_BACKUP, "sessionstore upgrade backup"); 45 46 await reInitSessionFile(); 47 48 // Ensure none of the sessionstore files and backups exists 49 for (let k of Paths.loadOrder) { 50 ok( 51 !(await IOUtils.exists(Paths[k])), 52 "After wipe " + k + " sessionstore file doesn't exist" 53 ); 54 } 55 ok( 56 !(await IOUtils.exists(OLD_BACKUP)), 57 "After wipe, old backup doesn't exist" 58 ); 59 ok( 60 !(await IOUtils.exists(OLD_UPGRADE_BACKUP)), 61 "After wipe, old upgrade backup doesn't exist" 62 ); 63 64 // Open a new tab, save session, ensure that the correct files exist. 65 let URL_BASE = 66 "http://example.com/?atomic_backup_test_creation=" + Math.random(); 67 let URL = URL_BASE + "?first_write"; 68 let tab = BrowserTestUtils.addTab(gBrowser, URL); 69 70 info("Testing situation after a single write"); 71 await promiseBrowserLoaded(tab.linkedBrowser); 72 await TabStateFlusher.flush(tab.linkedBrowser); 73 await SessionSaver.run(); 74 75 ok( 76 await IOUtils.exists(Paths.recovery), 77 "After write, recovery sessionstore file exists again" 78 ); 79 ok( 80 !(await IOUtils.exists(Paths.recoveryBackup)), 81 "After write, recoveryBackup sessionstore doesn't exist" 82 ); 83 ok( 84 (await promiseRead(Paths.recovery)).includes(URL), 85 "Recovery sessionstore file contains the required tab" 86 ); 87 ok( 88 !(await IOUtils.exists(Paths.clean)), 89 "After first write, clean shutdown " + 90 "sessionstore doesn't exist, since we haven't shutdown yet" 91 ); 92 93 // Open a second tab, save session, ensure that the correct files exist. 94 info("Testing situation after a second write"); 95 let URL2 = URL_BASE + "?second_write"; 96 BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, URL2); 97 await promiseBrowserLoaded(tab.linkedBrowser); 98 await TabStateFlusher.flush(tab.linkedBrowser); 99 await SessionSaver.run(); 100 101 ok( 102 await IOUtils.exists(Paths.recovery), 103 "After second write, recovery sessionstore file still exists" 104 ); 105 ok( 106 (await promiseRead(Paths.recovery)).includes(URL2), 107 "Recovery sessionstore file contains the latest url" 108 ); 109 ok( 110 await IOUtils.exists(Paths.recoveryBackup), 111 "After write, recoveryBackup sessionstore now exists" 112 ); 113 let backup = await promiseRead(Paths.recoveryBackup); 114 ok(!backup.includes(URL2), "Recovery backup doesn't contain the latest url"); 115 ok(backup.includes(URL), "Recovery backup contains the original url"); 116 ok( 117 !(await IOUtils.exists(Paths.clean)), 118 "After first write, clean shutdown " + 119 "sessionstore doesn't exist, since we haven't shutdown yet" 120 ); 121 122 info("Reinitialize, ensure that we haven't leaked sensitive files"); 123 await SessionFile.read(); // Reinitializes SessionFile 124 await SessionSaver.run(); 125 ok( 126 !(await IOUtils.exists(Paths.clean)), 127 "After second write, clean shutdown " + 128 "sessionstore doesn't exist, since we haven't shutdown yet" 129 ); 130 Assert.strictEqual( 131 Paths.upgradeBackup, 132 "", 133 "After second write, clean " + 134 "shutdown sessionstore doesn't exist, since we haven't shutdown yet" 135 ); 136 ok( 137 !(await IOUtils.exists(Paths.nextUpgradeBackup)), 138 "After second write, clean " + 139 "shutdown sessionstore doesn't exist, since we haven't shutdown yet" 140 ); 141 142 gBrowser.removeTab(tab); 143 }); 144 145 var promiseSource = async function (name) { 146 let URL = 147 "http://example.com/?atomic_backup_test_recovery=" + 148 Math.random() + 149 "&name=" + 150 name; 151 let tab = BrowserTestUtils.addTab(gBrowser, URL); 152 153 await promiseBrowserLoaded(tab.linkedBrowser); 154 await TabStateFlusher.flush(tab.linkedBrowser); 155 await SessionSaver.run(); 156 gBrowser.removeTab(tab); 157 158 let SOURCE = await promiseRead(Paths.recovery); 159 await SessionFile.wipe(); 160 return SOURCE; 161 }; 162 163 add_task(async function test_recovery() { 164 await reInitSessionFile(); 165 info("Attempting to recover from the recovery file"); 166 167 // Create Paths.recovery, ensure that we can recover from it. 168 let SOURCE = await promiseSource("Paths.recovery"); 169 await IOUtils.makeDirectory(Paths.backups); 170 await IOUtils.writeUTF8(Paths.recovery, SOURCE, { compress: true }); 171 is( 172 (await SessionFile.read()).source, 173 SOURCE, 174 "Recovered the correct source from the recovery file" 175 ); 176 177 info("Corrupting recovery file, attempting to recover from recovery backup"); 178 SOURCE = await promiseSource("Paths.recoveryBackup"); 179 await IOUtils.makeDirectory(Paths.backups); 180 await IOUtils.writeUTF8(Paths.recoveryBackup, SOURCE, { compress: true }); 181 await IOUtils.writeUTF8(Paths.recovery, "<Invalid JSON>", { compress: true }); 182 is( 183 (await SessionFile.read()).source, 184 SOURCE, 185 "Recovered the correct source from the recovery file" 186 ); 187 }); 188 189 add_task(async function test_recovery_inaccessible() { 190 // Can't do chmod() on non-UNIX platforms, we need that for this test. 191 if (AppConstants.platform != "macosx" && AppConstants.platform != "linux") { 192 return; 193 } 194 195 await reInitSessionFile(); 196 info( 197 "Making recovery file inaccessible, attempting to recover from recovery backup" 198 ); 199 let SOURCE_RECOVERY = await promiseSource("Paths.recovery"); 200 let SOURCE = await promiseSource("Paths.recoveryBackup"); 201 await IOUtils.makeDirectory(Paths.backups); 202 await IOUtils.writeUTF8(Paths.recoveryBackup, SOURCE, { compress: true }); 203 204 // Write a valid recovery file but make it inaccessible. 205 await IOUtils.writeUTF8(Paths.recovery, SOURCE_RECOVERY, { compress: true }); 206 await IOUtils.setPermissions(Paths.recovery, 0); 207 208 is( 209 (await SessionFile.read()).source, 210 SOURCE, 211 "Recovered the correct source from the recovery file" 212 ); 213 await IOUtils.setPermissions(Paths.recovery, 0o644); 214 }); 215 216 add_task(async function test_clean() { 217 await reInitSessionFile(); 218 let SOURCE = await promiseSource("Paths.clean"); 219 await IOUtils.writeUTF8(Paths.clean, SOURCE, { compress: true }); 220 await SessionFile.read(); 221 await SessionSaver.run(); 222 is( 223 await promiseRead(Paths.cleanBackup), 224 SOURCE, 225 "After first read/write, " + 226 "clean shutdown file has been moved to cleanBackup" 227 ); 228 }); 229 230 /** 231 * Tests loading of sessionstore when format version is known. 232 */ 233 add_task(async function test_version() { 234 info("Preparing sessionstore"); 235 let SOURCE = await promiseSource("Paths.clean"); 236 237 // Check there's a format version number 238 is( 239 JSON.parse(SOURCE).version[0], 240 "sessionrestore", 241 "Found sessionstore format version" 242 ); 243 244 // Create Paths.clean file 245 await IOUtils.makeDirectory(Paths.backups); 246 await IOUtils.writeUTF8(Paths.clean, SOURCE, { compress: true }); 247 248 info("Attempting to recover from the clean file"); 249 // Ensure that we can recover from Paths.recovery 250 is( 251 (await SessionFile.read()).source, 252 SOURCE, 253 "Recovered the correct source from the clean file" 254 ); 255 }); 256 257 /** 258 * Tests fallback to previous backups if format version is unknown. 259 */ 260 add_task(async function test_version_fallback() { 261 await reInitSessionFile(); 262 info("Preparing data, making sure that it has a version number"); 263 let SOURCE = await promiseSource("Paths.clean"); 264 let BACKUP_SOURCE = await promiseSource("Paths.cleanBackup"); 265 266 is( 267 JSON.parse(SOURCE).version[0], 268 "sessionrestore", 269 "Found sessionstore format version" 270 ); 271 is( 272 JSON.parse(BACKUP_SOURCE).version[0], 273 "sessionrestore", 274 "Found backup sessionstore format version" 275 ); 276 277 await IOUtils.makeDirectory(Paths.backups); 278 279 info( 280 "Modifying format version number to something incorrect, to make sure that we disregard the file." 281 ); 282 let parsedSource = JSON.parse(SOURCE); 283 parsedSource.version[0] = "bookmarks"; 284 await IOUtils.writeJSON(Paths.clean, parsedSource, { compress: true }); 285 await IOUtils.writeUTF8(Paths.cleanBackup, BACKUP_SOURCE, { compress: true }); 286 is( 287 (await SessionFile.read()).source, 288 BACKUP_SOURCE, 289 "Recovered the correct source from the backup recovery file" 290 ); 291 292 info( 293 "Modifying format version number to a future version, to make sure that we disregard the file." 294 ); 295 parsedSource = JSON.parse(SOURCE); 296 parsedSource.version[1] = Number.MAX_SAFE_INTEGER; 297 await IOUtils.writeJSON(Paths.clean, parsedSource, { compress: true }); 298 await IOUtils.writeUTF8(Paths.cleanBackup, BACKUP_SOURCE, { compress: true }); 299 is( 300 (await SessionFile.read()).source, 301 BACKUP_SOURCE, 302 "Recovered the correct source from the backup recovery file" 303 ); 304 }); 305 306 add_task(async function cleanup() { 307 await reInitSessionFile(); 308 });