filestore.go 6.8 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"
12
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
Jan Winkelmann's avatar
Jan Winkelmann committed
13
	e "github.com/ipfs/go-ipfs/core/commands/e"
14
	"github.com/ipfs/go-ipfs/filestore"
Jan Winkelmann's avatar
Jan Winkelmann committed
15

16 17 18
	cmds "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds"
	"gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
	cid "gx/ipfs/QmZFbDTY9jfSBms2MchvYM9oYRbAF19K7Pby47yDBfpPrb/go-cid"
19 20 21
)

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

var lsFileStore = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
33
	Helptext: cmdkit.HelpText{
34
		Tagline: "List objects in filestore.",
35 36 37 38 39 40 41 42 43 44
		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>
`,
45
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
46 47
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("obj", false, true, "Cid of objects to list."),
48
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
49 50
	Options: []cmdkit.Option{
		cmdkit.BoolOption("file-order", "sort the results based on the path of the backing file"),
51
	},
Jeromy's avatar
Jeromy committed
52
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) {
Steven Allen's avatar
Steven Allen committed
53
		_, fs, err := getFilestore(env)
54
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
55
			res.SetError(err, cmdkit.ErrNormal)
56 57
			return
		}
58
		args := req.Arguments
59
		if len(args) > 0 {
60
			out := perKeyActionToChan(req.Context, args, func(c *cid.Cid) *filestore.ListRes {
61
				return filestore.List(fs, c)
Jan Winkelmann's avatar
Jan Winkelmann committed
62 63 64 65 66 67
			})

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

76
			out := listResToChan(req.Context, next)
Jan Winkelmann's avatar
Jan Winkelmann committed
77 78 79 80
			err = res.Emit(out)
			if err != nil {
				log.Error(err)
			}
81 82
		}
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
83
	PostRun: cmds.PostRunMap{
84
		cmds.CLI: func(req *cmds.Request, re cmds.ResponseEmitter) cmds.ResponseEmitter {
Jan Winkelmann's avatar
Jan Winkelmann committed
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
			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())
					}
109
				}
Jan Winkelmann's avatar
Jan Winkelmann committed
110 111 112 113 114 115 116

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

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

Jan Winkelmann's avatar
Jan Winkelmann committed
122 123
var verifyFileStore = &oldCmds.Command{
	Helptext: cmdkit.HelpText{
124
		Tagline: "Verify objects in filestore.",
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
		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.
`,
145
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
146 147
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("obj", false, true, "Cid of objects to verify."),
148
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
149 150
	Options: []cmdkit.Option{
		cmdkit.BoolOption("file-order", "verify the objects based on the order of the backing file"),
151
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
152 153
	Run: func(req oldCmds.Request, res oldCmds.Response) {
		_, fs, err := getFilestore(req.InvocContext())
154
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
155
			res.SetError(err, cmdkit.ErrNormal)
156 157
			return
		}
158 159
		args := req.Arguments()
		if len(args) > 0 {
Jan Winkelmann's avatar
Jan Winkelmann committed
160
			out := perKeyActionToChan(req.Context(), args, func(c *cid.Cid) *filestore.ListRes {
161
				return filestore.Verify(fs, c)
Jan Winkelmann's avatar
Jan Winkelmann committed
162
			})
163 164
			res.SetOutput(out)
		} else {
165 166
			fileOrder, _, _ := req.Option("file-order").Bool()
			next, err := filestore.VerifyAll(fs, fileOrder)
167
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
168
				res.SetError(err, cmdkit.ErrNormal)
169 170
				return
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
171
			out := listResToChan(req.Context(), next)
172
			res.SetOutput(out)
173 174
		}
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
175 176 177 178 179 180 181 182
	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)
183
			if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
184
				return nil, e.TypeErr(r, v)
185
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
186 187 188

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

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

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

Jan Winkelmann's avatar
Jan Winkelmann committed
246
func listResToChan(ctx context.Context, next func() *filestore.ListRes) <-chan interface{} {
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
	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
}
264

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

280 281 282 283 284 285 286 287 288 289 290 291
				continue
			}
			r := action(c)
			select {
			case out <- r:
			case <-ctx.Done():
				return
			}
		}
	}()
	return out
}