Skip to content

Commit be61dea

Browse files
ifreundemaste
authored andcommitted
pkg: clarify argument parsing
Currently pkg parses all arguments and subcommands in a single getopt_long() loop. This results in quite a bit of accidental complexity, especially around the -r option for the bootstrap and add subcommands. This also results in the undocumented behavior of all options understood by pkg(7) being accepted anywhere in the argument list regardless of their position. This contradicts the behavior of pkg(8) and I find it quite unexpected. This patch splits the argument parsing done by pkg(7) into two phases for global options and subcommand options. This gives behavior that matches pkg(8), accepting the subset of pkg(8) options supported by pkg(7) in the exact same positions that pkg(8) accepts them. This is however a somewhat breaking change, as options are no longer accepted by pkg(7) in places they are rejected by pkg(8). For example, `pkg -f bootstrap` no longer works, only `pkg bootstrap -f`. The original motivation for writing this patch was investigating support for --rootdir in pkg(7). Since pkg(8) uses -r as the short form for both --rootdir and --repository depending on the position, I decided that pkg(7) needed to be brought inline with pkg(8)'s argument parsing behavior to avoid confusion. This patch also gets rid of args_add_message (unused since 40b9f92) Relnotes: yes Reviewed by: bapt Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D49977
1 parent d5e3cf4 commit be61dea

2 files changed

Lines changed: 87 additions & 92 deletions

File tree

usr.sbin/pkg/pkg.7

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2323
.\" SUCH DAMAGE.
2424
.\"
25-
.Dd March 30, 2025
25+
.Dd April 29, 2025
2626
.Dt PKG 7
2727
.Os
2828
.Sh NAME
@@ -33,8 +33,9 @@
3333
.Op Fl d
3434
.Ar command ...
3535
.Nm
36+
.Op Fl d
3637
.Cm add
37-
.Op Fl dfy
38+
.Op Fl fy
3839
.Op Fl r Ar reponame
3940
.Ar pkg.pkg
4041
.Nm

usr.sbin/pkg/pkg.c

Lines changed: 84 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -942,10 +942,6 @@ static const char args_bootstrap_message[] =
942942
"Too many arguments\n"
943943
"Usage: pkg [-4|-6] bootstrap [-f] [-y]\n";
944944

