repo.go 8.32 KB
Newer Older
1 2 3 4 5
package commands

import (
	"bytes"
	"fmt"
Jeromy's avatar
Jeromy committed
6 7 8
	"io"
	"os"
	"path/filepath"
9
	"strings"
Jeromy's avatar
Jeromy committed
10 11

	bstore "github.com/ipfs/go-ipfs/blocks/blockstore"
12 13
	cmds "github.com/ipfs/go-ipfs/commands"
	corerepo "github.com/ipfs/go-ipfs/core/corerepo"
michael's avatar
michael committed
14
	config "github.com/ipfs/go-ipfs/repo/config"
Michael Pfister's avatar
Michael Pfister committed
15
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
Michael Pfister's avatar
Michael Pfister committed
16
	lockfile "github.com/ipfs/go-ipfs/repo/fsrepo/lock"
Jeromy's avatar
Jeromy committed
17

18
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
19 20
)

Michael Pfister's avatar
Michael Pfister committed
21 22 23 24
type RepoVersion struct {
	Version string
}

25 26
var RepoCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
27
		Tagline: "Manipulate the IPFS repo.",
28 29 30 31 32 33
		ShortDescription: `
'ipfs repo' is a plumbing command used to manipulate the repo.
`,
	},

	Subcommands: map[string]*cmds.Command{
Michael Pfister's avatar
Michael Pfister committed
34 35 36
		"gc":      repoGcCmd,
		"stat":    repoStatCmd,
		"fsck":    RepoFsckCmd,
Michael Pfister's avatar
Michael Pfister committed
37
		"version": repoVersionCmd,
Jeromy's avatar
Jeromy committed
38
		"verify":  repoVerifyCmd,
39 40 41 42 43
	},
}

var repoGcCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
44
		Tagline: "Perform a garbage collection sweep on the repo.",
45 46 47 48 49 50 51
		ShortDescription: `
'ipfs repo gc' is a plumbing command that will sweep the local
set of stored objects and remove ones that are not pinned in
order to reclaim hard disk space.
`,
	},
	Options: []cmds.Option{
52
		cmds.BoolOption("quiet", "q", "Write minimal output.").Default(false),
53
	},
54
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
55
		n, err := req.InvocContext().GetNode()
56
		if err != nil {
57 58
			res.SetError(err, cmds.ErrNormal)
			return
59 60
		}

Jeromy's avatar
Jeromy committed
61
		gcOutChan, err := corerepo.GarbageCollectAsync(n, req.Context())
62
		if err != nil {
63 64
			res.SetError(err, cmds.ErrNormal)
			return
65 66
		}

Jeromy's avatar
Jeromy committed
67
		outChan := make(chan interface{})
68 69
		res.SetOutput((<-chan interface{})(outChan))

Jeromy's avatar
Jeromy committed
70 71 72 73 74 75
		go func() {
			defer close(outChan)
			for k := range gcOutChan {
				outChan <- k
			}
		}()
76
	},
77
	Type: corerepo.KeyRemoved{},
78 79
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
80
			outChan, ok := res.Output().(<-chan interface{})
81 82 83 84 85 86 87 88 89 90
			if !ok {
				return nil, u.ErrCast()
			}

			quiet, _, err := res.Request().Option("quiet").Bool()
			if err != nil {
				return nil, err
			}

			marshal := func(v interface{}) (io.Reader, error) {
91
				obj, ok := v.(*corerepo.KeyRemoved)
92 93 94 95
				if !ok {
					return nil, u.ErrCast()
				}

96
				buf := new(bytes.Buffer)
97 98 99 100 101 102 103 104 105 106 107
				if quiet {
					buf = bytes.NewBufferString(string(obj.Key) + "\n")
				} else {
					buf = bytes.NewBufferString(fmt.Sprintf("removed %s\n", obj.Key))
				}
				return buf, nil
			}

			return &cmds.ChannelMarshaler{
				Channel:   outChan,
				Marshaler: marshal,
108
				Res:       res,
109 110 111 112
			}, nil
		},
	},
}
113 114 115

