filestore.go 6.69 KB
Newer Older
1 2 3 4 5
package commands

import (
	"context"
	"fmt"
6
	"io"
Jan Winkelmann's avatar
Jan Winkelmann committed
7
	"os"
8

Jan Winkelmann's avatar
Jan Winkelmann committed
9
	oldCmds "github.com/ipfs/go-ipfs/commands"
Łukasz Magiera's avatar
Łukasz Magiera committed
10
	lgc "github.com/ipfs/go-ipfs/commands/legacy"
11
	"github.com/ipfs/go-ipfs/core"
Jan Winkelmann's avatar
Jan Winkelmann committed
12
	e "github.com/ipfs/go-ipfs/core/commands/e"
13
	"github.com/ipfs/go-ipfs/filestore"
Jan Winkelmann's avatar
Jan Winkelmann committed
14

15 16
	"gx/ipfs/QmPVqQHEfLpqK7JLCsUkyam7rhuV3MAeZ9gueQQCrBwCta/go-ipfs-cmdkit"
	cmds "gx/ipfs/QmUQb3xtNzkQCgTj2NjaqcJZNv2nfSSub2QAdy9DtQMRBT/go-ipfs-cmds"
Steven Allen's avatar
Steven Allen committed
17
	cid "gx/ipfs/QmYjnkEL7i731PirfVH1sis89evN7jt4otSHw5D2xXXwUV/go-cid"
18 19 20
)

var FileStoreCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
21
	Helptext: cmdkit.HelpText{
22 23 24
		Tagline: "Interact with filestore objects.",
	},
	Subcommands: map[string]*cmds.Command{
25 26 27
		"ls":     lsFileStore,
		"verify": lgc.NewCommand(verifyFileStore),
		"dups":   lgc.NewCommand(dupsFileStore),
28 29 30 31
	},
}

var lsFileStore = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
32
	Helptext: cmdkit.HelpText{
33
		Tagline: "List objects in filestore.",
34 35 36 37 38 39 40 41 42 43
		LongDescription: `
List objects in the filestore.

If one or more <obj> is specified only list those specific objects,
otherwise list all objects.

The output is:

<hash> <size> <path> <offset>
`,
44
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
45 46
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("obj", false, true, "Cid of objects to list."),
47
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
48 49
	Options: []cmdkit.Option{
		cmdkit.BoolOption("file-order", "sort the results based on the path of the backing file"),
50
	},
Jeromy's avatar
Jeromy committed
51
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) {
Steven Allen's avatar
Steven Allen committed
52
		_, fs, err := getFilestore(env)
53
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
54
			res.SetError(err, cmdkit.ErrNormal)
55 56
			return
		}
57
		args := req.Arguments
58
		if len(args) > 0 {
59
			out := perKeyActionToChan(req.Context, args, func(c *cid.Cid) *filestore.ListRes {
60
				return filestore.List(fs, c)
Jan Winkelmann's avatar
Jan Winkelmann committed
61 62 63 64 65 66
			})

			err = res.Emit(out)
			if err != nil {
				log.Error(err)
			}
67
		} else {
68
			fileOrder, _ := req.Options["file-order"].(bool)
69
			next, err := filestore.ListAll(fs, fileOrder)
70
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
71
				res.SetError(err, cmdkit.ErrNormal)
72 73
				return
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
74

75
			out := listResToChan(req.Context, next)
Jan Winkelmann's avatar
Jan Winkelmann committed
76 77 78 79
			err = res.Emit(out)
			if err != nil {
				log.Error(err)
			}
80 81
		}
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
82
	PostRun: cmds.PostRunMap{
83
		cmds.CLI: func(req *cmds.Request, re cmds.ResponseEmitter) cmds.ResponseEmitter {
Jan Winkelmann's avatar
Jan Winkelmann committed
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
			reNext, res := cmds.NewChanResponsePair(req)

			go func() {
				defer re.Close()

				var errors bool
				for {
					v, err := res.Next()
					if !cmds.HandleError(err, res, re) {
						break
					}

					r, ok := v.(*filestore.ListRes)
					if !ok {
						log.Error(e.New(e.TypeErr(r, v)))
						return
					}

					if r.ErrorMsg != "" {
						errors = true
						fmt.Fprintf(os.Stderr, "%s\n", r.ErrorMsg)
					} else {
						fmt.Fprintf(os.Stdout, "%s\n", r.FormatLong())
					}
108
				}
Jan Winkelmann's avatar
Jan Winkelmann committed
109 110 111 112 113 114 115

				if errors {
					re.SetError("errors while displaying some entries", cmdkit.ErrNormal)
				}
			}()

			return reNext
116
		},
117 118 119 120
	},
	Type: filestore.ListRes{},
}

