Skip to content
This repository was archived by the owner on Mar 10, 2020. It is now read-only.

Commit f3b6e5d

Browse files
authored
feat: adds support for -X symbolic mode and recursive chmod (#73)
* feat: adds support for -X symbolic mode and recursive chmod Also adds tests for `a` symbolic mode, e.g. `a+rwx`, etc. * fix: use pipeline customisation instead of bloating importer * fix: only update mtimes if they have been set previously * fix: return size of node when adding links * test: increase test coverage * chore: update deps * chore: add missing dep
1 parent ec38560 commit f3b6e5d

File tree

13 files changed

+324
-38
lines changed

13 files changed

+324
-38
lines changed

package.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"release": "aegir release",
2222
"release-minor": "aegir release --type minor",
2323
"release-major": "aegir release --type major",
24-
"coverage": "nyc --reporter=text --reporter=lcov npm run test:node",
24+
"coverage": "nyc --reporter=text --reporter=lcov --reporter=html npm run test:node",
2525
"dep-check": "aegir dep-check"
2626
},
2727
"repository": {
@@ -50,6 +50,7 @@
5050
"detect-webworker": "^1.0.0",
5151
"dirty-chai": "^2.0.1",
5252
"form-data": "^3.0.0",
53+
"ipfs-block": "^0.8.1",
5354
"ipfs-block-service": "~0.16.0",
5455
"ipfs-repo": "^0.30.1",
5556
"ipld": "~0.25.0",
@@ -72,11 +73,12 @@
7273
"interface-datastore": "^0.8.0",
7374
"ipfs-multipart": "^0.3.0",
7475
"ipfs-unixfs": "^0.3.0",
75-
"ipfs-unixfs-exporter": "^0.40.0",
76-
"ipfs-unixfs-importer": "^0.43.1",
76+
"ipfs-unixfs-exporter": "^0.41.0",
77+
"ipfs-unixfs-importer": "^0.44.0",
7778
"ipfs-utils": "^0.4.2",
7879
"ipld-dag-pb": "^0.18.0",
7980
"it-last": "^1.0.1",
81+
"it-pipe": "^1.0.1",
8082
"joi-browser": "^13.4.0",
8183
"mortice": "^2.0.0",
8284
"multicodec": "^1.0.0",

src/core/chmod.js

+71-16
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ const updateMfsRoot = require('./utils/update-mfs-root')
1212
const { DAGNode } = require('ipld-dag-pb')
1313
const mc = require('multicodec')
1414
const 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

1623
const 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+
142163
module.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, {

src/core/cp.js

-4
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ module.exports = (context) => {
3131
throw errCode(new Error('Please supply at least one source'), 'ERR_INVALID_PARAMS')
3232
}
3333

34-
if (!destination) {
35-
throw errCode(new Error('Please supply a destination'), 'ERR_INVALID_PARAMS')
36-
}
37-
3834
options.parents = options.p || options.parents
3935

4036
// make sure all sources exist

src/core/mkdir.js

-2
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,6 @@ const addEmptyDir = async (context, childName, emptyDir, parent, trail, options)
117117
name: childName,
118118
hashAlg: options.hashAlg,
119119
cidVersion: options.cidVersion,
120-
mode: options.mode,
121-
mtime: options.mtime,
122120
flush: options.flush
123121
})
124122

src/core/stat.js

+13-4
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const statters = {
7474
if (file.unixfs) {
7575
output.size = file.unixfs.fileSize()
7676
output.type = file.unixfs.type
77+
output.mode = file.unixfs.mode
7778

7879
if (file.unixfs.isDirectory()) {
7980
output.size = 0
@@ -87,10 +88,6 @@ const statters = {
8788
if (file.unixfs.mtime) {
8889
output.mtime = file.unixfs.mtime
8990
}
90-
91-
if (file.unixfs.mode !== undefined && file.unixfs.mode !== null) {
92-
output.mode = file.unixfs.mode
93-
}
9491
}
9592

9693
return output
@@ -102,5 +99,17 @@ const statters = {
10299
sizeLocal: undefined,
103100
withLocality: false
104101
}
102+
},
103+
identity: (file) => {
104+
return {
105+
cid: file.cid,
106+
size: file.node.digest.length,
107+
cumulativeSize: file.node.digest.length,
108+
blocks: 0,
109+
type: 'file', // for go compatibility
110+
local: undefined,
111+
sizeLocal: undefined,
112+
withLocality: false
113+
}
105114
}
106115
}

src/core/utils/add-link.js

+16-6
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,14 @@ const addToDirectory = async (context, options) => {
9494
options.parent.rmLink(options.name)
9595
options.parent.addLink(new DAGLink(options.name, options.size, options.cid))
9696

97-
// Update mtime
9897
const node = UnixFS.unmarshal(options.parent.Data)
99-
node.mtime = new Date()
100-
options.parent = new DAGNode(node.marshal(), options.parent.Links)
98+
99+
if (node.mtime) {
100+
// Update mtime if previously set
101+
node.mtime = new Date()
102+
103+
options.parent = new DAGNode(node.marshal(), options.parent.Links)
104+
}
101105

102106
const hashAlg = mh.names[options.hashAlg]
103107

@@ -110,7 +114,8 @@ const addToDirectory = async (context, options) => {
110114

111115
return {
112116
node: options.parent,
113-
cid
117+
cid,
118+
size: options.parent.size
114119
}
115120
}
116121

@@ -120,12 +125,13 @@ const addToShardedDirectory = async (context, options) => {
120125
} = await addFileToShardedDirectory(context, options)
121126

122127
const result = await last(shard.flush('', context.ipld))
128+
const node = await context.ipld.get(result.cid)
123129

124130
// we have written out the shard, but only one sub-shard will have been written so replace it in the original shard
125131
const oldLink = options.parent.Links
126132
.find(link => link.Name.substring(0, 2) === path[0].prefix)
127133

128-
const newLink = result.node.Links
134+
const newLink = node.Links
129135
.find(link => link.Name.substring(0, 2) === path[0].prefix)
130136

131137
if (oldLink) {
@@ -159,7 +165,11 @@ const addFileToShardedDirectory = async (context, options) => {
159165
mode: node.mode
160166
}, options)
161167
shard._bucket = rootBucket
162-
shard.mtime = new Date()
168+
169+
if (node.mtime) {
170+
// update mtime if previously set
171+
shard.mtime = new Date()
172+
}
163173

164174
// load subshards until the bucket & position no longer changes
165175
const position = await rootBucket._findNewBucketAndPos(file.name)

src/core/utils/hamt-utils.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ const updateHamtDirectory = async (context, links, bucket, options) => {
3434

3535
return {
3636
node: parent,
37-
cid
37+
cid,
38+
size: parent.size
3839
}
3940
}
4041

src/core/utils/update-tree.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const updateTree = async (context, trail, options) => {
4747
child = {
4848
cid: result.cid,
4949
name,
50-
size: result.node.size
50+
size: result.size
5151
}
5252
}
5353

0 commit comments

Comments
 (0)