Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | /** |
michael@0 | 6 | * Synchronous front-end for the JavaScript OS.File library. |
michael@0 | 7 | * Unix implementation. |
michael@0 | 8 | * |
michael@0 | 9 | * This front-end is meant to be imported by a worker thread. |
michael@0 | 10 | */ |
michael@0 | 11 | |
michael@0 | 12 | { |
michael@0 | 13 | if (typeof Components != "undefined") { |
michael@0 | 14 | // We do not wish osfile_unix_front.jsm to be used directly as a main thread |
michael@0 | 15 | // module yet. |
michael@0 | 16 | |
michael@0 | 17 | throw new Error("osfile_unix_front.jsm cannot be used from the main thread yet"); |
michael@0 | 18 | } |
michael@0 | 19 | (function(exports) { |
michael@0 | 20 | "use strict"; |
michael@0 | 21 | |
michael@0 | 22 | // exports.OS.Unix is created by osfile_unix_back.jsm |
michael@0 | 23 | if (exports.OS && exports.OS.File) { |
michael@0 | 24 | return; // Avoid double-initialization |
michael@0 | 25 | } |
michael@0 | 26 | |
michael@0 | 27 | let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); |
michael@0 | 28 | let Path = require("resource://gre/modules/osfile/ospath.jsm"); |
michael@0 | 29 | let SysAll = require("resource://gre/modules/osfile/osfile_unix_allthreads.jsm"); |
michael@0 | 30 | exports.OS.Unix.File._init(); |
michael@0 | 31 | let LOG = SharedAll.LOG.bind(SharedAll, "Unix front-end"); |
michael@0 | 32 | let Const = SharedAll.Constants.libc; |
michael@0 | 33 | let UnixFile = exports.OS.Unix.File; |
michael@0 | 34 | let Type = UnixFile.Type; |
michael@0 | 35 | |
michael@0 | 36 | /** |
michael@0 | 37 | * Representation of a file. |
michael@0 | 38 | * |
michael@0 | 39 | * You generally do not need to call this constructor yourself. Rather, |
michael@0 | 40 | * to open a file, use function |OS.File.open|. |
michael@0 | 41 | * |
michael@0 | 42 | * @param fd A OS-specific file descriptor. |
michael@0 | 43 | * @param {string} path File path of the file handle, used for error-reporting. |
michael@0 | 44 | * @constructor |
michael@0 | 45 | */ |
michael@0 | 46 | let File = function File(fd, path) { |
michael@0 | 47 | exports.OS.Shared.AbstractFile.call(this, fd, path); |
michael@0 | 48 | this._closeResult = null; |
michael@0 | 49 | }; |
michael@0 | 50 | File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype); |
michael@0 | 51 | |
michael@0 | 52 | /** |
michael@0 | 53 | * Close the file. |
michael@0 | 54 | * |
michael@0 | 55 | * This method has no effect if the file is already closed. However, |
michael@0 | 56 | * if the first call to |close| has thrown an error, further calls |
michael@0 | 57 | * will throw the same error. |
michael@0 | 58 | * |
michael@0 | 59 | * @throws File.Error If closing the file revealed an error that could |
michael@0 | 60 | * not be reported earlier. |
michael@0 | 61 | */ |
michael@0 | 62 | File.prototype.close = function close() { |
michael@0 | 63 | if (this._fd) { |
michael@0 | 64 | let fd = this._fd; |
michael@0 | 65 | this._fd = null; |
michael@0 | 66 | // Call |close(fd)|, detach finalizer if any |
michael@0 | 67 | // (|fd| may not be a CDataFinalizer if it has been |
michael@0 | 68 | // instantiated from a controller thread). |
michael@0 | 69 | let result = UnixFile._close(fd); |
michael@0 | 70 | if (typeof fd == "object" && "forget" in fd) { |
michael@0 | 71 | fd.forget(); |
michael@0 | 72 | } |
michael@0 | 73 | if (result == -1) { |
michael@0 | 74 | this._closeResult = new File.Error("close", ctypes.errno, this._path); |
michael@0 | 75 | } |
michael@0 | 76 | } |
michael@0 | 77 | if (this._closeResult) { |
michael@0 | 78 | throw this._closeResult; |
michael@0 | 79 | } |
michael@0 | 80 | return; |
michael@0 | 81 | }; |
michael@0 | 82 | |
michael@0 | 83 | /** |
michael@0 | 84 | * Read some bytes from a file. |
michael@0 | 85 | * |
michael@0 | 86 | * @param {C pointer} buffer A buffer for holding the data |
michael@0 | 87 | * once it is read. |
michael@0 | 88 | * @param {number} nbytes The number of bytes to read. It must not |
michael@0 | 89 | * exceed the size of |buffer| in bytes but it may exceed the number |
michael@0 | 90 | * of bytes unread in the file. |
michael@0 | 91 | * @param {*=} options Additional options for reading. Ignored in |
michael@0 | 92 | * this implementation. |
michael@0 | 93 | * |
michael@0 | 94 | * @return {number} The number of bytes effectively read. If zero, |
michael@0 | 95 | * the end of the file has been reached. |
michael@0 | 96 | * @throws {OS.File.Error} In case of I/O error. |
michael@0 | 97 | */ |
michael@0 | 98 | File.prototype._read = function _read(buffer, nbytes, options = {}) { |
michael@0 | 99 | // Populate the page cache with data from a file so the subsequent reads |
michael@0 | 100 | // from that file will not block on disk I/O. |
michael@0 | 101 | if (typeof(UnixFile.posix_fadvise) === 'function' && |
michael@0 | 102 | (options.sequential || !("sequential" in options))) { |
michael@0 | 103 | UnixFile.posix_fadvise(this.fd, 0, nbytes, |
michael@0 | 104 | OS.Constants.libc.POSIX_FADV_SEQUENTIAL); |
michael@0 | 105 | } |
michael@0 | 106 | return throw_on_negative("read", |
michael@0 | 107 | UnixFile.read(this.fd, buffer, nbytes), |
michael@0 | 108 | this._path |
michael@0 | 109 | ); |
michael@0 | 110 | }; |
michael@0 | 111 | |
michael@0 | 112 | /** |
michael@0 | 113 | * Write some bytes to a file. |
michael@0 | 114 | * |
michael@0 | 115 | * @param {C pointer} buffer A buffer holding the data that must be |
michael@0 | 116 | * written. |
michael@0 | 117 | * @param {number} nbytes The number of bytes to write. It must not |
michael@0 | 118 | * exceed the size of |buffer| in bytes. |
michael@0 | 119 | * @param {*=} options Additional options for writing. Ignored in |
michael@0 | 120 | * this implementation. |
michael@0 | 121 | * |
michael@0 | 122 | * @return {number} The number of bytes effectively written. |
michael@0 | 123 | * @throws {OS.File.Error} In case of I/O error. |
michael@0 | 124 | */ |
michael@0 | 125 | File.prototype._write = function _write(buffer, nbytes, options = {}) { |
michael@0 | 126 | return throw_on_negative("write", |
michael@0 | 127 | UnixFile.write(this.fd, buffer, nbytes), |
michael@0 | 128 | this._path |
michael@0 | 129 | ); |
michael@0 | 130 | }; |
michael@0 | 131 | |
michael@0 | 132 | /** |
michael@0 | 133 | * Return the current position in the file. |
michael@0 | 134 | */ |
michael@0 | 135 | File.prototype.getPosition = function getPosition(pos) { |
michael@0 | 136 | return this.setPosition(0, File.POS_CURRENT); |
michael@0 | 137 | }; |
michael@0 | 138 | |
michael@0 | 139 | /** |
michael@0 | 140 | * Change the current position in the file. |
michael@0 | 141 | * |
michael@0 | 142 | * @param {number} pos The new position. Whether this position |
michael@0 | 143 | * is considered from the current position, from the start of |
michael@0 | 144 | * the file or from the end of the file is determined by |
michael@0 | 145 | * argument |whence|. Note that |pos| may exceed the length of |
michael@0 | 146 | * the file. |
michael@0 | 147 | * @param {number=} whence The reference position. If omitted |
michael@0 | 148 | * or |OS.File.POS_START|, |pos| is relative to the start of the |
michael@0 | 149 | * file. If |OS.File.POS_CURRENT|, |pos| is relative to the |
michael@0 | 150 | * current position in the file. If |OS.File.POS_END|, |pos| is |
michael@0 | 151 | * relative to the end of the file. |
michael@0 | 152 | * |
michael@0 | 153 | * @return The new position in the file. |
michael@0 | 154 | */ |
michael@0 | 155 | File.prototype.setPosition = function setPosition(pos, whence) { |
michael@0 | 156 | if (whence === undefined) { |
michael@0 | 157 | whence = Const.SEEK_SET; |
michael@0 | 158 | } |
michael@0 | 159 | return throw_on_negative("setPosition", |
michael@0 | 160 | UnixFile.lseek(this.fd, pos, whence), |
michael@0 | 161 | this._path |
michael@0 | 162 | ); |
michael@0 | 163 | }; |
michael@0 | 164 | |
michael@0 | 165 | /** |
michael@0 | 166 | * Fetch the information on the file. |
michael@0 | 167 | * |
michael@0 | 168 | * @return File.Info The information on |this| file. |
michael@0 | 169 | */ |
michael@0 | 170 | File.prototype.stat = function stat() { |
michael@0 | 171 | throw_on_negative("stat", UnixFile.fstat(this.fd, gStatDataPtr), |
michael@0 | 172 | this._path); |
michael@0 | 173 | return new File.Info(gStatData, this._path); |
michael@0 | 174 | }; |
michael@0 | 175 | |
michael@0 | 176 | /** |
michael@0 | 177 | * Set the file's access permissions. Without any options, the |
michael@0 | 178 | * permissions are set to an approximation of what they would |
michael@0 | 179 | * have been if the file had been created in its current |
michael@0 | 180 | * directory in the "most typical" fashion for the operating |
michael@0 | 181 | * system. In the current implementation, this means we set |
michael@0 | 182 | * the POSIX file mode to (0666 & ~umask). |
michael@0 | 183 | * |
michael@0 | 184 | * This operation is likely to fail if applied to a file that was |
michael@0 | 185 | * not created by the currently running program (more precisely, |
michael@0 | 186 | * if it was created by a program running under a different OS-level |
michael@0 | 187 | * user account). It may also fail, or silently do nothing, if the |
michael@0 | 188 | * filesystem containing the file does not support access permissions. |
michael@0 | 189 | * |
michael@0 | 190 | * @param {*=} options |
michael@0 | 191 | * - {number} unixMode If present, the POSIX file mode is set to |
michael@0 | 192 | * exactly this value, unless |unixHonorUmask| is |
michael@0 | 193 | * also present. |
michael@0 | 194 | * - {bool} unixHonorUmask If true, any |unixMode| value is modified by |
michael@0 | 195 | * the process umask, as open() would have done. |
michael@0 | 196 | */ |
michael@0 | 197 | File.prototype.setPermissions = function setPermissions(options = {}) { |
michael@0 | 198 | throw_on_negative("setPermissions", |
michael@0 | 199 | UnixFile.fchmod(this.fd, unixMode(options)), |
michael@0 | 200 | this._path); |
michael@0 | 201 | }; |
michael@0 | 202 | |
michael@0 | 203 | /** |
michael@0 | 204 | * Set the last access and modification date of the file. |
michael@0 | 205 | * The time stamp resolution is 1 second at best, but might be worse |
michael@0 | 206 | * depending on the platform. |
michael@0 | 207 | * |
michael@0 | 208 | * @param {Date,number=} accessDate The last access date. If numeric, |
michael@0 | 209 | * milliseconds since epoch. If omitted or null, then the current date |
michael@0 | 210 | * will be used. |
michael@0 | 211 | * @param {Date,number=} modificationDate The last modification date. If |
michael@0 | 212 | * numeric, milliseconds since epoch. If omitted or null, then the current |
michael@0 | 213 | * date will be used. |
michael@0 | 214 | * |
michael@0 | 215 | * @throws {TypeError} In case of invalid parameters. |
michael@0 | 216 | * @throws {OS.File.Error} In case of I/O error. |
michael@0 | 217 | */ |
michael@0 | 218 | File.prototype.setDates = function setDates(accessDate, modificationDate) { |
michael@0 | 219 | accessDate = normalizeDate("File.prototype.setDates", accessDate); |
michael@0 | 220 | modificationDate = normalizeDate("File.prototype.setDates", |
michael@0 | 221 | modificationDate); |
michael@0 | 222 | gTimevals[0].tv_sec = (accessDate / 1000) | 0; |
michael@0 | 223 | gTimevals[0].tv_usec = 0; |
michael@0 | 224 | gTimevals[1].tv_sec = (modificationDate / 1000) | 0; |
michael@0 | 225 | gTimevals[1].tv_usec = 0; |
michael@0 | 226 | throw_on_negative("setDates", |
michael@0 | 227 | UnixFile.futimes(this.fd, gTimevalsPtr), |
michael@0 | 228 | this._path); |
michael@0 | 229 | }; |
michael@0 | 230 | |
michael@0 | 231 | /** |
michael@0 | 232 | * Flushes the file's buffers and causes all buffered data |
michael@0 | 233 | * to be written. |
michael@0 | 234 | * Disk flushes are very expensive and therefore should be used carefully, |
michael@0 | 235 | * sparingly and only in scenarios where it is vital that data survives |
michael@0 | 236 | * system crashes. Even though the function will be executed off the |
michael@0 | 237 | * main-thread, it might still affect the overall performance of any |
michael@0 | 238 | * running application. |
michael@0 | 239 | * |
michael@0 | 240 | * @throws {OS.File.Error} In case of I/O error. |
michael@0 | 241 | */ |
michael@0 | 242 | File.prototype.flush = function flush() { |
michael@0 | 243 | throw_on_negative("flush", UnixFile.fsync(this.fd), this._path); |
michael@0 | 244 | }; |
michael@0 | 245 | |
michael@0 | 246 | // The default unix mode for opening (0600) |
michael@0 | 247 | const DEFAULT_UNIX_MODE = 384; |
michael@0 | 248 | |
michael@0 | 249 | /** |
michael@0 | 250 | * Open a file |
michael@0 | 251 | * |
michael@0 | 252 | * @param {string} path The path to the file. |
michael@0 | 253 | * @param {*=} mode The opening mode for the file, as |
michael@0 | 254 | * an object that may contain the following fields: |
michael@0 | 255 | * |
michael@0 | 256 | * - {bool} truncate If |true|, the file will be opened |
michael@0 | 257 | * for writing. If the file does not exist, it will be |
michael@0 | 258 | * created. If the file exists, its contents will be |
michael@0 | 259 | * erased. Cannot be specified with |create|. |
michael@0 | 260 | * - {bool} create If |true|, the file will be opened |
michael@0 | 261 | * for writing. If the file exists, this function fails. |
michael@0 | 262 | * If the file does not exist, it will be created. Cannot |
michael@0 | 263 | * be specified with |truncate| or |existing|. |
michael@0 | 264 | * - {bool} existing. If the file does not exist, this function |
michael@0 | 265 | * fails. Cannot be specified with |create|. |
michael@0 | 266 | * - {bool} read If |true|, the file will be opened for |
michael@0 | 267 | * reading. The file may also be opened for writing, depending |
michael@0 | 268 | * on the other fields of |mode|. |
michael@0 | 269 | * - {bool} write If |true|, the file will be opened for |
michael@0 | 270 | * writing. The file may also be opened for reading, depending |
michael@0 | 271 | * on the other fields of |mode|. |
michael@0 | 272 | * - {bool} append If |true|, the file will be opened for appending, |
michael@0 | 273 | * meaning the equivalent of |.setPosition(0, POS_END)| is executed |
michael@0 | 274 | * before each write. The default is |true|, i.e. opening a file for |
michael@0 | 275 | * appending. Specify |append: false| to open the file in regular mode. |
michael@0 | 276 | * |
michael@0 | 277 | * If neither |truncate|, |create| or |write| is specified, the file |
michael@0 | 278 | * is opened for reading. |
michael@0 | 279 | * |
michael@0 | 280 | * Note that |false|, |null| or |undefined| flags are simply ignored. |
michael@0 | 281 | * |
michael@0 | 282 | * @param {*=} options Additional options for file opening. This |
michael@0 | 283 | * implementation interprets the following fields: |
michael@0 | 284 | * |
michael@0 | 285 | * - {number} unixFlags If specified, file opening flags, as |
michael@0 | 286 | * per libc function |open|. Replaces |mode|. |
michael@0 | 287 | * - {number} unixMode If specified, a file creation mode, |
michael@0 | 288 | * as per libc function |open|. If unspecified, files are |
michael@0 | 289 | * created with a default mode of 0600 (file is private to the |
michael@0 | 290 | * user, the user can read and write). |
michael@0 | 291 | * |
michael@0 | 292 | * @return {File} A file object. |
michael@0 | 293 | * @throws {OS.File.Error} If the file could not be opened. |
michael@0 | 294 | */ |
michael@0 | 295 | File.open = function Unix_open(path, mode, options = {}) { |
michael@0 | 296 | let omode = options.unixMode !== undefined ? |
michael@0 | 297 | options.unixMode : DEFAULT_UNIX_MODE; |
michael@0 | 298 | let flags; |
michael@0 | 299 | if (options.unixFlags !== undefined) { |
michael@0 | 300 | flags = options.unixFlags; |
michael@0 | 301 | } else { |
michael@0 | 302 | mode = OS.Shared.AbstractFile.normalizeOpenMode(mode); |
michael@0 | 303 | // Handle read/write |
michael@0 | 304 | if (!mode.write) { |
michael@0 | 305 | flags = Const.O_RDONLY; |
michael@0 | 306 | } else if (mode.read) { |
michael@0 | 307 | flags = Const.O_RDWR; |
michael@0 | 308 | } else { |
michael@0 | 309 | flags = Const.O_WRONLY; |
michael@0 | 310 | } |
michael@0 | 311 | // Finally, handle create/existing/trunc |
michael@0 | 312 | if (mode.trunc) { |
michael@0 | 313 | if (mode.existing) { |
michael@0 | 314 | flags |= Const.O_TRUNC; |
michael@0 | 315 | } else { |
michael@0 | 316 | flags |= Const.O_CREAT | Const.O_TRUNC; |
michael@0 | 317 | } |
michael@0 | 318 | } else if (mode.create) { |
michael@0 | 319 | flags |= Const.O_CREAT | Const.O_EXCL; |
michael@0 | 320 | } else if (mode.read && !mode.write) { |
michael@0 | 321 | // flags are sufficient |
michael@0 | 322 | } else if (!mode.existing) { |
michael@0 | 323 | flags |= Const.O_CREAT; |
michael@0 | 324 | } |
michael@0 | 325 | if (mode.append) { |
michael@0 | 326 | flags |= Const.O_APPEND; |
michael@0 | 327 | } |
michael@0 | 328 | } |
michael@0 | 329 | return error_or_file(UnixFile.open(path, flags, omode), path); |
michael@0 | 330 | }; |
michael@0 | 331 | |
michael@0 | 332 | /** |
michael@0 | 333 | * Checks if a file exists |
michael@0 | 334 | * |
michael@0 | 335 | * @param {string} path The path to the file. |
michael@0 | 336 | * |
michael@0 | 337 | * @return {bool} true if the file exists, false otherwise. |
michael@0 | 338 | */ |
michael@0 | 339 | File.exists = function Unix_exists(path) { |
michael@0 | 340 | if (UnixFile.access(path, Const.F_OK) == -1) { |
michael@0 | 341 | return false; |
michael@0 | 342 | } else { |
michael@0 | 343 | return true; |
michael@0 | 344 | } |
michael@0 | 345 | }; |
michael@0 | 346 | |
michael@0 | 347 | /** |
michael@0 | 348 | * Remove an existing file. |
michael@0 | 349 | * |
michael@0 | 350 | * @param {string} path The name of the file. |
michael@0 | 351 | * @param {*=} options Additional options. |
michael@0 | 352 | * - {bool} ignoreAbsent If |false|, throw an error if the file does |
michael@0 | 353 | * not exist. |true| by default. |
michael@0 | 354 | * |
michael@0 | 355 | * @throws {OS.File.Error} In case of I/O error. |
michael@0 | 356 | */ |
michael@0 | 357 | File.remove = function remove(path, options = {}) { |
michael@0 | 358 | let result = UnixFile.unlink(path); |
michael@0 | 359 | if (result == -1) { |
michael@0 | 360 | if ((!("ignoreAbsent" in options) || options.ignoreAbsent) && |
michael@0 | 361 | ctypes.errno == Const.ENOENT) { |
michael@0 | 362 | return; |
michael@0 | 363 | } |
michael@0 | 364 | throw new File.Error("remove", ctypes.errno, path); |
michael@0 | 365 | } |
michael@0 | 366 | }; |
michael@0 | 367 | |
michael@0 | 368 | /** |
michael@0 | 369 | * Remove an empty directory. |
michael@0 | 370 | * |
michael@0 | 371 | * @param {string} path The name of the directory to remove. |
michael@0 | 372 | * @param {*=} options Additional options. |
michael@0 | 373 | * - {bool} ignoreAbsent If |false|, throw an error if the directory |
michael@0 | 374 | * does not exist. |true| by default |
michael@0 | 375 | */ |
michael@0 | 376 | File.removeEmptyDir = function removeEmptyDir(path, options = {}) { |
michael@0 | 377 | let result = UnixFile.rmdir(path); |
michael@0 | 378 | if (result == -1) { |
michael@0 | 379 | if ((!("ignoreAbsent" in options) || options.ignoreAbsent) && |
michael@0 | 380 | ctypes.errno == Const.ENOENT) { |
michael@0 | 381 | return; |
michael@0 | 382 | } |
michael@0 | 383 | throw new File.Error("removeEmptyDir", ctypes.errno, path); |
michael@0 | 384 | } |
michael@0 | 385 | }; |
michael@0 | 386 | |
michael@0 | 387 | /** |
michael@0 | 388 | * Gets the number of bytes available on disk to the current user. |
michael@0 | 389 | * |
michael@0 | 390 | * @param {string} sourcePath Platform-specific path to a directory on |
michael@0 | 391 | * the disk to query for free available bytes. |
michael@0 | 392 | * |
michael@0 | 393 | * @return {number} The number of bytes available for the current user. |
michael@0 | 394 | * @throws {OS.File.Error} In case of any error. |
michael@0 | 395 | */ |
michael@0 | 396 | File.getAvailableFreeSpace = function Unix_getAvailableFreeSpace(sourcePath) { |
michael@0 | 397 | let fileSystemInfo = new Type.statvfs.implementation(); |
michael@0 | 398 | let fileSystemInfoPtr = fileSystemInfo.address(); |
michael@0 | 399 | |
michael@0 | 400 | throw_on_negative("statvfs", UnixFile.statvfs(sourcePath, fileSystemInfoPtr)); |
michael@0 | 401 | |
michael@0 | 402 | let bytes = new Type.uint64_t.implementation( |
michael@0 | 403 | fileSystemInfo.f_bsize * fileSystemInfo.f_bavail); |
michael@0 | 404 | |
michael@0 | 405 | return bytes.value; |
michael@0 | 406 | }; |
michael@0 | 407 | |
michael@0 | 408 | /** |
michael@0 | 409 | * Default mode for opening directories. |
michael@0 | 410 | */ |
michael@0 | 411 | const DEFAULT_UNIX_MODE_DIR = Const.S_IRWXU; |
michael@0 | 412 | |
michael@0 | 413 | /** |
michael@0 | 414 | * Create a directory. |
michael@0 | 415 | * |
michael@0 | 416 | * @param {string} path The name of the directory. |
michael@0 | 417 | * @param {*=} options Additional options. This |
michael@0 | 418 | * implementation interprets the following fields: |
michael@0 | 419 | * |
michael@0 | 420 | * - {number} unixMode If specified, a file creation mode, |
michael@0 | 421 | * as per libc function |mkdir|. If unspecified, dirs are |
michael@0 | 422 | * created with a default mode of 0700 (dir is private to |
michael@0 | 423 | * the user, the user can read, write and execute). |
michael@0 | 424 | * - {bool} ignoreExisting If |false|, throw error if the directory |
michael@0 | 425 | * already exists. |true| by default |
michael@0 | 426 | * - {string} from If specified, the call to |makeDir| creates all the |
michael@0 | 427 | * ancestors of |path| that are descendants of |from|. Note that |from| |
michael@0 | 428 | * and its existing descendants must be user-writeable and that |path| |
michael@0 | 429 | * must be a descendant of |from|. |
michael@0 | 430 | * Example: |
michael@0 | 431 | * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir }); |
michael@0 | 432 | * creates directories profileDir/foo, profileDir/foo/bar |
michael@0 | 433 | */ |
michael@0 | 434 | File._makeDir = function makeDir(path, options = {}) { |
michael@0 | 435 | let omode = options.unixMode !== undefined ? options.unixMode : DEFAULT_UNIX_MODE_DIR; |
michael@0 | 436 | let result = UnixFile.mkdir(path, omode); |
michael@0 | 437 | if (result == -1) { |
michael@0 | 438 | if ((!("ignoreExisting" in options) || options.ignoreExisting) && |
michael@0 | 439 | (ctypes.errno == Const.EEXIST || ctypes.errno == Const.EISDIR)) { |
michael@0 | 440 | return; |
michael@0 | 441 | } |
michael@0 | 442 | throw new File.Error("makeDir", ctypes.errno, path); |
michael@0 | 443 | } |
michael@0 | 444 | }; |
michael@0 | 445 | |
michael@0 | 446 | /** |
michael@0 | 447 | * Copy a file to a destination. |
michael@0 | 448 | * |
michael@0 | 449 | * @param {string} sourcePath The platform-specific path at which |
michael@0 | 450 | * the file may currently be found. |
michael@0 | 451 | * @param {string} destPath The platform-specific path at which the |
michael@0 | 452 | * file should be copied. |
michael@0 | 453 | * @param {*=} options An object which may contain the following fields: |
michael@0 | 454 | * |
michael@0 | 455 | * @option {bool} noOverwrite - If set, this function will fail if |
michael@0 | 456 | * a file already exists at |destPath|. Otherwise, if this file exists, |
michael@0 | 457 | * it will be erased silently. |
michael@0 | 458 | * |
michael@0 | 459 | * @throws {OS.File.Error} In case of any error. |
michael@0 | 460 | * |
michael@0 | 461 | * General note: The behavior of this function is defined only when |
michael@0 | 462 | * it is called on a single file. If it is called on a directory, the |
michael@0 | 463 | * behavior is undefined and may not be the same across all platforms. |
michael@0 | 464 | * |
michael@0 | 465 | * General note: The behavior of this function with respect to metadata |
michael@0 | 466 | * is unspecified. Metadata may or may not be copied with the file. The |
michael@0 | 467 | * behavior may not be the same across all platforms. |
michael@0 | 468 | */ |
michael@0 | 469 | File.copy = null; |
michael@0 | 470 | |
michael@0 | 471 | /** |
michael@0 | 472 | * Move a file to a destination. |
michael@0 | 473 | * |
michael@0 | 474 | * @param {string} sourcePath The platform-specific path at which |
michael@0 | 475 | * the file may currently be found. |
michael@0 | 476 | * @param {string} destPath The platform-specific path at which the |
michael@0 | 477 | * file should be moved. |
michael@0 | 478 | * @param {*=} options An object which may contain the following fields: |
michael@0 | 479 | * |
michael@0 | 480 | * @option {bool} noOverwrite - If set, this function will fail if |
michael@0 | 481 | * a file already exists at |destPath|. Otherwise, if this file exists, |
michael@0 | 482 | * it will be erased silently. |
michael@0 | 483 | * @option {bool} noCopy - If set, this function will fail if the |
michael@0 | 484 | * operation is more sophisticated than a simple renaming, i.e. if |
michael@0 | 485 | * |sourcePath| and |destPath| are not situated on the same device. |
michael@0 | 486 | * |
michael@0 | 487 | * @throws {OS.File.Error} In case of any error. |
michael@0 | 488 | * |
michael@0 | 489 | * General note: The behavior of this function is defined only when |
michael@0 | 490 | * it is called on a single file. If it is called on a directory, the |
michael@0 | 491 | * behavior is undefined and may not be the same across all platforms. |
michael@0 | 492 | * |
michael@0 | 493 | * General note: The behavior of this function with respect to metadata |
michael@0 | 494 | * is unspecified. Metadata may or may not be moved with the file. The |
michael@0 | 495 | * behavior may not be the same across all platforms. |
michael@0 | 496 | */ |
michael@0 | 497 | File.move = null; |
michael@0 | 498 | |
michael@0 | 499 | if (UnixFile.copyfile) { |
michael@0 | 500 | // This implementation uses |copyfile(3)|, from the BSD library. |
michael@0 | 501 | // Adding copying of hierarchies and/or attributes is just a flag |
michael@0 | 502 | // away. |
michael@0 | 503 | File.copy = function copyfile(sourcePath, destPath, options = {}) { |
michael@0 | 504 | let flags = Const.COPYFILE_DATA; |
michael@0 | 505 | if (options.noOverwrite) { |
michael@0 | 506 | flags |= Const.COPYFILE_EXCL; |
michael@0 | 507 | } |
michael@0 | 508 | throw_on_negative("copy", |
michael@0 | 509 | UnixFile.copyfile(sourcePath, destPath, null, flags), |
michael@0 | 510 | sourcePath |
michael@0 | 511 | ); |
michael@0 | 512 | }; |
michael@0 | 513 | } else { |
michael@0 | 514 | // If the OS does not implement file copying for us, we need to |
michael@0 | 515 | // implement it ourselves. For this purpose, we need to define |
michael@0 | 516 | // a pumping function. |
michael@0 | 517 | |
michael@0 | 518 | /** |
michael@0 | 519 | * Copy bytes from one file to another one. |
michael@0 | 520 | * |
michael@0 | 521 | * @param {File} source The file containing the data to be copied. It |
michael@0 | 522 | * should be opened for reading. |
michael@0 | 523 | * @param {File} dest The file to which the data should be written. It |
michael@0 | 524 | * should be opened for writing. |
michael@0 | 525 | * @param {*=} options An object which may contain the following fields: |
michael@0 | 526 | * |
michael@0 | 527 | * @option {number} nbytes The maximal number of bytes to |
michael@0 | 528 | * copy. If unspecified, copy everything from the current |
michael@0 | 529 | * position. |
michael@0 | 530 | * @option {number} bufSize A hint regarding the size of the |
michael@0 | 531 | * buffer to use for copying. The implementation may decide to |
michael@0 | 532 | * ignore this hint. |
michael@0 | 533 | * @option {bool} unixUserland Will force the copy operation to be |
michael@0 | 534 | * caried out in user land, instead of using optimized syscalls such |
michael@0 | 535 | * as splice(2). |
michael@0 | 536 | * |
michael@0 | 537 | * @throws {OS.File.Error} In case of error. |
michael@0 | 538 | */ |
michael@0 | 539 | let pump; |
michael@0 | 540 | |
michael@0 | 541 | // A buffer used by |pump_userland| |
michael@0 | 542 | let pump_buffer = null; |
michael@0 | 543 | |
michael@0 | 544 | // An implementation of |pump| using |read|/|write| |
michael@0 | 545 | let pump_userland = function pump_userland(source, dest, options = {}) { |
michael@0 | 546 | let bufSize = options.bufSize > 0 ? options.bufSize : 4096; |
michael@0 | 547 | let nbytes = options.nbytes > 0 ? options.nbytes : Infinity; |
michael@0 | 548 | if (!pump_buffer || pump_buffer.length < bufSize) { |
michael@0 | 549 | pump_buffer = new (ctypes.ArrayType(ctypes.char))(bufSize); |
michael@0 | 550 | } |
michael@0 | 551 | let read = source._read.bind(source); |
michael@0 | 552 | let write = dest._write.bind(dest); |
michael@0 | 553 | // Perform actual copy |
michael@0 | 554 | let total_read = 0; |
michael@0 | 555 | while (true) { |
michael@0 | 556 | let chunk_size = Math.min(nbytes, bufSize); |
michael@0 | 557 | let bytes_just_read = read(pump_buffer, bufSize); |
michael@0 | 558 | if (bytes_just_read == 0) { |
michael@0 | 559 | return total_read; |
michael@0 | 560 | } |
michael@0 | 561 | total_read += bytes_just_read; |
michael@0 | 562 | let bytes_written = 0; |
michael@0 | 563 | do { |
michael@0 | 564 | bytes_written += write( |
michael@0 | 565 | pump_buffer.addressOfElement(bytes_written), |
michael@0 | 566 | bytes_just_read - bytes_written |
michael@0 | 567 | ); |
michael@0 | 568 | } while (bytes_written < bytes_just_read); |
michael@0 | 569 | nbytes -= bytes_written; |
michael@0 | 570 | if (nbytes <= 0) { |
michael@0 | 571 | return total_read; |
michael@0 | 572 | } |
michael@0 | 573 | } |
michael@0 | 574 | }; |
michael@0 | 575 | |
michael@0 | 576 | // Fortunately, under Linux, that pumping function can be optimized. |
michael@0 | 577 | if (UnixFile.splice) { |
michael@0 | 578 | const BUFSIZE = 1 << 17; |
michael@0 | 579 | |
michael@0 | 580 | // An implementation of |pump| using |splice| (for Linux/Android) |
michael@0 | 581 | pump = function pump_splice(source, dest, options = {}) { |
michael@0 | 582 | let nbytes = options.nbytes > 0 ? options.nbytes : Infinity; |
michael@0 | 583 | let pipe = []; |
michael@0 | 584 | throw_on_negative("pump", UnixFile.pipe(pipe)); |
michael@0 | 585 | let pipe_read = pipe[0]; |
michael@0 | 586 | let pipe_write = pipe[1]; |
michael@0 | 587 | let source_fd = source.fd; |
michael@0 | 588 | let dest_fd = dest.fd; |
michael@0 | 589 | let total_read = 0; |
michael@0 | 590 | let total_written = 0; |
michael@0 | 591 | try { |
michael@0 | 592 | while (true) { |
michael@0 | 593 | let chunk_size = Math.min(nbytes, BUFSIZE); |
michael@0 | 594 | let bytes_read = throw_on_negative("pump", |
michael@0 | 595 | UnixFile.splice(source_fd, null, |
michael@0 | 596 | pipe_write, null, chunk_size, 0) |
michael@0 | 597 | ); |
michael@0 | 598 | if (!bytes_read) { |
michael@0 | 599 | break; |
michael@0 | 600 | } |
michael@0 | 601 | total_read += bytes_read; |
michael@0 | 602 | let bytes_written = throw_on_negative( |
michael@0 | 603 | "pump", |
michael@0 | 604 | UnixFile.splice(pipe_read, null, |
michael@0 | 605 | dest_fd, null, bytes_read, |
michael@0 | 606 | (bytes_read == chunk_size)?Const.SPLICE_F_MORE:0 |
michael@0 | 607 | )); |
michael@0 | 608 | if (!bytes_written) { |
michael@0 | 609 | // This should never happen |
michael@0 | 610 | throw new Error("Internal error: pipe disconnected"); |
michael@0 | 611 | } |
michael@0 | 612 | total_written += bytes_written; |
michael@0 | 613 | nbytes -= bytes_read; |
michael@0 | 614 | if (!nbytes) { |
michael@0 | 615 | break; |
michael@0 | 616 | } |
michael@0 | 617 | } |
michael@0 | 618 | return total_written; |
michael@0 | 619 | } catch (x) { |
michael@0 | 620 | if (x.unixErrno == Const.EINVAL) { |
michael@0 | 621 | // We *might* be on a file system that does not support splice. |
michael@0 | 622 | // Try again with a fallback pump. |
michael@0 | 623 | if (total_read) { |
michael@0 | 624 | source.setPosition(-total_read, File.POS_CURRENT); |
michael@0 | 625 | } |
michael@0 | 626 | if (total_written) { |
michael@0 | 627 | dest.setPosition(-total_written, File.POS_CURRENT); |
michael@0 | 628 | } |
michael@0 | 629 | return pump_userland(source, dest, options); |
michael@0 | 630 | } |
michael@0 | 631 | throw x; |
michael@0 | 632 | } finally { |
michael@0 | 633 | pipe_read.dispose(); |
michael@0 | 634 | pipe_write.dispose(); |
michael@0 | 635 | } |
michael@0 | 636 | }; |
michael@0 | 637 | } else { |
michael@0 | 638 | // Fallback implementation of pump for other Unix platforms. |
michael@0 | 639 | pump = pump_userland; |
michael@0 | 640 | } |
michael@0 | 641 | |
michael@0 | 642 | // Implement |copy| using |pump|. |
michael@0 | 643 | // This implementation would require some work before being able to |
michael@0 | 644 | // copy directories |
michael@0 | 645 | File.copy = function copy(sourcePath, destPath, options = {}) { |
michael@0 | 646 | let source, dest; |
michael@0 | 647 | let result; |
michael@0 | 648 | try { |
michael@0 | 649 | source = File.open(sourcePath); |
michael@0 | 650 | // Need to open the output file with |append:false|, or else |splice| |
michael@0 | 651 | // won't work. |
michael@0 | 652 | if (options.noOverwrite) { |
michael@0 | 653 | dest = File.open(destPath, {create:true, append:false}); |
michael@0 | 654 | } else { |
michael@0 | 655 | dest = File.open(destPath, {trunc:true, append:false}); |
michael@0 | 656 | } |
michael@0 | 657 | if (options.unixUserland) { |
michael@0 | 658 | result = pump_userland(source, dest, options); |
michael@0 | 659 | } else { |
michael@0 | 660 | result = pump(source, dest, options); |
michael@0 | 661 | } |
michael@0 | 662 | } catch (x) { |
michael@0 | 663 | if (dest) { |
michael@0 | 664 | dest.close(); |
michael@0 | 665 | } |
michael@0 | 666 | if (source) { |
michael@0 | 667 | source.close(); |
michael@0 | 668 | } |
michael@0 | 669 | throw x; |
michael@0 | 670 | } |
michael@0 | 671 | }; |
michael@0 | 672 | } // End of definition of copy |
michael@0 | 673 | |
michael@0 | 674 | // Implement |move| using |rename| (wherever possible) or |copy| |
michael@0 | 675 | // (if files are on distinct devices). |
michael@0 | 676 | File.move = function move(sourcePath, destPath, options = {}) { |
michael@0 | 677 | // An implementation using |rename| whenever possible or |
michael@0 | 678 | // |File.pump| when required, for other Unices. |
michael@0 | 679 | // It can move directories on one file system, not |
michael@0 | 680 | // across file systems |
michael@0 | 681 | |
michael@0 | 682 | // If necessary, fail if the destination file exists |
michael@0 | 683 | if (options.noOverwrite) { |
michael@0 | 684 | let fd = UnixFile.open(destPath, Const.O_RDONLY, 0); |
michael@0 | 685 | if (fd != -1) { |
michael@0 | 686 | fd.dispose(); |
michael@0 | 687 | // The file exists and we have access |
michael@0 | 688 | throw new File.Error("move", Const.EEXIST, sourcePath); |
michael@0 | 689 | } else if (ctypes.errno == Const.EACCESS) { |
michael@0 | 690 | // The file exists and we don't have access |
michael@0 | 691 | throw new File.Error("move", Const.EEXIST, sourcePath); |
michael@0 | 692 | } |
michael@0 | 693 | } |
michael@0 | 694 | |
michael@0 | 695 | // If we can, rename the file |
michael@0 | 696 | let result = UnixFile.rename(sourcePath, destPath); |
michael@0 | 697 | if (result != -1) |
michael@0 | 698 | return; |
michael@0 | 699 | |
michael@0 | 700 | // If the error is not EXDEV ("not on the same device"), |
michael@0 | 701 | // or if the error is EXDEV and we have passed an option |
michael@0 | 702 | // that prevents us from crossing devices, throw the |
michael@0 | 703 | // error. |
michael@0 | 704 | if (ctypes.errno != Const.EXDEV || options.noCopy) { |
michael@0 | 705 | throw new File.Error("move", ctypes.errno, sourcePath); |
michael@0 | 706 | } |
michael@0 | 707 | |
michael@0 | 708 | // Otherwise, copy and remove. |
michael@0 | 709 | File.copy(sourcePath, destPath, options); |
michael@0 | 710 | // FIXME: Clean-up in case of copy error? |
michael@0 | 711 | File.remove(sourcePath); |
michael@0 | 712 | }; |
michael@0 | 713 | |
michael@0 | 714 | File.unixSymLink = function unixSymLink(sourcePath, destPath) { |
michael@0 | 715 | throw_on_negative("symlink", UnixFile.symlink(sourcePath, destPath), |
michael@0 | 716 | sourcePath); |
michael@0 | 717 | }; |
michael@0 | 718 | |
michael@0 | 719 | /** |
michael@0 | 720 | * Iterate on one directory. |
michael@0 | 721 | * |
michael@0 | 722 | * This iterator will not enter subdirectories. |
michael@0 | 723 | * |
michael@0 | 724 | * @param {string} path The directory upon which to iterate. |
michael@0 | 725 | * @param {*=} options Ignored in this implementation. |
michael@0 | 726 | * |
michael@0 | 727 | * @throws {File.Error} If |path| does not represent a directory or |
michael@0 | 728 | * if the directory cannot be iterated. |
michael@0 | 729 | * @constructor |
michael@0 | 730 | */ |
michael@0 | 731 | File.DirectoryIterator = function DirectoryIterator(path, options) { |
michael@0 | 732 | exports.OS.Shared.AbstractFile.AbstractIterator.call(this); |
michael@0 | 733 | this._path = path; |
michael@0 | 734 | this._dir = UnixFile.opendir(this._path); |
michael@0 | 735 | if (this._dir == null) { |
michael@0 | 736 | let error = ctypes.errno; |
michael@0 | 737 | if (error != Const.ENOENT) { |
michael@0 | 738 | throw new File.Error("DirectoryIterator", error, path); |
michael@0 | 739 | } |
michael@0 | 740 | this._exists = false; |
michael@0 | 741 | this._closed = true; |
michael@0 | 742 | } else { |
michael@0 | 743 | this._exists = true; |
michael@0 | 744 | this._closed = false; |
michael@0 | 745 | } |
michael@0 | 746 | }; |
michael@0 | 747 | File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype); |
michael@0 | 748 | |
michael@0 | 749 | /** |
michael@0 | 750 | * Return the next entry in the directory, if any such entry is |
michael@0 | 751 | * available. |
michael@0 | 752 | * |
michael@0 | 753 | * Skip special directories "." and "..". |
michael@0 | 754 | * |
michael@0 | 755 | * @return {File.Entry} The next entry in the directory. |
michael@0 | 756 | * @throws {StopIteration} Once all files in the directory have been |
michael@0 | 757 | * encountered. |
michael@0 | 758 | */ |
michael@0 | 759 | File.DirectoryIterator.prototype.next = function next() { |
michael@0 | 760 | if (!this._exists) { |
michael@0 | 761 | throw File.Error.noSuchFile("DirectoryIterator.prototype.next", this._path); |
michael@0 | 762 | } |
michael@0 | 763 | if (this._closed) { |
michael@0 | 764 | throw StopIteration; |
michael@0 | 765 | } |
michael@0 | 766 | for (let entry = UnixFile.readdir(this._dir); |
michael@0 | 767 | entry != null && !entry.isNull(); |
michael@0 | 768 | entry = UnixFile.readdir(this._dir)) { |
michael@0 | 769 | let contents = entry.contents; |
michael@0 | 770 | let name = contents.d_name.readString(); |
michael@0 | 771 | if (name == "." || name == "..") { |
michael@0 | 772 | continue; |
michael@0 | 773 | } |
michael@0 | 774 | |
michael@0 | 775 | let isDir, isSymLink; |
michael@0 | 776 | if (!("d_type" in contents)) { |
michael@0 | 777 | // |dirent| doesn't have d_type on some platforms (e.g. Solaris). |
michael@0 | 778 | let path = Path.join(this._path, name); |
michael@0 | 779 | throw_on_negative("lstat", UnixFile.lstat(path, gStatDataPtr), this._path); |
michael@0 | 780 | isDir = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFDIR; |
michael@0 | 781 | isSymLink = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFLNK; |
michael@0 | 782 | } else { |
michael@0 | 783 | isDir = contents.d_type == Const.DT_DIR; |
michael@0 | 784 | isSymLink = contents.d_type == Const.DT_LNK; |
michael@0 | 785 | } |
michael@0 | 786 | |
michael@0 | 787 | return new File.DirectoryIterator.Entry(isDir, isSymLink, name, this._path); |
michael@0 | 788 | } |
michael@0 | 789 | this.close(); |
michael@0 | 790 | throw StopIteration; |
michael@0 | 791 | }; |
michael@0 | 792 | |
michael@0 | 793 | /** |
michael@0 | 794 | * Close the iterator and recover all resources. |
michael@0 | 795 | * You should call this once you have finished iterating on a directory. |
michael@0 | 796 | */ |
michael@0 | 797 | File.DirectoryIterator.prototype.close = function close() { |
michael@0 | 798 | if (this._closed) return; |
michael@0 | 799 | this._closed = true; |
michael@0 | 800 | UnixFile.closedir(this._dir); |
michael@0 | 801 | this._dir = null; |
michael@0 | 802 | }; |
michael@0 | 803 | |
michael@0 | 804 | /** |
michael@0 | 805 | * Determine whether the directory exists. |
michael@0 | 806 | * |
michael@0 | 807 | * @return {boolean} |
michael@0 | 808 | */ |
michael@0 | 809 | File.DirectoryIterator.prototype.exists = function exists() { |
michael@0 | 810 | return this._exists; |
michael@0 | 811 | }; |
michael@0 | 812 | |
michael@0 | 813 | /** |
michael@0 | 814 | * Return directory as |File| |
michael@0 | 815 | */ |
michael@0 | 816 | File.DirectoryIterator.prototype.unixAsFile = function unixAsFile() { |
michael@0 | 817 | if (!this._dir) throw File.Error.closed("unixAsFile", this._path); |
michael@0 | 818 | return error_or_file(UnixFile.dirfd(this._dir), this._path); |
michael@0 | 819 | }; |
michael@0 | 820 | |
michael@0 | 821 | /** |
michael@0 | 822 | * An entry in a directory. |
michael@0 | 823 | */ |
michael@0 | 824 | File.DirectoryIterator.Entry = function Entry(isDir, isSymLink, name, parent) { |
michael@0 | 825 | // Copy the relevant part of |unix_entry| to ensure that |
michael@0 | 826 | // our data is not overwritten prematurely. |
michael@0 | 827 | this._parent = parent; |
michael@0 | 828 | let path = Path.join(this._parent, name); |
michael@0 | 829 | |
michael@0 | 830 | SysAll.AbstractEntry.call(this, isDir, isSymLink, name, path); |
michael@0 | 831 | }; |
michael@0 | 832 | File.DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype); |
michael@0 | 833 | |
michael@0 | 834 | /** |
michael@0 | 835 | * Return a version of an instance of |
michael@0 | 836 | * File.DirectoryIterator.Entry that can be sent from a worker |
michael@0 | 837 | * thread to the main thread. Note that deserialization is |
michael@0 | 838 | * asymmetric and returns an object with a different |
michael@0 | 839 | * implementation. |
michael@0 | 840 | */ |
michael@0 | 841 | File.DirectoryIterator.Entry.toMsg = function toMsg(value) { |
michael@0 | 842 | if (!value instanceof File.DirectoryIterator.Entry) { |
michael@0 | 843 | throw new TypeError("parameter of " + |
michael@0 | 844 | "File.DirectoryIterator.Entry.toMsg must be a " + |
michael@0 | 845 | "File.DirectoryIterator.Entry"); |
michael@0 | 846 | } |
michael@0 | 847 | let serialized = {}; |
michael@0 | 848 | for (let key in File.DirectoryIterator.Entry.prototype) { |
michael@0 | 849 | serialized[key] = value[key]; |
michael@0 | 850 | } |
michael@0 | 851 | return serialized; |
michael@0 | 852 | }; |
michael@0 | 853 | |
michael@0 | 854 | let gStatData = new Type.stat.implementation(); |
michael@0 | 855 | let gStatDataPtr = gStatData.address(); |
michael@0 | 856 | let gTimevals = new Type.timevals.implementation(); |
michael@0 | 857 | let gTimevalsPtr = gTimevals.address(); |
michael@0 | 858 | let MODE_MASK = 4095 /*= 07777*/; |
michael@0 | 859 | File.Info = function Info(stat, path) { |
michael@0 | 860 | let isDir = (stat.st_mode & Const.S_IFMT) == Const.S_IFDIR; |
michael@0 | 861 | let isSymLink = (stat.st_mode & Const.S_IFMT) == Const.S_IFLNK; |
michael@0 | 862 | let size = Type.off_t.importFromC(stat.st_size); |
michael@0 | 863 | |
michael@0 | 864 | let lastAccessDate = new Date(stat.st_atime * 1000); |
michael@0 | 865 | let lastModificationDate = new Date(stat.st_mtime * 1000); |
michael@0 | 866 | let unixLastStatusChangeDate = new Date(stat.st_ctime * 1000); |
michael@0 | 867 | |
michael@0 | 868 | let unixOwner = Type.uid_t.importFromC(stat.st_uid); |
michael@0 | 869 | let unixGroup = Type.gid_t.importFromC(stat.st_gid); |
michael@0 | 870 | let unixMode = Type.mode_t.importFromC(stat.st_mode & MODE_MASK); |
michael@0 | 871 | |
michael@0 | 872 | SysAll.AbstractInfo.call(this, path, isDir, isSymLink, size, |
michael@0 | 873 | lastAccessDate, lastModificationDate, unixLastStatusChangeDate, |
michael@0 | 874 | unixOwner, unixGroup, unixMode); |
michael@0 | 875 | |
michael@0 | 876 | // Some platforms (e.g. MacOS X, some BSDs) store a file creation date |
michael@0 | 877 | if ("OSFILE_OFFSETOF_STAT_ST_BIRTHTIME" in Const) { |
michael@0 | 878 | let date = new Date(stat.st_birthtime * 1000); |
michael@0 | 879 | |
michael@0 | 880 | /** |
michael@0 | 881 | * The date of creation of this file. |
michael@0 | 882 | * |
michael@0 | 883 | * Note that the date returned by this method is not always |
michael@0 | 884 | * reliable. Not all file systems are able to provide this |
michael@0 | 885 | * information. |
michael@0 | 886 | * |
michael@0 | 887 | * @type {Date} |
michael@0 | 888 | */ |
michael@0 | 889 | this.macBirthDate = date; |
michael@0 | 890 | } |
michael@0 | 891 | }; |
michael@0 | 892 | File.Info.prototype = Object.create(SysAll.AbstractInfo.prototype); |
michael@0 | 893 | |
michael@0 | 894 | // Deprecated, use macBirthDate/winBirthDate instead |
michael@0 | 895 | Object.defineProperty(File.Info.prototype, "creationDate", { |
michael@0 | 896 | get: function creationDate() { |
michael@0 | 897 | // On the Macintosh, returns the birth date if available. |
michael@0 | 898 | // On other Unix, as the birth date is not available, |
michael@0 | 899 | // returns the epoch. |
michael@0 | 900 | return this.macBirthDate || new Date(0); |
michael@0 | 901 | } |
michael@0 | 902 | }); |
michael@0 | 903 | |
michael@0 | 904 | /** |
michael@0 | 905 | * Return a version of an instance of File.Info that can be sent |
michael@0 | 906 | * from a worker thread to the main thread. Note that deserialization |
michael@0 | 907 | * is asymmetric and returns an object with a different implementation. |
michael@0 | 908 | */ |
michael@0 | 909 | File.Info.toMsg = function toMsg(stat) { |
michael@0 | 910 | if (!stat instanceof File.Info) { |
michael@0 | 911 | throw new TypeError("parameter of File.Info.toMsg must be a File.Info"); |
michael@0 | 912 | } |
michael@0 | 913 | let serialized = {}; |
michael@0 | 914 | for (let key in File.Info.prototype) { |
michael@0 | 915 | serialized[key] = stat[key]; |
michael@0 | 916 | } |
michael@0 | 917 | return serialized; |
michael@0 | 918 | }; |
michael@0 | 919 | |
michael@0 | 920 | /** |
michael@0 | 921 | * Fetch the information on a file. |
michael@0 | 922 | * |
michael@0 | 923 | * @param {string} path The full name of the file to open. |
michael@0 | 924 | * @param {*=} options Additional options. In this implementation: |
michael@0 | 925 | * |
michael@0 | 926 | * - {bool} unixNoFollowingLinks If set and |true|, if |path| |
michael@0 | 927 | * represents a symbolic link, the call will return the information |
michael@0 | 928 | * of the link itself, rather than that of the target file. |
michael@0 | 929 | * |
michael@0 | 930 | * @return {File.Information} |
michael@0 | 931 | */ |
michael@0 | 932 | File.stat = function stat(path, options = {}) { |
michael@0 | 933 | if (options.unixNoFollowingLinks) { |
michael@0 | 934 | throw_on_negative("stat", UnixFile.lstat(path, gStatDataPtr), path); |
michael@0 | 935 | } else { |
michael@0 | 936 | throw_on_negative("stat", UnixFile.stat(path, gStatDataPtr), path); |
michael@0 | 937 | } |
michael@0 | 938 | return new File.Info(gStatData, path); |
michael@0 | 939 | }; |
michael@0 | 940 | |
michael@0 | 941 | /** |
michael@0 | 942 | * Set the file's access permissions. Without any options, the |
michael@0 | 943 | * permissions are set to an approximation of what they would |
michael@0 | 944 | * have been if the file had been created in its current |
michael@0 | 945 | * directory in the "most typical" fashion for the operating |
michael@0 | 946 | * system. In the current implementation, this means we set |
michael@0 | 947 | * the POSIX file mode to (0666 & ~umask). |
michael@0 | 948 | * |
michael@0 | 949 | * This operation is likely to fail if applied to a file that was |
michael@0 | 950 | * not created by the currently running program (more precisely, |
michael@0 | 951 | * if it was created by a program running under a different OS-level |
michael@0 | 952 | * user account). It may also fail, or silently do nothing, if the |
michael@0 | 953 | * filesystem containing the file does not support access permissions. |
michael@0 | 954 | * |
michael@0 | 955 | * @param {string} path The name of the file to reset the permissions of. |
michael@0 | 956 | * @param {*=} options |
michael@0 | 957 | * - {number} unixMode If present, the POSIX file mode is set to |
michael@0 | 958 | * exactly this value, unless |unixHonorUmask| is |
michael@0 | 959 | * also present. |
michael@0 | 960 | * - {bool} unixHonorUmask If true, any |unixMode| value is modified by |
michael@0 | 961 | * the process umask, as open() would have done. |
michael@0 | 962 | */ |
michael@0 | 963 | File.setPermissions = function setPermissions(path, options = {}) { |
michael@0 | 964 | throw_on_negative("setPermissions", |
michael@0 | 965 | UnixFile.chmod(path, unixMode(options)), |
michael@0 | 966 | path); |
michael@0 | 967 | }; |
michael@0 | 968 | |
michael@0 | 969 | /** |
michael@0 | 970 | * Set the last access and modification date of the file. |
michael@0 | 971 | * The time stamp resolution is 1 second at best, but might be worse |
michael@0 | 972 | * depending on the platform. |
michael@0 | 973 | * |
michael@0 | 974 | * @param {string} path The full name of the file to set the dates for. |
michael@0 | 975 | * @param {Date,number=} accessDate The last access date. If numeric, |
michael@0 | 976 | * milliseconds since epoch. If omitted or null, then the current date |
michael@0 | 977 | * will be used. |
michael@0 | 978 | * @param {Date,number=} modificationDate The last modification date. If |
michael@0 | 979 | * numeric, milliseconds since epoch. If omitted or null, then the current |
michael@0 | 980 | * date will be used. |
michael@0 | 981 | * |
michael@0 | 982 | * @throws {TypeError} In case of invalid paramters. |
michael@0 | 983 | * @throws {OS.File.Error} In case of I/O error. |
michael@0 | 984 | */ |
michael@0 | 985 | File.setDates = function setDates(path, accessDate, modificationDate) { |
michael@0 | 986 | accessDate = normalizeDate("File.setDates", accessDate); |
michael@0 | 987 | modificationDate = normalizeDate("File.setDates", modificationDate); |
michael@0 | 988 | gTimevals[0].tv_sec = (accessDate / 1000) | 0; |
michael@0 | 989 | gTimevals[0].tv_usec = 0; |
michael@0 | 990 | gTimevals[1].tv_sec = (modificationDate / 1000) | 0; |
michael@0 | 991 | gTimevals[1].tv_usec = 0; |
michael@0 | 992 | throw_on_negative("setDates", |
michael@0 | 993 | UnixFile.utimes(path, gTimevalsPtr), |
michael@0 | 994 | path); |
michael@0 | 995 | }; |
michael@0 | 996 | |
michael@0 | 997 | File.read = exports.OS.Shared.AbstractFile.read; |
michael@0 | 998 | File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic; |
michael@0 | 999 | File.openUnique = exports.OS.Shared.AbstractFile.openUnique; |
michael@0 | 1000 | File.makeDir = exports.OS.Shared.AbstractFile.makeDir; |
michael@0 | 1001 | |
michael@0 | 1002 | /** |
michael@0 | 1003 | * Remove an existing directory and its contents. |
michael@0 | 1004 | * |
michael@0 | 1005 | * @param {string} path The name of the directory. |
michael@0 | 1006 | * @param {*=} options Additional options. |
michael@0 | 1007 | * - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't |
michael@0 | 1008 | * exist. |true| by default. |
michael@0 | 1009 | * - {boolean} ignorePermissions If |true|, remove the file even when lacking write |
michael@0 | 1010 | * permission. |
michael@0 | 1011 | * |
michael@0 | 1012 | * @throws {OS.File.Error} In case of I/O error, in particular if |path| is |
michael@0 | 1013 | * not a directory. |
michael@0 | 1014 | * |
michael@0 | 1015 | * Note: This function will remove a symlink even if it points a directory. |
michael@0 | 1016 | */ |
michael@0 | 1017 | File.removeDir = function(path, options) { |
michael@0 | 1018 | let isSymLink; |
michael@0 | 1019 | try { |
michael@0 | 1020 | let info = File.stat(path, {unixNoFollowingLinks: true}); |
michael@0 | 1021 | isSymLink = info.isSymLink; |
michael@0 | 1022 | } catch (e) { |
michael@0 | 1023 | if ((!("ignoreAbsent" in options) || options.ignoreAbsent) && |
michael@0 | 1024 | ctypes.errno == Const.ENOENT) { |
michael@0 | 1025 | return; |
michael@0 | 1026 | } |
michael@0 | 1027 | throw e; |
michael@0 | 1028 | } |
michael@0 | 1029 | if (isSymLink) { |
michael@0 | 1030 | // A Unix symlink itself is not a directory even if it points |
michael@0 | 1031 | // a directory. |
michael@0 | 1032 | File.remove(path, options); |
michael@0 | 1033 | return; |
michael@0 | 1034 | } |
michael@0 | 1035 | exports.OS.Shared.AbstractFile.removeRecursive(path, options); |
michael@0 | 1036 | }; |
michael@0 | 1037 | |
michael@0 | 1038 | /** |
michael@0 | 1039 | * Get the current directory by getCurrentDirectory. |
michael@0 | 1040 | */ |
michael@0 | 1041 | File.getCurrentDirectory = function getCurrentDirectory() { |
michael@0 | 1042 | let path = UnixFile.get_current_dir_name?UnixFile.get_current_dir_name(): |
michael@0 | 1043 | UnixFile.getwd_auto(null); |
michael@0 | 1044 | throw_on_null("getCurrentDirectory", path); |
michael@0 | 1045 | return path.readString(); |
michael@0 | 1046 | }; |
michael@0 | 1047 | |
michael@0 | 1048 | /** |
michael@0 | 1049 | * Set the current directory by setCurrentDirectory. |
michael@0 | 1050 | */ |
michael@0 | 1051 | File.setCurrentDirectory = function setCurrentDirectory(path) { |
michael@0 | 1052 | throw_on_negative("setCurrentDirectory", |
michael@0 | 1053 | UnixFile.chdir(path), |
michael@0 | 1054 | path |
michael@0 | 1055 | ); |
michael@0 | 1056 | }; |
michael@0 | 1057 | |
michael@0 | 1058 | /** |
michael@0 | 1059 | * Get/set the current directory. |
michael@0 | 1060 | */ |
michael@0 | 1061 | Object.defineProperty(File, "curDir", { |
michael@0 | 1062 | set: function(path) { |
michael@0 | 1063 | this.setCurrentDirectory(path); |
michael@0 | 1064 | }, |
michael@0 | 1065 | get: function() { |
michael@0 | 1066 | return this.getCurrentDirectory(); |
michael@0 | 1067 | } |
michael@0 | 1068 | } |
michael@0 | 1069 | ); |
michael@0 | 1070 | |
michael@0 | 1071 | // Utility functions |
michael@0 | 1072 | |
michael@0 | 1073 | /** |
michael@0 | 1074 | * Turn the result of |open| into an Error or a File |
michael@0 | 1075 | * @param {number} maybe The result of the |open| operation that may |
michael@0 | 1076 | * represent either an error or a success. If -1, this function raises |
michael@0 | 1077 | * an error holding ctypes.errno, otherwise it returns the opened file. |
michael@0 | 1078 | * @param {string=} path The path of the file. |
michael@0 | 1079 | */ |
michael@0 | 1080 | function error_or_file(maybe, path) { |
michael@0 | 1081 | if (maybe == -1) { |
michael@0 | 1082 | throw new File.Error("open", ctypes.errno, path); |
michael@0 | 1083 | } |
michael@0 | 1084 | return new File(maybe, path); |
michael@0 | 1085 | } |
michael@0 | 1086 | |
michael@0 | 1087 | /** |
michael@0 | 1088 | * Utility function to sort errors represented as "-1" from successes. |
michael@0 | 1089 | * |
michael@0 | 1090 | * @param {string=} operation The name of the operation. If unspecified, |
michael@0 | 1091 | * the name of the caller function. |
michael@0 | 1092 | * @param {number} result The result of the operation that may |
michael@0 | 1093 | * represent either an error or a success. If -1, this function raises |
michael@0 | 1094 | * an error holding ctypes.errno, otherwise it returns |result|. |
michael@0 | 1095 | * @param {string=} path The path of the file. |
michael@0 | 1096 | */ |
michael@0 | 1097 | function throw_on_negative(operation, result, path) { |
michael@0 | 1098 | if (result < 0) { |
michael@0 | 1099 | throw new File.Error(operation, ctypes.errno, path); |
michael@0 | 1100 | } |
michael@0 | 1101 | return result; |
michael@0 | 1102 | } |
michael@0 | 1103 | |
michael@0 | 1104 | /** |
michael@0 | 1105 | * Utility function to sort errors represented as |null| from successes. |
michael@0 | 1106 | * |
michael@0 | 1107 | * @param {string=} operation The name of the operation. If unspecified, |
michael@0 | 1108 | * the name of the caller function. |
michael@0 | 1109 | * @param {pointer} result The result of the operation that may |
michael@0 | 1110 | * represent either an error or a success. If |null|, this function raises |
michael@0 | 1111 | * an error holding ctypes.errno, otherwise it returns |result|. |
michael@0 | 1112 | * @param {string=} path The path of the file. |
michael@0 | 1113 | */ |
michael@0 | 1114 | function throw_on_null(operation, result, path) { |
michael@0 | 1115 | if (result == null || (result.isNull && result.isNull())) { |
michael@0 | 1116 | throw new File.Error(operation, ctypes.errno, path); |
michael@0 | 1117 | } |
michael@0 | 1118 | return result; |
michael@0 | 1119 | } |
michael@0 | 1120 | |
michael@0 | 1121 | /** |
michael@0 | 1122 | * Normalize and verify a Date or numeric date value. |
michael@0 | 1123 | * |
michael@0 | 1124 | * @param {string} fn Function name of the calling function. |
michael@0 | 1125 | * @param {Date,number} date The date to normalize. If omitted or null, |
michael@0 | 1126 | * then the current date will be used. |
michael@0 | 1127 | * |
michael@0 | 1128 | * @throws {TypeError} Invalid date provided. |
michael@0 | 1129 | * |
michael@0 | 1130 | * @return {number} Sanitized, numeric date in milliseconds since epoch. |
michael@0 | 1131 | */ |
michael@0 | 1132 | function normalizeDate(fn, date) { |
michael@0 | 1133 | if (typeof date !== "number" && !date) { |
michael@0 | 1134 | // |date| was Omitted or null. |
michael@0 | 1135 | date = Date.now(); |
michael@0 | 1136 | } else if (typeof date.getTime === "function") { |
michael@0 | 1137 | // Input might be a date or date-like object. |
michael@0 | 1138 | date = date.getTime(); |
michael@0 | 1139 | } |
michael@0 | 1140 | |
michael@0 | 1141 | if (isNaN(date)) { |
michael@0 | 1142 | throw new TypeError("|date| parameter of " + fn + " must be a " + |
michael@0 | 1143 | "|Date| instance or number"); |
michael@0 | 1144 | } |
michael@0 | 1145 | return date; |
michael@0 | 1146 | }; |
michael@0 | 1147 | |
michael@0 | 1148 | /** |
michael@0 | 1149 | * Helper used by both versions of setPermissions. |
michael@0 | 1150 | */ |
michael@0 | 1151 | function unixMode(options) { |
michael@0 | 1152 | let mode = 438; /* 0666 */ |
michael@0 | 1153 | let unixHonorUmask = true; |
michael@0 | 1154 | if ("unixMode" in options) { |
michael@0 | 1155 | unixHonorUmask = false; |
michael@0 | 1156 | mode = options.unixMode; |
michael@0 | 1157 | } |
michael@0 | 1158 | if ("unixHonorUmask" in options) { |
michael@0 | 1159 | unixHonorUmask = options.unixHonorUmask; |
michael@0 | 1160 | } |
michael@0 | 1161 | if (unixHonorUmask) { |
michael@0 | 1162 | mode &= ~SharedAll.Constants.Sys.umask; |
michael@0 | 1163 | } |
michael@0 | 1164 | return mode; |
michael@0 | 1165 | } |
michael@0 | 1166 | |
michael@0 | 1167 | File.Unix = exports.OS.Unix.File; |
michael@0 | 1168 | File.Error = SysAll.Error; |
michael@0 | 1169 | exports.OS.File = File; |
michael@0 | 1170 | exports.OS.Shared.Type = Type; |
michael@0 | 1171 | |
michael@0 | 1172 | Object.defineProperty(File, "POS_START", { value: SysAll.POS_START }); |
michael@0 | 1173 | Object.defineProperty(File, "POS_CURRENT", { value: SysAll.POS_CURRENT }); |
michael@0 | 1174 | Object.defineProperty(File, "POS_END", { value: SysAll.POS_END }); |
michael@0 | 1175 | })(this); |
michael@0 | 1176 | } |