master
go 356 lines 6.7 KB
Raw
1 package rpc
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io"
9 "os"
10 "strconv"
11 "time"
12
13 "github.com/ipfs/boxo/files"
14 unixfs "github.com/ipfs/boxo/ipld/unixfs"
15 "github.com/ipfs/boxo/path"
16 "github.com/ipfs/go-cid"
17 )
18
19 const forwardSeekLimit = 1 << 14 // 16k
20
21 func (api *UnixfsAPI) Get(ctx context.Context, p path.Path) (files.Node, error) {
22 if p.Mutable() { // use resolved path in case we are dealing with IPNS / MFS
23 var err error
24 p, _, err = api.core().ResolvePath(ctx, p)
25 if err != nil {
26 return nil, err
27 }
28 }
29
30 var stat struct {
31 Hash string
32 Type string
33 Size int64 // unixfs size
34 Mode string
35 Mtime int64
36 MtimeNsecs int
37 }
38 err := api.core().Request("files/stat", p.String()).Exec(ctx, &stat)
39 if err != nil {
40 return nil, err
41 }
42
43 mode, err := stringToFileMode(stat.Mode)
44 if err != nil {
45 return nil, err
46 }
47
48 var modTime time.Time
49 if stat.Mtime != 0 {
50 modTime = time.Unix(stat.Mtime, int64(stat.MtimeNsecs)).UTC()
51 }
52
53 switch stat.Type {
54 case "file":
55 return api.getFile(ctx, p, stat.Size, mode, modTime)
56 case "directory":
57 return api.getDir(ctx, p, stat.Size, mode, modTime)
58 case "symlink":
59 return api.getSymlink(ctx, p, modTime)
60 default:
61 return nil, fmt.Errorf("unsupported file type '%s'", stat.Type)
62 }
63 }
64
65 type apiFile struct {
66 ctx context.Context
67 core *HttpApi
68 size int64
69 path path.Path
70
71 mode os.FileMode
72 mtime time.Time
73
74 r *Response
75 at int64
76 }
77
78 func (f *apiFile) reset() error {
79 if f.r != nil {
80 _ = f.r.Cancel()
81 f.r = nil
82 }
83 req := f.core.Request("cat", f.path.String())
84 if f.at != 0 {
85 req.Option("offset", f.at)
86 }
87 resp, err := req.Send(f.ctx)
88 if err != nil {
89 return err
90 }
91 if resp.Error != nil {
92 return resp.Error
93 }
94 f.r = resp
95 return nil
96 }
97
98 func (f *apiFile) Read(p []byte) (int, error) {
99 n, err := f.r.Output.Read(p)
100 if n > 0 {
101 f.at += int64(n)
102 }
103 return n, err
104 }
105
106 func (f *apiFile) ReadAt(p []byte, off int64) (int, error) {
107 // Always make a new request. This method should be parallel-safe.
108 resp, err := f.core.Request("cat", f.path.String()).
109 Option("offset", off).Option("length", len(p)).Send(f.ctx)
110 if err != nil {
111 return 0, err
112 }
113 if resp.Error != nil {
114 return 0, resp.Error
115 }
116 defer resp.Output.Close()
117
118 n, err := io.ReadFull(resp.Output, p)
119 if err == io.ErrUnexpectedEOF {
120 err = io.EOF
121 }
122 return n, err
123 }
124
125 func (f *apiFile) Seek(offset int64, whence int) (int64, error) {
126 switch whence {
127 case io.SeekEnd:
128 offset = f.size + offset
129 case io.SeekCurrent:
130 offset = f.at + offset
131 }
132 if f.at == offset { // noop
133 return offset, nil
134 }
135
136 if f.at < offset && offset-f.at < forwardSeekLimit { // forward skip
137 r, err := io.CopyN(io.Discard, f.r.Output, offset-f.at)
138
139 f.at += r
140 return f.at, err
141 }
142 f.at = offset
143 return f.at, f.reset()
144 }
145
146 func (f *apiFile) Close() error {
147 if f.r != nil {
148 return f.r.Cancel()
149 }
150 return nil
151 }
152
153 func (f *apiFile) Mode() os.FileMode {
154 return f.mode
155 }
156
157 func (f *apiFile) ModTime() time.Time {
158 return f.mtime
159 }
160
161 func (f *apiFile) Size() (int64, error) {
162 return f.size, nil
163 }
164
165 func stringToFileMode(mode string) (os.FileMode, error) {
166 if mode == "" {
167 return 0, nil
168 }
169 mode64, err := strconv.ParseUint(mode, 8, 32)
170 if err != nil {
171 return 0, fmt.Errorf("cannot parse mode %s: %s", mode, err)
172 }
173 return os.FileMode(uint32(mode64)), nil
174 }
175
176 func (api *UnixfsAPI) getFile(ctx context.Context, p path.Path, size int64, mode os.FileMode, mtime time.Time) (files.Node, error) {
177 f := &apiFile{
178 ctx: ctx,
179 core: api.core(),
180 size: size,
181 path: p,
182 mode: mode,
183 mtime: mtime,
184 }
185
186 return f, f.reset()
187 }
188
189 type apiIter struct {
190 ctx context.Context
191 core *UnixfsAPI
192
193 err error
194
195 dec *json.Decoder
196 curFile files.Node
197 cur lsLink
198 }
199
200 func (it *apiIter) Err() error {
201 return it.err
202 }
203
204 func (it *apiIter) Name() string {
205 return it.cur.Name
206 }
207
208 func (it *apiIter) Next() bool {
209 if it.ctx.Err() != nil {
210 it.err = it.ctx.Err()
211 return false
212 }
213
214 var out lsOutput
215 if err := it.dec.Decode(&out); err != nil {
216 if err != io.EOF {
217 it.err = err
218 }
219 return false
220 }
221
222 if len(out.Objects) != 1 {
223 it.err = fmt.Errorf("ls returned more objects than expected (%d)", len(out.Objects))
224 return false
225 }
226
227 if len(out.Objects[0].Links) != 1 {
228 it.err = fmt.Errorf("ls returned more links than expected (%d)", len(out.Objects[0].Links))
229 return false
230 }
231
232 it.cur = out.Objects[0].Links[0]
233 c, err := cid.Parse(it.cur.Hash)
234 if err != nil {
235 it.err = err
236 return false
237 }
238
239 switch it.cur.Type {
240 case unixfs.THAMTShard, unixfs.TMetadata, unixfs.TDirectory:
241 it.curFile, err = it.core.getDir(it.ctx, path.FromCid(c), int64(it.cur.Size), it.cur.Mode, it.cur.ModTime)
242 if err != nil {
243 it.err = err
244 return false
245 }
246 case unixfs.TFile:
247 it.curFile, err = it.core.getFile(it.ctx, path.FromCid(c), int64(it.cur.Size), it.cur.Mode, it.cur.ModTime)
248 if err != nil {
249 it.err = err
250 return false
251 }
252 case unixfs.TSymlink:
253 it.curFile, err = it.core.getSymlink(it.ctx, path.FromCid(c), it.cur.ModTime)
254 if err != nil {
255 it.err = err
256 return false
257 }
258 default:
259 it.err = fmt.Errorf("file type %d not supported", it.cur.Type)
260 return false
261 }
262 return true
263 }
264
265 func (it *apiIter) Node() files.Node {
266 return it.curFile
267 }
268
269 type apiDir struct {
270 ctx context.Context
271 core *UnixfsAPI
272 size int64
273 path path.Path
274
275 mode os.FileMode
276 mtime time.Time
277
278 dec *json.Decoder
279 }
280
281 func (d *apiDir) Close() error {
282 return nil
283 }
284
285 func (d *apiDir) Mode() os.FileMode {
286 return d.mode
287 }
288
289 func (d *apiDir) ModTime() time.Time {
290 return d.mtime
291 }
292
293 func (d *apiDir) Size() (int64, error) {
294 return d.size, nil
295 }
296
297 func (d *apiDir) Entries() files.DirIterator {
298 return &apiIter{
299 ctx: d.ctx,
300 core: d.core,
301 dec: d.dec,
302 }
303 }
304
305 func (api *UnixfsAPI) getDir(ctx context.Context, p path.Path, size int64, mode os.FileMode, modTime time.Time) (files.Node, error) {
306 resp, err := api.core().Request("ls", p.String()).
307 Option("resolve-size", true).
308 Option("stream", true).Send(ctx)
309 if err != nil {
310 return nil, err
311 }
312 if resp.Error != nil {
313 return nil, resp.Error
314 }
315
316 data, _ := io.ReadAll(resp.Output)
317 rdr := bytes.NewReader(data)
318
319 d := &apiDir{
320 ctx: ctx,
321 core: api,
322 size: size,
323 path: p,
324 mode: mode,
325 mtime: modTime,
326
327 //dec: json.NewDecoder(resp.Output),
328 dec: json.NewDecoder(rdr),
329 }
330
331 return d, nil
332 }
333
334 func (api *UnixfsAPI) getSymlink(ctx context.Context, p path.Path, modTime time.Time) (files.Node, error) {
335 resp, err := api.core().Request("cat", p.String()).
336 Option("resolve-size", true).
337 Option("stream", true).Send(ctx)
338 if err != nil {
339 return nil, err
340 }
341 if resp.Error != nil {
342 return nil, resp.Error
343 }
344
345 target, err := io.ReadAll(resp.Output)
346 if err != nil {
347 return nil, err
348 }
349
350 return files.NewSymlinkFile(string(target), modTime), nil
351 }
352
353 var (
354 _ files.File = &apiFile{}
355 _ files.Directory = &apiDir{}
356 )