var repoStatCmd = &cmds.Command{
	Helptext: cmds.HelpText{
David Dias's avatar
David Dias committed
116 117 118 119
		Tagline: "Get stats for the currently used repo.",
		ShortDescription: `
'ipfs repo stat' is a plumbing command that will scan the local
set of stored objects and print repo statistics. It outputs to stdout:
120 121 122
NumObjects      int Number of objects in the local repo.
RepoPath        string The path to the repo being currently used.
RepoSize        int Size in bytes that the repo is currently taking.
Michael Pfister's avatar
Michael Pfister committed
123
Version         string The repo version.
David Dias's avatar
David Dias committed
124
`,
125 126 127 128 129 130 131 132
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

David Dias's avatar
David Dias committed
133
		stat, err := corerepo.RepoStat(n, req.Context())
134 135 136 137 138
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

David Dias's avatar
David Dias committed
139
		res.SetOutput(stat)
140
	},
David Dias's avatar
David Dias committed
141
	Options: []cmds.Option{
142
		cmds.BoolOption("human", "Output RepoSize in MiB.").Default(false),
David Dias's avatar
David Dias committed
143 144
	},
	Type: corerepo.Stat{},
145 146
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
David Dias's avatar
David Dias committed
147
			stat, ok := res.Output().(*corerepo.Stat)
148 149 150 151
			if !ok {
				return nil, u.ErrCast()
			}

David Dias's avatar
David Dias committed
152 153 154 155 156 157 158 159 160 161 162 163 164 165
			human, _, err := res.Request().Option("human").Bool()
			if err != nil {
				return nil, err
			}

			buf := new(bytes.Buffer)
			fmt.Fprintf(buf, "NumObjects \t %d\n", stat.NumObjects)
			sizeInMiB := stat.RepoSize / (1024 * 1024)
			if human && sizeInMiB > 0 {
				fmt.Fprintf(buf, "RepoSize (MiB) \t %d\n", sizeInMiB)
			} else {
				fmt.Fprintf(buf, "RepoSize \t %d\n", stat.RepoSize)
			}
			fmt.Fprintf(buf, "RepoPath \t %s\n", stat.RepoPath)
166
			fmt.Fprintf(buf, "Version \t %s\n", stat.Version)
Thomas Gardner's avatar
Thomas Gardner committed
167

David Dias's avatar
David Dias committed
168
			return buf, nil
169 170 171
		},
	},
}
michael's avatar
michael committed
172 173 174

