Skip to content

Options after SNAPSHOTS_DIR are silently treated as snapshot paths, causing exit code 1 #438

@jdmanring

Description

@jdmanring

Environment:

  • grub-btrfs 4.14-1.1

What happens:

When grub-btrfsd is invoked with options placed after the snapshot path:

grub-btrfsd /path/to/snapshots --syslog

the daemon exits immediately with code 1 and the error:

[!] No directory found at --syslog
[!] Please specify a valid snapshot directory

Root cause:

grub-btrfsd uses bash getopts for argument parsing. getopts stops processing
options at the first non-option argument (standard POSIX behavior). With the
invocation above:

  1. parse_arguments runs getopts — sees /path/to/snapshots first (not an option)
    OPTIND stays at 1, the while loop exits without processing --syslog
  2. Back in main(): shift $(( OPTIND - 1 )) shifts by 0
  3. snapdirs=( "${@}" ) = ( "/path/to/snapshots" "--syslog" )
  4. checks() runs [ -d --syslog ] → false → exits with code 1

This is consistent POSIX behavior, but is not obvious when writing service unit
files or run scripts where the primary argument (the snapshot path) is often
placed immediately after the program name, with behavioral flags appended.
The same issue affects any option placed after the path — e.g.
grub-btrfsd --syslog /path --verbose will treat --verbose as a snapshot path.

The exit code (1) is identical to the missing inotifywait error, and if the
misplaced flag is --syslog, syslog is never configured — leaving no log trace
under a service supervisor.

Additional bug found while investigating:

-o (short form of --timeshift-old) is handled in the case block but is
absent from the getopts option string (line 81 in 4.14-1.1: getopts :l:ctvrsh-:).
The short option -o is therefore silently broken — it triggers the *) unknown
option branch and exits with code 1. Only the long form --timeshift-old works.

Fix:

Minimum: add a note to the help output:

Note: all OPTIONS must appear before SNAPSHOTS_DIRS

More robust: switch from getopts to GNU getopt, which supports mixed
option/argument ordering (and correctly maps all short options including -o):

ARGS=$(getopt -o cl:tvrosh \
  --long no-color,log-file:,timeshift-auto,timeshift-old,verbose,recursive,syslog,help \
  -- "$@")
eval set -- "$ARGS"

Note: -l requires an argument (log file path), so it carries the : suffix;
-s (syslog) takes no argument. The original getopts string correctly has l:
but this fix clarifies the mapping explicitly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions