]> git.ozlabs.org Git - ccan/blob - ccan/opt/usage.c
opt: avoid bad access on empty usage message, free all mem in tests.
[ccan] / ccan / opt / usage.c
1 /* Licensed under GPLv2+ - see LICENSE file for details */
2 #include <ccan/opt/opt.h>
3 #if HAVE_SYS_TERMIOS_H
4 #include <sys/ioctl.h>
5 #include <sys/termios.h> /* Required on Solaris for struct winsize */
6 #endif
7 #if HAVE_SYS_UNISTD_H
8 #include <sys/unistd.h> /* Required on Solaris for ioctl */
9 #endif
10 #include <string.h>
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <stdint.h>
14 #include <stdarg.h>
15 #include "private.h"
16
17 /* We only use this for pointer comparisons. */
18 const char opt_hidden[1];
19
20 #define MIN_DESC_WIDTH 40
21 #define MIN_TOTAL_WIDTH 50
22
23 static unsigned int get_columns(void)
24 {
25         int ws_col = 0;
26         const char *env = getenv("COLUMNS");
27
28         if (env)
29                 ws_col = atoi(env);
30
31 #ifdef TIOCGWINSZ
32         if (!ws_col)
33         {
34                 struct winsize w;
35                 if (ioctl(0, TIOCGWINSZ, &w) != -1)
36                         ws_col = w.ws_col;
37         }
38 #endif
39         if (!ws_col)
40                 ws_col = 80;
41
42         return ws_col;
43 }
44
45 /* Return number of chars of words to put on this line.
46  * Prefix is set to number to skip at start, maxlen is max width, returns
47  * length (after prefix) to put on this line.
48  * start is set if we start a new line in the source description. */
49 static size_t consume_words(const char *words, size_t maxlen, size_t *prefix,
50                             bool *start)
51 {
52         size_t oldlen, len;
53
54         /* Always swollow leading whitespace. */
55         *prefix = strspn(words, " \n");
56         words += *prefix;
57
58         /* Leading whitespace at start of line means literal. */
59         if (*start && *prefix) {
60                 oldlen = strcspn(words, "\n");
61         } else {
62                 /* Use at least one word, even if it takes us over maxlen. */
63                 oldlen = len = strcspn(words, " ");
64                 while (len <= maxlen) {
65                         oldlen = len;
66                         len += strspn(words+len, " ");
67                         if (words[len] == '\n')
68                                 break;
69                         len += strcspn(words+len, " \n");
70                         if (len == oldlen)
71                                 break;
72                 }
73         }
74
75         if (oldlen != 0)
76                 *start = (words[oldlen - 1] == '\n');
77         return oldlen;
78 }
79
80 static char *add_str_len(char *base, size_t *len, size_t *max,
81                          const char *str, size_t slen)
82 {
83         if (slen >= *max - *len)
84                 base = opt_alloc.realloc(base, *max = (*max * 2 + slen + 1));
85         memcpy(base + *len, str, slen);
86         *len += slen;
87         return base;
88 }
89
90 static char *add_str(char *base, size_t *len, size_t *max, const char *str)
91 {
92         return add_str_len(base, len, max, str, strlen(str));
93 }
94
95 static char *add_indent(char *base, size_t *len, size_t *max, size_t indent)
96 {
97         if (indent >= *max - *len)
98                 base = opt_alloc.realloc(base, *max = (*max * 2 + indent + 1));
99         memset(base + *len, ' ', indent);
100         *len += indent;
101         return base;
102 }
103
104 static char *add_desc(char *base, size_t *len, size_t *max,
105                       unsigned int indent, unsigned int width,
106                       const struct opt_table *opt)
107 {
108         size_t off, prefix, l;
109         const char *p;
110         bool same_line = false, start = true;
111
112         base = add_str(base, len, max, opt->names);
113         off = strlen(opt->names);
114         if ((opt->type & OPT_HASARG)
115             && !strchr(opt->names, ' ')
116             && !strchr(opt->names, '=')) {
117                 base = add_str(base, len, max, " <arg>");
118                 off += strlen(" <arg>");
119         }
120
121         /* Do we start description on next line? */
122         if (off + 2 > indent) {
123                 base = add_str(base, len, max, "\n");
124                 off = 0;
125         } else {
126                 base = add_indent(base, len, max, indent - off);
127                 off = indent;
128                 same_line = true;
129         }
130
131         /* Indent description. */
132         p = opt->desc;
133         while ((l = consume_words(p, width - indent, &prefix, &start)) != 0) {
134                 if (!same_line)
135                         base = add_indent(base, len, max, indent);
136                 p += prefix;
137                 base = add_str_len(base, len, max, p, l);
138                 base = add_str(base, len, max, "\n");
139                 off = indent + l;
140                 p += l;
141                 same_line = false;
142         }
143
144         /* Empty description?  Make it match normal case. */
145         if (same_line)
146                 base = add_str(base, len, max, "\n");
147
148         if (opt->show) {
149                 char buf[OPT_SHOW_LEN + sizeof("...")];
150                 strcpy(buf + OPT_SHOW_LEN, "...");
151                 opt->show(buf, opt->u.arg);
152
153                 /* If it doesn't fit on this line, indent. */
154                 if (off + strlen(" (default: ") + strlen(buf) + strlen(")")
155                     > width) {
156                         base = add_indent(base, len, max, indent);
157                 } else {
158                         /* Remove \n. */
159                         (*len)--;
160                 }
161
162                 base = add_str(base, len, max, " (default: ");
163                 base = add_str(base, len, max, buf);
164                 base = add_str(base, len, max, ")\n");
165         }
166         return base;
167 }
168
169 char *opt_usage(const char *argv0, const char *extra)
170 {
171         unsigned int i;
172         size_t max, len, width, indent;
173         char *ret;
174
175         width = get_columns();
176         if (width < MIN_TOTAL_WIDTH)
177                 width = MIN_TOTAL_WIDTH;
178
179         /* Figure out longest option. */
180         indent = 0;
181         for (i = 0; i < opt_count; i++) {
182                 size_t l;
183                 if (opt_table[i].desc == opt_hidden)
184                         continue;
185                 if (opt_table[i].type == OPT_SUBTABLE)
186                         continue;
187                 l = strlen(opt_table[i].names);
188                 if (opt_table[i].type == OPT_HASARG
189                     && !strchr(opt_table[i].names, ' ')
190                     && !strchr(opt_table[i].names, '='))
191                         l += strlen(" <arg>");
192                 if (l + 2 > indent)
193                         indent = l + 2;
194         }
195
196         /* Now we know how much to indent */
197         if (indent + MIN_DESC_WIDTH > width)
198                 indent = width - MIN_DESC_WIDTH;
199
200         len = max = 0;
201         ret = NULL;
202
203         ret = add_str(ret, &len, &max, "Usage: ");
204         ret = add_str(ret, &len, &max, argv0);
205
206         /* Find usage message from among registered options if necessary. */
207         if (!extra) {
208                 extra = "";
209                 for (i = 0; i < opt_count; i++) {
210                         if (opt_table[i].cb == (void *)opt_usage_and_exit
211                             && opt_table[i].u.carg) {
212                                 extra = opt_table[i].u.carg;
213                                 break;
214                         }
215                 }
216         }
217         ret = add_str(ret, &len, &max, " ");
218         ret = add_str(ret, &len, &max, extra);
219         ret = add_str(ret, &len, &max, "\n");
220
221         for (i = 0; i < opt_count; i++) {
222                 if (opt_table[i].desc == opt_hidden)
223                         continue;
224                 if (opt_table[i].type == OPT_SUBTABLE) {
225                         ret = add_str(ret, &len, &max, opt_table[i].desc);
226                         ret = add_str(ret, &len, &max, ":\n");
227                         continue;
228                 }
229                 ret = add_desc(ret, &len, &max, indent, width, &opt_table[i]);
230         }
231         ret[len] = '\0';
232         return ret;
233 }
234
235 void opt_usage_exit_fail(const char *msg, ...)
236 {
237         va_list ap;
238
239         if (opt_argv0)
240                 fprintf(stderr, "%s: ", opt_argv0);
241         va_start(ap, msg);
242         vfprintf(stderr, msg, ap);
243         va_end(ap);
244         fprintf(stderr, "\n%s",
245                 opt_usage(opt_argv0 ? opt_argv0 : "<program>", NULL));
246         exit(1);
247 }