Skip to content

Commit ff88b0b

Browse files
authored
Add informative error messages when parsing args
This change also comes with some under the hood modifications to how args are handled. New function: long long optionsParseNum(const char *str, long long min, long long max, const char *errmsg[static 1]); Parses the string representation of an integer in str, and simultaneously ensures that it is >= min and <= max. On success, returns the integer and sets *errmsg to NULL. On error, returns 0 and sets *errmsg to a pointer to a string containing the reason why the number can't be parsed. Author : Guilherme Janczak <guilherme.janczak@yandex.com> Suggested-by : Zev Weiss <zev@bewilderbeest.net> Reviewed-by : NRK <nrk@disroot.org Reviewed-by : Daniel T. Borelli <danieltborelli@gmail.com> Closes #200, closes #206
1 parent ae4deba commit ff88b0b

File tree

7 files changed

+170
-134
lines changed

7 files changed

+170
-134
lines changed

src/note.c

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* note.c
22
33
Copyright 2019-2022 Daniel T. Borelli <danieltborelli@gmail.com>
4-
Copyright 2021-2022 Guilherme Janczak <guilherme.janczak@yandex.com>
4+
Copyright 2021-2023 Guilherme Janczak <guilherme.janczak@yandex.com>
55
Copyright 2021 IFo Hancroft <contact@ifohancroft.com>
66
Copyright 2021 Peter Wu <peterwu@hotmail.com>
77
@@ -28,6 +28,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2828

2929
#include <assert.h>
3030
#include <err.h>
31+
#include <limits.h>
3132
#include <stdio.h>
3233
#include <stdlib.h>
3334
#include <string.h>
@@ -114,6 +115,7 @@ void scrotNoteNew(char const *const format)
114115
const char type = *++token;
115116
char *savePtr = NULL;
116117
char *c;
118+
const char *errmsg;
117119

118120
nextSpace(&token);
119121

@@ -129,7 +131,12 @@ void scrotNoteNew(char const *const format)
129131
if (!number)
130132
errx(EXIT_FAILURE, "Error --note option : Malformed syntax for -f, required number.");
131133

132-
const int fontSize = optionsParseRequiredNumber(++number);
134+
const int fontSize = optionsParseNum(++number, 1, INT_MAX,
135+
&errmsg);
136+
if (errmsg) {
137+
errx(EXIT_FAILURE, "option --note: font size '%s' is %s",
138+
number, errmsg);
139+
}
133140

134141
if (fontSize < 6)
135142
warnx("Warning: --note option: font size < 6");
@@ -161,9 +168,17 @@ void scrotNoteNew(char const *const format)
161168