var RepoFsckCmd = &cmds.Command{
	Helptext: cmds.HelpText{
Richard Littauer's avatar
Richard Littauer committed
175
		Tagline: "Removes repo lockfiles.",
michael's avatar
michael committed
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
		ShortDescription: `
'ipfs repo fsck' is a plumbing command that will remove repo and level db
lockfiles, as well as the api file. This command can only run when no ipfs
daemons are running.
`,
	},
	Run: func(req cmds.Request, res cmds.Response) {
		configRoot := req.InvocContext().ConfigRoot

		dsPath, err := config.DataStorePath(configRoot)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		dsLockFile := filepath.Join(dsPath, "LOCK") // TODO: get this lockfile programmatically
		repoLockFile := filepath.Join(configRoot, lockfile.LockFile)
		apiFile := filepath.Join(configRoot, "api") // TODO: get this programmatically

		log.Infof("Removing repo lockfile: %s", repoLockFile)
		log.Infof("Removing datastore lockfile: %s", dsLockFile)
		log.Infof("Removing api file: %s", apiFile)

		err = os.Remove(repoLockFile)
		if err != nil && !os.IsNotExist(err) {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		err = os.Remove(dsLockFile)
		if err != nil && !os.IsNotExist(err) {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		err = os.Remove(apiFile)
		if err != nil && !os.IsNotExist(err) {
			res.SetError(err, cmds.ErrNormal)
			return
		}

Jeromy's avatar
Jeromy committed
215
		res.SetOutput(&MessageOutput{"Lockfiles have been removed.\n"})
michael's avatar
michael committed
216 217 218 219
	},
	Type: MessageOutput{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: MessageTextMarshaler,
Michael Pfister's avatar
Michael Pfister committed
220 221 222
	},
}

Jeromy's avatar
Jeromy committed
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
type VerifyProgress struct {
	Message  string
	Progress int
}

var repoVerifyCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Verify all blocks in repo are not corrupted.",
	},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		out := make(chan interface{})
		go func() {
			defer close(out)
			bs := bstore.NewBlockstore(nd.Repo.Datastore())

			bs.RuntimeHashing(true)

			keys, err := bs.AllKeysChan(req.Context())
			if err != nil {
				log.Error(err)
				return
			}

			var fails int
			var i int
			for k := range keys {
				_, err := bs.Get(k)
				if err != nil {
					out <- &VerifyProgress{
						Message: fmt.Sprintf("block %s was corrupt (%s)", k, err),
					}
					fails++
				}
				i++
				out <- &VerifyProgress{Progress: i}
			}
			if fails == 0 {
				out <- &VerifyProgress{Message: "verify complete, all blocks validated."}
267 268
			} else {
				out <- &VerifyProgress{Message: "verify complete, some blocks were corrupt."}
Jeromy's avatar
Jeromy committed
269 270 271 272 273
			}
		}()

		res.SetOutput((<-chan interface{})(out))
	},
274
	Type: VerifyProgress{},
Jeromy's avatar
Jeromy committed
275 276 277 278 279 280 281 282 283 284 285 286
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			out := res.Output().(<-chan interface{})

			marshal := func(v interface{}) (io.Reader, error) {
				obj, ok := v.(*VerifyProgress)
				if !ok {
					return nil, u.ErrCast()
				}

				buf := new(bytes.Buffer)
				if obj.Message != "" {
287 288 289
					if strings.Contains(obj.Message, "blocks were corrupt") {
						return nil, fmt.Errorf(obj.Message)
					}
Jeromy's avatar
Jeromy committed
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
					if len(obj.Message) < 20 {
						obj.Message += "             "
					}
					fmt.Fprintln(buf, obj.Message)
					return buf, nil
				}

				fmt.Fprintf(buf, "%d blocks processed.\r", obj.Progress)
				return buf, nil
			}

			return &cmds.ChannelMarshaler{
				Channel:   out,
				Marshaler: marshal,
				Res:       res,
			}, nil
		},
	},
}

Michael Pfister's avatar
Michael Pfister committed
310 311 312 313 314 315 316 317 318 319 320 321 322
var repoVersionCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Show the repo version.",
		ShortDescription: `
'ipfs repo version' returns the current repo version.
`,
	},

	Options: []cmds.Option{
		cmds.BoolOption("quiet", "q", "Write minimal output."),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		res.SetOutput(&RepoVersion{
323
			Version: fmt.Sprint(fsrepo.RepoVersion),
Michael Pfister's avatar
Michael Pfister committed
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
		})
	},
	Type: RepoVersion{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			response := res.Output().(*RepoVersion)

			quiet, _, err := res.Request().Option("quiet").Bool()
			if err != nil {
				return nil, err
			}

			buf := new(bytes.Buffer)
			if quiet {
				buf = bytes.NewBufferString(fmt.Sprintf("fs-repo@%s\n", response.Version))
			} else {
				buf = bytes.NewBufferString(fmt.Sprintf("ipfs repo version fs-repo@%s\n", response.Version))
			}
			return buf, nil

		},
michael's avatar
michael committed
345 346
	},
}