toolkit/components/osfile/modules/osfile_unix_front.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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 }

mercurial