Jan Winkelmann's avatar
Jan Winkelmann committed
121 122
var verifyFileStore = &oldCmds.Command{
	Helptext: cmdkit.HelpText{
123
		Tagline: "Verify objects in filestore.",
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
		LongDescription: `
Verify objects in the filestore.

If one or more <obj> is specified only verify those specific objects,
otherwise verify all objects.

The output is:

<status> <hash> <size> <path> <offset>

Where <status> is one of:
ok:       the block can be reconstructed
changed:  the contents of the backing file have changed
no-file:  the backing file could not be found
error:    there was some other problem reading the file
missing:  <obj> could not be found in the filestore
ERROR:    internal error, most likely due to a corrupt database

For ERROR entries the error will also be printed to stderr.
`,
144
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
145 146
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("obj", false, true, "Cid of objects to verify."),
147
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
148 149
	Options: []cmdkit.Option{
		cmdkit.BoolOption("file-order", "verify the objects based on the order of the backing file"),
150
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
151 152
	Run: func(req oldCmds.Request, res oldCmds.Response) {
		_, fs, err := getFilestore(req.InvocContext())
153
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
154
			res.SetError(err, cmdkit.ErrNormal)
155 156
			return
		}
157 158
		args := req.Arguments()
		if len(args) > 0 {
Jan Winkelmann's avatar
Jan Winkelmann committed
159
			out := perKeyActionToChan(req.Context(), args, func(c *cid.Cid) *filestore.ListRes {
160
				return filestore.Verify(fs, c)
Jan Winkelmann's avatar
Jan Winkelmann committed
161
			})
162 163
			res.SetOutput(out)
		} else {
164 165
			fileOrder, _, _ := req.Option("file-order").Bool()
			next, err := filestore.VerifyAll(fs, fileOrder)
166
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
167
				res.SetError(err, cmdkit.ErrNormal)
168 169
				return
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
170
			out := listResToChan(req.Context(), next)
171
			res.SetOutput(out)
172 173
		}
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
174 175 176 177 178 179 180 181
	Marshalers: oldCmds.MarshalerMap{
		oldCmds.Text: func(res oldCmds.Response) (io.Reader, error) {
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			r, ok := v.(*filestore.ListRes)
182
			if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
183
				return nil, e.TypeErr(r, v)
184
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
185 186 187

			if r.Status == filestore.StatusOtherError {
				fmt.Fprintf(res.Stderr(), "%s\n", r.ErrorMsg)
188
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
189
			fmt.Fprintf(res.Stdout(), "%s %s\n", r.Status.Format(), r.FormatLong())
190 191
			return nil, nil
		},
192 193 194 195
	},
	Type: filestore.ListRes{},
}

Jan Winkelmann's avatar
Jan Winkelmann committed
196 197
var dupsFileStore = &oldCmds.Command{
	Helptext: cmdkit.HelpText{
198
		Tagline: "List blocks that are both in the filestore and standard block storage.",
199
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
200 201
	Run: func(req oldCmds.Request, res oldCmds.Response) {
		_, fs, err := getFilestore(req.InvocContext())
202
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
203
			res.SetError(err, cmdkit.ErrNormal)
204 205 206 207
			return
		}
		ch, err := fs.FileManager().AllKeysChan(req.Context())
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
208
			res.SetError(err, cmdkit.ErrNormal)
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
			return
		}

		out := make(chan interface{}, 128)
		res.SetOutput((<-chan interface{})(out))

		go func() {
			defer close(out)
			for cid := range ch {
				have, err := fs.MainBlockstore().Has(cid)
				if err != nil {
					out <- &RefWrapper{Err: err.Error()}
					return
				}
				if have {
					out <- &RefWrapper{Ref: cid.String()}
				}
			}
		}()
	},
	Marshalers: refsMarshallerMap,
	Type:       RefWrapper{},
}

233 234
func getFilestore(env interface{}) (*core.IpfsNode, *filestore.Filestore, error) {
	n, err := GetNode(env)
235 236 237 238 239
	if err != nil {
		return nil, nil, err
	}
	fs := n.Filestore
	if fs == nil {
240
		return n, nil, filestore.ErrFilestoreNotEnabled
241 242 243 244
	}
	return n, fs, err
}

Jan Winkelmann's avatar
Jan Winkelmann committed
245
func listResToChan(ctx context.Context, next func() *filestore.ListRes) <-chan interface{} {
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
	out := make(chan interface{}, 128)
	go func() {
		defer close(out)
		for {
			r := next()
			if r == nil {
				return
			}
			select {
			case out <- r:
			case <-ctx.Done():
				return
			}
		}
	}()
	return out
}
263

Jan Winkelmann's avatar
Jan Winkelmann committed
264
func perKeyActionToChan(ctx context.Context, args []string, action func(*cid.Cid) *filestore.ListRes) <-chan interface{} {
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
	out := make(chan interface{}, 128)
	go func() {
		defer close(out)
		for _, arg := range args {
			c, err := cid.Decode(arg)
			if err != nil {
				out <- &filestore.ListRes{
					Status:   filestore.StatusOtherError,
					ErrorMsg: fmt.Sprintf("%s: %v", arg, err),
				}
				continue
			}
			r := action(c)
			select {
			case out <- r:
			case <-ctx.Done():
				return
			}
		}
	}()
	return out
}