162169
while (c) {
163170
token = c;
164-
165-
const int color = optionsParseRequireRange(
166-
optionsParseRequiredNumber(c), 0 ,255);
171+
char *const space = strchr(c, ' ');
172+
173+
if (space)
174+
*space = '\0';
175+
const int color = optionsParseNum(c, 0, 255, &errmsg);
176+
if (errmsg) {
177+
errx(EXIT_FAILURE, "option --note: color '%s' is %s", c,
178+
errmsg);
179+
}
180+
if (space)
181+
*space = ' ';
167182

168183
switch (++numberColors) {
169184
case 1:

src/options.c

+127-93
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Copyright 2019-2022 Daniel T. Borelli <danieltborelli@gmail.com>
1313
Copyright 2019 Jade Auer <jade@trashwitch.dev>
1414
Copyright 2020 Sean Brennan <zettix1@gmail.com>
1515
Copyright 2021 Christopher R. Nelson <christopher.nelson@languidnights.com>
16-
Copyright 2021-2022 Guilherme Janczak <guilherme.janczak@yandex.com>
16+
Copyright 2021-2023 Guilherme Janczak <guilherme.janczak@yandex.com>
1717
Copyright 2021 IFo Hancroft <contact@ifohancroft.com>
1818
Copyright 2021 Peter Wu <peterwu@hotmail.com>
1919
Copyright 2021 Wilson Smith <01wsmith+gh@gmail.com>
@@ -89,42 +89,57 @@ struct ScrotOptions opt = {
8989

9090
static void showUsage(void);
9191
static void showVersion(void);
92-
93-
int optionsParseRequiredNumber(const char *str)
92+
static void optionsParseThumbnail(char *);
93+
94+
/* optionsParseNum: "string to number" function.
95+
*
96+
* Parses the string representation of an integer in str, and simultaneously
97+
* ensures that it is >= min and <= max.
98+
*
99+
* Returns the integer and sets *errmsg to NULL on success.
100+
* Returns 0 and sets *errmsg to a pointer to a string containing the
101+
* reason why the number can't be parsed on error.
102+
*
103+
* usage:
104+
* char *errmsg;
105+
* unsigned int nonnegative;
106+
* if ((nonnegative = optionsParseNum(optarg, 0, UINT_MAX, &errmsg)) == NULL)
107+
* errx(EXIT_FAILURE, "-n: '%s' is %s", optarg, errmsg);
108+
*/
109+
long long optionsParseNum(const char *str, long long min, long long max,
110+
const char *errmsg[static 1])
94111
{
95-
assert(NULL != str); // fix yout caller function,
96-
// the user does not impose this behavior
97112
char *end = NULL;
98-
long ret = 0L;
99-
errno = 0;
100-
101-
ret = strtol(str, &end, 10);
102-
103-
if (errno)
104-
goto range_error;
105-
106-
if (str == end)
107-
errx(EXIT_FAILURE, "the option is not a number: %s", end);
108-
109-
if (ret > INT_MAX || ret < INT_MIN) {
110-
errno = ERANGE;
111-
goto range_error;
113+
long long rval;
114+
int saved_errno = errno;
115+
if (str == NULL) {
116+
*errmsg = "missing";
117+
return 0;
112118
}
119+
*errmsg = NULL;
113120

114-
return ret;
115-
116-
range_error:
117-
err(EXIT_FAILURE, "error strtol");
118-
}
119-
120-
static int nonNegativeNumber(int number)
121-
{
122-
return (number < 0) ? 0 : number;
123-
}
121+
errno = 0;
122+
rval = strtoll(str, &end, 10);
123+
if (errno == ERANGE) {
124+
*errmsg = "not representable";
125+
} else if (*str == '\0') {
126+
*errmsg = "the null string";
127+
} else if (*end != '\0') {
128+
*errmsg = "not a number";
129+
} else if (rval < min) {
130+
/*
131+
* rval could be set to 0 due to strtoll() returning error and this
132+
* could be smaller than min or larger than max. To make sure we don't
133+
* return the wrong error message, put min/max checks after everything
134+
* else.
135+
*/
136+
*errmsg = min == 0 ? "negative" : "too small";
137+
} else if (rval > max) {
138+
*errmsg = "too large";
139+
}
140+
errno = saved_errno;
124141

125-
int optionsParseRequireRange(int n, int lo, int hi)
126-
{
127-
return (n < lo ? lo : n > hi ? hi : n);
142+
return (*errmsg ? 0 : rval);
128143
}
129144

130145
static bool optionsParseIsString(const char *const str)
@@ -202,11 +217,11 @@ static void optionsParseSelection(const char *optarg)
202217
errx(EXIT_FAILURE, "option --select: Invalid parameter.");
203218

204219
if (opt.selection.mode == SELECTION_MODE_BLUR) {
205-
int const num = nonNegativeNumber(optionsParseRequiredNumber(value));
206-
207-
opt.selection.paramNum = optionsParseRequireRange(num,
208-
SELECTION_MODE_BLUR_MIN, SELECTION_MODE_BLUR_MAX);
209-
220+
const char *errmsg;
221+
opt.selection.paramNum = optionsParseNum(value,
222+
SELECTION_MODE_BLUR_MIN, SELECTION_MODE_BLUR_MAX, &errmsg);
223+
if (errmsg)
224+
errx(EXIT_FAILURE, "option --select: '%s' is %s", value, errmsg);
210225
} else { // SELECTION_MODE_HIDE
211226

212227
checkMaxInputFileName(value);
@@ -236,6 +251,7 @@ static void optionsParseLine(char *optarg)
236251

237252
char *subopts = optarg;
238253
char *value = NULL;
254+
const char *errmsg;
239255

240256
while (*subopts != '\0') {
241257
switch (getsubopt(&subopts, token, &value)) {
@@ -255,16 +271,12 @@ static void optionsParseLine(char *optarg)
255271
}
256272
break;
257273
case Width:
258-
if (!optionsParseIsString(value)) {
259-
errx(EXIT_FAILURE, "Missing value for suboption '%s'",
260-
token[Width]);
261-
}
262-
263-
opt.lineWidth = optionsParseRequiredNumber(value);
264-
265-
if (opt.lineWidth <= 0 || opt.lineWidth > 8) {
266-
errx(EXIT_FAILURE, "Value of the range (1..8) for "
267-
"suboption '%s': %d", token[Width], opt.lineWidth);
274+
opt.lineWidth = optionsParseNum(value, 1, 8, &errmsg);
275+
if (errmsg) {
276+
if (value == NULL)
277+
value = "(null)";
278+
errx(EXIT_FAILURE, "option --line: suboption '%s': '%s' is %s",
279+
token[Width], value, errmsg);
268280
}
269281
break;
270282
case Color:
@@ -293,11 +305,14 @@ static void optionsParseLine(char *optarg)
293305
opt.lineMode = estrdup(value);
294306
break;
295307
case Opacity:
296-
if (!optionsParseIsString(value)) {
297-
errx(EXIT_FAILURE, "Missing value for suboption '%s'",
298-
token[Opacity]);
308+
opt.lineOpacity = optionsParseNum(value,
309+
SELECTION_OPACITY_MIN, SELECTION_OPACITY_MAX, &errmsg);
310+
if (errmsg) {
311+
if (value == NULL)
312+
value = "(null)";
313+
errx(EXIT_FAILURE, "option --line: suboption %s: '%s' is %s",
314+
token[Opacity], value, errmsg);
299315
}
300-
opt.lineOpacity = optionsParseRequiredNumber(value);
301316
break;
302317
default:
303318
errx(EXIT_FAILURE, "No match found for token: '%s'", value);
@@ -378,6 +393,7 @@ void optionsParse(int argc, char *argv[])
378393
{ 0, 0, 0, 0 }
379394
};
380395
int optch = 0, cmdx = 0;
396+
const char *errmsg;
381397

382398
/* Now to pass some optionarinos */
383399
while ((optch = getopt_long(argc, argv, stropts, lopts, &cmdx)) != EOF) {
@@ -394,7 +410,11 @@ void optionsParse(int argc, char *argv[])
394410
opt.border = 1;
395411
break;
396412
case 'd':
397-
opt.delay = nonNegativeNumber(optionsParseRequiredNumber(optarg));
413+
opt.delay = optionsParseNum(optarg, 0, INT_MAX, &errmsg);
414+
if (errmsg) {
415+
errx(EXIT_FAILURE, "option --delay: '%s' is %s", optarg,
416+
errmsg);
417+
}
398418
break;
399419
case 'e':
400420
opt.exec = estrdup(optarg);
@@ -403,7 +423,11 @@ void optionsParse(int argc, char *argv[])
403423
opt.multidisp = 1;
404424
break;
405425
case 'q':
406-
opt.quality = optionsParseRequiredNumber(optarg);
426+
opt.quality = optionsParseNum(optarg, 1, 100, &errmsg);
427+
if (errmsg) {
428+
errx(EXIT_FAILURE, "option --quality: '%s' is %s", optarg,
429+
errmsg);
430+
}
407431
break;
408432
case 's':
409433
optionsParseSelection(optarg);
@@ -458,7 +482,11 @@ void optionsParse(int argc, char *argv[])
458482
optionsParseFileName(optarg);
459483
break;
460484
case 'M':
461-
opt.monitor = nonNegativeNumber(optionsParseRequiredNumber(optarg));
485+
opt.monitor = optionsParseNum(optarg, 0, INT_MAX, &errmsg);
486+
if (errmsg) {
487+
errx(EXIT_FAILURE, "option --monitor: '%s' is %s", optarg,
488+
errmsg);
489+
}
462490
break;
463491
case '?':
464492
exit(EXIT_FAILURE);
@@ -480,7 +508,7 @@ void optionsParse(int argc, char *argv[])
480508
free(opt.outputFile);
481509
opt.outputFile = getPathOfStdout();
482510
opt.overwrite = 1;
483-
opt.thumb = 0;
511+
opt.thumbWorP = 0;
484512
}
485513
} else
486514
warnx("unrecognised option %s", argv[optind++]);
@@ -533,24 +561,30 @@ char *optionsNameThumbnail(const char *name)
533561
void optionsParseAutoselect(char *optarg)
534562
{
535563
char *token;
536-
const char tokenDelimiter[2] = ",";
537-
int dimensions[4];
564+
int *dimensions[] = {&opt.autoselectX, &opt.autoselectY, &opt.autoselectW,
565+
&opt.autoselectH, NULL /* Sentinel. */};
538566
int i = 0;
567+
int min;
568+
const char *errmsg;
569+
570+
/* Geometry dimensions must be in format x,y,w,h */
571+
token = strtok(optarg, ",");
572+
for (; token != NULL; token = strtok(NULL, ",")) {
573+
if (dimensions[i] == NULL)
574+
errx(EXIT_FAILURE, "option --autoselect: too many dimensions");
575+
576+
min = i >= 2; /* X,Y offsets may be 0. Width and height may not. */
577+
*dimensions[i] = optionsParseNum(token, min, INT_MAX, &errmsg);
578+
if (errmsg) {
579+
errx(EXIT_FAILURE, "option --autoselect: '%s' is %s", token,
580+
errmsg);
581+
}
582+
i++;
539583

540-
if (strchr(optarg, ',')) { /* geometry dimensions must be in format x,y,w,h */
541-
dimensions[i++] = optionsParseRequiredNumber(strtok(optarg, tokenDelimiter));
542-
while ((token = strtok(NULL, tokenDelimiter)))
543-
dimensions[i++] = optionsParseRequiredNumber(token);
544584
opt.autoselect = 1;
545-
opt.autoselectX = dimensions[0];
546-
opt.autoselectY = dimensions[1];
547-
opt.autoselectW = dimensions[2];
548-
opt.autoselectH = dimensions[3];
549-
550-
if (i != 4)
551-
errx(EXIT_FAILURE, "option 'autoselect' require 4 arguments");
552-
} else
553-
errx(EXIT_FAILURE, "invalid format for option -- 'autoselect'");
585+
}
586+
if (i < 4)
587+
errx(EXIT_FAILURE, "option --autoselect: too few dimensions");
554588
}
555589

556590
void optionsParseDisplay(char *optarg)
@@ -560,33 +594,33 @@ void optionsParseDisplay(char *optarg)
560594
err(EXIT_FAILURE, "Unable to allocate display");
561595
}
562596

563-
void optionsParseThumbnail(char *optarg)
597+
static void optionsParseThumbnail(char *optarg)
564598
{
565-
char *token;
599+
char *height;
600+
const char *errmsg;
601+
602+
if ((height = strchr(optarg, 'x')) != NULL) { /* optarg is a resolution. */
603+
/* optarg holds the width, height holds the height. */
604+
*height++ = '\0';
566605

567-
if (strchr(optarg, 'x')) { /* We want to specify the geometry */
568-
token = strtok(optarg, "x");
569-
opt.thumbWidth = optionsParseRequiredNumber(token);
570-
token = strtok(NULL, "x");
571-
if (token) {
572-
opt.thumbWidth = optionsParseRequiredNumber(optarg);
573-
opt.thumbHeight = optionsParseRequiredNumber(token);
574-
575-
if (opt.thumbWidth < 0)
576-
opt.thumbWidth = 1;
577-
if (opt.thumbHeight < 0)
578-
opt.thumbHeight = 1;
579-
580-
if (!opt.thumbWidth && !opt.thumbHeight)
581-
opt.thumb = 0;
582-
else
583-
opt.thumb = 1;
606+
opt.thumbWorP = optionsParseNum(optarg, 1, INT_MAX, &errmsg);
607+
if (errmsg) {
608+
errx(EXIT_FAILURE, "option --thumb: resolution width '%s' is %s",
609+
optarg, errmsg);
584610
}
585-
} else {
586-
opt.thumb = optionsParseRequireRange(
587-
optionsParseRequiredNumber(optarg), 1, 100);
588-
}
589611

612+
opt.thumbH = optionsParseNum(height, 1, INT_MAX, &errmsg);
613+
if (errmsg) {
614+
errx(EXIT_FAILURE, "option --thumb: resolution height '%s' is %s",
615+
height, errmsg);
616+
}
617+
} else { /* optarg is a percentage. */
618+
opt.thumbWorP = optionsParseNum(optarg, 1, INT_MAX, &errmsg);
619+
if (errmsg) {
620+
errx(EXIT_FAILURE, "option --thumb: percentage '%s' is %s", optarg,
621+
errmsg);
622+
}
623+
}
590624
}
591625

592626
void optionsParseFileName(const char *optarg)

0 commit comments

Comments
 (0)