@@ -12,6 +12,13 @@ const updateMfsRoot = require('./utils/update-mfs-root')
1212const { DAGNode } = require ( 'ipld-dag-pb' )
1313const mc = require ( 'multicodec' )
1414const mh = require ( 'multihashes' )
15+ const pipe = require ( 'it-pipe' )
16+ const importer = require ( 'ipfs-unixfs-importer' )
17+ const exporter = require ( 'ipfs-unixfs-exporter' )
18+ const last = require ( 'it-last' )
19+ const cp = require ( './cp' )
20+ const rm = require ( './rm' )
21+ const persist = require ( 'ipfs-unixfs-importer/src/utils/persist' )
1522
1623const defaultOptions = {
1724 flush : true ,
@@ -21,10 +28,10 @@ const defaultOptions = {
2128 recursive : false
2229}
2330
24- function calculateModification ( mode ) {
31+ function calculateModification ( mode , originalMode , isDirectory ) {
2532 let modification = 0
2633
27- if ( mode . includes ( 'x' ) ) {
34+ if ( mode . includes ( 'x' ) || ( mode . includes ( 'X' ) && ( isDirectory || ( originalMode & 0o1 || originalMode & 0o10 || originalMode & 0o100 ) ) ) ) {
2835 modification += 1
2936 }
3037
@@ -76,7 +83,7 @@ function calculateSpecial (references, mode, modification) {
7683}
7784
7885// https://en.wikipedia.org/wiki/Chmod#Symbolic_modes
79- function parseSymbolicMode ( input , originalMode ) {
86+ function parseSymbolicMode ( input , originalMode , isDirectory ) {
8087 if ( ! originalMode ) {
8188 originalMode = 0
8289 }
@@ -98,7 +105,7 @@ function parseSymbolicMode (input, originalMode) {
98105 references = 'ugo'
99106 }
100107
101- let modification = calculateModification ( mode )
108+ let modification = calculateModification ( mode , originalMode , isDirectory )
102109 modification = calculateUGO ( references , modification )
103110 modification = calculateSpecial ( references , mode , modification )
104111
@@ -139,6 +146,20 @@ function parseSymbolicMode (input, originalMode) {
139146 }
140147}
141148
149+ function calculateMode ( mode , metadata ) {
150+ if ( typeof mode === 'string' || mode instanceof String ) {
151+ if ( mode . match ( / ^ \d + $ / g) ) {
152+ mode = parseInt ( mode , 8 )
153+ } else {
154+ mode = mode . split ( ',' ) . reduce ( ( curr , acc ) => {
155+ return parseSymbolicMode ( acc , curr , metadata . isDirectory ( ) )
156+ } , metadata . mode )
157+ }
158+ }
159+
160+ return mode
161+ }
162+
142163module . exports = ( context ) => {
143164 return async function mfsChmod ( path , mode , options ) {
144165 options = applyDefaultOptions ( options , defaultOptions )
@@ -155,20 +176,54 @@ module.exports = (context) => {
155176 throw errCode ( new Error ( `${ path } was not a UnixFS node` ) , 'ERR_NOT_UNIXFS' )
156177 }
157178
158- let node = await context . ipld . get ( cid )
159- const metadata = UnixFS . unmarshal ( node . Data )
160-
161- if ( typeof mode === 'string' || mode instanceof String ) {
162- if ( mode . match ( / ^ \d + $ / g) ) {
163- mode = parseInt ( mode , 8 )
164- } else {
165- mode = mode . split ( ',' ) . reduce ( ( curr , acc ) => {
166- return parseSymbolicMode ( acc , curr )
167- } , metadata . mode )
168- }
179+ if ( options . recursive ) {
180+ // recursively export from root CID, change perms of each entry then reimport
181+ // but do not reimport files, only manipulate dag-pb nodes
182+ const root = await pipe (
183+ async function * ( ) {
184+ for await ( const entry of exporter . recursive ( cid , context . ipld ) ) {
185+ let node = await context . ipld . get ( entry . cid )
186+ entry . unixfs . mode = calculateMode ( mode , entry . unixfs )
187+ node = new DAGNode ( entry . unixfs . marshal ( ) , node . Links )
188+
189+ yield {
190+ path : entry . path ,
191+ content : node
192+ }
193+ }
194+ } ,
195+ ( source ) => importer ( source , context . ipld , {
196+ ...options ,
197+ dagBuilder : async function * ( source , ipld , options ) {
198+ for await ( const entry of source ) {
199+ yield async function ( ) {
200+ const cid = await persist ( entry . content , ipld , options )
201+
202+ return {
203+ cid,
204+ path : entry . path ,
205+ unixfs : UnixFS . unmarshal ( entry . content . Data ) ,
206+ node : entry . content
207+ }
208+ }
209+ }
210+ }
211+ } ) ,
212+ ( nodes ) => last ( nodes )
213+ )
214+
215+ // remove old path from mfs
216+ await rm ( context ) ( path , options )
217+
218+ // add newly created tree to mfs at path
219+ await cp ( context ) ( `/ipfs/${ root . cid } ` , path , options )
220+
221+ return
169222 }
170223
171- metadata . mode = mode
224+ let node = await context . ipld . get ( cid )
225+ const metadata = UnixFS . unmarshal ( node . Data )
226+ metadata . mode = calculateMode ( mode , metadata )
172227 node = new DAGNode ( metadata . marshal ( ) , node . Links )
173228
174229 const updatedCid = await context . ipld . put ( node , mc . DAG_PB , {
0 commit comments