@@ -2701,3 +2701,182 @@ t.test('using strip option when top level file exists', t => {
27012701 check ( t , path )
27022702 } )
27032703} )
2704+
2705+ t . test ( 'handle EPERMs when creating symlinks' , t => {
2706+ // https://github.com/npm/node-tar/issues/265
2707+ const msg = 'You do not have sufficient privilege to perform this operation.'
2708+ const er = Object . assign ( new Error ( msg ) , {
2709+ code : 'EPERM' ,
2710+ } )
2711+ t . teardown ( mutateFS . fail ( 'symlink' , er ) )
2712+ const data = makeTar ( [
2713+ {
2714+ path : 'x' ,
2715+ type : 'Directory' ,
2716+ } ,
2717+ {
2718+ path : 'x/y' ,
2719+ type : 'File' ,
2720+ size : 'hello, world' . length ,
2721+ } ,
2722+ 'hello, world' ,
2723+ {
2724+ path : 'x/link1' ,
2725+ type : 'SymbolicLink' ,
2726+ linkpath : './y' ,
2727+ } ,
2728+ {
2729+ path : 'x/link2' ,
2730+ type : 'SymbolicLink' ,
2731+ linkpath : './y' ,
2732+ } ,
2733+ {
2734+ path : 'x/link3' ,
2735+ type : 'SymbolicLink' ,
2736+ linkpath : './y' ,
2737+ } ,
2738+ {
2739+ path : 'x/z' ,
2740+ type : 'File' ,
2741+ size : 'hello, world' . length ,
2742+ } ,
2743+ 'hello, world' ,
2744+ '' ,
2745+ '' ,
2746+ ] )
2747+
2748+ const dir = path . resolve ( unpackdir , 'eperm-symlinks' )
2749+ mkdirp . sync ( `${ dir } /sync` )
2750+ mkdirp . sync ( `${ dir } /async` )
2751+
2752+ const check = path => {
2753+ t . match ( WARNINGS , [
2754+ [ 'TAR_ENTRY_ERROR' , msg ] ,
2755+ [ 'TAR_ENTRY_ERROR' , msg ] ,
2756+ [ 'TAR_ENTRY_ERROR' , msg ] ,
2757+ ] , 'got expected warnings' )
2758+ t . equal ( WARNINGS . length , 3 )
2759+ WARNINGS . length = 0
2760+ t . equal ( fs . readFileSync ( `${ path } /x/y` , 'utf8' ) , 'hello, world' )
2761+ t . equal ( fs . readFileSync ( `${ path } /x/z` , 'utf8' ) , 'hello, world' )
2762+ t . throws ( ( ) => fs . statSync ( `${ path } /x/link1` ) , { code : 'ENOENT' } )
2763+ t . throws ( ( ) => fs . statSync ( `${ path } /x/link2` ) , { code : 'ENOENT' } )
2764+ t . throws ( ( ) => fs . statSync ( `${ path } /x/link3` ) , { code : 'ENOENT' } )
2765+ }
2766+
2767+ const WARNINGS = [ ]
2768+ const u = new Unpack ( {
2769+ cwd : `${ dir } /async` ,
2770+ onwarn : ( code , msg , er ) => WARNINGS . push ( [ code , msg ] ) ,
2771+ } )
2772+ u . on ( 'end' , ( ) => {
2773+ check ( `${ dir } /async` )
2774+ const u = new UnpackSync ( {
2775+ cwd : `${ dir } /sync` ,
2776+ onwarn : ( code , msg , er ) => WARNINGS . push ( [ code , msg ] ) ,
2777+ } )
2778+ u . end ( data )
2779+ check ( `${ dir } /sync` )
2780+ t . end ( )
2781+ } )
2782+ u . end ( data )
2783+ } )
2784+
2785+ t . test ( 'close fd when error writing' , t => {
2786+ const data = makeTar ( [
2787+ {
2788+ type : 'Directory' ,
2789+ path : 'x' ,
2790+ } ,
2791+ {
2792+ type : 'File' ,
2793+ size : 1 ,
2794+ path : 'x/y' ,
2795+ } ,
2796+ '.' ,
2797+ '' ,
2798+ '' ,
2799+ ] )
2800+ t . teardown ( mutateFS . fail ( 'write' , new Error ( 'nope' ) ) )
2801+ const CLOSES = [ ]
2802+ const OPENS = { }
2803+ const { open} = require ( 'fs' )
2804+ t . teardown ( ( ) => fs . open = open )
2805+ fs . open = ( ...args ) => {
2806+ const cb = args . pop ( )
2807+ args . push ( ( er , fd ) => {
2808+ OPENS [ args [ 0 ] ] = fd
2809+ cb ( er , fd )
2810+ } )
2811+ return open . call ( fs , ...args )
2812+ }
2813+ t . teardown ( mutateFS . mutateArgs ( 'close' , ( [ fd ] ) => {
2814+ CLOSES . push ( fd )
2815+ return [ fd ]
2816+ } ) )
2817+ const WARNINGS = [ ]
2818+ const dir = path . resolve ( unpackdir , 'close-on-write-error' )
2819+ mkdirp . sync ( dir )
2820+ const unpack = new Unpack ( {
2821+ cwd : dir ,
2822+ onwarn : ( code , msg ) => WARNINGS . push ( [ code , msg ] ) ,
2823+ } )
2824+ unpack . on ( 'end' , ( ) => {
2825+ for ( const [ path , fd ] of Object . entries ( OPENS ) )
2826+ t . equal ( CLOSES . includes ( fd ) , true , 'closed fd for ' + path )
2827+ t . end ( )
2828+ } )
2829+ unpack . end ( data )
2830+ } )
2831+
2832+ t . test ( 'close fd when error setting mtime' , t => {
2833+ const data = makeTar ( [
2834+ {
2835+ type : 'Directory' ,
2836+ path : 'x' ,
2837+ } ,
2838+ {
2839+ type : 'File' ,
2840+ size : 1 ,
2841+ path : 'x/y' ,
2842+ atime : new Date ( '1979-07-01T19:10:00.000Z' ) ,
2843+ ctime : new Date ( '2011-03-27T22:16:31.000Z' ) ,
2844+ mtime : new Date ( '2011-03-27T22:16:31.000Z' ) ,
2845+ } ,
2846+ '.' ,
2847+ '' ,
2848+ '' ,
2849+ ] )
2850+ // have to clobber these both, because we fall back
2851+ t . teardown ( mutateFS . fail ( 'futimes' , new Error ( 'nope' ) ) )
2852+ t . teardown ( mutateFS . fail ( 'utimes' , new Error ( 'nooooope' ) ) )
2853+ const CLOSES = [ ]
2854+ const OPENS = { }
2855+ const { open} = require ( 'fs' )
2856+ t . teardown ( ( ) => fs . open = open )
2857+ fs . open = ( ...args ) => {
2858+ const cb = args . pop ( )
2859+ args . push ( ( er , fd ) => {
2860+ OPENS [ args [ 0 ] ] = fd
2861+ cb ( er , fd )
2862+ } )
2863+ return open . call ( fs , ...args )
2864+ }
2865+ t . teardown ( mutateFS . mutateArgs ( 'close' , ( [ fd ] ) => {
2866+ CLOSES . push ( fd )
2867+ return [ fd ]
2868+ } ) )
2869+ const WARNINGS = [ ]
2870+ const dir = path . resolve ( unpackdir , 'close-on-futimes-error' )
2871+ mkdirp . sync ( dir )
2872+ const unpack = new Unpack ( {
2873+ cwd : dir ,
2874+ onwarn : ( code , msg ) => WARNINGS . push ( [ code , msg ] ) ,
2875+ } )
2876+ unpack . on ( 'end' , ( ) => {
2877+ for ( const [ path , fd ] of Object . entries ( OPENS ) )
2878+ t . equal ( CLOSES . includes ( fd ) , true , 'closed fd for ' + path )
2879+ t . end ( )
2880+ } )
2881+ unpack . end ( data )
2882+ } )
0 commit comments