source: node_modules/tar/lib/write-entry.js@ 81bc7da

finki-main main
Last change on this file since 81bc7da was 81bc7da, checked in by Klimentina Efremova <klimentina08642@…>, 3 months ago

Initial commit

  • Property mode set to 100644
File size: 14.9 KB
Line 
1'use strict'
2const { Minipass } = require('minipass')
3const Pax = require('./pax.js')
4const Header = require('./header.js')
5const fs = require('fs')
6const path = require('path')
7const normPath = require('./normalize-windows-path.js')
8const stripSlash = require('./strip-trailing-slashes.js')
9
10const prefixPath = (path, prefix) => {
11 if (!prefix) {
12 return normPath(path)
13 }
14 path = normPath(path).replace(/^\.(\/|$)/, '')
15 return stripSlash(prefix) + '/' + path
16}
17
18const maxReadSize = 16 * 1024 * 1024
19const PROCESS = Symbol('process')
20const FILE = Symbol('file')
21const DIRECTORY = Symbol('directory')
22const SYMLINK = Symbol('symlink')
23const HARDLINK = Symbol('hardlink')
24const HEADER = Symbol('header')
25const READ = Symbol('read')
26const LSTAT = Symbol('lstat')
27const ONLSTAT = Symbol('onlstat')
28const ONREAD = Symbol('onread')
29const ONREADLINK = Symbol('onreadlink')
30const OPENFILE = Symbol('openfile')
31const ONOPENFILE = Symbol('onopenfile')
32const CLOSE = Symbol('close')
33const MODE = Symbol('mode')
34const AWAITDRAIN = Symbol('awaitDrain')
35const ONDRAIN = Symbol('ondrain')
36const PREFIX = Symbol('prefix')
37const HAD_ERROR = Symbol('hadError')
38const warner = require('./warn-mixin.js')
39const winchars = require('./winchars.js')
40const stripAbsolutePath = require('./strip-absolute-path.js')
41
42const modeFix = require('./mode-fix.js')
43
44const WriteEntry = warner(class WriteEntry extends Minipass {
45 constructor (p, opt) {
46 opt = opt || {}
47 super(opt)
48 if (typeof p !== 'string') {
49 throw new TypeError('path is required')
50 }
51 this.path = normPath(p)
52 // suppress atime, ctime, uid, gid, uname, gname
53 this.portable = !!opt.portable
54 // until node has builtin pwnam functions, this'll have to do
55 this.myuid = process.getuid && process.getuid() || 0
56 this.myuser = process.env.USER || ''
57 this.maxReadSize = opt.maxReadSize || maxReadSize
58 this.linkCache = opt.linkCache || new Map()
59 this.statCache = opt.statCache || new Map()
60 this.preservePaths = !!opt.preservePaths
61 this.cwd = normPath(opt.cwd || process.cwd())
62 this.strict = !!opt.strict
63 this.noPax = !!opt.noPax
64 this.noMtime = !!opt.noMtime
65 this.mtime = opt.mtime || null
66 this.prefix = opt.prefix ? normPath(opt.prefix) : null
67
68 this.fd = null
69 this.blockLen = null
70 this.blockRemain = null
71 this.buf = null
72 this.offset = null
73 this.length = null
74 this.pos = null
75 this.remain = null
76
77 if (typeof opt.onwarn === 'function') {
78 this.on('warn', opt.onwarn)
79 }
80
81 let pathWarn = false
82 if (!this.preservePaths) {
83 const [root, stripped] = stripAbsolutePath(this.path)
84 if (root) {
85 this.path = stripped
86 pathWarn = root
87 }
88 }
89
90 this.win32 = !!opt.win32 || process.platform === 'win32'
91 if (this.win32) {
92 // force the \ to / normalization, since we might not *actually*
93 // be on windows, but want \ to be considered a path separator.
94 this.path = winchars.decode(this.path.replace(/\\/g, '/'))
95 p = p.replace(/\\/g, '/')
96 }
97
98 this.absolute = normPath(opt.absolute || path.resolve(this.cwd, p))
99
100 if (this.path === '') {
101 this.path = './'
102 }
103
104 if (pathWarn) {
105 this.warn('TAR_ENTRY_INFO', `stripping ${pathWarn} from absolute path`, {
106 entry: this,
107 path: pathWarn + this.path,
108 })
109 }
110
111 if (this.statCache.has(this.absolute)) {
112 this[ONLSTAT](this.statCache.get(this.absolute))
113 } else {
114 this[LSTAT]()
115 }
116 }
117
118 emit (ev, ...data) {
119 if (ev === 'error') {
120 this[HAD_ERROR] = true
121 }
122 return super.emit(ev, ...data)
123 }
124
125 [LSTAT] () {
126 fs.lstat(this.absolute, (er, stat) => {
127 if (er) {
128 return this.emit('error', er)
129 }
130 this[ONLSTAT](stat)
131 })
132 }
133
134 [ONLSTAT] (stat) {
135 this.statCache.set(this.absolute, stat)
136 this.stat = stat
137 if (!stat.isFile()) {
138 stat.size = 0
139 }
140 this.type = getType(stat)
141 this.emit('stat', stat)
142 this[PROCESS]()
143 }
144
145 [PROCESS] () {
146 switch (this.type) {
147 case 'File': return this[FILE]()
148 case 'Directory': return this[DIRECTORY]()
149 case 'SymbolicLink': return this[SYMLINK]()
150 // unsupported types are ignored.
151 default: return this.end()
152 }
153 }
154
155 [MODE] (mode) {
156 return modeFix(mode, this.type === 'Directory', this.portable)
157 }
158
159 [PREFIX] (path) {
160 return prefixPath(path, this.prefix)
161 }
162
163 [HEADER] () {
164 if (this.type === 'Directory' && this.portable) {
165 this.noMtime = true
166 }
167
168 this.header = new Header({
169 path: this[PREFIX](this.path),
170 // only apply the prefix to hard links.
171 linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath)
172 : this.linkpath,
173 // only the permissions and setuid/setgid/sticky bitflags
174 // not the higher-order bits that specify file type
175 mode: this[MODE](this.stat.mode),
176 uid: this.portable ? null : this.stat.uid,
177 gid: this.portable ? null : this.stat.gid,
178 size: this.stat.size,
179 mtime: this.noMtime ? null : this.mtime || this.stat.mtime,
180 type: this.type,
181 uname: this.portable ? null :
182 this.stat.uid === this.myuid ? this.myuser : '',
183 atime: this.portable ? null : this.stat.atime,
184 ctime: this.portable ? null : this.stat.ctime,
185 })
186
187 if (this.header.encode() && !this.noPax) {
188 super.write(new Pax({
189 atime: this.portable ? null : this.header.atime,
190 ctime: this.portable ? null : this.header.ctime,
191 gid: this.portable ? null : this.header.gid,
192 mtime: this.noMtime ? null : this.mtime || this.header.mtime,
193 path: this[PREFIX](this.path),
194 linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath)
195 : this.linkpath,
196 size: this.header.size,
197 uid: this.portable ? null : this.header.uid,
198 uname: this.portable ? null : this.header.uname,
199 dev: this.portable ? null : this.stat.dev,
200 ino: this.portable ? null : this.stat.ino,
201 nlink: this.portable ? null : this.stat.nlink,
202 }).encode())
203 }
204 super.write(this.header.block)
205 }
206
207 [DIRECTORY] () {
208 if (this.path.slice(-1) !== '/') {
209 this.path += '/'
210 }
211 this.stat.size = 0
212 this[HEADER]()
213 this.end()
214 }
215
216 [SYMLINK] () {
217 fs.readlink(this.absolute, (er, linkpath) => {
218 if (er) {
219 return this.emit('error', er)
220 }
221 this[ONREADLINK](linkpath)
222 })
223 }
224
225 [ONREADLINK] (linkpath) {
226 this.linkpath = normPath(linkpath)
227 this[HEADER]()
228 this.end()
229 }
230
231 [HARDLINK] (linkpath) {
232 this.type = 'Link'
233 this.linkpath = normPath(path.relative(this.cwd, linkpath))
234 this.stat.size = 0
235 this[HEADER]()
236 this.end()
237 }
238
239 [FILE] () {
240 if (this.stat.nlink > 1) {
241 const linkKey = this.stat.dev + ':' + this.stat.ino
242 if (this.linkCache.has(linkKey)) {
243 const linkpath = this.linkCache.get(linkKey)
244 if (linkpath.indexOf(this.cwd) === 0) {
245 return this[HARDLINK](linkpath)
246 }
247 }
248 this.linkCache.set(linkKey, this.absolute)
249 }
250
251 this[HEADER]()
252 if (this.stat.size === 0) {
253 return this.end()
254 }
255
256 this[OPENFILE]()
257 }
258
259 [OPENFILE] () {
260 fs.open(this.absolute, 'r', (er, fd) => {
261 if (er) {
262 return this.emit('error', er)
263 }
264 this[ONOPENFILE](fd)
265 })
266 }
267
268 [ONOPENFILE] (fd) {
269 this.fd = fd
270 if (this[HAD_ERROR]) {
271 return this[CLOSE]()
272 }
273
274 this.blockLen = 512 * Math.ceil(this.stat.size / 512)
275 this.blockRemain = this.blockLen
276 const bufLen = Math.min(this.blockLen, this.maxReadSize)
277 this.buf = Buffer.allocUnsafe(bufLen)
278 this.offset = 0
279 this.pos = 0
280 this.remain = this.stat.size
281 this.length = this.buf.length
282 this[READ]()
283 }
284
285 [READ] () {
286 const { fd, buf, offset, length, pos } = this
287 fs.read(fd, buf, offset, length, pos, (er, bytesRead) => {
288 if (er) {
289 // ignoring the error from close(2) is a bad practice, but at
290 // this point we already have an error, don't need another one
291 return this[CLOSE](() => this.emit('error', er))
292 }
293 this[ONREAD](bytesRead)
294 })
295 }
296
297 [CLOSE] (cb) {
298 fs.close(this.fd, cb)
299 }
300
301 [ONREAD] (bytesRead) {
302 if (bytesRead <= 0 && this.remain > 0) {
303 const er = new Error('encountered unexpected EOF')
304 er.path = this.absolute
305 er.syscall = 'read'
306 er.code = 'EOF'
307 return this[CLOSE](() => this.emit('error', er))
308 }
309
310 if (bytesRead > this.remain) {
311 const er = new Error('did not encounter expected EOF')
312 er.path = this.absolute
313 er.syscall = 'read'
314 er.code = 'EOF'
315 return this[CLOSE](() => this.emit('error', er))
316 }
317
318 // null out the rest of the buffer, if we could fit the block padding
319 // at the end of this loop, we've incremented bytesRead and this.remain
320 // to be incremented up to the blockRemain level, as if we had expected
321 // to get a null-padded file, and read it until the end. then we will
322 // decrement both remain and blockRemain by bytesRead, and know that we
323 // reached the expected EOF, without any null buffer to append.
324 if (bytesRead === this.remain) {
325 for (let i = bytesRead; i < this.length && bytesRead < this.blockRemain; i++) {
326 this.buf[i + this.offset] = 0
327 bytesRead++
328 this.remain++
329 }
330 }
331
332 const writeBuf = this.offset === 0 && bytesRead === this.buf.length ?
333 this.buf : this.buf.slice(this.offset, this.offset + bytesRead)
334
335 const flushed = this.write(writeBuf)
336 if (!flushed) {
337 this[AWAITDRAIN](() => this[ONDRAIN]())
338 } else {
339 this[ONDRAIN]()
340 }
341 }
342
343 [AWAITDRAIN] (cb) {
344 this.once('drain', cb)
345 }
346
347 write (writeBuf) {
348 if (this.blockRemain < writeBuf.length) {
349 const er = new Error('writing more data than expected')
350 er.path = this.absolute
351 return this.emit('error', er)
352 }
353 this.remain -= writeBuf.length
354 this.blockRemain -= writeBuf.length
355 this.pos += writeBuf.length
356 this.offset += writeBuf.length
357 return super.write(writeBuf)
358 }
359
360 [ONDRAIN] () {
361 if (!this.remain) {
362 if (this.blockRemain) {
363 super.write(Buffer.alloc(this.blockRemain))
364 }
365 return this[CLOSE](er => er ? this.emit('error', er) : this.end())
366 }
367
368 if (this.offset >= this.length) {
369 // if we only have a smaller bit left to read, alloc a smaller buffer
370 // otherwise, keep it the same length it was before.
371 this.buf = Buffer.allocUnsafe(Math.min(this.blockRemain, this.buf.length))
372 this.offset = 0
373 }
374 this.length = this.buf.length - this.offset
375 this[READ]()
376 }
377})
378
379class WriteEntrySync extends WriteEntry {
380 [LSTAT] () {
381 this[ONLSTAT](fs.lstatSync(this.absolute))
382 }
383
384 [SYMLINK] () {
385 this[ONREADLINK](fs.readlinkSync(this.absolute))
386 }
387
388 [OPENFILE] () {
389 this[ONOPENFILE](fs.openSync(this.absolute, 'r'))
390 }
391
392 [READ] () {
393 let threw = true
394 try {
395 const { fd, buf, offset, length, pos } = this
396 const bytesRead = fs.readSync(fd, buf, offset, length, pos)
397 this[ONREAD](bytesRead)
398 threw = false
399 } finally {
400 // ignoring the error from close(2) is a bad practice, but at
401 // this point we already have an error, don't need another one
402 if (threw) {
403 try {
404 this[CLOSE](() => {})
405 } catch (er) {}
406 }
407 }
408 }
409
410 [AWAITDRAIN] (cb) {
411 cb()
412 }
413
414 [CLOSE] (cb) {
415 fs.closeSync(this.fd)
416 cb()
417 }
418}
419
420const WriteEntryTar = warner(class WriteEntryTar extends Minipass {
421 constructor (readEntry, opt) {
422 opt = opt || {}
423 super(opt)
424 this.preservePaths = !!opt.preservePaths
425 this.portable = !!opt.portable
426 this.strict = !!opt.strict
427 this.noPax = !!opt.noPax
428 this.noMtime = !!opt.noMtime
429
430 this.readEntry = readEntry
431 this.type = readEntry.type
432 if (this.type === 'Directory' && this.portable) {
433 this.noMtime = true
434 }
435
436 this.prefix = opt.prefix || null
437
438 this.path = normPath(readEntry.path)
439 this.mode = this[MODE](readEntry.mode)
440 this.uid = this.portable ? null : readEntry.uid
441 this.gid = this.portable ? null : readEntry.gid
442 this.uname = this.portable ? null : readEntry.uname
443 this.gname = this.portable ? null : readEntry.gname
444 this.size = readEntry.size
445 this.mtime = this.noMtime ? null : opt.mtime || readEntry.mtime
446 this.atime = this.portable ? null : readEntry.atime
447 this.ctime = this.portable ? null : readEntry.ctime
448 this.linkpath = normPath(readEntry.linkpath)
449
450 if (typeof opt.onwarn === 'function') {
451 this.on('warn', opt.onwarn)
452 }
453
454 let pathWarn = false
455 if (!this.preservePaths) {
456 const [root, stripped] = stripAbsolutePath(this.path)
457 if (root) {
458 this.path = stripped
459 pathWarn = root
460 }
461 }
462
463 this.remain = readEntry.size
464 this.blockRemain = readEntry.startBlockSize
465
466 this.header = new Header({
467 path: this[PREFIX](this.path),
468 linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath)
469 : this.linkpath,
470 // only the permissions and setuid/setgid/sticky bitflags
471 // not the higher-order bits that specify file type
472 mode: this.mode,
473 uid: this.portable ? null : this.uid,
474 gid: this.portable ? null : this.gid,
475 size: this.size,
476 mtime: this.noMtime ? null : this.mtime,
477 type: this.type,
478 uname: this.portable ? null : this.uname,
479 atime: this.portable ? null : this.atime,
480 ctime: this.portable ? null : this.ctime,
481 })
482
483 if (pathWarn) {
484 this.warn('TAR_ENTRY_INFO', `stripping ${pathWarn} from absolute path`, {
485 entry: this,
486 path: pathWarn + this.path,
487 })
488 }
489
490 if (this.header.encode() && !this.noPax) {
491 super.write(new Pax({
492 atime: this.portable ? null : this.atime,
493 ctime: this.portable ? null : this.ctime,
494 gid: this.portable ? null : this.gid,
495 mtime: this.noMtime ? null : this.mtime,
496 path: this[PREFIX](this.path),
497 linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath)
498 : this.linkpath,
499 size: this.size,
500 uid: this.portable ? null : this.uid,
501 uname: this.portable ? null : this.uname,
502 dev: this.portable ? null : this.readEntry.dev,
503 ino: this.portable ? null : this.readEntry.ino,
504 nlink: this.portable ? null : this.readEntry.nlink,
505 }).encode())
506 }
507
508 super.write(this.header.block)
509 readEntry.pipe(this)
510 }
511
512 [PREFIX] (path) {
513 return prefixPath(path, this.prefix)
514 }
515
516 [MODE] (mode) {
517 return modeFix(mode, this.type === 'Directory', this.portable)
518 }
519
520 write (data) {
521 const writeLen = data.length
522 if (writeLen > this.blockRemain) {
523 throw new Error('writing more to entry than is appropriate')
524 }
525 this.blockRemain -= writeLen
526 return super.write(data)
527 }
528
529 end () {
530 if (this.blockRemain) {
531 super.write(Buffer.alloc(this.blockRemain))
532 }
533 return super.end()
534 }
535})
536
537WriteEntry.Sync = WriteEntrySync
538WriteEntry.Tar = WriteEntryTar
539
540const getType = stat =>
541 stat.isFile() ? 'File'
542 : stat.isDirectory() ? 'Directory'
543 : stat.isSymbolicLink() ? 'SymbolicLink'
544 : 'Unsupported'
545
546module.exports = WriteEntry
Note: See TracBrowser for help on using the repository browser.