1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 Kern Sibbald
5 
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8 
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13 
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16 
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  *  Kern Sibbald, January  MMXII
21  *
22  *  Selection list. A string of integers separated by commas
23  *   representing items selected. Ranges of the form nn-mm
24  *   are also permitted.
25  */
26 
27 #include "bacula.h"
28 
29 /*
30  * Returns next item
31  *   error if returns -1 and errmsg set
32  *   end of items if returns -1 and errmsg NULL
33  */
next()34 int64_t sellist::next()
35 {
36    errmsg = NULL;
37    if (beg <= end) {     /* scan done? */
38       //printf("Return %lld\n", beg);
39       return beg++;
40    }
41    if (e == NULL) {
42       goto bail_out;      /* nothing to scan */
43    }
44    /*
45     * As we walk the list, we set EOF in
46     *   the end of the next item to ease scanning,
47     *   but save and then restore the character.
48     */
49    for (p=e; p && *p; p=e) {
50       esave = hsave = 0;
51       /* Check for list */
52       e = strpbrk(p, ", ");
53       if (e) {                       /* have list */
54          esave = *e;
55          *e++ = 0;
56       }
57       /* Check for range */
58       h = strchr(p, '-');             /* range? */
59       if (h == p) {
60          errmsg = _("Negative numbers not permitted.\n");
61          goto bail_out;
62       }
63       if (h) {                        /* have range */
64          hsave = *h;
65          *h++ = 0;
66          if (!is_an_integer(h)) {
67             errmsg = _("Range end is not integer.\n");
68             goto bail_out;
69          }
70          skip_spaces(&p);
71          if (!is_an_integer(p)) {
72             errmsg = _("Range start is not an integer.\n");
73             goto bail_out;
74          }
75          beg = str_to_int64(p);
76          end = str_to_int64(h);
77          //printf("beg=%lld end=%lld\n", beg, end);
78          if (end <= beg) {
79             errmsg = _("Range end not bigger than start.\n");
80             goto bail_out;
81          }
82       } else {                           /* not list, not range */
83          skip_spaces(&p);
84          /* Check for abort (.) */
85          if (*p == '.') {
86             errmsg = _("User cancel requested.\n");
87             goto bail_out;
88          }
89          /* Check for all keyword */
90          if (strncasecmp(p, "all", 3) == 0) {
91             all = true;
92             errmsg = NULL;
93             //printf("Return 0 i.e. all\n");
94             return 0;
95          }
96          if (!is_an_integer(p)) {
97             errmsg = _("Input value is not an integer.\n");
98             goto bail_out;
99          }
100          beg = end = str_to_int64(p);
101       }
102       if (esave) {
103          *(e-1) = esave;
104       }
105       if (hsave) {
106          *(h-1) = hsave;
107       }
108       if (beg <= 0 || end <= 0) {
109          errmsg = _("Selection items must be be greater than zero.\n");
110          goto bail_out;
111       }
112       if (beg <= end) {
113          //printf("Return %lld\n", beg);
114          return beg++;
115       }
116    }
117    //printf("Rtn=-1. End of items\n");
118    /* End of items */
119    begin();
120    e = NULL;
121    return -1;          /* No error */
122 
123 bail_out:
124    if (errmsg) {
125       //printf("Bail out rtn=-1. p=%c err=%s\n", *p, errmsg);
126    } else {
127       //printf("Rtn=-1. End of items\n");
128    }
129    e = NULL;
130    return -1;          /* Error, errmsg set */
131 }
132 
133 
134 /*
135  * Set selection string and optionally scan it
136  *   returns false on error in string
137  *   returns true if OK
138  */
139 bool sellist::set_string(const char *string, bool scan=true)
140 {
141    bool ok = true;
142    /*
143     * Copy string, because we write into it,
144     *  then scan through it once to find any
145     *  errors.
146     */
147    if (expanded) {
148       free(expanded);
149       expanded = NULL;
150    }
151    if (str) {
152       free(str);
153    }
154    str = bstrdup(string);
155    begin();
156    num_items = 0;
157    if (scan) {
158       while (next() >= 0) {
159          num_items++;
160       }
161       ok = get_errmsg() == NULL;
162    }
163    if (ok) {
164       begin();
165    } else {
166       e = NULL;
167    }
168    return ok;
169 }
170 
171 /*
172  * Get the expanded list of values separated by commas,
173  * useful for SQL queries
174  */
get_expanded_list()175 char *sellist::get_expanded_list()
176 {
177    int32_t expandedsize = 512;
178    int32_t len;
179    int64_t val;
180    char    *p, *tmp;
181    char    ed1[50];
182 
183    if (!expanded) {
184       p = expanded = (char *)malloc(expandedsize * sizeof(char));
185       *p = 0;
186 
187       while ((val = next()) >= 0) {
188          edit_int64(val, ed1);
189          len = strlen(ed1);
190 
191          /* Alloc more space if needed */
192          if ((p + len + 1) > (expanded + expandedsize)) {
193             expandedsize = expandedsize * 2;
194 
195             tmp = (char *) realloc(expanded, expandedsize);
196 
197             /* Compute new addresses for p and expanded */
198             p = tmp + (p - expanded);
199             expanded = tmp;
200          }
201 
202          /* If not at the begining of the string, add a "," */
203          if (p != expanded) {
204             strcpy(p, ",");
205             p++;
206          }
207 
208          strcpy(p, ed1);
209          p += len;
210       }
211    }
212    return expanded;
213 }
214 
215 #ifndef TEST_PROGRAM
216 #define TEST_PROGRAM_A
217 #endif
218 
219 #ifdef TEST_PROGRAM
220 #include "unittests.h"
221 
222 struct test {
223    const int nr;
224    const char *sinp;
225    const char *sout;
226    const bool res;
227 };
228 
229 static struct test tests[] = {
230    { 1, "1,70", "1,70", true, },
231    { 2, "1", "1", true, },
232    { 3, "256", "256", true, },
233    { 4, "1-5", "1,2,3,4,5", true, },
234    { 5, "1-5,7", "1,2,3,4,5,7", true, },
235    { 6, "1 10 20 30", "1,10,20,30", true, },
236    { 7, "1-5,7,20 21", "1,2,3,4,5,7,20,21", true, },
237    { 8, "all", "0", true, },
238    { 9, "12a", "", false, },
239    {10, "12-11", "", false, },
240    {11, "12-13a", "", false, },
241    {12, "a123", "", false, },
242    {13, "1  3", "", false, },
243    {0, "dummy", "dummy", false, },
244 };
245 
246 #define ntests ((int)(sizeof(tests)/sizeof(struct test)))
247 #define MSGLEN 80
248 
main()249 int main()
250 {
251    Unittests sellist_test("sellist_test");
252    const char *msg;
253    sellist sl;
254    char buf[MSGLEN];
255    bool check_rc;
256 
257    for (int i = 0; i < ntests; i++) {
258       if (tests[i].nr > 0){
259          snprintf(buf, MSGLEN, "Checking test: %d - %s", tests[i].nr, tests[i].sinp);
260          check_rc = sl.set_string(tests[i].sinp, true);
261          msg = sl.get_expanded_list();
262          ok(check_rc == tests[i].res && strcmp(msg, tests[i].sout) == 0, buf);
263       }
264    }
265 
266    return report();
267 }
268 #endif   /* TEST_PROGRAM */
269