1 // commands for packing and unpacking tarballs
2 // this file is used by lib/cache.js
4 var npm = require("../npm.js")
5 , fs = require("graceful-fs")
6 , writeFileAtomic = require("write-file-atomic")
7 , writeStreamAtomic = require("fs-write-stream-atomic")
8 , path = require("path")
9 , log = require("npmlog")
10 , uidNumber = require("uid-number")
11 , rm = require("./gently-rm.js")
12 , readJson = require("read-package-json")
13 , myUid = process.getuid && process.getuid()
14 , myGid = process.getgid && process.getgid()
15 , tar = require("tar")
16 , zlib = require("zlib")
17 , fstream = require("fstream")
18 , Packer = require("fstream-npm")
19 , lifecycle = require("./lifecycle.js")
21 if (process.env.SUDO_UID && myUid === 0) {
22 if (!isNaN(process.env.SUDO_UID)) myUid = +process.env.SUDO_UID
23 if (!isNaN(process.env.SUDO_GID)) myGid = +process.env.SUDO_GID
27 exports.unpack = unpack
29 function pack (tarball, folder, pkg, dfc, cb) {
30 log.verbose("tar pack", [tarball, folder])
31 if (typeof cb !== "function") cb = dfc, dfc = false
33 log.verbose("tarball", tarball)
34 log.verbose("folder", folder)
38 return lifecycle(pkg, "prepublish", folder, function (er) {
40 pack_(tarball, folder, pkg, cb)
43 pack_(tarball, folder, pkg, cb)
47 function pack_ (tarball, folder, pkg, cb) {
48 new Packer({ path: folder, type: "Directory", isDirectory: true })
49 .on("error", function (er) {
50 if (er) log.error("tar pack", "Error reading " + folder)
54 // By default, npm includes some proprietary attributes in the
55 // package tarball. This is sane, and allowed by the spec.
56 // However, npm *itself* excludes these from its own package,
57 // so that it can be more easily bootstrapped using old and
58 // non-compliant tar implementations.
59 .pipe(tar.Pack({ noProprietary: !npm.config.get("proprietary-attribs") }))
60 .on("error", function (er) {
61 if (er) log.error("tar.pack", "tar creation error", tarball)
65 .on("error", function (er) {
66 if (er) log.error("tar.pack", "gzip error "+tarball)
69 .pipe(writeStreamAtomic(tarball))
70 .on("error", function (er) {
71 if (er) log.error("tar.pack", "Could not write "+tarball)
78 function unpack (tarball, unpackTarget, dMode, fMode, uid, gid, cb) {
79 log.verbose("tar", "unpack", tarball)
80 log.verbose("tar", "unpacking to", unpackTarget)
81 if (typeof cb !== "function") cb = gid, gid = null
82 if (typeof cb !== "function") cb = uid, uid = null
83 if (typeof cb !== "function") cb = fMode, fMode = npm.modes.file
84 if (typeof cb !== "function") cb = dMode, dMode = npm.modes.exec
86 uidNumber(uid, gid, function (er, uid, gid) {
88 unpack_(tarball, unpackTarget, dMode, fMode, uid, gid, cb)
92 function unpack_ ( tarball, unpackTarget, dMode, fMode, uid, gid, cb ) {
93 rm(unpackTarget, function (er) {
95 // gzip {tarball} --decompress --stdout \
96 // | tar -mvxpf - --strip-components=1 -C {unpackTarget}
97 gunzTarPerm( tarball, unpackTarget
100 , function (er, folder) {
101 if (er) return cb(er)
102 readJson(path.resolve(folder, "package.json"), cb)
108 function gunzTarPerm (tarball, target, dMode, fMode, uid, gid, cb_) {
109 if (!dMode) dMode = npm.modes.exec
110 if (!fMode) fMode = npm.modes.file
111 log.silly("gunzTarPerm", "modes", [dMode.toString(8), fMode.toString(8)])
120 var fst = fs.createReadStream(tarball)
122 fst.on("open", function (fd) {
123 fs.fstat(fd, function (er, st) {
124 if (er) return fst.emit("error", er)
126 er = new Error("0-byte tarball\n" +
127 "Please run `npm cache clean`")
128 fst.emit("error", er)
133 // figure out who we're supposed to be, if we're not pretending
134 // to be a specific user.
135 if (npm.config.get("unsafe-perm") && process.platform !== "win32") {
140 function extractEntry (entry) {
141 log.silly("gunzTarPerm", "extractEntry", entry.path)
142 // never create things that are user-unreadable,
143 // or dirs that are user-un-listable. Only leads to headaches.
144 var originalMode = entry.mode = entry.mode || entry.props.mode
145 entry.mode = entry.mode | (entry.type === "Directory" ? dMode : fMode)
146 entry.mode = entry.mode & (~npm.modes.umask)
147 entry.props.mode = entry.mode
148 if (originalMode !== entry.mode) {
149 log.silly( "gunzTarPerm", "modified mode"
150 , [entry.path, originalMode, entry.mode])
153 // if there's a specific owner uid/gid that we want, then set that
154 if (process.platform !== "win32" &&
155 typeof uid === "number" &&
156 typeof gid === "number") {
157 entry.props.uid = entry.uid = uid
158 entry.props.gid = entry.gid = gid
162 var extractOpts = { type: "Directory", path: target, strip: 1 }
164 if (process.platform !== "win32" &&
165 typeof uid === "number" &&
166 typeof gid === "number") {
167 extractOpts.uid = uid
168 extractOpts.gid = gid
172 extractOpts.filter = function () {
173 // symbolic links are not allowed in packages.
174 if (this.type.match(/^.*Link$/)) {
175 log.warn( "excluding symbolic link"
176 , this.path.substr(target.length + 1)
177 + " -> " + this.linkpath )
181 // Note: This mirrors logic in the fs read operations that are
182 // employed during tarball creation, in the fstream-npm module.
183 // It is duplicated here to handle tarballs that are created
184 // using other means, such as system tar or git archive.
185 if (this.type === "File") {
186 var base = path.basename(this.path)
187 if (base === ".npmignore") {
188 sawIgnores[ this.path ] = true
189 } else if (base === ".gitignore") {
190 var npmignore = this.path.replace(/\.gitignore$/, ".npmignore")
191 if (sawIgnores[npmignore]) {
192 // Skip this one, already seen.
195 // Rename, may be clobbered later.
196 this.path = npmignore
197 this._path = npmignore
207 .on("error", function (er) {
208 if (er) log.error("tar.unpack", "error reading "+tarball)
211 .on("data", function OD (c) {
212 // detect what it is.
213 // Then, depending on that, we'll figure out whether it's
214 // a single-file module, gzipped tarball, or naked tarball.
215 // gzipped files all start with 1f8b08
221 .on("error", function (er) {
222 if (er) log.error("tar.unpack", "unzip error "+tarball)
225 .pipe(tar.Extract(extractOpts))
226 .on("entry", extractEntry)
227 .on("error", function (er) {
228 if (er) log.error("tar.unpack", "untar error "+tarball)
232 } else if (hasTarHeader(c)) {
235 .pipe(tar.Extract(extractOpts))
236 .on("entry", extractEntry)
237 .on("error", function (er) {
238 if (er) log.error("tar.unpack", "untar error "+tarball)
244 var jsOpts = { path: path.resolve(target, "index.js") }
246 if (process.platform !== "win32" &&
247 typeof uid === "number" &&
248 typeof gid === "number") {
254 .pipe(fstream.Writer(jsOpts))
255 .on("error", function (er) {
256 if (er) log.error("tar.unpack", "copy error "+tarball)
259 .on("close", function () {
260 var j = path.resolve(target, "package.json")
261 readJson(j, function (er, d) {
263 log.error("not a package", tarball)
266 writeFileAtomic(j, JSON.stringify(d) + "\n", cb)
271 // now un-hook, and re-emit the chunk
272 fst.removeListener("data", OD)
277 function hasTarHeader (c) {
278 return c[257] === 0x75 && // tar archives have 7573746172 at position
279 c[258] === 0x73 && // 257 and 003030 or 202000 at position 262