repo_description: :shell: Portable Unix shell commands for Node.js
repo_topics: bash javascript node nodejs shell shelljs unix
code: var fs = require('fs');
  var path = require('path');
  var common = require('./common');
  
  common.register('cp', _cp, {
    cmdOptions: {
      'f': '!no_force',
      'n': 'no_force',
      'u': 'update',
      'R': 'recursive',
      'r': 'recursive',
      'L': 'followsymlink',
      'P': 'noFollowsymlink',
      'p': 'preserve',
    },
    wrapOutput: false,
  });
  
  // Buffered file copy, synchronous
  // (Using readFileSync() + writeFileSync() could easily cause a memory overflow
  //  with large files)
  function copyFileSync(srcFile, destFile, options) {
    if (!fs.existsSync(srcFile)) {
      common.error('copyFileSync: no such file or directory: ' + srcFile);
    }
  
    // Bail if src and dest are the same file to avoid data loss
    if (path.relative(srcFile, destFile) === '') {
      common.error("'" + destFile + "' and '" + srcFile + "' are the same file", { continue: true });
      return;
    }
  
    var isWindows = process.platform === 'win32';
  
    // Check the mtimes of the files if the '-u' flag is provided
    try {
      if (options.update && common.statFollowLinks(srcFile).mtime < fs.statSync(destFile).mtime) {
        return;
      }
    } catch (e) {
      // If we're here, destFile probably doesn't exist, so just do a normal copy
    }
  
    if (common.statNoFollowLinks(srcFile).isSymbolicLink() && !options.followsymlink) {
      try {
        common.statNoFollowLinks(destFile);
        common.unlinkSync(destFile); // re-link it
      } catch (e) {
        // it doesn't exist, so no work needs to be done
      }
  
      var symlinkFull = fs.readlinkSync(srcFile);
      fs.symlinkSync(symlinkFull, destFile, isWindows ? 'junction' : null);
    } else {
      var buf = common.buffer();
      var bufLength = buf.length;
      var bytesRead = bufLength;
      var pos = 0;
      var fdr = null;
      var fdw = null;
      var srcStat = common.statFollowLinks(srcFile);
  
      try {
        fdr = fs.openSync(srcFile, 'r');
      } catch (e) {
        /* istanbul ignore next */
        common.error('copyFileSync: could not read src file (' + srcFile + ')');
      }
  
      try {
        fdw = fs.openSync(destFile, 'w', srcStat.mode);
      } catch (e) {
        /* istanbul ignore next */
        common.error('copyFileSync: could not write to dest file (code=' + e.code + '):' + destFile);
      }
  
      while (bytesRead === bufLength) {
        bytesRead = fs.readSync(fdr, buf, 0, bufLength, pos);
        fs.writeSync(fdw, buf, 0, bytesRead);
        pos += bytesRead;
      }
  
      if (options.preserve) {
        fs.fchownSync(fdw, srcStat.uid, srcStat.gid);
        // Note: utimesSync does not work (rounds to seconds), but futimesSync has
        // millisecond precision.
        fs.futimesSync(fdw, srcStat.atime, srcStat.mtime);
      }
  
      fs.closeSync(fdr);
      fs.closeSync(fdw);
    }
  }
  
  // Recursively copies 'sourceDir' into 'destDir'
  // Adapted from https://github.com/ryanmcgrath/wrench-js
  //
  // Copyright (c) 2010 Ryan McGrath
  // Copyright (c) 2012 Artur Adib
  //
  // Licensed under the MIT License
  // http://www.opensource.org/licenses/mit-license.php
  function cpdirSyncRecursive(sourceDir, destDir, currentDepth, opts) {
    if (!opts) opts = {};
  
    // Ensure there is not a run away recursive copy
    if (currentDepth >= common.config.maxdepth) return;
    currentDepth++;
  
    var isWindows = process.platform === 'win32';
  
    // Create the directory where all our junk is moving to; read the mode/etc. of
    // the source directory (we'll set this on the destDir at the end).
    var checkDir = common.statFollowLinks(sourceDir);
    try {
      fs.mkdirSync(destDir);
    } catch (e) {
      // if the directory already exists, that's okay
      if (e.code !== 'EEXIST') throw e;
    }
  
    var files = fs.readdirSync(sourceDir);
  
    for (var i = 0; i < files.length; i++) {
      var srcFile = sourceDir + '/' + files[i];
      var destFile = destDir + '/' + files[i];
      var srcFileStat = common.statNoFollowLinks(srcFile);
  
      var symlinkFull;
      if (opts.followsymlink) {
        if (cpcheckcycle(sourceDir, srcFile)) {
          // Cycle link found.
          console.error('Cycle link found.');
          symlinkFull = fs.readlinkSync(srcFile);
          fs.symlinkSync(symlinkFull, destFile, isWindows ? 'junction' : null);
          continue;
        }
      }
      if (srcFileStat.isDirectory()) {
        /* recursion this thing right on back. */
        cpdirSyncRecursive(srcFile, destFile, currentDepth, opts);
      } else if (srcFileStat.isSymbolicLink() && !opts.followsymlink) {
        symlinkFull = fs.readlinkSync(srcFile);
        try {
          common.statNoFollowLinks(destFile);
          common.unlinkSync(destFile); // re-link it
        } catch (e) {
          // it doesn't exist, so no work needs to be done
        }
        fs.symlinkSync(symlinkFull, destFile, isWindows ? 'junction' : null);
      } else if (srcFileStat.isSymbolicLink() && opts.followsymlink) {
        srcFileStat = common.statFollowLinks(srcFile);
        if (srcFileStat.isDirectory()) {
          cpdirSyncRecursive(srcFile, destFile, currentDepth, opts);
        } else {
          copyFileSync(srcFile, destFile, opts);
        }
      } else if (fs.existsSync(destFile) && opts.no_force) {
        common.log('skipping existing file: ' + files[i]);
      } else {
        copyFileSync(srcFile, destFile, opts);
      }
    } // for files
  
    // finally change the mode for the newly created directory (otherwise, we
    // couldn't add files to a read-only directory).
    // var checkDir = common.statFollowLinks(sourceDir);
    if (opts.preserve) {
      fs.utimesSync(destDir, checkDir.atime, checkDir.mtime);
    }
    fs.chmodSync(destDir, checkDir.mode);
  } // cpdirSyncRecursive
  
  // Checks if cureent file was created recently
  function checkRecentCreated(sources, index) {
    var lookedSource = sources[index];
    return sources.slice(0, index).some(function (src) {
      return path.basename(src) === path.basename(lookedSource);
    });
  }
  
  function cpcheckcycle(sourceDir, srcFile) {
    var srcFileStat = common.statNoFollowLinks(srcFile);
    if (srcFileStat.isSymbolicLink()) {
      // Do cycle check. For example:
      //   $ mkdir -p 1/2/3/4
      //   $ cd  1/2/3/4
      //   $ ln -s ../../3 link
      //   $ cd ../../../..
      //   $ cp -RL 1 copy
      var cyclecheck = common.statFollowLinks(srcFile);
      if (cyclecheck.isDirectory()) {
        var sourcerealpath = fs.realpathSync(sourceDir);
        var symlinkrealpath = fs.realpathSync(srcFile);
        var re = new RegExp(symlinkrealpath);
        if (re.test(sourcerealpath)) {
          return true;
        }
      }
    }
    return false;
  }
  
  //@
  //@ ### cp([options,] source [, source ...], dest)
  //@ ### cp([options,] source_array, dest)
  //@
  //@ Available options:
  //@
  //@ + `-f`: force (default behavior)
  //@ + `-n`: no-clobber
  //@ + `-u`: only copy if `source` is newer than `dest`
  //@ + `-r`, `-R`: recursive
  //@ + `-L`: follow symlinks
  //@ + `-P`: don't follow symlinks
  //@ + `-p`: preserve file mode, ownership, and timestamps
  //@
  //@ Examples:
  //@
  //@ ```javascript
  //@ cp('file1', 'dir1');
  //@ cp('-R', 'path/to/dir/', '~/newCopy/');
  //@ cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp');
  //@ cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above
  //@ ```
  //@
  //@ Copies files. Returns a [ShellString](#shellstringstr) indicating success
  //@ or failure.
  function _cp(options, sources, dest) {
    // If we're missing -R, it actually implies -L (unless -P is explicit)
    if (options.followsymlink) {
      options.noFollowsymlink = false;
    }
    if (!options.recursive && !options.noFollowsymlink) {
      options.followsymlink = true;
    }
  
    // Get sources, dest
    if (arguments.length < 3) {
      common.error('missing <source> and/or <dest>');
    } else {
      sources = [].slice.call(arguments, 1, arguments.length - 1);
      dest = arguments[arguments.length - 1];
    }
  
    var destExists = fs.existsSync(dest);
    var destStat = destExists && common.statFollowLinks(dest);
  
    // Dest is not existing dir, but multiple sources given
    if ((!destExists || !destStat.isDirectory()) && sources.length > 1) {
      common.error('dest is not a directory (too many sources)');
    }
  
    // Dest is an existing file, but -n is given
    if (destExists && destStat.isFile() && options.no_force) {
      return new common.ShellString('', '', 0);
    }
  
    sources.forEach(function (src, srcIndex) {
      if (!fs.existsSync(src)) {
        if (src === '') src = "''"; // if src was empty string, display empty string
        common.error('no such file or directory: ' + src, { continue: true });
        return; // skip file
      }
      var srcStat = common.statFollowLinks(src);
      if (!options.noFollowsymlink && srcStat.isDirectory()) {
        if (!options.recursive) {
          // Non-Recursive
          common.error("omitting directory '" + src + "'", { continue: true });
        } else {
          // Recursive
          // 'cp /a/source dest' should create 'source' in 'dest'
          var newDest = (destStat && destStat.isDirectory()) ?
              path.join(dest, path.basename(src)) :
              dest;
  
          if (path.relative(src, newDest) === '') {
            // a directory cannot be copied into itself, but we want to continue copying other sources
            common.error("'" + newDest + "' and '" + src + "' are the same file", { continue: true });
            return;
          }
  
          try {
            common.statFollowLinks(path.dirname(dest));
            cpdirSyncRecursive(src, newDest, 0, options);
          } catch (e) {
            /* istanbul ignore next */
            common.error("cannot create directory '" + dest + "': No such file or directory");
          }
        }
      } else {
        // If here, src is a file
  
        // When copying to '/path/dir':
        //    thisDest = '/path/dir/file1'
        var thisDest = dest;
        if (destStat && destStat.isDirectory()) {
          thisDest = path.normalize(dest + '/' + path.basename(src));
        }
  
        var thisDestExists = fs.existsSync(thisDest);
        if (thisDestExists && checkRecentCreated(sources, srcIndex)) {
          // cannot overwrite file created recently in current execution, but we want to continue copying other files
          if (!options.no_force) {
            common.error("will not overwrite just-created '" + thisDest + "' with '" + src + "'", { continue: true });
          }
          return;
        }
  
        if (thisDestExists && options.no_force) {
          return; // skip file
        }
  
        copyFileSync(src, thisDest, options);
      }
    }); // forEach(src)
  
    return new common.ShellString('', common.state.error, common.state.errorCode);
  }
  module.exports = _cp;
  