945-
static const char args_add_message[] =
946-
"Too many arguments\n"
947-
"Usage: pkg add [-f] [-y] {pkg.pkg}\n";
948-
949945
static int
950946
pkg_query_yes_no(void)
951947
{
@@ -1072,137 +1068,138 @@ int
10721068
main(int argc, char *argv[])
10731069
{
10741070
char pkgpath[MAXPATHLEN];
1071+
char **original_argv;
10751072
const char *pkgarg, *repo_name;
10761073
bool activation_test, add_pkg, bootstrap_only, force, yes;
10771074
signed char ch;
10781075
const char *fetchOpts;
1079-
char *command;
10801076
struct repositories *repositories;
10811077

10821078
activation_test = false;
10831079
add_pkg = false;
10841080
bootstrap_only = false;
1085-
command = NULL;
10861081
fetchOpts = "";
10871082
force = false;
1083+
original_argv = argv;
10881084
pkgarg = NULL;
10891085
repo_name = NULL;
10901086
yes = false;
10911087

10921088
struct option longopts[] = {
10931089
{ "debug", no_argument, NULL, 'd' },
1094-
{ "force", no_argument, NULL, 'f' },
10951090
{ "only-ipv4", no_argument, NULL, '4' },
10961091
{ "only-ipv6", no_argument, NULL, '6' },
1097-
{ "yes", no_argument, NULL, 'y' },
10981092
{ NULL, 0, NULL, 0 },
10991093
};
11001094

11011095
snprintf(pkgpath, MAXPATHLEN, "%s/sbin/pkg", getlocalbase());
11021096

1103-
while ((ch = getopt_long(argc, argv, "-:dfr::yN46", longopts, NULL)) != -1) {
1097+
while ((ch = getopt_long(argc, argv, "+:dN46", longopts, NULL)) != -1) {
11041098
switch (ch) {
11051099
case 'd':
11061100
debug++;
11071101
break;
1108-
case 'f':
1109-
force = true;
1110-
break;
11111102
case 'N':
11121103
activation_test = true;
11131104
break;
1114-
case 'y':
1115-
yes = true;
1116-
break;
11171105
case '4':
11181106
fetchOpts = "4";
11191107
break;
11201108
case '6':
11211109
fetchOpts = "6";
11221110
break;
1123-
case 'r':
1124-
/*
1125-
* The repository can only be specified for an explicit
1126-
* bootstrap request at this time, so that we don't
1127-
* confuse the user if they're trying to use a verb that
1128-
* has some other conflicting meaning but we need to
1129-
* bootstrap.
1130-
*
1131-
* For that reason, we specify that -r has an optional
1132-
* argument above and process the next index ourselves.
1133-
* This is mostly significant because getopt(3) will
1134-
* otherwise eat the next argument, which could be
1135-
* something we need to try and make sense of.
1136-
*
1137-
* At worst this gets us false positives that we ignore
1138-
* in other contexts, and we have to do a little fudging
1139-
* in order to support separating -r from the reponame
1140-
* with a space since it's not actually optional in
1141-
* the bootstrap/add sense.
1142-
*/
1143-
if (add_pkg || bootstrap_only) {
1144-
if (optarg != NULL) {
1145-
repo_name = optarg;
1146-
} else if (optind < argc) {
1147-
repo_name = argv[optind];
1148-
}
1111+
default:
1112+
break;
1113+
}
1114+
}
1115+
if (debug > 1)
1116+
fetchDebug = 1;
11491117

1150-
if (repo_name == NULL || *repo_name == '\0') {
1151-
fprintf(stderr,
1152-
"Must specify a repository with -r!\n");
1153-
exit(EXIT_FAILURE);
1154-
}
1118+
argc -= optind;
1119+
argv += optind;
1120+
1121+
if (argc >= 1) {
1122+
if (strcmp(argv[0], "bootstrap") == 0) {
1123+
bootstrap_only = true;
1124+
} else if (strcmp(argv[0], "add") == 0) {
1125+
add_pkg = true;
1126+
}
11551127

1156-
if (optarg == NULL) {
1157-
/* Advance past repo name. */
1158-
optreset = 1;
1159-
optind++;
1128+
optreset = 1;
1129+
optind = 1;
1130+
if (bootstrap_only || add_pkg) {
1131+
struct option sub_longopts[] = {
1132+
{ "force", no_argument, NULL, 'f' },
1133+
{ "yes", no_argument, NULL, 'y' },
1134+
{ NULL, 0, NULL, 0 },
1135+
};
1136+
while ((ch = getopt_long(argc, argv, "+:fr:y",
1137+
sub_longopts, NULL)) != -1) {
1138+
switch (ch) {
1139+
case 'f':
1140+
force = true;
1141+
break;
1142+
case 'r':
1143+
repo_name = optarg;
1144+
break;
1145+
case 'y':
1146+
yes = true;
1147+
break;
1148+
case ':':
1149+
fprintf(stderr, "Option -%c requires an argument\n", optopt);
1150+
exit(EXIT_FAILURE);
1151+
break;
1152+
default:
1153+
break;
11601154
}
11611155
}
1162-
break;
1163-
case 1:
1164-
// Non-option arguments, first one is the command
1165-
if (command == NULL) {
1166-
command = argv[optind-1];
1167-
if (strcmp(command, "add") == 0) {
1168-
add_pkg = true;
1169-
}
1170-
else if (strcmp(command, "bootstrap") == 0) {
1171-
bootstrap_only = true;
1156+
} else {
1157+
/*
1158+
* Parse -y and --yes regardless of the pkg subcommand
1159+
* specified. This is necessary to make, for example,
1160+
* `pkg install -y foobar` work as expected when pkg is
1161+
* not yet bootstrapped.
1162+
*/
1163+
struct option sub_longopts[] = {
1164+
{ "yes", no_argument, NULL, 'y' },
1165+
{ NULL, 0, NULL, 0 },
1166+
};
1167+
while ((ch = getopt_long(argc, argv, "+y",
1168+
sub_longopts, NULL)) != -1) {
1169+
switch (ch) {
1170+
case 'y':
1171+
yes = true;
1172+
break;
1173+
default:
1174+
break;
11721175
}
11731176
}
1174-
// bootstrap doesn't accept other arguments
1175-
else if (bootstrap_only) {
1176-
fprintf(stderr, args_bootstrap_message);
1177+
1178+
}
1179+
argc -= optind;
1180+
argv += optind;
1181+
1182+
if (bootstrap_only && argc > 0) {
1183+
fprintf(stderr, args_bootstrap_message);
1184+
exit(EXIT_FAILURE);
1185+
}
1186+
1187+
if (add_pkg) {
1188+
if (argc < 1) {
1189+
fprintf(stderr, "Path to pkg.pkg required\n");
11771190
exit(EXIT_FAILURE);
1178-
}
1179-
else if (add_pkg && pkgarg != NULL) {
1191+
} else if (argc == 1 && pkg_is_pkg_pkg(argv[0])) {
1192+
pkgarg = argv[0];
1193+
} else {
11801194
/*
1181-
* Additional arguments also means it's not a
1182-
* local bootstrap request.
1195+
* If the target package is not pkg.pkg
1196+
* or there is more than one target package,
1197+
* this is not a local bootstrap request.
11831198
*/
11841199
add_pkg = false;
11851200
}
1186-
else if (add_pkg) {
1187-
/*
1188-
* If it's not a request for pkg or pkg-devel,
1189-
* then we must assume they were trying to
1190-
* install some other local package and we
1191-
* should try to bootstrap from the repo.
1192-
*/
1193-
if (!pkg_is_pkg_pkg(argv[optind-1])) {
1194-
add_pkg = false;
1195-
} else {
1196-
pkgarg = argv[optind-1];
1197-
}
1198-
}
1199-
break;
1200-
default:
1201-
break;
12021201
}
12031202
}
1204-
if (debug > 1)
1205-
fetchDebug = 1;
12061203

12071204
if ((bootstrap_only && force) || access(pkgpath, X_OK) == -1) {
12081205
struct repository *repo;
@@ -1218,10 +1215,7 @@ main(int argc, char *argv[])
12181215
config_init(repo_name);
12191216

12201217
if (add_pkg) {
1221-
if (pkgarg == NULL) {
1222-
fprintf(stderr, "Path to pkg.pkg required\n");
1223-
exit(EXIT_FAILURE);
1224-
}
1218+
assert(pkgarg != NULL);
12251219
if (access(pkgarg, R_OK) == -1) {
12261220
fprintf(stderr, "No such file: %s\n", pkgarg);
12271221
exit(EXIT_FAILURE);
@@ -1263,7 +1257,7 @@ main(int argc, char *argv[])
12631257
exit(EXIT_SUCCESS);
12641258
}
12651259

1266-
execv(pkgpath, argv);
1260+
execv(pkgpath, original_argv);
12671261

12681262
/* NOT REACHED */
12691263
return (EXIT_FAILURE);

0 commit comments

Comments
 (0)