/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

{
  if (typeof Components != "undefined") {
    // We do not wish osfile_unix_back.jsm to be used directly as a main thread
    // module yet. When time comes, it will be loaded by a combination of
    // a main thread front-end/worker thread implementation that makes sure
    // that we are not executing synchronous IO code in the main thread.

    throw new Error("osfile_unix_back.jsm cannot be used from the main thread yet");
  }
  (function(exports) {
     "use strict";
     if (!exports.OS) {
       exports.OS = {};
     }
     if (!exports.OS.Unix) {
       exports.OS.Unix = {};
     }
     if (exports.OS.Unix.File) {
       return; // Avoid double-initialization
     }
     exports.OS.Unix.File = {};

     let LOG = exports.OS.Shared.LOG.bind(OS.Shared, "Unix", "back");
     let libc = exports.OS.Shared.Unix.libc;

     /**
      * Initialize the Unix module.
      *
      * @param {function=} declareFFI
      */
     // FIXME: Both |init| and |aDeclareFFI| are deprecated, we should remove them
     let init = function init(aDeclareFFI) {
       let declareFFI;
       if (aDeclareFFI) {
         declareFFI = aDeclareFFI.bind(null, libc);
       } else {
         declareFFI = exports.OS.Shared.Unix.declareFFI;
       }

       // Shorthands
       let OSUnix = exports.OS.Unix;
       let UnixFile = exports.OS.Unix.File;
       if (!exports.OS.Types) {
         exports.OS.Types = {};
       }
       let Type = exports.OS.Shared.Type;
       let Types = Type;

       // Initialize types that require additional OS-specific
       // support - either finalization or matching against
       // OS-specific constants.

       /**
        * A file descriptor.
        */
       Types.fd = Type.int.withName("fd");
       Types.fd.importFromC = function importFromC(fd_int) {
         return ctypes.CDataFinalizer(fd_int, _close);
       };


       /**
        * A C integer holding -1 in case of error or a file descriptor
        * in case of success.
        */
       Types.negativeone_or_fd = Types.fd.withName("negativeone_or_fd");
       Types.negativeone_or_fd.importFromC =
         function importFromC(fd_int) {
           if (fd_int == -1) {
             return -1;
           }
           return ctypes.CDataFinalizer(fd_int, _close);
         };

       /**
        * A C integer holding -1 in case of error or a meaningless value
        * in case of success.
        */
       Types.negativeone_or_nothing =
         Types.int.withName("negativeone_or_nothing");

       /**
        * A C integer holding -1 in case of error or a positive integer
        * in case of success.
        */
       Types.negativeone_or_ssize_t =
         Types.ssize_t.withName("negativeone_or_ssize_t");

       /**
        * Various libc integer types
        */
       Types.mode_t =
         Types.intn_t(OS.Constants.libc.OSFILE_SIZEOF_MODE_T).withName("mode_t");
       Types.uid_t =
         Types.intn_t(OS.Constants.libc.OSFILE_SIZEOF_UID_T).withName("uid_t");
       Types.gid_t =
         Types.intn_t(OS.Constants.libc.OSFILE_SIZEOF_GID_T).withName("gid_t");

       /**
        * Type |time_t|
        */
       Types.time_t =
         Types.intn_t(OS.Constants.libc.OSFILE_SIZEOF_TIME_T).withName("time_t");

       // Structure |dirent|
       // Building this type is rather complicated, as its layout varies between
       // variants of Unix. For this reason, we rely on a number of constants
       // (computed in C from the C data structures) that give us the layout.
       // The structure we compute looks like
       //  { int8_t[...] before_d_type; // ignored content
       //    int8_t      d_type       ;
       //    int8_t[...] before_d_name; // ignored content
       //    char[...]   d_name;
       //    };
       {
         let dirent = new OS.Shared.HollowStructure("dirent",
           OS.Constants.libc.OSFILE_SIZEOF_DIRENT);
         dirent.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_DIRENT_D_TYPE,
           "d_type", ctypes.uint8_t);
         dirent.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_DIRENT_D_NAME,
           "d_name", ctypes.ArrayType(ctypes.char, OS.Constants.libc.OSFILE_SIZEOF_DIRENT_D_NAME));

         // We now have built |dirent|.
         Types.dirent = dirent.getType();
       }
       Types.null_or_dirent_ptr =
         new Type("null_of_dirent",
                  Types.dirent.out_ptr.implementation);

       // Structure |stat|
       // Same technique
       {
         let stat = new OS.Shared.HollowStructure("stat",
           OS.Constants.libc.OSFILE_SIZEOF_STAT);
         stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_MODE,
                        "st_mode", Types.mode_t.implementation);
         stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_UID,
                          "st_uid", Types.uid_t.implementation);
         stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_GID,
                          "st_gid", Types.gid_t.implementation);

         // Here, things get complicated with different data structures.
         // Some platforms have |time_t st_atime| and some platforms have
         // |timespec st_atimespec|. However, since |timespec| starts with
         // a |time_t|, followed by nanoseconds, we just cheat and pretend
         // that everybody has |time_t st_atime|, possibly followed by padding
         stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_ATIME,
                          "st_atime", Types.time_t.implementation);
         stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_MTIME,
                          "st_mtime", Types.time_t.implementation);
         stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_CTIME,
                          "st_ctime", Types.time_t.implementation);

         // To complicate further, MacOS and some BSDs have a field |birthtime|
         if ("OSFILE_OFFSETOF_STAT_ST_BIRTHTIME" in OS.Constants.libc) {
           stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_BIRTHTIME,
                             "st_birthtime", Types.time_t.implementation);
         }

         stat.add_field_at(OS.Constants.libc.OSFILE_OFFSETOF_STAT_ST_SIZE,
                        "st_size", Types.size_t.implementation);
         Types.stat = stat.getType();
       }

       // Structure |DIR|
       if ("OSFILE_SIZEOF_DIR" in OS.Constants.libc) {
         // On platforms for which we need to access the fields of DIR
         // directly (e.g. because certain functions are implemented
         // as macros), we need to define DIR as a hollow structure.
         let DIR = new OS.Shared.HollowStructure(
           "DIR",
           OS.Constants.libc.OSFILE_SIZEOF_DIR);

         DIR.add_field_at(
           OS.Constants.libc.OSFILE_OFFSETOF_DIR_DD_FD,
           "dd_fd",
           Types.fd.implementation);

         Types.DIR = DIR.getType();
       } else {
         // On other platforms, we keep DIR as a blackbox
         Types.DIR =
           new Type("DIR",
             ctypes.StructType("DIR"));
       }

       Types.null_or_DIR_ptr =
         Types.DIR.out_ptr.withName("null_or_DIR*");
       Types.null_or_DIR_ptr.importFromC = function importFromC(dir) {
         if (dir == null || dir.isNull()) {
           return null;
         }
         return ctypes.CDataFinalizer(dir, _close_dir);
       };

       // Declare libc functions as functions of |OS.Unix.File|

       // Finalizer-related functions
       let _close = UnixFile._close =
         libc.declare("close", ctypes.default_abi,
                        /*return */ctypes.int,
                        /*fd*/     ctypes.int);

       UnixFile.close = function close(fd) {
         // Detach the finalizer and call |_close|.
         return fd.dispose();
       };

       let _close_dir =
         libc.declare("closedir", ctypes.default_abi,
                        /*return */ctypes.int,
                        /*dirp*/   Types.DIR.in_ptr.implementation);

       UnixFile.closedir = function closedir(fd) {
         // Detach the finalizer and call |_close_dir|.
         return fd.dispose();
       };

       UnixFile.free =
         libc.declare("free", ctypes.default_abi,
                       /*return*/ ctypes.void_t,
                       /*ptr*/    ctypes.voidptr_t);

       // Other functions
       UnixFile.access =
         declareFFI("access", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*path*/   Types.path,
                    /*mode*/   Types.int);

       UnixFile.chdir =
         declareFFI("chdir", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*path*/   Types.path);

       UnixFile.chmod =
         declareFFI("chmod", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*path*/   Types.path,
                    /*mode*/   Types.mode_t);

       UnixFile.chown =
         declareFFI("chown", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*path*/   Types.path,
                    /*uid*/    Types.uid_t,
                    /*gid*/    Types.gid_t);

       UnixFile.copyfile =
         declareFFI("copyfile", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*source*/ Types.path,
                    /*dest*/   Types.path,
                    /*state*/  Types.void_t.in_ptr, // Ignored atm
                    /*flags*/  Types.uint32_t);

       UnixFile.dup =
         declareFFI("dup", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_fd,
                    /*fd*/     Types.fd);

       if ("OSFILE_SIZEOF_DIR" in OS.Constants.libc) {
         // On platforms for which |dirfd| is a macro
         UnixFile.dirfd =
           function dirfd(DIRp) {
             return Types.DIR.in_ptr.implementation(DIRp).contents.dd_fd;
           };
       } else {
         // On platforms for which |dirfd| is a function
         UnixFile.dirfd =
           declareFFI("dirfd", ctypes.default_abi,
                      /*return*/ Types.negativeone_or_fd,
                      /*dir*/    Types.DIR.in_ptr);
       }

       UnixFile.chdir =
         declareFFI("chdir", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*path*/   Types.path);

       UnixFile.fchdir =
         declareFFI("fchdir", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*fd*/     Types.fd);

       UnixFile.fchown =
         declareFFI("fchown", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*fd*/     Types.fd,
                    /*uid_t*/  Types.uid_t,
                    /*gid_t*/  Types.gid_t);

       UnixFile.fsync =
         declareFFI("fsync", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*fd*/     Types.fd);

       UnixFile.getcwd =
         declareFFI("getcwd", ctypes.default_abi,
                    /*return*/ Types.out_path,
                    /*buf*/    Types.out_path,
                    /*size*/   Types.size_t);

       UnixFile.getwd =
         declareFFI("getwd", ctypes.default_abi,
                    /*return*/ Types.out_path,
                    /*buf*/    Types.out_path);

       // Two variants of |getwd| which allocate the memory
       // dynamically.

       // Linux/Android version
       UnixFile.get_current_dir_name =
         declareFFI("get_current_dir_name", ctypes.default_abi,
                    /*return*/ Types.out_path.releaseWith(UnixFile.free));

       // MacOS/BSD version (will return NULL on Linux/Android)
       UnixFile.getwd_auto =
         declareFFI("getwd", ctypes.default_abi,
                    /*return*/ Types.out_path.releaseWith(UnixFile.free),
                    /*buf*/    Types.void_t.out_ptr);

       UnixFile.fdatasync =
         declareFFI("fdatasync", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*fd*/     Types.fd); // Note: MacOS/BSD-specific

       UnixFile.ftruncate =
         declareFFI("ftruncate", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*fd*/     Types.fd,
                    /*length*/ Types.off_t);

       if (OS.Constants.libc._DARWIN_FEATURE_64_BIT_INODE) {
         UnixFile.fstat =
           declareFFI("fstat$INODE64", ctypes.default_abi,
                      /*return*/ Types.negativeone_or_nothing,
                      /*path*/   Types.fd,
                      /*buf*/    Types.stat.out_ptr
                     );
       } else {
         UnixFile.fstat =
           declareFFI("fstat", ctypes.default_abi,
                      /*return*/ Types.negativeone_or_nothing,
                      /*path*/   Types.fd,
                      /*buf*/    Types.stat.out_ptr
                     );
       }

       UnixFile.lchown =
         declareFFI("lchown", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*path*/   Types.path,
                    /*uid_t*/  Types.uid_t,
                    /*gid_t*/  Types.gid_t);

       UnixFile.link =
         declareFFI("link", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*source*/ Types.path,
                    /*dest*/   Types.path);

       UnixFile.lseek =
         declareFFI("lseek", ctypes.default_abi,
                    /*return*/ Types.off_t,
                    /*fd*/     Types.fd,
                    /*offset*/ Types.off_t,
                    /*whence*/ Types.int);

       UnixFile.mkdir =
         declareFFI("mkdir", ctypes.default_abi,
                    /*return*/ Types.int,
                    /*path*/ Types.path,
                    /*mode*/ Types.int);

       UnixFile.mkstemp =
         declareFFI("mkstemp", ctypes.default_abi,
                    /*return*/ Types.fd,
                    /*template*/Types.out_path);

       UnixFile.open =
         declareFFI("open", ctypes.default_abi,
                    /*return*/Types.negativeone_or_fd,
                    /*path*/  Types.path,
                    /*oflags*/Types.int,
                    /*mode*/  Types.int);

       UnixFile.opendir =
         declareFFI("opendir", ctypes.default_abi,
                    /*return*/ Types.null_or_DIR_ptr,
                    /*path*/   Types.path);

       UnixFile.pread =
         declareFFI("pread", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_ssize_t,
                    /*fd*/     Types.fd,
                    /*buf*/    Types.void_t.out_ptr,
                    /*nbytes*/ Types.size_t,
                    /*offset*/ Types.off_t);

       UnixFile.pwrite =
         declareFFI("pwrite", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_ssize_t,
                    /*fd*/     Types.fd,
                    /*buf*/    Types.void_t.in_ptr,
                    /*nbytes*/ Types.size_t,
                    /*offset*/ Types.off_t);

       UnixFile.read =
         declareFFI("read", ctypes.default_abi,
                    /*return*/Types.negativeone_or_ssize_t,
                    /*fd*/    Types.fd,
                    /*buf*/   Types.void_t.out_ptr,
                    /*nbytes*/Types.size_t);

       if (OS.Constants.libc._DARWIN_FEATURE_64_BIT_INODE) {
         // Special case for MacOS X 10.5+
         // Symbol name "readdir" still exists but is used for a
         // deprecated function that does not match the
         // constants of |OS.Constants.libc|.
         UnixFile.readdir =
           declareFFI("readdir$INODE64", ctypes.default_abi,
                     /*return*/Types.null_or_dirent_ptr,
                      /*dir*/   Types.DIR.in_ptr); // For MacOS X
       } else {
         UnixFile.readdir =
           declareFFI("readdir", ctypes.default_abi,
                      /*return*/Types.null_or_dirent_ptr,
                      /*dir*/   Types.DIR.in_ptr); // Other Unices
       }

       UnixFile.rename =
         declareFFI("rename", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*old*/    Types.path,
                    /*new*/    Types.path);

       UnixFile.rmdir =
         declareFFI("rmdir", ctypes.default_abi,
                    /*return*/ Types.int,
                    /*path*/   Types.path);

       UnixFile.splice =
         declareFFI("splice", ctypes.default_abi,
                    /*return*/ Types.long,
                    /*fd_in*/  Types.fd,
                    /*off_in*/ Types.off_t.in_ptr,
                    /*fd_out*/ Types.fd,
                    /*off_out*/Types.off_t.in_ptr,
                    /*len*/    Types.size_t,
                    /*flags*/  Types.unsigned_int); // Linux/Android-specific

       UnixFile.symlink =
         declareFFI("symlink", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*source*/ Types.path,
                    /*dest*/   Types.path);

       UnixFile.truncate =
         declareFFI("truncate", ctypes.default_abi,
                    /*return*/Types.negativeone_or_nothing,
                    /*path*/  Types.path,
                    /*length*/ Types.off_t);

       UnixFile.unlink =
         declareFFI("unlink", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_nothing,
                    /*path*/ Types.path);

       UnixFile.write =
         declareFFI("write", ctypes.default_abi,
                    /*return*/ Types.negativeone_or_ssize_t,
                    /*fd*/     Types.fd,
                    /*buf*/    Types.void_t.in_ptr,
                    /*nbytes*/ Types.size_t);

       // Weird cases that require special treatment

       // OSes use a variety of hacks to differentiate between
       // 32-bits and 64-bits versions of |stat|, |lstat|, |fstat|.
       if (OS.Constants.libc._DARWIN_FEATURE_64_BIT_INODE) {
         // MacOS X 64-bits
         UnixFile.stat =
           declareFFI("stat$INODE64", ctypes.default_abi,
                      /*return*/ Types.negativeone_or_nothing,
                      /*path*/   Types.path,
                      /*buf*/    Types.stat.out_ptr
                     );
         UnixFile.lstat =
           declareFFI("lstat$INODE64", ctypes.default_abi,
                      /*return*/ Types.negativeone_or_nothing,
                      /*path*/   Types.path,
                      /*buf*/    Types.stat.out_ptr
                     );
         UnixFile.fstat =
           declareFFI("fstat$INODE64", ctypes.default_abi,
                      /*return*/ Types.negativeone_or_nothing,
                      /*path*/   Types.fd,
                      /*buf*/    Types.stat.out_ptr
                     );
       } else if (OS.Constants.libc._STAT_VER != undefined) {
         const ver = OS.Constants.libc._STAT_VER;
         // Linux, all widths
         let xstat =
           declareFFI("__xstat", ctypes.default_abi,
                      /*return*/    Types.negativeone_or_nothing,
                      /*_stat_ver*/ Types.int,
                      /*path*/      Types.path,
                      /*buf*/       Types.stat.out_ptr);
         let lxstat =
           declareFFI("__lxstat", ctypes.default_abi,
                      /*return*/    Types.negativeone_or_nothing,
                      /*_stat_ver*/ Types.int,
                      /*path*/      Types.path,
                      /*buf*/       Types.stat.out_ptr);
         let fxstat =
           declareFFI("__fxstat", ctypes.default_abi,
                      /*return*/    Types.negativeone_or_nothing,
                      /*_stat_ver*/ Types.int,
                      /*fd*/        Types.fd,
                      /*buf*/       Types.stat.out_ptr);

         UnixFile.stat = function stat(path, buf) {
           return xstat(ver, path, buf);
         };
         UnixFile.lstat = function stat(path, buf) {
           return lxstat(ver, path, buf);
         };
         UnixFile.fstat = function stat(fd, buf) {
           return fxstat(ver, fd, buf);
         };
       } else {
         // Mac OS X 32-bits, other Unix
         UnixFile.stat =
           declareFFI("stat", ctypes.default_abi,
                      /*return*/ Types.negativeone_or_nothing,
                      /*path*/   Types.path,
                      /*buf*/    Types.stat.out_ptr
                     );
         UnixFile.lstat =
           declareFFI("lstat", ctypes.default_abi,
                      /*return*/ Types.negativeone_or_nothing,
                      /*path*/   Types.path,
                      /*buf*/    Types.stat.out_ptr
                     );
         UnixFile.fstat =
           declareFFI("fstat", ctypes.default_abi,
                      /*return*/ Types.negativeone_or_nothing,
                      /*fd*/     Types.fd,
                      /*buf*/    Types.stat.out_ptr
                     );
       }

       // We cannot make a C array of CDataFinalizer, so
       // pipe cannot be directly defined as a C function.

       let _pipe =
         declareFFI("pipe", ctypes.default_abi,
           /*return*/ Types.negativeone_or_nothing,
           /*fds*/    new Type("two file descriptors",
             ctypes.ArrayType(ctypes.int, 2)));

       // A shared per-thread buffer used to communicate with |pipe|
       let _pipebuf = new (ctypes.ArrayType(ctypes.int, 2))();

       UnixFile.pipe = function pipe(array) {
         let result = _pipe(_pipebuf);
         if (result == -1) {
           return result;
         }
         array[0] = ctypes.CDataFinalizer(_pipebuf[0], _close);
         array[1] = ctypes.CDataFinalizer(_pipebuf[1], _close);
         return result;
       };
     };
     exports.OS.Unix.File._init = init;
   })(this);
}
