1 /*
2 * \brief Argument list string handling
3 * \author Norman Feske
4 * \date 2006-05-22
5 *
6 * Each argument has the form:
7 *
8 * ! <key>=<value>
9 *
10 * Key is an identifier that begins with a letter or underline
11 * and may also contain digits.
12 *
13 * A list of arguments is specified by using comma as separator,
14 * whereas the first argument is considered as the weakest. If
15 * we replace an argument value of an existing argument, the
16 * existing argument is removed and we append a new argument at
17 * the end of the string.
18 */
19
20 /*
21 * Copyright (C) 2006-2013 Genode Labs GmbH
22 *
23 * This file is part of the Genode OS framework, which is distributed
24 * under the terms of the GNU General Public License version 2.
25 */
26
27 #ifndef _INCLUDE__UTIL__ARG_STRING_H_
28 #define _INCLUDE__UTIL__ARG_STRING_H_
29
30 #include <util/token.h>
31 #include <util/string.h>
32 #include <base/snprintf.h>
33
34 namespace Genode {
35
36 class Arg_string;
37 class Arg;
38 }
39
40
41 class Genode::Arg
42 {
43 /**
44 * Define tokenizer used for argument-string parsing
45 *
46 * Argument-string tokens accept C-style identifiers.
47 */
48 typedef ::Genode::Token<Scanner_policy_identifier_with_underline> Token;
49
50 friend class Arg_string;
51
52 private:
53
54 Token _key;
55 Token _value;
56
57 /**
58 * Return long value of argument
59 *
60 * \param out_value argument converted to unsigned long value
61 * \param out_sign 1 if positive; -1 if negative
62 * \return true if no syntactic anomaly occured
63 *
64 * This method handles the numberic modifiers G (2^30),
65 * M (2^20), and K (2^10).
66 */
67 bool read_ulong(unsigned long *out_value, int *out_sign) const
68 {
69 Token t = _value;
70
71 /* check for sign; default is positive */
72 *out_sign = 1;
73 if (t[0] == `+`)
74 t = t.next();
75 else if (t[0] == `-`) {
76 *out_sign = -1;
77 t = t.next();
78 }
79
80 /* stop if token after sign is no number */
81 if (t.type() != Token::NUMBER)
82 return false;
83
84 /* read numeric value and skip the corresponding tokens */
85 Number_of_bytes value;
86 size_t n = ascii_to(t.start(), value);
87
88 if (n == 0)
89 return false;
90
91 t = Token(t.start() + n);
92 *out_value = value;
93
94 /* check for strange characters at the end of the number */
95 t = t.eat_whitespace();
96 if (t && (t[0] != `,`)) return false;
97
98 return true;
99 }
100
101 public:
102
103 /**
104 * Construct argument from Token(s)
105 */
106 Arg(Token t = Token()) : _key(t), _value(0)
107 {
108 for (; t && (t[0] != `,`); t = t.next().eat_whitespace())
109 if (t[0] == `=`) {
110 _value = t.next().eat_whitespace();
111 break;
112 }
113 }
114
115 inline bool valid() const { return _key; }
116
117 unsigned long ulong_value(unsigned long default_value) const
118 {
119 unsigned long value = 0;
120 int sign = 1;
121
122 bool valid = read_ulong(&value, &sign);
123 if (sign < 0)
124 return default_value;
125
126 return valid ? value : default_value;
127 }
128
129 long long_value(long default_value) const
130 {
131 unsigned long value = 0;
132 int sign = 1;
133
134 bool valid = read_ulong(&value, &sign);
135
136 /* FIXME we should check for overflows here! */
137 return valid ? sign*value : default_value;
138 }
139
140 bool bool_value(bool default_value) const
141 {
142 bool result = default_value;
143 switch(_value.type()) {
144
145 /* result is passed to `ascii_to` by reference */
146 case Token::IDENT:;
147 if (ascii_to(_value.start(), result) == _value.len())
148 return result;
149
150 case Token::STRING:
151 if (ascii_to(_value.start()+1, result) == _value.len()-2)
152 return result;
153
154 default:
155 /* read values 0 (false) / !0 (true) */
156 unsigned long value;
157 int sign;
158 bool valid = read_ulong(&value, &sign);
159
160 return valid ? value : default_value;
161 }
162 }
163
164 void key(char *dst, size_t dst_len) const
165 {
166 _key.string(dst, dst_len);
167 }
168
169 void string(char *dst, size_t dst_len, const char *default_string) const
170 {
171 /* check for one-word string w/o quotes */
172 if (_value.type() == Token::IDENT) {
173 size_t len = min(dst_len - 1, _value.len());
174 memcpy(dst, _value.start(), len);
175 dst[len] = 0;
176 return;
177 }
178
179 /* stop here if _value is not a string */
180 if (_value.type() != Token::STRING) {
181 strncpy(dst, default_string, dst_len);
182 return;
183 }
184
185 /* unpack string to dst */
186 size_t num_chars = min(dst_len - 1, _value.len());
187 unpack_string(_value.start(), dst, num_chars);
188 }
189 };
190
191
192 class Genode::Arg_string
193 {
194 typedef Arg::Token Token;
195
196 private:
197
198 static Token _next_key(Token t)
199 {
200 for (; t; t = t.next().eat_whitespace())
201
202 /* if we find a comma, return token after comma */
203 if (t[0] == `,`) return t.next().eat_whitespace();
204
205 return Token();
206 }
207
208 /**
209 * Find key token in argument string
210 */
211 static Token _find_key(const char *args, const char *key)
212 {
213 for (Token t(args); t; t = _next_key(t))
214
215 /* check if key matches */
216 if ((t.type() == Token::IDENT) && !strcmp(key, t.start(), t.len()))
217 return t;
218
219 return Token();
220 }
221
222 /**
223 * Append source string to destination string
224 *
225 * NOTE: check string length before calling this method!
226 *
227 * \return last character of result string
228 */
229 static char *_append(char *dst, const char *src)
230 {
231 unsigned src_len = strlen(src);
232 while (*dst) dst++;
233 memcpy(dst, src, src_len + 1);
234 return dst + src_len;
235 }
236
237 public:
238
239 /**
240 * Find argument by its key
241 */
242 static Arg find_arg(const char *args, const char *key) {
243 return (args && key) ? Arg(_find_key(args, key)) : Arg(); }
244
245 static Arg first_arg(const char *args) {
246 return Arg(Token(args)); }
247
248 /**
249 * Remove argument with the specified key
250 */
251 static bool remove_arg(char *args, const char *key)
252 {
253 if (!args || !key) return false;
254
255 Token beg = _find_key(args, key);
256 Token next = _next_key(beg);
257
258 /* no such key to remove - we are done */
259 if (!beg) return true;
260
261 /* if argument is the last one, null-terminate string right here */
262 if (!next) {
263
264 /* eat all pending whitespaces at the end of the string */
265 char *s = max(beg.start() - 1, args);
266 while (s > args && (*s == ` `)) s--;
267
268 /* write string-terminating zero */
269 *s = 0;
270 } else
271 memcpy(beg.start(), next.start(), strlen(next.start()) + 1);
272
273 return true;
274 }
275
276 /**
277 * Add new argument
278 */
279 static bool add_arg(char *args, unsigned args_len,
280 const char *key, const char *value,
281 Token::Type type = Token::Type::IDENT)
282 {
283 if (!args || !key || !value) return false;
284
285 unsigned old_len = strlen(args);
286
287 /* check if args string has enough capacity */
288 if ((type == Token::Type::STRING
289 && old_len + strlen(key) + strlen(value) + 4 > args_len)
290 || (old_len + strlen(key) + strlen(value) + 2 > args_len))
291 return false;
292
293 args += old_len;
294
295 if (old_len)
296 args = _append(args, ", ");
297
298 if (type == Token::Type::STRING)
299 _append(_append(_append(_append(args, key), "=\""), value), "\"");
300 else
301 _append(_append(_append(args, key), "="), value);
302 return true;
303 }
304
305 /**
306 * Assign new value to argument
307 */
308 static bool set_arg(char *args, unsigned args_len,
309 const char *key, const char *value)
310 {
311 return remove_arg(args, key) && add_arg(args, args_len, key, value);
312 }
313
314 /**
315 * Assign new integer argument
316 */
317 static bool set_arg(char *args, unsigned args_len,
318 const char *key, int value)
319 {
320 enum { STRING_LONG_MAX = 32 };
321 char buf[STRING_LONG_MAX];
322 snprintf(buf, sizeof(buf), "%d", value);
323 return remove_arg(args, key) && add_arg(args, args_len, key, buf);
324 }
325
326 /**
327 * Assign new string argument
328 */
329 static bool set_arg_string(char *args, unsigned args_len,
330 const char *key, const char *value)
331 {
332 return remove_arg(args, key)
333 && add_arg(args, args_len, key, value, Token::Type::STRING);
334 }
335 };
336
337 #endif /* _INCLUDE__UTIL__ARG_STRING_H_ */