Skip to content

Commit 2de21bc

Browse files
Merge pull request #3653 from ipfs/kevina/filestore-util
Basic Filestore Utilties
2 parents a3fc6f1 + 1eddb60 commit 2de21bc

File tree

6 files changed

+625
-7
lines changed

6 files changed

+625
-7
lines changed

core/commands/filestore.go

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
cmds "github.com/ipfs/go-ipfs/commands"
8+
"github.com/ipfs/go-ipfs/core"
9+
"github.com/ipfs/go-ipfs/filestore"
10+
cid "gx/ipfs/QmV5gPoRsjN1Gid3LMdNZTyfCtP2DsvqEbMAmz82RmmiGk/go-cid"
11+
u "gx/ipfs/QmZuY8aV7zbNXVy6DyN9SmnuH3o9nG852F4aTiSBpts8d1/go-ipfs-util"
12+
)
13+
14+
var FileStoreCmd = &cmds.Command{
15+
Helptext: cmds.HelpText{
16+
Tagline: "Interact with filestore objects.",
17+
},
18+
Subcommands: map[string]*cmds.Command{
19+
"ls": lsFileStore,
20+
"verify": verifyFileStore,
21+
"dups": dupsFileStore,
22+
},
23+
}
24+
25+
var lsFileStore = &cmds.Command{
26+
Helptext: cmds.HelpText{
27+
Tagline: "List objects in filestore.",
28+
LongDescription: `
29+
List objects in the filestore.
30+
31+
If one or more <obj> is specified only list those specific objects,
32+
otherwise list all objects.
33+
34+
The output is:
35+
36+
<hash> <size> <path> <offset>
37+
`,
38+
},
39+
Arguments: []cmds.Argument{
40+
cmds.StringArg("obj", false, true, "Cid of objects to list."),
41+
},
42+
Run: func(req cmds.Request, res cmds.Response) {
43+
_, fs, err := getFilestore(req)
44+
if err != nil {
45+
res.SetError(err, cmds.ErrNormal)
46+
return
47+
}
48+
args := req.Arguments()
49+
if len(args) > 0 {
50+
out := perKeyActionToChan(args, func(c *cid.Cid) *filestore.ListRes {
51+
return filestore.List(fs, c)
52+
}, req.Context())
53+
res.SetOutput(out)
54+
} else {
55+
next, err := filestore.ListAll(fs)
56+
if err != nil {
57+
res.SetError(err, cmds.ErrNormal)
58+
return
59+
}
60+
out := listResToChan(next, req.Context())
61+
res.SetOutput(out)
62+
}
63+
},
64+
PostRun: func(req cmds.Request, res cmds.Response) {
65+
if res.Error() != nil {
66+
return
67+
}
68+
outChan, ok := res.Output().(<-chan interface{})
69+
if !ok {
70+
res.SetError(u.ErrCast(), cmds.ErrNormal)
71+
return
72+
}
73+
res.SetOutput(nil)
74+
errors := false
75+
for r0 := range outChan {
76+
r := r0.(*filestore.ListRes)
77+
if r.ErrorMsg != "" {
78+
errors = true
79+
fmt.Fprintf(res.Stderr(), "%s\n", r.ErrorMsg)
80+
} else {
81+
fmt.Fprintf(res.Stdout(), "%s\n", r.FormatLong())
82+
}
83+
}
84+
if errors {
85+
res.SetError(fmt.Errorf("errors while displaying some entries"), cmds.ErrNormal)
86+
}
87+
},
88+
Type: filestore.ListRes{},
89+
}
90+
91+
var verifyFileStore = &cmds.Command{
92+
Helptext: cmds.HelpText{
93+
Tagline: "Verify objects in filestore.",
94+
LongDescription: `
95+
Verify objects in the filestore.
96+
97+
If one or more <obj> is specified only verify those specific objects,
98+
otherwise verify all objects.
99+
100+
The output is:
101+
102+
<status> <hash> <size> <path> <offset>
103+
104+
Where <status> is one of:
105+
ok: the block can be reconstructed
106+
changed: the contents of the backing file have changed
107+
no-file: the backing file could not be found
108+
error: there was some other problem reading the file
109+
missing: <obj> could not be found in the filestore
110+
ERROR: internal error, most likely due to a corrupt database
111+
112+
For ERROR entries the error will also be printed to stderr.
113+
`,
114+
},
115+
Arguments: []cmds.Argument{
116+
cmds.StringArg("obj", false, true, "Cid of objects to verify."),
117+
},
118+
Run: func(req cmds.Request, res cmds.Response) {
119+
_, fs, err := getFilestore(req)
120+
if err != nil {
121+
res.SetError(err, cmds.ErrNormal)
122+
return
123+
}
124+
args := req.Arguments()
125+
if len(args) > 0 {
126+
out := perKeyActionToChan(args, func(c *cid.Cid) *filestore.ListRes {
127+
return filestore.Verify(fs, c)
128+
}, req.Context())
129+
res.SetOutput(out)
130+
} else {
131+
next, err := filestore.VerifyAll(fs)
132+
if err != nil {
133+
res.SetError(err, cmds.ErrNormal)
134+
return
135+
}
136+
out := listResToChan(next, req.Context())
137+
res.SetOutput(out)
138+
}
139+
},
140+
PostRun: func(req cmds.Request, res cmds.Response) {
141+
if res.Error() != nil {
142+
return
143+
}
144+
outChan, ok := res.Output().(<-chan interface{})
145+
if !ok {
146+
res.SetError(u.ErrCast(), cmds.ErrNormal)
147+
return
148+
}
149+
res.SetOutput(nil)
150+
for r0 := range outChan {
151+
r := r0.(*filestore.ListRes)
152+
if r.Status == filestore.StatusOtherError {
153+
fmt.Fprintf(res.Stderr(), "%s\n", r.ErrorMsg)
154+
}
155+
fmt.Fprintf(res.Stdout(), "%s %s\n", r.Status.Format(), r.FormatLong())
156+
}
157+
},
158+
Type: filestore.ListRes{},
159+
}
160+
161+
var dupsFileStore = &cmds.Command{
162+
Helptext: cmds.HelpText{
163+
Tagline: "List blocks that are both in the filestore and standard block storage.",
164+
},
165+
Run: func(req cmds.Request, res cmds.Response) {
166+
_, fs, err := getFilestore(req)
167+
if err != nil {
168+
res.SetError(err, cmds.ErrNormal)
169+
return
170+
}
171+
ch, err := fs.FileManager().AllKeysChan(req.Context())
172+
if err != nil {
173+
res.SetError(err, cmds.ErrNormal)
174+
return
175+
}
176+
177+
out := make(chan interface{}, 128)
178+
res.SetOutput((<-chan interface{})(out))
179+
180+
go func() {
181+
defer close(out)
182+
for cid := range ch {
183+
have, err := fs.MainBlockstore().Has(cid)
184+
if err != nil {
185+
out <- &RefWrapper{Err: err.Error()}
186+
return
187+
}
188+
if have {
189+
out <- &RefWrapper{Ref: cid.String()}
190+
}
191+
}
192+
}()
193+
},
194+
Marshalers: refsMarshallerMap,
195+
Type: RefWrapper{},
196+
}
197+
198+
func getFilestore(req cmds.Request) (*core.IpfsNode, *filestore.Filestore, error) {
199+
n, err := req.InvocContext().GetNode()
200+
if err != nil {
201+
return nil, nil, err
202+
}
203+
fs := n.Filestore
204+
if fs == nil {
205+
return n, nil, fmt.Errorf("filestore not enabled")
206+
}
207+
return n, fs, err
208+
}
209+
210+
func listResToChan(next func() *filestore.ListRes, ctx context.Context) <-chan interface{} {
211+
out := make(chan interface{}, 128)
212+
go func() {
213+
defer close(out)
214+
for {
215+
r := next()
216+
if r == nil {
217+
return
218+
}
219+
select {
220+
case out <- r:
221+
case <-ctx.Done():
222+
return
223+
}
224+
}
225+
}()
226+
return out
227+
}
228+
229+
func perKeyActionToChan(args []string, action func(*cid.Cid) *filestore.ListRes, ctx context.Context) <-chan interface{} {
230+
out := make(chan interface{}, 128)
231+
go func() {
232+
defer close(out)
233+
for _, arg := range args {
234+
c, err := cid.Decode(arg)
235+
if err != nil {
236+
out <- &filestore.ListRes{
237+
Status: filestore.StatusOtherError,
238+
ErrorMsg: fmt.Sprintf("%s: %v", arg, err),
239+
}
240+
continue
241+
}
242+
r := action(c)
243+
select {
244+
case out <- r:
245+
case <-ctx.Done():
246+
return
247+
}
248+
}
249+
}()
250+
return out
251+
}

