aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorJoe Richey joerichey@google.com <joerichey@google.com>2017-10-17 18:10:54 -0700
committerJoseph Richey <joerichey94@gmail.com>2017-10-19 02:22:28 -0700
commit7847ab8270efab472b7b6a4bf9a57f5b83cb7212 (patch)
treedc8cb96be83a978389cd59793d18ad13af8df312 /cmd
parent36b313c802f9a8d23f2ad8ce5a59aa05f5925a2f (diff)
fmt almost done
Diffstat (limited to 'cmd')
-rw-r--r--cmd/errors.go2
-rw-r--r--cmd/format.go79
-rw-r--r--cmd/fscrypt/commands.go200
-rw-r--r--cmd/fscrypt/flags.go73
-rw-r--r--cmd/fscrypt/fscrypt.go257
-rw-r--r--cmd/fscrypt/keys.go29
-rw-r--r--cmd/fscrypt/prompt.go50
-rw-r--r--cmd/fscrypt/setup.go4
-rw-r--r--cmd/fscrypt/status.go12
-rw-r--r--cmd/run.go34
10 files changed, 367 insertions, 373 deletions
diff --git a/cmd/errors.go b/cmd/errors.go
index d4aca6d..c67c0f1 100644
--- a/cmd/errors.go
+++ b/cmd/errors.go
@@ -109,7 +109,7 @@ func (ctx *Context) processError(err error) {
// Errors with a help text should print it out.
if helpText := ctx.getHelpText(err); helpText != "" {
fmt.Fprintln(os.Stderr)
- fmt.Fprintln(os.Stderr, WrapText(helpText, 0))
+ fmt.Fprintln(os.Stderr, helpText)
}
os.Exit(FailureCode)
return
diff --git a/cmd/format.go b/cmd/format.go
index 877938c..993b955 100644
--- a/cmd/format.go
+++ b/cmd/format.go
@@ -20,6 +20,7 @@
package cmd
import (
+ "bufio"
"bytes"
"fmt"
"io"
@@ -45,11 +46,9 @@ var (
// LineLength is the maximum length of any output. If not set, the width
// of the terminal be detected and assigned to LineLength.
LineLength int
- // FallbackLineLength is the LineLength used if detection fails. By
- // default we fall back to punch cards.
- FallbackLineLength = 80
- // MaxLineLength is the maximum allowed detected value of LineLength.
- MaxLineLength = 120
+ // DefaultLineLength is the LineLength we use if we cannot detect the
+ // terminal width. By default we fall back to punch cards.
+ DefaultLineLength = 80
// Output is the io.Writer all commands should use for their normal
// output (errors should just return the appropriate error). If not set,
// it is automatically set based on the provided flags.
@@ -58,26 +57,34 @@ var (
// We use the width of the terminal unless we cannot get the width.
func init() {
- if LineLength > 0 {
- return
+ if LineLength == 0 {
+ var err error
+ LineLength, _, err = terminal.GetSize(int(os.Stdout.Fd()))
+ if err != nil {
+ LineLength = DefaultLineLength
+ }
}
- width, _, err := terminal.GetSize(int(os.Stdout.Fd()))
- if err != nil {
- LineLength = FallbackLineLength
- } else {
- LineLength = util.MinInt(width, MaxLineLength)
+}
+
+// MaxSubcommandLength returns the length of the longest subcommand (where the
+// length of the command is Name + Title). Return 0 if there aren't subcommands.
+func (c *Command) MaxNameLength() (max int) {
+ for _, s := range c.SubCommands {
+ max = util.MaxInt(max, len(s.Name))
}
+ return
}
// WrapText wraps an input string so that each line begins with numTabs tabs
// (except the first line) and ends with a newline (except the last line), and
// each line has length less than lineLength. If the text contains a word which
-// is too long, that word gets its own line.
-func WrapText(text string, numTabs int) string {
+// is too long, that word gets its own line. The first line's calculated length
+// is startSpaces less (to account for strange offsets on the first line).
+func WrapText(startSpaces, numTabs int, text string) string {
// We use a buffer to format the wrapped text so we get O(n) runtime
var buffer bytes.Buffer
spaceLeft := 0
- maxTextLen := LineLength - numTabs*TabWidth
+ maxTextLen := LineLength - startSpaces
delimiter := strings.Repeat("\t", numTabs)
for i, word := range strings.Fields(text) {
wordLen := utf8.RuneCountInString(word)
@@ -119,14 +126,23 @@ func Pluralize(count int, word string) string {
return fmt.Sprintf("%d %ss", count, word)
}
+// ReadLine returns a line of input from standard input. An empty string is
+// returned if the user didn't insert anything, we're in quiet mode or on error.
+// This function should be the only way user input is acquired from an
+// application (except for passwords).
+func ReadLine() (string, error) {
+ if QuietFlag.Value {
+ return "", nil
+ }
+ scanner := bufio.NewScanner(os.Stdin)
+ scanner.Scan()
+ return scanner.Text(), scanner.Err()
+}
+
// AskQuestion asks the user a yes or no question. Returning a boolean on a
// successful answer and an error if there was not a response from the user.
// Returns the defaultChoice on empty input (or in quiet mode).
func AskQuestion(question string, defaultChoice bool) (bool, error) {
- // If in quiet mode, we just use the default.
- if QuietFlag.Value {
- return defaultChoice, nil
- }
// Loop until failure or valid input.
for {
if defaultChoice {
@@ -135,7 +151,7 @@ func AskQuestion(question string, defaultChoice bool) (bool, error) {
fmt.Fprintf(Output, "%s %s ", question, defaultNoSuffix)
}
- input, err := util.ReadLine()
+ input, err := ReadLine()
if err != nil {
return false, err
}
@@ -159,16 +175,8 @@ func AskConfirmation(question, warning string, defaultChoice bool) error {
return nil
}
- // Defaults of "no" require forcing.
- if QuietFlag.Value {
- if defaultChoice {
- return nil
- }
- return ErrMustForce
- }
-
if warning != "" {
- fmt.Fprintln(Output, WrapText("WARNING: "+warning, 0))
+ fmt.Fprintln(Output, "WARNING: "+warning)
}
confirmed, err := AskQuestion(question, defaultChoice)
@@ -176,6 +184,10 @@ func AskConfirmation(question, warning string, defaultChoice bool) error {
return err
}
if !confirmed {
+ // To override a "false" default, use ForceFlag.
+ if QuietFlag.Value {
+ return ErrMustForce
+ }
return ErrCanceled
}
return nil
@@ -185,7 +197,14 @@ func AskConfirmation(question, warning string, defaultChoice bool) error {
// the provided Context and writer. Panics if text cannot be executed.
func ExecuteTemplate(w io.Writer, text string, ctx *Context) {
tmpl := template.Must(template.New("").Funcs(template.FuncMap{
- "WrapText": WrapText,
+ "WrapText": WrapText,
+ "LineLength": func() int { return LineLength },
+ "add": func(nums ...int) (sum int) {
+ for _, num := range nums {
+ sum += num
+ }
+ return
+ },
}).Parse(text))
if err := tmpl.Execute(w, ctx); err != nil {
panic(err)
diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go
index bbfcf2b..b29435a 100644
--- a/cmd/fscrypt/commands.go
+++ b/cmd/fscrypt/commands.go
@@ -408,79 +408,6 @@ func unlockPath(path string) error {
// Action: destoryMetadataAction,
// }
-// func destoryMetadataAction(c *cli.Context) error {
-// switch c.NArg() {
-// case 0:
-// switch {
-// case protectorFlag.Value != "":
-// // Case (1) - protector destroy
-// protector, err := getProtectorFromFlag(protectorFlag.Value, nil)
-// if err != nil {
-// return newExitError(c, err)
-// }
-
-// prompt := fmt.Sprintf("Destroy protector %s on %q?",
-// protector.Descriptor(), protector.Context.Mount.Path)
-// warning := "All files protected only with this protector will be lost!!"
-// if err := askConfirmation(prompt, false, warning); err != nil {
-// return newExitError(c, err)
-// }
-// if err := protector.Destroy(); err != nil {
-// return newExitError(c, err)
-// }
-
-// fmt.Fprintf(c.App.Writer, "Protector %s deleted from filesystem %q.\n",
-// protector.Descriptor(), protector.Context.Mount.Path)
-// case policyFlag.Value != "":
-// // Case (2) - policy destroy
-// policy, err := getPolicyFromFlag(policyFlag.Value, nil)
-// if err != nil {
-// return newExitError(c, err)
-// }
-
-// prompt := fmt.Sprintf("Destroy policy %s on %q?",
-// policy.Descriptor(), policy.Context.Mount.Path)
-// warning := "All files using this policy will be lost!!"
-// if err := askConfirmation(prompt, false, warning); err != nil {
-// return newExitError(c, err)
-// }
-// if err := policy.Destroy(); err != nil {
-// return newExitError(c, err)
-// }
-
-// fmt.Fprintf(c.App.Writer, "Policy %s deleted from filesystem %q.\n",
-// policy.Descriptor(), policy.Context.Mount.Path)
-// default:
-// message := fmt.Sprintf("Must specify one of: %s, %s, or %s",
-// mountpointArg,
-// shortDisplay(protectorFlag),
-// shortDisplay(policyFlag))
-// return &usageError{c, message}
-// }
-// case 1:
-// // Case (3) - mountpoint destroy
-// path := c.Args().Get(0)
-// ctx, err := actions.NewContextFromMountpoint(path, nil)
-// if err != nil {
-// return newExitError(c, err)
-// }
-
-// prompt := fmt.Sprintf("Destroy all the metadata on %q?", ctx.Mount.Path)
-// warning := "All the encrypted files on this filesystem will be lost!!"
-// if err := askConfirmation(prompt, false, warning); err != nil {
-// return newExitError(c, err)
-// }
-// if err := ctx.Mount.RemoveAllMetadata(); err != nil {
-// return newExitError(c, err)
-// }
-
-// fmt.Fprintf(c.App.Writer, "All metadata on %q deleted.\n", ctx.Mount.Path)
-// default:
-// return expectedArgsErr(c, 1, true)
-// }
-// return nil
-// }
-
// var changePassphrase = cli.Command{
// Name: "change-passphrase",
// ArgsUsage: shortDisplay(protectorFlag),
@@ -492,31 +419,6 @@ func unlockPath(path string) error {
// Action: changePassphraseAction,
// }
-// func changePassphraseAction(c *cli.Context) error {
-// if c.NArg() != 0 {
-// return expectedArgsErr(c, 0, false)
-// }
-// if err := checkRequiredFlags(c, []*stringFlag{protectorFlag}); err != nil {
-// return err
-// }
-
-// protector, err := getProtectorFromFlag(protectorFlag.Value, nil)
-// if err != nil {
-// return newExitError(c, err)
-// }
-// if err := protector.Unlock(oldExistingKeyFn); err != nil {
-// return newExitError(c, err)
-// }
-// defer protector.Lock()
-// if err := protector.Rewrap(newCreateKeyFn); err != nil {
-// return newExitError(c, err)
-// }
-
-// fmt.Fprintf(c.App.Writer, "Passphrase for protector %s successfully changed.\n",
-// protector.Descriptor())
-// return nil
-// }
-
// var addProtectorToPolicy = cli.Command{
// Name: "add-protector-to-policy",
// ArgsUsage: fmt.Sprintf("%s %s", shortDisplay(protectorFlag), shortDisplay(policyFlag)),
@@ -530,49 +432,6 @@ func unlockPath(path string) error {
// Action: addProtectorAction,
// }
-// func addProtectorAction(c *cli.Context) error {
-// if c.NArg() != 0 {
-// return expectedArgsErr(c, 0, false)
-// }
-// if err := checkRequiredFlags(c, []*stringFlag{protectorFlag, policyFlag}); err != nil {
-// return err
-// }
-
-// protector, err := getProtectorFromFlag(protectorFlag.Value, nil)
-// if err != nil {
-// return newExitError(c, err)
-// }
-// policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser)
-// if err != nil {
-// return newExitError(c, err)
-// }
-// // Sanity check before unlocking everything
-// if err := policy.AddProtector(protector); errors.Cause(err) != actions.ErrLocked {
-// return newExitError(c, err)
-// }
-
-// prompt := fmt.Sprintf("Protect policy %s with protector %s?",
-// policy.Descriptor(), protector.Descriptor())
-// warning := "All files using this policy will be accessible with this protector!!"
-// if err := askConfirmation(prompt, true, warning); err != nil {
-// return newExitError(c, err)
-// }
-
-// if err := protector.Unlock(existingKeyFn); err != nil {
-// return newExitError(c, err)
-// }
-// if err := policy.Unlock(optionFn, existingKeyFn); err != nil {
-// return newExitError(c, err)
-// }
-// if err := policy.AddProtector(protector); err != nil {
-// return newExitError(c, err)
-// }
-
-// fmt.Fprintf(c.App.Writer, "Protector %s now protecting policy %s.\n",
-// protector.Descriptor(), policy.Descriptor())
-// return nil
-// }
-
// var removeProtectorFromPolicy = cli.Command{
// Name: "remove-protector-from-policy",
// ArgsUsage: fmt.Sprintf("%s %s", shortDisplay(protectorFlag), shortDisplay(policyFlag)),
@@ -587,40 +446,6 @@ func unlockPath(path string) error {
// Action: removeProtectorAction,
// }
-// func removeProtectorAction(c *cli.Context) error {
-// if c.NArg() != 0 {
-// return expectedArgsErr(c, 0, false)
-// }
-// if err := checkRequiredFlags(c, []*stringFlag{protectorFlag, policyFlag}); err != nil {
-// return err
-// }
-
-// // We do not need to unlock anything for this operation
-// protector, err := getProtectorFromFlag(protectorFlag.Value, nil)
-// if err != nil {
-// return newExitError(c, err)
-// }
-// policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser)
-// if err != nil {
-// return newExitError(c, err)
-// }
-
-// prompt := fmt.Sprintf("Stop protecting policy %s with protector %s?",
-// policy.Descriptor(), protector.Descriptor())
-// warning := "All files using this policy will NO LONGER be accessible with this protector!!"
-// if err := askConfirmation(prompt, false, warning); err != nil {
-// return newExitError(c, err)
-// }
-
-// if err := policy.RemoveProtector(protector); err != nil {
-// return newExitError(c, err)
-// }
-
-// fmt.Fprintf(c.App.Writer, "Protector %s no longer protecting policy %s.\n",
-// protector.Descriptor(), policy.Descriptor())
-// return nil
-// }
-
// var dumpMetadata = cli.Command{
// Name: "dump",
// ArgsUsage: fmt.Sprintf("[%s | %s]", shortDisplay(protectorFlag), shortDisplay(policyFlag)),
@@ -635,28 +460,3 @@ func unlockPath(path string) error {
// Flags: []cli.Flag{protectorFlag, policyFlag},
// Action: dumpMetadataAction,
// }
-
-// func dumpMetadataAction(c *cli.Context) error {
-// switch {
-// case protectorFlag.Value != "":
-// // Case (1) - protector print
-// protector, err := getProtectorFromFlag(protectorFlag.Value, nil)
-// if err != nil {
-// return newExitError(c, err)
-// }
-// fmt.Fprintln(c.App.Writer, protector)
-// case policyFlag.Value != "":
-// // Case (2) - policy print
-// policy, err := getPolicyFromFlag(policyFlag.Value, nil)
-// if err != nil {
-// return newExitError(c, err)
-// }
-// fmt.Fprintln(c.App.Writer, policy)
-// default:
-// message := fmt.Sprintf("Must specify one of: %s or %s",
-// shortDisplay(protectorFlag),
-// shortDisplay(policyFlag))
-// return &usageError{c, message}
-// }
-// return nil
-// }
diff --git a/cmd/fscrypt/flags.go b/cmd/fscrypt/flags.go
index 5983053..084fa19 100644
--- a/cmd/fscrypt/flags.go
+++ b/cmd/fscrypt/flags.go
@@ -37,23 +37,18 @@ import (
// Bool flags: used to switch some behavior on or off
var (
legacyFlag = &cmd.BoolFlag{
- Name: "legacy",
- Usage: `Allow for support of older kernels with ext4 (before
- v4.8) and F2FS (before v4.6) filesystems.`,
+ Name: "legacy",
+ Usage: `Configure fscrypt to support older kernels.`,
Default: true,
}
skipUnlockFlag = &cmd.BoolFlag{
- Name: "skip-unlock",
- Usage: `Leave the directory in a locked state after setup.
- "fscrypt unlock" will need to be run in order to use the
- directory.`,
+ Name: "skip-unlock",
+ Usage: "Leave the directory in a locked state after setup.",
}
dropCachesFlag = &cmd.BoolFlag{
Name: "drop-caches",
Usage: `After purging the keys from the keyring, drop the
- associated caches for the purge to take effect. Without
- this flag, cached encrypted files may still have their
- plaintext visible. Requires root privileges.`,
+ associated caches for the purge to take effect.`,
Default: true,
}
)
@@ -62,78 +57,62 @@ var (
var (
timeTargetFlag = &cmd.DurationFlag{
Name: "time",
- ArgName: "TIME",
+ ArgName: "time",
Usage: `Set the global options so that passphrase hashing takes
- TIME long. TIME should be formatted as a sequence of
- decimal numbers, each with optional fraction and a unit
- suffix, such as "300ms", "1.5s" or "2h45m". Valid time
- units are "ms", "s", "m", and "h".`,
+ <time> long.`,
Default: 1 * time.Second,
}
sourceFlag = &cmd.StringFlag{
Name: "source",
- ArgName: "SOURCE",
- Usage: fmt.Sprintf(`New protectors will have type SOURCE. SOURCE
- can be one of pam_passphrase, custom_passphrase, or
- raw_key. If not specified, the user will be prompted for
- the source, with a default pulled from %s.`,
- actions.ConfigFileLocation),
+ ArgName: "source",
+ Usage: `New protectors will have type <source> (one of
+ pam_passphrase, custom_passphrase, or raw_key).`,
}
nameFlag = &cmd.StringFlag{
Name: "name",
- ArgName: "PROTECTOR_NAME",
- Usage: `New custom_passphrase and raw_key protectors will be
- named PROTECTOR_NAME. If not specified, the user will be
- prompted for a name.`,
+ ArgName: "name",
+ Usage: "Use <name> as the name for a new protector.",
}
keyFileFlag = &cmd.StringFlag{
Name: "key",
- ArgName: "FILE",
- Usage: `Use the contents of FILE as the wrapping key when
- creating or unlocking raw_key protectors. FILE should be
- formatted as raw binary and should be exactly 32 bytes
- long.`,
+ ArgName: "path",
+ Usage: "Use the file at <path> as the protector key.",
}
userFlag = &cmd.StringFlag{
Name: "user",
- ArgName: "USERNAME",
- Usage: `Specifiy which user should be used for login passphrases
+ ArgName: "username",
+ Usage: `Specify which user should be used for login passphrases
or to which user's keyring keys should be provisioned.`,
}
- mountpointIDArg = "MOUNTPOINT:ID"
+ mountpointIDArg = usedMountpointArg.ArgName + ":id"
protectorFlag = &cmd.StringFlag{
Name: "protector",
ArgName: mountpointIDArg,
- Usage: `Specify an existing protector on filesystem MOUNTPOINT
- with protector descriptor ID which should be used in the
- command.`,
+ Usage: fmt.Sprintf(`An existing protector on %s with hexadecimal
+ id <id>.`, usedMountpointArg),
}
unlockWithFlag = &cmd.StringFlag{
Name: "unlock-with",
ArgName: mountpointIDArg,
- Usage: `Specify an existing protector on filesystem MOUNTPOINT
- with protector descriptor ID which should be used to
- unlock a policy (usually specified with --policy). This
- flag is only useful if a policy is protected with
- multiple protectors. If not specified, the user will be
- prompted for a protector.`,
+ Usage: fmt.Sprintf(`The protector that should be used to unlock
+ the policy specified with %s.`, policyFlag),
}
policyFlag = &cmd.StringFlag{
Name: "policy",
ArgName: mountpointIDArg,
- Usage: `Specify an existing policy on filesystem MOUNTPOINT with
- key descriptor ID which should be used in the command.`,
+ Usage: fmt.Sprintf(`An existing policy on %s with hexadecimal id
+ <id>.`, usedMountpointArg),
}
)
-// The first group is optional and corresponds to the mountpoint. The second
-// group is required and corresponds to the descriptor.
+// The first group corresponds to the mountpoint string. The second group
+// corresponds to the hexideciamal descriptor.
var idFlagRegex = regexp.MustCompile("^([[:print:]]+):([[:alnum:]]+)$")
func matchMetadataFlag(flagValue string) (mountpoint, descriptor string, err error) {
matches := idFlagRegex.FindStringSubmatch(flagValue)
if matches == nil {
- return "", "", fmt.Errorf("flag value %q does not have format %s",
+ return "", "", fmt.Errorf("flag value %q does not have format %q",
flagValue, mountpointIDArg)
}
log.Printf("parsed flag: mountpoint=%q descriptor=%s", matches[1], matches[2])
diff --git a/cmd/fscrypt/fscrypt.go b/cmd/fscrypt/fscrypt.go
index 63b2cbc..2054193 100644
--- a/cmd/fscrypt/fscrypt.go
+++ b/cmd/fscrypt/fscrypt.go
@@ -63,12 +63,12 @@ var baseFlags = []cmd.Flag{cmd.VerboseFlag, cmd.QuietFlag, cmd.HelpFlag}
var fscryptCommand = cmd.Command{
Title: "manage linux filesystem encryption",
UsageLines: []string{
- fmt.Sprintf("<command> [arguments] [command options] [%s | %s]",
+ fmt.Sprintf("<command> [arguments] [options] [%s|%s]",
cmd.VerboseFlag, cmd.QuietFlag),
cmd.VersionUsage,
},
SubCommands: []*cmd.Command{setupCommand, encryptCommand, unlockCommand,
- purgeCommand, statusCommand, metadataCommand, cmd.VersionCommand}, Flags: baseFlags,
+ purgeCommand, statusCommand, metadataCommand, cmd.VersionCommand}, Flags: baseFlags,
ManPage: &cmd.ManPage{Name: "fscrypt", Section: 8},
}
@@ -108,7 +108,7 @@ var encryptCommand = &cmd.Command{
Arguments: []*cmd.Argument{directoryToEncryptArg},
InheritFlags: true,
Flags: []cmd.Flag{sourceFlag, nameFlag, protectorFlag, policyFlag,
- keyFileFlag, userFlag, skipUnlockFlag},
+ unlockWithFlag, keyFileFlag, userFlag, skipUnlockFlag},
ManPage: &cmd.ManPage{Name: "fscrypt-encrypt", Section: 8},
Action: encryptAction,
}
@@ -140,7 +140,7 @@ var unlockCommand = &cmd.Command{
UsageLines: []string{"???"}, // TODO(joerichey)
Arguments: []*cmd.Argument{encryptedPathArg},
InheritFlags: true,
- Flags: []cmd.Flag{protectorFlag, policyFlag, keyFileFlag, userFlag},
+ Flags: []cmd.Flag{unlockWithFlag, keyFileFlag, userFlag},
ManPage: &cmd.ManPage{Name: "fscrypt-unlock", Section: 8},
Action: unlockAction,
}
@@ -220,7 +220,7 @@ var statusCommand = &cmd.Command{
Name: "status",
Title: "get the status of the system or a path",
UsageLines: []string{"", usedMountpointArg.String(), encryptedPathArg.String()},
- Arguments: []*cmd.Argument{usedMountpointArg, encryptedPathArg},
+ Arguments: []*cmd.Argument{usedMountpointArg, encryptedPathArg},
Flags: []cmd.Flag{cmd.VerboseFlag, cmd.HelpFlag},
ManPage: &cmd.ManPage{Name: "fscrypt-status", Section: 8},
Action: statusAction,
@@ -254,10 +254,10 @@ func statusAction(c *cmd.Context) error {
var metadataCommand = &cmd.Command{
Name: "metadata",
Title: "manipulate fscrypt metadata directly",
- UsageLines: []string{fmt.Sprintf("<command> [command options] [%s] [%s]",
+ UsageLines: []string{fmt.Sprintf("<command> [options] [%s] [%s]",
protectorFlag, policyFlag)},
- SubCommands: []*cmd.Command{createCommand}, // destroyCommand, changeCommand,
- // addProtectorCommand, removeProtectorCommand, dumpCommand},
+ SubCommands: []*cmd.Command{createCommand, destroyCommand, changeCommand,
+ addProtectorCommand, removeProtectorCommand, dumpCommand},
InheritFlags: true,
Flags: []cmd.Flag{protectorFlag, policyFlag},
ManPage: &cmd.ManPage{Name: "fscrypt-metadata", Section: 8},
@@ -268,7 +268,7 @@ var createCommand = &cmd.Command{
Title: "manually create metadata on a filesystem",
UsageLines: []string{
fmt.Sprintf("protector %s", usedMountpointArg),
- fmt.Sprintf("policy %s, %s", usedMountpointArg, protectorFlag),
+ fmt.Sprintf("policy %s %s", usedMountpointArg, protectorFlag),
},
SubCommands: []*cmd.Command{createProtectorCommand, createPolicyCommand},
Arguments: []*cmd.Argument{usedMountpointArg},
@@ -363,66 +363,73 @@ func createPolicyAction(c *cmd.Context) error {
}
var destroyCommand = &cmd.Command{
- Name: "destroy",
+ Name: "destroy",
Title: "directly delete an existing protector or policy",
UsageLines: []string{
fmt.Sprintf("%s [%s]", protectorFlag, cmd.ForceFlag),
fmt.Sprintf("%s [%s]", policyFlag, cmd.ForceFlag),
fmt.Sprintf("%s [%s]", usedMountpointArg, cmd.ForceFlag),
},
- Arguments: []*cmd.Argument{usedMountpointArg},
+ Arguments: []*cmd.Argument{usedMountpointArg},
InheritFlags: true,
- Flags: []cmd.Flag{cmd.ForceFlag},
- Action: destroyAction,
+ Flags: []cmd.Flag{cmd.ForceFlag},
+ Action: destroyAction,
}
-func destoryAction(c *cmd.Context) error {
+func destroyAction(c *cmd.Context) error {
+ if err := cmd.CheckExpectedArgs(c, 1, true); err != nil {
+ return err
+ }
+ hasProtector := protectorFlag.Value != ""
+ hasPolicy := policyFlag.Value != ""
+ hasMount := len(c.Args) == 1
+ if (hasProtector && hasPolicy) || (hasPolicy && hasMount) || (hasMount && hasProtector) {
+ return cmd.UsageError(fmt.Sprintf("Multiple of %s, %s, %s provided",
+ protectorFlag, policyFlag, usedMountpointArg))
+ }
+
switch {
- case protectorFlag.Value != "":
- if len(c.Args) != 0 {
- break
- }
+ case hasProtector:
// Case (1) - protector destroy
protector, err := getProtectorFromFlag(protectorFlag.Value, nil)
if err != nil {
- return newExitError(c, err)
+ return err
}
prompt := fmt.Sprintf("Destroy protector %s on %q?",
protector.Descriptor(), protector.Context.Mount.Path)
warning := "All files protected only with this protector will be lost!!"
- if err := askConfirmation(prompt, false, warning); err != nil {
- return newExitError(c, err)
+ if err = cmd.AskConfirmation(prompt, warning, false); err != nil {
+ return err
}
- if err := protector.Destroy(); err != nil {
- return newExitError(c, err)
+ if err = protector.Destroy(); err != nil {
+ return err
}
- fmt.Fprintf(c.App.Writer, "Protector %s deleted from filesystem %q.\n",
+ fmt.Fprintf(cmd.Output, "Protector %s deleted from filesystem %q.\n",
protector.Descriptor(), protector.Context.Mount.Path)
- case policyFlag.Value != "":
- if len(c.Args) != 0 {
- break
- }
+ return nil
+ case hasPolicy:
// Case (2) - policy destroy
policy, err := getPolicyFromFlag(policyFlag.Value, nil)
if err != nil {
- return newExitError(c, err)
+ return err
}
prompt := fmt.Sprintf("Destroy policy %s on %q?",
policy.Descriptor(), policy.Context.Mount.Path)
- warning := "All files using this policy will be lost!!"
- if err := askConfirmation(prompt, false, warning); err != nil {
- return newExitError(c, err)
+ warning := "All files and directories using this policy will be lost!"
+ if err = cmd.AskConfirmation(prompt, warning, false); err != nil {
+ return err
}
- if err := policy.Destroy(); err != nil {
- return newExitError(c, err)
+ if err = policy.Destroy(); err != nil {
+ return err
}
- fmt.Fprintf(c.App.Writer, "Policy %s deleted from filesystem %q.\n",
+ fmt.Fprintf(cmd.Output, "Policy %s deleted from filesystem %q.\n",
policy.Descriptor(), policy.Context.Mount.Path)
- case len(c.Args) == 1:
+ return nil
+ case hasMount:
// Case (3) - mountpoint destroy
ctx, err := actions.NewContextFromMountpoint(c.Args[0], nil)
if err != nil {
@@ -440,8 +447,182 @@ func destoryAction(c *cmd.Context) error {
fmt.Fprintf(cmd.Output, "All metadata on %q deleted.\n", ctx.Mount.Path)
return nil
+ default:
+ return cmd.UsageError(fmt.Sprintf("None of %s, %s, %s provided",
+ protectorFlag, policyFlag, usedMountpointArg))
}
- return cmd.UsageError(fmt.Sprintf("Must specify exactly one of: %s, %s, or %s",
- usedMountpointArg, protectorFlag, policyFlag))
}
+var changeCommand = &cmd.Command{
+ Name: "change-passphrase",
+ Title: "change the passphrase for a protector",
+ UsageLines: []string{protectorFlag.String()},
+ InheritFlags: true,
+ Flags: []cmd.Flag{protectorFlag},
+ Action: changeAction,
+}
+
+func changeAction(c *cmd.Context) error {
+ if err := cmd.CheckExpectedArgs(c, 0, false); err != nil {
+ return err
+ }
+ if err := cmd.CheckRequiredFlags([]*cmd.StringFlag{protectorFlag}); err != nil {
+ return err
+ }
+
+ protector, err := getProtectorFromFlag(protectorFlag.Value, nil)
+ if err != nil {
+ return err
+ }
+ if err := protector.Unlock(oldExistingKeyFn); err != nil {
+ return err
+ }
+ defer protector.Lock()
+ if err := protector.Rewrap(newCreateKeyFn); err != nil {
+ return err
+ }
+
+ fmt.Fprintf(cmd.Output, "Passphrase for protector %s successfully changed.\n",
+ protector.Descriptor())
+ return nil
+}
+
+var addProtectorCommand = &cmd.Command{
+ Name: "add-protector-to-policy",
+ Title: "start protecting a policy with a protector",
+ UsageLines: []string{fmt.Sprintf("%s [%s] %s [%s]", protectorFlag,
+ keyFileFlag, policyFlag, unlockWithFlag)},
+ InheritFlags: true,
+ Flags: []cmd.Flag{keyFileFlag, unlockWithFlag},
+ Action: addProtectorAction,
+}
+
+func addProtectorAction(c *cmd.Context) error {
+ if err := cmd.CheckExpectedArgs(c, 0, false); err != nil {
+ return err
+ }
+ err := cmd.CheckRequiredFlags([]*cmd.StringFlag{protectorFlag, policyFlag})
+ if err != nil {
+ return err
+ }
+
+ protector, err := getProtectorFromFlag(protectorFlag.Value, nil)
+ if err != nil {
+ return err
+ }
+ policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser)
+ if err != nil {
+ return err
+ }
+ // Sanity check before unlocking everything
+ if err := policy.AddProtector(protector); errors.Cause(err) != actions.ErrLocked {
+ return err
+ }
+
+ prompt := fmt.Sprintf("Protect policy %s with protector %s?",
+ policy.Descriptor(), protector.Descriptor())
+ warning := "All files using this policy will be accessible with this protector!"
+ if err := cmd.AskConfirmation(prompt, warning, true); err != nil {
+ return err
+ }
+
+ if err := protector.Unlock(existingKeyFn); err != nil {
+ return err
+ }
+ if err := policy.Unlock(optionFn, existingKeyFn); err != nil {
+ return err
+ }
+ if err := policy.AddProtector(protector); err != nil {
+ return err
+ }
+
+ fmt.Fprintf(cmd.Output, "Protector %s now protecting policy %s.\n",
+ protector.Descriptor(), policy.Descriptor())
+ return nil
+}
+
+var removeProtectorCommand = &cmd.Command{
+ Name: "remove-protector-from-policy",
+ Title: "stop protecting a policy with a protector",
+ UsageLines: []string{fmt.Sprintf("%s %s [%s]", protectorFlag,
+ policyFlag, cmd.ForceFlag)},
+ InheritFlags: true,
+ Flags: []cmd.Flag{keyFileFlag, unlockWithFlag},
+ Action: removeProtectorAction,
+}
+
+func removeProtectorAction(c *cmd.Context) error {
+ if err := cmd.CheckExpectedArgs(c, 0, false); err != nil {
+ return err
+ }
+ err := cmd.CheckRequiredFlags([]*cmd.StringFlag{protectorFlag, policyFlag})
+ if err != nil {
+ return err
+ }
+
+ // We do not need to unlock anything for this operation
+ protector, err := getProtectorFromFlag(protectorFlag.Value, nil)
+ if err != nil {
+ return err
+ }
+ policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser)
+ if err != nil {
+ return err
+ }
+
+ prompt := fmt.Sprintf("Stop protecting policy %s with protector %s?",
+ policy.Descriptor(), protector.Descriptor())
+ warning := "All files using this policy will NOT BE ACCESSIBLE with this protector!"
+ if err := cmd.AskConfirmation(prompt, warning, false); err != nil {
+ return err
+ }
+ if err := policy.RemoveProtector(protector); err != nil {
+ return err
+ }
+
+ fmt.Fprintf(cmd.Output, "Protector %s no longer protecting policy %s.\n",
+ protector.Descriptor(), policy.Descriptor())
+ return nil
+}
+
+var dumpCommand = &cmd.Command{
+ Name: "dump",
+ Title: "display debug info about protectors and policies",
+ UsageLines: []string{protectorFlag.String(), policyFlag.String()},
+ Flags: []cmd.Flag{protectorFlag, policyFlag, cmd.VerboseFlag, cmd.HelpFlag},
+ Action: dumpAction,
+}
+
+func dumpAction(c *cmd.Context) error {
+ if err := cmd.CheckExpectedArgs(c, 0, false); err != nil {
+ return err
+ }
+ hasProtector := protectorFlag.Value != ""
+ hasPolicy := policyFlag.Value != ""
+ if hasProtector && hasPolicy {
+ return cmd.UsageError(fmt.Sprintf("Multiple of %s, %s provided",
+ protectorFlag, policyFlag))
+ }
+
+ switch {
+ case hasProtector:
+ // Case (1) - protector dump
+ protector, err := getProtectorFromFlag(protectorFlag.Value, nil)
+ if err != nil {
+ return err
+ }
+ fmt.Fprintln(cmd.Output, protector)
+ return nil
+ case hasPolicy:
+ // Case (2) - policy dump
+ policy, err := getPolicyFromFlag(policyFlag.Value, nil)
+ if err != nil {
+ return err
+ }
+ fmt.Fprintln(cmd.Output, policy)
+ return nil
+ default:
+ return cmd.UsageError(fmt.Sprintf("None of %s, %s provided",
+ protectorFlag, policyFlag))
+ }
+}
diff --git a/cmd/fscrypt/keys.go b/cmd/fscrypt/keys.go
index 872ca2a..ec02148 100644
--- a/cmd/fscrypt/keys.go
+++ b/cmd/fscrypt/keys.go
@@ -31,9 +31,11 @@ import (
"golang.org/x/crypto/ssh/terminal"
"github.com/google/fscrypt/actions"
+ "github.com/google/fscrypt/cmd"
"github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/metadata"
"github.com/google/fscrypt/pam"
+ "github.com/google/fscrypt/util"
)
// The file descriptor for standard input
@@ -71,7 +73,7 @@ func (p passphraseReader) Read(buf []byte) (int, error) {
case '\r', '\n':
return position, io.EOF
case 3, 4:
- return position, ErrCanceled
+ return position, cmd.ErrCanceled
case 8, 127:
if position > 0 {
position--
@@ -86,9 +88,7 @@ func (p passphraseReader) Read(buf []byte) (int, error) {
// passphrase into a key. If we are not reading from a terminal, just read into
// the passphrase into the key normally.
func getPassphraseKey(prompt string) (*crypto.Key, error) {
- if !quietFlag.Value {
- fmt.Print(prompt)
- }
+ fmt.Fprint(cmd.Output, prompt)
// Only disable echo if stdin is actually a terminal.
if terminal.IsTerminal(stdinFd) {
@@ -116,7 +116,7 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun
panic("this KeyFunc does not support retrying")
}
// Don't retry for non-interactive sessions
- if quietFlag.Value {
+ if cmd.QuietFlag.Value {
return nil, ErrWrongKey
}
fmt.Println("Incorrect Passphrase")
@@ -124,8 +124,8 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun
switch info.Source() {
case metadata.SourceType_pam_passphrase:
- prompt := fmt.Sprintf("Enter %slogin passphrase for %s: ",
- prefix, formatUsername(info.UID()))
+ username := util.GetUser(int(info.UID())).Username
+ prompt := fmt.Sprintf("Enter %s%s: ", prefix, formatInfo(info))
key, err := getPassphraseKey(prompt)
if err != nil {
return nil, err
@@ -134,13 +134,7 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun
// To confirm, check that the passphrase is the user's
// login passphrase.
if shouldConfirm {
- username, err := usernameFromID(info.UID())
- if err != nil {
- key.Wipe()
- return nil, err
- }
-
- err = pam.IsUserLoginToken(username, key, quietFlag.Value)
+ err = pam.IsUserLoginToken(username, key, cmd.QuietFlag.Value)
if err != nil {
key.Wipe()
return nil, err
@@ -149,8 +143,7 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun
return key, nil
case metadata.SourceType_custom_passphrase:
- prompt := fmt.Sprintf("Enter %scustom passphrase for protector %q: ",
- prefix, info.Name())
+ prompt := fmt.Sprintf("Enter %s%s: ", prefix, formatInfo(info))
key, err := getPassphraseKey(prompt)
if err != nil {
return nil, err
@@ -158,7 +151,7 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun
// To confirm, make sure the user types the same
// passphrase in again.
- if shouldConfirm && !quietFlag.Value {
+ if shouldConfirm && !cmd.QuietFlag.Value {
key2, err := getPassphraseKey("Confirm passphrase: ")
if err != nil {
key.Wipe()
@@ -178,7 +171,7 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun
if prefix != "" {
return nil, ErrNotPassphrase
}
- prompt := fmt.Sprintf("Enter key file for protector %q: ", info.Name())
+ prompt := fmt.Sprintf("Enter path to %s: ", formatInfo(info))
// Raw keys use a file containing the key data.
file, err := promptForKeyFile(prompt)
if err != nil {
diff --git a/cmd/fscrypt/prompt.go b/cmd/fscrypt/prompt.go
index 57d0fc7..c9b3147 100644
--- a/cmd/fscrypt/prompt.go
+++ b/cmd/fscrypt/prompt.go
@@ -26,6 +26,7 @@ import (
"strconv"
"github.com/google/fscrypt/actions"
+ "github.com/google/fscrypt/cmd"
"github.com/google/fscrypt/metadata"
"github.com/google/fscrypt/util"
)
@@ -41,11 +42,11 @@ var sourceDescriptions = map[metadata.SourceType]string{
func formatInfo(data actions.ProtectorInfo) string {
switch data.Source() {
case metadata.SourceType_pam_passphrase:
- return "login protector for " + formatUsername(data.UID())
+ return "login passphrase for " + util.GetUser(int(data.UID())).Username
case metadata.SourceType_custom_passphrase:
- return fmt.Sprintf("custom protector %q", data.Name())
+ return fmt.Sprintf("custom passphrase for protector %q", data.Name())
case metadata.SourceType_raw_key:
- return fmt.Sprintf("raw key protector %q", data.Name())
+ return fmt.Sprintf("raw key for protector %q", data.Name())
default:
panic(ErrInvalidSource)
}
@@ -59,13 +60,13 @@ func promptForName(ctx *actions.Context) (string, error) {
}
// Don't ask for a name if we do not need it
- if quietFlag.Value || ctx.Config.Source == metadata.SourceType_pam_passphrase {
+ if cmd.QuietFlag.Value || ctx.Config.Source == metadata.SourceType_pam_passphrase {
return "", nil
}
for {
- fmt.Print("Enter a name for the new protector: ")
- name, err := util.ReadLine()
+ fmt.Fprint(cmd.Output, "Enter a name for the new protector: ")
+ name, err := cmd.ReadLine()
if err != nil {
return "", err
}
@@ -88,23 +89,18 @@ func promptForSource(ctx *actions.Context) error {
return nil
}
- // Just use the default in quiet mode
- if quietFlag.Value {
- return nil
- }
-
// We print all the sources with their number, description, and name.
- fmt.Println("Your data can be protected with one of the following sources:")
+ fmt.Fprintln(cmd.Output, "Your data can be protected with one of the following sources:")
for idx := 1; idx < len(metadata.SourceType_value); idx++ {
source := metadata.SourceType(idx)
description := sourceDescriptions[source]
- fmt.Printf("%d - %s (%s)\n", idx, description, source)
+ fmt.Fprintf(cmd.Output, "%d - %s (%s)\n", idx, description, source)
}
for {
- fmt.Printf("Enter the source number for the new protector [%d - %s]: ",
+ fmt.Fprintf(cmd.Output, "Enter the source number for the new protector [%d - %s]: ",
ctx.Config.Source, ctx.Config.Source)
- input, err := util.ReadLine()
+ input, err := cmd.ReadLine()
if err != nil {
return err
}
@@ -114,7 +110,7 @@ func promptForSource(ctx *actions.Context) error {
return nil
}
- // Check for a valid index, reprompt if invalid.
+ // Check for a valid index, prompt again if invalid.
index, err := strconv.Atoi(input)
if err == nil && index >= 1 && index < len(metadata.SourceType_value) {
ctx.Config.Source = metadata.SourceType(index)
@@ -130,14 +126,14 @@ func promptForKeyFile(prompt string) (*os.File, error) {
if keyFileFlag.Value != "" {
return os.Open(keyFileFlag.Value)
}
- if quietFlag.Value {
+ if cmd.QuietFlag.Value {
return nil, ErrSpecifyKeyFile
}
// Prompt for a valid path until we get a file we can open.
for {
- fmt.Print(prompt)
- filename, err := util.ReadLine()
+ fmt.Fprint(cmd.Output, prompt)
+ filename, err := cmd.ReadLine()
if err != nil {
return nil, err
}
@@ -145,7 +141,7 @@ func promptForKeyFile(prompt string) (*os.File, error) {
if err == nil {
return file, nil
}
- fmt.Println(err)
+ fmt.Fprintln(cmd.Output, err)
}
}
@@ -155,7 +151,7 @@ func promptForKeyFile(prompt string) (*os.File, error) {
// from, that protector is automatically selected.
func promptForProtector(options []*actions.ProtectorOption) (int, error) {
numOptions := len(options)
- log.Printf("selecting from %s", pluralize(numOptions, "protector"))
+ log.Printf("selecting from %s", cmd.Pluralize(numOptions, "protector"))
// Get the number of load errors.
numLoadErrors := 0
@@ -172,12 +168,12 @@ func promptForProtector(options []*actions.ProtectorOption) (int, error) {
if numOptions == 1 {
return 0, nil
}
- if quietFlag.Value {
+ if cmd.QuietFlag.Value {
return 0, ErrSpecifyProtector
}
// List all of the protector options which did not have a load error.
- fmt.Println("The available protectors are: ")
+ fmt.Fprintln(cmd.Output, "The available protectors are: ")
for idx, option := range options {
if option.LoadError != nil {
continue
@@ -187,16 +183,16 @@ func promptForProtector(options []*actions.ProtectorOption) (int, error) {
if option.LinkedMount != nil {
description += fmt.Sprintf(" (linked protector on %q)", option.LinkedMount.Path)
}
- fmt.Println(description)
+ fmt.Fprintln(cmd.Output, description)
}
if numLoadErrors > 0 {
- fmt.Print(wrapText("NOTE: %d of the %d protectors failed to load. "+loadHelpText, 0))
+ fmt.Fprintln(cmd.Output, "NOTE: %d of the %d protectors failed to load. "+loadHelpText)
}
for {
- fmt.Print("Enter the number of protector to use: ")
- input, err := util.ReadLine()
+ fmt.Fprint(cmd.Output, "Enter the number of protector to use: ")
+ input, err := cmd.ReadLine()
if err != nil {
return 0, err
}
diff --git a/cmd/fscrypt/setup.go b/cmd/fscrypt/setup.go
index 63f0c51..a421783 100644
--- a/cmd/fscrypt/setup.go
+++ b/cmd/fscrypt/setup.go
@@ -70,8 +70,8 @@ func setupFilesystem(path string) error {
return err
}
- fmt.Fprintf(Output, "Metadata directories created at %q.\n", ctx.Mount.BaseDir())
- fmt.Fprintf(Output, "Filesystem %q (%s) ready for use with %s encryption.\n",
+ fmt.Fprintf(cmd.Output, "Metadata directories created at %q.\n", ctx.Mount.BaseDir())
+ fmt.Fprintf(cmd.Output, "Filesystem %q (%s) ready for use with %s encryption.\n",
ctx.Mount.Path, ctx.Mount.Device, ctx.Mount.Filesystem)
return nil
}
diff --git a/cmd/fscrypt/status.go b/cmd/fscrypt/status.go
index 87799a8..e9c2d8d 100644
--- a/cmd/fscrypt/status.go
+++ b/cmd/fscrypt/status.go
@@ -38,7 +38,7 @@ import (
// Creates a writer which correctly aligns tabs with the specified header.
// Must call Flush() when done.
func makeTableWriter(w io.Writer, header string) *tabwriter.Writer {
- tableWriter := tabwriter.NewWriter(w, 0, indentLength, indentLength, ' ', 0)
+ tableWriter := tabwriter.NewWriter(cmd.Output, 0, cmd.TabWidth, cmd.TabWidth, ' ', 0)
fmt.Fprintln(tableWriter, header)
return tableWriter
}
@@ -140,8 +140,10 @@ func writeFilesystemStatus(ctx *actions.Context) error {
return err
}
- fmt.Fprintf(w, "%s filesystem %q has %s and %s\n\n", ctx.Mount.Filesystem, ctx.Mount.Path,
- pluralize(len(options), "protector"), pluralize(len(policyDescriptors), "policy"))
+ fmt.Fprintf(cmd.Output, "%s filesystem %q has %s and %s\n\n",
+ ctx.Mount.Filesystem, ctx.Mount.Path,
+ cmd.Pluralize(len(options), "protector"),
+ cmd.Pluralize(len(policyDescriptors), "policy"))
if len(options) > 0 {
writeOptions(options)
@@ -183,7 +185,7 @@ func writePathStatus(path string) error {
fmt.Fprintln(cmd.Output)
options := policy.ProtectorOptions()
- fmt.Fprintf(cmd.Output, "Protected with %s:\n", pluralize(len(options), "protector"))
- writeOptions(cmd.Output, options)
+ fmt.Fprintf(cmd.Output, "Protected with %s:\n", cmd.Pluralize(len(options), "protector"))
+ writeOptions(options)
return nil
}
diff --git a/cmd/run.go b/cmd/run.go
index 7765303..7517662 100644
--- a/cmd/run.go
+++ b/cmd/run.go
@@ -48,22 +48,46 @@ var TemplateTitle = "{{.FullName}}{{if .Command.Title}} - {{.Command.Title}}{{en
var TemplateUsage = `{{with $lines := .Command.UsageLines}}
Usage:
{{- range $lines}}
- {{$.FullName}} {{. -}}
+ {{printf "%s %s" $.FullName . | WrapText 8 2 -}}
{{end}}
{{end -}}
{{with $commands := .Command.SubCommands}}
+{{with $n := $.Command.MaxNameLength -}}
+{{with $fmt := printf "%%-%ds - %%s" $n -}}
Commands:
+{{if le (add 8 $n 3 $.Command.MaxTitleLength) LineLength -}}
+
+{{range $commands -}}
+
+{{end -}}
+
+{{else -}}
+
+{{range $commands}}
+{{end -}}
+
+{{end}}{{end}}{{end}}
+{{end -}}
+
+
{{- range $commands}}
- {{.Name}}{{if .Title}} - {{.Title}}{{end -}}
-{{end}}
+ {{if not .Title -}}
+ {{.Name -}}
+ {{else if le (add 8 $n 3 (len .Title)) LineLength -}}
+ {{printf $fmt .Name .Title -}}
+ {{else -}}
+ {{.Name}}
+ {{WrapText 16 2 .Title -}}
+ {{end -}}
+{{end}}{{end}}{{end}}
{{end -}}
{{with $arguments := .FullArguments}}
Arguments:
{{- range $arguments}}
{{.}}
- {{WrapText .Usage 2 -}}
+ {{WrapText 16 2 .Usage -}}
{{end}}
{{end -}}
@@ -71,7 +95,7 @@ Arguments:
Options:
{{- range $flags}}
{{.}}
- {{WrapText .FullUsage 2 -}}
+ {{WrapText 16 2 .FullUsage -}}
{{end}}
{{end -}}