text: // commands for packing and unpacking tarballs
  // this file is used by lib/cache.js
  
  var fs = require('graceful-fs')
  var path = require('path')
  var writeFileAtomic = require('write-file-atomic')
  var writeStreamAtomic = require('fs-write-stream-atomic')
  var log = require('npmlog')
  var uidNumber = require('uid-number')
  var readJson = require('read-package-json')
  var tar = require('tar')
  var zlib = require('zlib')
  var fstream = require('fstream')
  var Packer = require('fstream-npm')
  var iferr = require('iferr')
  var inherits = require('inherits')
  var npm = require('../npm.js')
  var rm = require('./gently-rm.js')
  var myUid = process.getuid && process.getuid()
  var myGid = process.getgid && process.getgid()
  var readPackageTree = require('read-package-tree')
  var union = require('lodash.union')
  
  if (process.env.SUDO_UID && myUid === 0) {
    if (!isNaN(process.env.SUDO_UID)) myUid = +process.env.SUDO_UID
    if (!isNaN(process.env.SUDO_GID)) myGid = +process.env.SUDO_GID
  }
  
  exports.pack = pack
  exports.unpack = unpack
  
  function pack (tarball, folder, pkg, cb) {
    log.verbose('tar pack', [tarball, folder])
  
    log.verbose('tarball', tarball)
    log.verbose('folder', folder)
  
    var recalculateMetadata = require('../install/deps.js').recalculateMetadata
    readPackageTree(folder, iferr(cb, function (tree) {
      recalculateMetadata(tree, log.newGroup('pack:' + pkg), iferr(cb, function () {
        pack_(tarball, folder, tree, pkg, cb)
      }))
    }))
  }
  
  function BundledPacker (props) {
    Packer.call(this, props)
    this.tree = props.tree
    var flattenTree = require('../install/flatten-tree.js')
    this.flatTree = props.flatTree || flattenTree(props.tree)
  }
  inherits(BundledPacker, Packer)
  
  BundledPacker.prototype.getChildProps = function (stat) {
    var props = Packer.prototype.getChildProps.call(this, stat)
    props.tree = this.tree
    props.flatTree = this.flatTree
    return props
  }
  
  BundledPacker.prototype.applyIgnores = function (entry, partial, entryObj) {
    // package.json files can never be ignored.
    if (entry === 'package.json') return true
  
    // readme files should never be ignored.
    if (entry.match(/^readme(\.[^\.]*)$/i)) return true
  
    // license files should never be ignored.
    if (entry.match(/^(license|licence)(\.[^\.]*)?$/i)) return true
  
    // changelogs should never be ignored.
    if (entry.match(/^(changes|changelog|history)(\.[^\.]*)?$/i)) return true
  
    // special rules.  see below.
    if (entry === 'node_modules' && this.packageRoot) return true
  
    // some files are *never* allowed under any circumstances
    if (entry === '.git' ||
        entry === '.lock-wscript' ||
        entry.match(/^\.wafpickle-[0-9]+$/) ||
        entry === 'CVS' ||
        entry === '.svn' ||
        entry === '.hg' ||
        entry.match(/^\..*\.swp$/) ||
        entry === '.DS_Store' ||
        entry.match(/^\._/) ||
        entry === 'npm-debug.log'
      ) {
      return false
    }
  
    // in a node_modules folder, we only include bundled dependencies
    // also, prevent packages in node_modules from being affected
    // by rules set in the containing package, so that
    // bundles don't get busted.
    // Also, once in a bundle, everything is installed as-is
    // To prevent infinite cycles in the case of cyclic deps that are
    // linked with npm link, even in a bundle, deps are only bundled
    // if they're not already present at a higher level.
    if (this.bundleMagic) {
      // bubbling up.  stop here and allow anything the bundled pkg allows
      if (entry.indexOf('/') !== -1) return true
  
      // never include the .bin.  It's typically full of platform-specific
      // stuff like symlinks and .cmd files anyway.
      if (entry === '.bin') return false
  
      // the package root.
      var p = this.parent
      // the package before this one.
      var pp = p && p.parent
  
      // if this entry has already been bundled, and is a symlink,
      // and it is the *same* symlink as this one, then exclude it.
      if (pp && pp.bundleLinks && this.bundleLinks &&
          pp.bundleLinks[entry] &&
          pp.bundleLinks[entry] === this.bundleLinks[entry]) {
        return false
      }
  
      // since it's *not* a symbolic link, if we're *already* in a bundle,
      // then we should include everything.
      if (pp && pp.package && pp.basename === 'node_modules') {
        return true
      }
  
      // only include it at this point if it's a bundleDependency
      return this.isBundled(entry)
    }
    // if (this.bundled) return true
  
    return Packer.prototype.applyIgnores.call(this, entry, partial, entryObj)
  }
  
  function nameMatch (name) { return function (other) { return name === other.package.name } }
  
  BundledPacker.prototype.isBundled = function (name) {
    var bd = this.package && this.package.bundleDependencies
    if (!bd) return false
  
    if (!Array.isArray(bd)) {
      throw new Error(this.package.name + '\'s `bundledDependencies` should ' +
                      'be an array')
    }
  
    if (bd.indexOf(name) !== -1) return true
    var pkg = this.tree.children.filter(nameMatch(name))[0]
    if (!pkg) return false
    var requiredBy = union([], pkg.package._requiredBy)
    var seen = {}
    while (requiredBy.length) {
      var req = requiredBy.shift()
      if (seen[req]) continue
      seen[req] = true
      var reqPkg = this.flatTree[req]
      if (!reqPkg) continue
      if (reqPkg.parent === this.tree && bd.indexOf(reqPkg.package.name) !== -1) {
        return true
      }
      requiredBy = union(requiredBy, reqPkg.package._requiredBy)
    }
    return false
  }
  
  function pack_ (tarball, folder, tree, pkg, cb) {
    new BundledPacker({ path: folder, tree: tree, type: 'Directory', isDirectory: true })
      .on('error', function (er) {
        if (er) log.error('tar pack', 'Error reading ' + folder)
        return cb(er)
      })
  
      // By default, npm includes some proprietary attributes in the
      // package tarball.  This is sane, and allowed by the spec.
      // However, npm *itself* excludes these from its own package,
      // so that it can be more easily bootstrapped using old and
      // non-compliant tar implementations.
      .pipe(tar.Pack({ noProprietary: !npm.config.get('proprietary-attribs') }))
      .on('error', function (er) {
        if (er) log.error('tar.pack', 'tar creation error', tarball)
        cb(er)
      })
      .pipe(zlib.Gzip())
      .on('error', function (er) {
        if (er) log.error('tar.pack', 'gzip error ' + tarball)
        cb(er)
      })
      .pipe(writeStreamAtomic(tarball))
      .on('error', function (er) {
        if (er) log.error('tar.pack', 'Could not write ' + tarball)
        cb(er)
      })
      .on('close', cb)
  }
  
  function unpack (tarball, unpackTarget, dMode, fMode, uid, gid, cb) {
    log.verbose('tar', 'unpack', tarball)
    log.verbose('tar', 'unpacking to', unpackTarget)
    if (typeof cb !== 'function') {
      cb = gid
      gid = null
    }
    if (typeof cb !== 'function') {
      cb = uid
      uid = null
    }
    if (typeof cb !== 'function') {
      cb = fMode
      fMode = npm.modes.file
    }
    if (typeof cb !== 'function') {
      cb = dMode
      dMode = npm.modes.exec
    }
  
    uidNumber(uid, gid, function (er, uid, gid) {
      if (er) return cb(er)
      unpack_(tarball, unpackTarget, dMode, fMode, uid, gid, cb)
    })
  }
  
  function unpack_ (tarball, unpackTarget, dMode, fMode, uid, gid, cb) {
    rm(unpackTarget, function (er) {
      if (er) return cb(er)
      // gzip {tarball} --decompress --stdout \
      //   | tar -mvxpf - --strip-components=1 -C {unpackTarget}
      gunzTarPerm(tarball, unpackTarget,
                  dMode, fMode,
                  uid, gid,
                  function (er, folder) {
        if (er) return cb(er)
        readJson(path.resolve(folder, 'package.json'), cb)
      })
    })
  }
  
  function gunzTarPerm (tarball, target, dMode, fMode, uid, gid, cb_) {
    if (!dMode) dMode = npm.modes.exec
    if (!fMode) fMode = npm.modes.file
    log.silly('gunzTarPerm', 'modes', [dMode.toString(8), fMode.toString(8)])
  
    var cbCalled = false
    function cb (er) {
      if (cbCalled) return
      cbCalled = true
      cb_(er, target)
    }
  
    var fst = fs.createReadStream(tarball)
  
    fst.on('open', function (fd) {
      fs.fstat(fd, function (er, st) {
        if (er) return fst.emit('error', er)
        if (st.size === 0) {
          er = new Error('0-byte tarball\n' +
                         'Please run `npm cache clean`')
          fst.emit('error', er)
        }
      })
    })
  
    // figure out who we're supposed to be, if we're not pretending
    // to be a specific user.
    if (npm.config.get('unsafe-perm') && process.platform !== 'win32') {
      uid = myUid
      gid = myGid
    }
  
    function extractEntry (entry) {
      log.silly('gunzTarPerm', 'extractEntry', entry.path)
      // never create things that are user-unreadable,
      // or dirs that are user-un-listable. Only leads to headaches.
      var originalMode = entry.mode = entry.mode || entry.props.mode
      entry.mode = entry.mode | (entry.type === 'Directory' ? dMode : fMode)
      entry.mode = entry.mode & (~npm.modes.umask)
      entry.props.mode = entry.mode
      if (originalMode !== entry.mode) {
        log.silly('gunzTarPerm', 'modified mode',
                  [entry.path, originalMode, entry.mode])
      }
  
      // if there's a specific owner uid/gid that we want, then set that
      if (process.platform !== 'win32' &&
          typeof uid === 'number' &&
          typeof gid === 'number') {
        entry.props.uid = entry.uid = uid
        entry.props.gid = entry.gid = gid
      }
    }
  
    var extractOpts = { type: 'Directory', path: target, strip: 1 }
  
    if (process.platform !== 'win32' &&
        typeof uid === 'number' &&
        typeof gid === 'number') {
      extractOpts.uid = uid
      extractOpts.gid = gid
    }
  
    var sawIgnores = {}
    extractOpts.filter = function () {
      // symbolic links are not allowed in packages.
      if (this.type.match(/^.*Link$/)) {
        log.warn('excluding symbolic link',
                 this.path.substr(target.length + 1) +
                 ' -> ' + this.linkpath)
        return false
      }
  
      // Note: This mirrors logic in the fs read operations that are
      // employed during tarball creation, in the fstream-npm module.
      // It is duplicated here to handle tarballs that are created
      // using other means, such as system tar or git archive.
      if (this.type === 'File') {
        var base = path.basename(this.path)
        if (base === '.npmignore') {
          sawIgnores[ this.path ] = true
        } else if (base === '.gitignore') {
          var npmignore = this.path.replace(/\.gitignore$/, '.npmignore')
          if (sawIgnores[npmignore]) {
            // Skip this one, already seen.
            return false
          } else {
            // Rename, may be clobbered later.
            this.path = npmignore
            this._path = npmignore
          }
        }
      }
  
      return true
    }
  
    fst
      .on('error', function (er) {
        if (er) log.error('tar.unpack', 'error reading ' + tarball)
        cb(er)
      })
      .on('data', function OD (c) {
        // detect what it is.
        // Then, depending on that, we'll figure out whether it's
        // a single-file module, gzipped tarball, or naked tarball.
        // gzipped files all start with 1f8b08
        if (c[0] === 0x1F &&
            c[1] === 0x8B &&
            c[2] === 0x08) {
          fst
            .pipe(zlib.Unzip())
            .on('error', function (er) {
              if (er) log.error('tar.unpack', 'unzip error ' + tarball)
              cb(er)
            })
            .pipe(tar.Extract(extractOpts))
            .on('entry', extractEntry)
            .on('error', function (er) {
              if (er) log.error('tar.unpack', 'untar error ' + tarball)
              cb(er)
            })
            .on('close', cb)
        } else if (hasTarHeader(c)) {
          // naked tar
          fst
            .pipe(tar.Extract(extractOpts))
            .on('entry', extractEntry)
            .on('error', function (er) {
              if (er) log.error('tar.unpack', 'untar error ' + tarball)
              cb(er)
            })
            .on('close', cb)
        } else {
          // naked js file
          var jsOpts = { path: path.resolve(target, 'index.js') }
  
          if (process.platform !== 'win32' &&
              typeof uid === 'number' &&
              typeof gid === 'number') {
            jsOpts.uid = uid
            jsOpts.gid = gid
          }
  
          fst
            .pipe(fstream.Writer(jsOpts))
            .on('error', function (er) {
              if (er) log.error('tar.unpack', 'copy error ' + tarball)
              cb(er)
            })
            .on('close', function () {
              var j = path.resolve(target, 'package.json')
              readJson(j, function (er, d) {
                if (er) {
                  log.error('not a package', tarball)
                  return cb(er)
                }
                writeFileAtomic(j, JSON.stringify(d) + '\n', cb)
              })
            })
        }
  
        // now un-hook, and re-emit the chunk
        fst.removeListener('data', OD)
        fst.emit('data', c)
      })
  }
  
  function hasTarHeader (c) {
    return c[257] === 0x75 && // tar archives have 7573746172 at position
           c[258] === 0x73 && // 257 and 003030 or 202000 at position 262
           c[259] === 0x74 &&
           c[260] === 0x61 &&
           c[261] === 0x72 &&
  
         ((c[262] === 0x00 &&
           c[263] === 0x30 &&
           c[264] === 0x30) ||
  
          (c[262] === 0x20 &&
           c[263] === 0x20 &&
           c[264] === 0x00))
  }
  