core/commands/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ ADVANCED COMMANDS
4747
pin Pin objects to local storage
4848
repo Manipulate the IPFS repository
4949
stats Various operational stats
50+
filestore Manage the filestore (experimental)
5051
5152
NETWORK COMMANDS
5253
id Show info about IPFS peers
@@ -124,6 +125,7 @@ var rootSubcommands = map[string]*cmds.Command{
124125
"update": ExternalBinary(),
125126
"version": VersionCmd,
126127
"bitswap": BitswapCmd,
128+
"filestore": FileStoreCmd,
127129
}
128130

129131
// RootRO is the readonly version of Root

filestore/filestore.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ type Filestore struct {
1919
bs blockstore.Blockstore
2020
}
2121

22+
func (f *Filestore) FileManager() *FileManager {
23+
return f.fm
24+
}
25+
26+
func (f *Filestore) MainBlockstore() blockstore.Blockstore {
27+
return f.bs
28+
}
29+
2230
func NewFilestore(bs blockstore.Blockstore, fm *FileManager) *Filestore {
2331
return &Filestore{fm, bs}
2432
}

filestore/fsrefstore.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ type FileManager struct {
2828
}
2929

3030
type CorruptReferenceError struct {
31-
Err error
31+
Code Status
32+
Err error
3233
}
3334

