adb-binary.js (6614B)
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/. */ 4 5 "use strict"; 6 7 const { dumpn } = require("resource://devtools/shared/DevToolsUtils.js"); 8 9 const lazy = {}; 10 11 ChromeUtils.defineESModuleGetters(lazy, { 12 ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs", 13 FileUtils: "resource://gre/modules/FileUtils.sys.mjs", 14 NetUtil: "resource://gre/modules/NetUtil.sys.mjs", 15 }); 16 loader.lazyGetter(this, "UNPACKED_ROOT_PATH", () => { 17 return PathUtils.join(PathUtils.localProfileDir, "adb"); 18 }); 19 loader.lazyGetter(this, "EXTENSION_ID", () => { 20 return Services.prefs.getCharPref("devtools.remote.adb.extensionID"); 21 }); 22 loader.lazyGetter(this, "ADB_BINARY_PATH", () => { 23 let adbBinaryPath = PathUtils.join(UNPACKED_ROOT_PATH, "adb"); 24 if (Services.appinfo.OS === "WINNT") { 25 adbBinaryPath += ".exe"; 26 } 27 return adbBinaryPath; 28 }); 29 30 const MANIFEST = "manifest.json"; 31 32 /** 33 * Read contents from a given uri in the devtools-adb-extension and parse the 34 * contents as JSON. 35 */ 36 async function readFromExtension(fileUri) { 37 return new Promise(resolve => { 38 lazy.NetUtil.asyncFetch( 39 { 40 uri: fileUri, 41 loadUsingSystemPrincipal: true, 42 }, 43 input => { 44 try { 45 const string = lazy.NetUtil.readInputStreamToString( 46 input, 47 input.available() 48 ); 49 resolve(JSON.parse(string)); 50 } catch (e) { 51 dumpn(`Could not read ${fileUri} in the extension: ${e}`); 52 resolve(null); 53 } 54 } 55 ); 56 }); 57 } 58 59 /** 60 * Unpack file from the extension. 61 * Uses NetUtil to read and write, since it's required for reading. 62 * 63 * @param {string} file 64 * The path name of the file in the extension. 65 */ 66 async function unpackFile(file) { 67 const policy = lazy.ExtensionParent.WebExtensionPolicy.getByID(EXTENSION_ID); 68 if (!policy) { 69 return; 70 } 71 72 // Assumes that destination dir already exists. 73 const basePath = file.substring(file.lastIndexOf("/") + 1); 74 const filePath = PathUtils.join(UNPACKED_ROOT_PATH, basePath); 75 await new Promise((resolve, reject) => { 76 lazy.NetUtil.asyncFetch( 77 { 78 uri: policy.getURL(file), 79 loadUsingSystemPrincipal: true, 80 }, 81 input => { 82 try { 83 // Since we have to use NetUtil to read, probably it's okay to use for 84 // writing, rather than bouncing to IOUtils...? 85 const outputFile = new lazy.FileUtils.File(filePath); 86 const output = lazy.FileUtils.openAtomicFileOutputStream(outputFile); 87 lazy.NetUtil.asyncCopy(input, output, resolve); 88 } catch (e) { 89 dumpn(`Could not unpack file ${file} in the extension: ${e}`); 90 reject(e); 91 } 92 } 93 ); 94 }); 95 // Mark binaries as executable. 96 await IOUtils.setPermissions(filePath, 0o744); 97 } 98 99 /** 100 * Extract files in the extension into local profile directory and returns 101 * if it fails. 102 */ 103 async function extractFiles() { 104 const policy = lazy.ExtensionParent.WebExtensionPolicy.getByID(EXTENSION_ID); 105 if (!policy) { 106 return false; 107 } 108 const uri = policy.getURL("adb.json"); 109 const adbInfo = await readFromExtension(uri); 110 if (!adbInfo) { 111 return false; 112 } 113 114 let filesForAdb; 115 try { 116 // The adbInfo is an object looks like this; 117 // 118 // { 119 // "Linux": { 120 // "x86": [ 121 // "linux/adb" 122 // ], 123 // "x86_64": [ 124 // "linux64/adb" 125 // ] 126 // }, 127 // ... 128 129 // XPCOMABI looks this; x86_64-gcc3, so drop the compiler name. 130 let architecture = Services.appinfo.XPCOMABI.split("-")[0]; 131 if (architecture === "aarch64") { 132 // Fallback on x86 or x86_64 binaries for aarch64 - See Bug 1522149 133 const hasX86Binary = !!adbInfo[Services.appinfo.OS].x86; 134 architecture = hasX86Binary ? "x86" : "x86_64"; 135 } 136 filesForAdb = adbInfo[Services.appinfo.OS][architecture]; 137 } catch (e) { 138 return false; 139 } 140 141 // manifest.json isn't in adb.json but has to be unpacked for version 142 // comparison 143 filesForAdb.push(MANIFEST); 144 145 await IOUtils.makeDirectory(UNPACKED_ROOT_PATH); 146 147 for (const file of filesForAdb) { 148 try { 149 await unpackFile(file); 150 } catch (e) { 151 return false; 152 } 153 } 154 155 return true; 156 } 157 158 /** 159 * Read the manifest from inside the devtools-adb-extension. 160 * Uses NetUtil since data is packed inside the extension, not a local file. 161 */ 162 async function getManifestFromExtension() { 163 const policy = lazy.ExtensionParent.WebExtensionPolicy.getByID(EXTENSION_ID); 164 if (!policy) { 165 return null; 166 } 167 168 const manifestUri = policy.getURL(MANIFEST); 169 return readFromExtension(manifestUri); 170 } 171 172 /** 173 * Returns whether manifest.json has already been unpacked. 174 */ 175 async function isManifestUnpacked() { 176 const manifestPath = PathUtils.join(UNPACKED_ROOT_PATH, MANIFEST); 177 return IOUtils.exists(manifestPath); 178 } 179 180 /** 181 * Read the manifest from the unpacked binary directory. 182 * Uses IOUtils since this is a local file. 183 */ 184 async function getManifestFromUnpacked() { 185 if (!(await isManifestUnpacked())) { 186 throw new Error("Manifest doesn't exist at unpacked path"); 187 } 188 189 const manifestPath = PathUtils.join(UNPACKED_ROOT_PATH, MANIFEST); 190 const binary = await IOUtils.read(manifestPath); 191 const json = new TextDecoder().decode(binary); 192 let data; 193 try { 194 data = JSON.parse(json); 195 } catch (e) {} 196 return data; 197 } 198 199 /** 200 * Check state of binary unpacking, including the location and manifest. 201 */ 202 async function isUnpacked() { 203 if (!(await isManifestUnpacked())) { 204 dumpn("Needs unpacking, no manifest found"); 205 return false; 206 } 207 208 const manifestInExtension = await getManifestFromExtension(); 209 const unpackedManifest = await getManifestFromUnpacked(); 210 if (manifestInExtension.version != unpackedManifest.version) { 211 dumpn( 212 `Needs unpacking, extension version ${manifestInExtension.version} != ` + 213 `unpacked version ${unpackedManifest.version}` 214 ); 215 return false; 216 } 217 dumpn("Already unpacked"); 218 return true; 219 } 220 221 /** 222 * Get a file object for the adb binary from the 'adb@mozilla.org' extension 223 * which has been already installed. 224 * 225 * @return {nsIFile} 226 * File object for the binary. 227 */ 228 async function getFileForBinary() { 229 if (!(await isUnpacked()) && !(await extractFiles())) { 230 return null; 231 } 232 233 const file = new lazy.FileUtils.File(ADB_BINARY_PATH); 234 if (!file.exists()) { 235 return null; 236 } 237 return file; 238 } 239 240 exports.getFileForBinary = getFileForBinary;