3435
func (c CorruptReferenceError) Error() string {
@@ -108,6 +109,10 @@ func (f *FileManager) getDataObj(c *cid.Cid) (*pb.DataObj, error) {
108109
//
109110
}
110111

112+
return unmarshalDataObj(o)
113+
}
114+
115+
func unmarshalDataObj(o interface{}) (*pb.DataObj, error) {
111116
data, ok := o.([]byte)
112117
if !ok {
113118
return nil, fmt.Errorf("stored filestore dataobj was not a []byte")
@@ -127,20 +132,24 @@ func (f *FileManager) readDataObj(c *cid.Cid, d *pb.DataObj) ([]byte, error) {
127132
abspath := filepath.Join(f.root, p)
128133

129134
fi, err := os.Open(abspath)
130-
if err != nil {
131-
return nil, &CorruptReferenceError{err}
135+
if os.IsNotExist(err) {
136+
return nil, &CorruptReferenceError{StatusFileNotFound, err}
137+
} else if err != nil {
138+
return nil, &CorruptReferenceError{StatusFileError, err}
132139
}
133140
defer fi.Close()
134141

135142
_, err = fi.Seek(int64(d.GetOffset()), os.SEEK_SET)
136143
if err != nil {
137-
return nil, &CorruptReferenceError{err}
144+
return nil, &CorruptReferenceError{StatusFileError, err}
138145
}
139146

140147
outbuf := make([]byte, d.GetSize_())
141148
_, err = io.ReadFull(fi, outbuf)
142-
if err != nil {
143-
return nil, &CorruptReferenceError{err}
149+
if err == io.EOF || err == io.ErrUnexpectedEOF {
150+
return nil, &CorruptReferenceError{StatusFileChanged, err}
151+
} else if err != nil {
152+
return nil, &CorruptReferenceError{StatusFileError, err}
144153
}
145154

146155
outcid, err := c.Prefix().Sum(outbuf)
@@ -149,7 +158,8 @@ func (f *FileManager) readDataObj(c *cid.Cid, d *pb.DataObj) ([]byte, error) {
149158
}
150159

151160
if !c.Equals(outcid) {
152-
return nil, &CorruptReferenceError{fmt.Errorf("data in file did not match. %s offset %d", d.GetFilePath(), d.GetOffset())}
161+
return nil, &CorruptReferenceError{StatusFileChanged,
162+
fmt.Errorf("data in file did not match. %s offset %d", d.GetFilePath(), d.GetOffset())}
153163
}
154164

155165
return outbuf, nil

0 commit comments

Comments
 (0)