63ebfc1e269175459c029385d220b2076f55dcb8
[oweals/gnunet.git] / src / util / strings.c
1 /*
2      This file is part of GNUnet.
3      (C) 2005, 2006 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file util/strings.c
23  * @brief string functions
24  * @author Nils Durner
25  * @author Christian Grothoff
26  */
27
28 #include "platform.h"
29 #if HAVE_ICONV
30 #include <iconv.h>
31 #endif
32 #include "gnunet_common.h"
33 #include "gnunet_strings_lib.h"
34
35 #define LOG(kind,...) GNUNET_log_from (kind, "util", __VA_ARGS__)
36
37 #define LOG_STRERROR(kind,syscall) GNUNET_log_from_strerror (kind, "util", syscall)
38
39
40 /**
41  * Fill a buffer of the given size with
42  * count 0-terminated strings (given as varargs).
43  * If "buffer" is NULL, only compute the amount of
44  * space required (sum of "strlen(arg)+1").
45  *
46  * Unlike using "snprintf" with "%s", this function
47  * will add 0-terminators after each string.  The
48  * "GNUNET_string_buffer_tokenize" function can be
49  * used to parse the buffer back into individual
50  * strings.
51  *
52  * @param buffer the buffer to fill with strings, can
53  *               be NULL in which case only the necessary
54  *               amount of space will be calculated
55  * @param size number of bytes available in buffer
56  * @param count number of strings that follow
57  * @param ... count 0-terminated strings to copy to buffer
58  * @return number of bytes written to the buffer
59  *         (or number of bytes that would have been written)
60  */
61 size_t
62 GNUNET_STRINGS_buffer_fill (char *buffer, size_t size, unsigned int count,
63                             ...)
64 {
65   size_t needed;
66   size_t slen;
67   const char *s;
68   va_list ap;
69
70   needed = 0;
71   va_start (ap, count);
72   while (count > 0)
73     {
74       s = va_arg (ap, const char *);
75
76       slen = strlen (s) + 1;
77       if (buffer != NULL)
78         {
79           GNUNET_assert (needed + slen <= size);
80           memcpy (&buffer[needed], s, slen);
81         }
82       needed += slen;
83       count--;
84     }
85   va_end (ap);
86   return needed;
87 }
88
89
90 /**
91  * Given a buffer of a given size, find "count"
92  * 0-terminated strings in the buffer and assign
93  * the count (varargs) of type "const char**" to the
94  * locations of the respective strings in the
95  * buffer.
96  *
97  * @param buffer the buffer to parse
98  * @param size size of the buffer
99  * @param count number of strings to locate
100  * @return offset of the character after the last 0-termination
101  *         in the buffer, or 0 on error.
102  */
103 unsigned int
104 GNUNET_STRINGS_buffer_tokenize (const char *buffer, size_t size,
105                                 unsigned int count, ...)
106 {
107   unsigned int start;
108   unsigned int needed;
109   const char **r;
110   va_list ap;
111
112   needed = 0;
113   va_start (ap, count);
114   while (count > 0)
115     {
116       r = va_arg (ap, const char **);
117
118       start = needed;
119       while ((needed < size) && (buffer[needed] != '\0'))
120         needed++;
121       if (needed == size)
122         {
123           va_end (ap);
124           return 0;             /* error */
125         }
126       *r = &buffer[start];
127       needed++;                 /* skip 0-termination */
128       count--;
129     }
130   va_end (ap);
131   return needed;
132 }
133
134
135 /**
136  * Convert a given filesize into a fancy human-readable format.
137  *
138  * @param size number of bytes
139  * @return fancy representation of the size (possibly rounded) for humans
140  */
141 char *
142 GNUNET_STRINGS_byte_size_fancy (unsigned long long size)
143 {
144   const char *unit = _( /* size unit */ "b");
145   char *ret;
146
147   if (size > 5 * 1024)
148     {
149       size = size / 1024;
150       unit = _( /* size unit */ "KiB");
151       if (size > 5 * 1024)
152         {
153           size = size / 1024;
154           unit = _( /* size unit */ "MiB");
155           if (size > 5 * 1024)
156             {
157               size = size / 1024;
158               unit = _( /* size unit */ "GiB");
159               if (size > 5 * 1024)
160                 {
161                   size = size / 1024;
162                   unit = _( /* size unit */ "TiB");
163                 }
164             }
165         }
166     }
167   ret = GNUNET_malloc (32);
168   GNUNET_snprintf (ret, 32, "%llu %s", size, unit);
169   return ret;
170 }
171
172
173 /**
174  * Convert a given fancy human-readable size to bytes.
175  *
176  * @param fancy_size human readable string (i.e. 1 MB)
177  * @param size set to the size in bytes
178  * @return GNUNET_OK on success, GNUNET_SYSERR on error
179  */
180 int
181 GNUNET_STRINGS_fancy_size_to_bytes (const char *fancy_size,
182                                     unsigned long long *size)
183 {
184   struct { 
185     const char *name; 
186     unsigned long long value;
187   } table[] = {
188     { "B", 1 },
189     { "KiB", 1024 },
190     { "kB",  1000 },
191     { "MiB", 1024 * 1024 },
192     { "MB",  1000 * 1000 },
193     { "GiB", 1024 * 1024 * 1024 },
194     { "GB",  1000 * 1000 * 1000 },
195     { "TiB", 1024LL * 1024LL * 1024LL * 1024LL },
196     { "TB",  1000LL * 1000LL * 1000LL * 1024LL },
197     { "PiB", 1024LL * 1024LL * 1024LL * 1024LL * 1024LL },
198     { "PB",  1000LL * 1000LL * 1000LL * 1024LL * 1000LL},
199     { "EiB", 1024LL * 1024LL * 1024LL * 1024LL * 1024LL * 1024LL},
200     { "EB",  1000LL * 1000LL * 1000LL * 1024LL * 1000LL * 1000LL },
201     { NULL, 0 }
202   };
203   unsigned long long ret;
204   char *in;
205   const char *tok;
206   unsigned long long last;
207   unsigned int i;
208
209   ret = 0;
210   last = 0;
211   in = GNUNET_strdup (fancy_size);
212   for (tok = strtok (in, " "); tok != NULL; tok = strtok (NULL, " "))
213   {
214     fprintf (stderr, "%s - %llu %llu\n", tok, ret, last);
215     i=0;
216     while ( (table[i].name != NULL) &&
217             (0 != strcasecmp (table[i].name, tok) ) )
218       i++;
219     if (table[i].name != NULL)
220       last *= table[i].value;
221     else
222     {
223       ret += last;
224       last = 0;
225       if (1 != sscanf (tok, "%llu", &last))
226         return GNUNET_SYSERR; /* expected number */
227     }      
228   }
229   ret += last;
230   *size = ret;
231   return GNUNET_OK;
232 }
233
234
235 /**
236  * Convert a given fancy human-readable time to our internal
237  * representation.
238  *
239  * @param fancy_size human readable string (i.e. 1 minute)
240  * @param rtime set to the relative time
241  * @return GNUNET_OK on success, GNUNET_SYSERR on error
242  */
243 int
244 GNUNET_STRINGS_fancy_time_to_relative (const char *fancy_size,
245                                        struct GNUNET_TIME_Relative *rtime)
246 {
247   struct { 
248     const char *name; 
249     unsigned long long value;
250   } table[] = {
251     { "ms", 1 },
252     { "s", 1000 },
253     { "\"", 1000 },
254     { "min",  60 * 1000 },
255     { "minutes", 60 * 1000 },
256     { "'", 60 * 1000 },
257     { "h", 60 * 60 * 1000 },
258     { "d", 24 * 60 * 60 * 1000 },
259     { "a", 31557600 /* year */ },
260     { NULL, 0 }
261   };
262   unsigned long long ret;
263   char *in;
264   const char *tok;
265   unsigned long long last;
266   unsigned int i;
267   
268   if ((0 == strcasecmp (fancy_size, "infinity")) ||
269       (0 == strcasecmp (fancy_size, "forever")))
270     {
271       *rtime = GNUNET_TIME_UNIT_FOREVER_REL;
272       return GNUNET_OK;
273     }
274   ret = 0;
275   last = 0;
276   in = GNUNET_strdup (fancy_size);
277   for (tok = strtok (in, " "); tok != NULL; tok = strtok (NULL, " "))
278   {
279     i=0;
280     while ( (table[i].name != NULL) &&
281             (0 != strcasecmp (table[i].name, tok) ) )
282       i++;
283     if (table[i].name != NULL)
284       last *= table[i].value;
285     else
286     {
287       ret += last;
288       last = 0;
289       if (1 != sscanf (tok, "%llu", &last))
290         return GNUNET_SYSERR; /* expected number */
291     }      
292   }
293   ret += last;
294   rtime->rel_value = (uint64_t) ret;
295   return GNUNET_OK;
296 }
297
298
299 /**
300  * Convert the len characters long character sequence
301  * given in input that is in the given charset
302  * to UTF-8.
303  * @return the converted string (0-terminated),
304  *  if conversion fails, a copy of the orignal
305  *  string is returned.
306  */
307 char *
308 GNUNET_STRINGS_to_utf8 (const char *input, size_t len, const char *charset)
309 {
310   char *ret;
311
312 #if ENABLE_NLS && HAVE_ICONV
313   size_t tmpSize;
314   size_t finSize;
315   char *tmp;
316   char *itmp;
317   iconv_t cd;
318
319   cd = iconv_open ("UTF-8", charset);
320   if (cd == (iconv_t) - 1)
321     {
322       LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, "iconv_open");
323       LOG (GNUNET_ERROR_TYPE_WARNING,
324            _("Character set requested was `%s'\n"), charset);
325       ret = GNUNET_malloc (len + 1);
326       memcpy (ret, input, len);
327       ret[len] = '\0';
328       return ret;
329     }
330   tmpSize = 3 * len + 4;
331   tmp = GNUNET_malloc (tmpSize);
332   itmp = tmp;
333   finSize = tmpSize;
334   if (iconv (cd,
335 #if FREEBSD || DARWIN || WINDOWS
336              (const char **) &input,
337 #else
338              (char **) &input,
339 #endif
340              &len, &itmp, &finSize) == SIZE_MAX)
341     {
342       LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, "iconv");
343       iconv_close (cd);
344       GNUNET_free (tmp);
345       ret = GNUNET_malloc (len + 1);
346       memcpy (ret, input, len);
347       ret[len] = '\0';
348       return ret;
349     }
350   ret = GNUNET_malloc (tmpSize - finSize + 1);
351   memcpy (ret, tmp, tmpSize - finSize);
352   ret[tmpSize - finSize] = '\0';
353   GNUNET_free (tmp);
354   if (0 != iconv_close (cd))
355     LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, "iconv_close");
356   return ret;
357 #else
358   ret = GNUNET_malloc (len + 1);
359   memcpy (ret, input, len);
360   ret[len] = '\0';
361   return ret;
362 #endif
363 }
364
365
366 /**
367  * Complete filename (a la shell) from abbrevition.
368  * @param fil the name of the file, may contain ~/ or
369  *        be relative to the current directory
370  * @returns the full file name,
371  *          NULL is returned on error
372  */
373 char *
374 GNUNET_STRINGS_filename_expand (const char *fil)
375 {
376   char *buffer;
377
378 #ifndef MINGW
379   size_t len;
380   size_t n;
381   char *fm;
382   const char *fil_ptr;
383 #else
384   char *fn;
385   long lRet;
386 #endif
387
388   if (fil == NULL)
389     return NULL;
390
391 #ifndef MINGW
392   if (fil[0] == DIR_SEPARATOR)
393     /* absolute path, just copy */
394     return GNUNET_strdup (fil);
395   if (fil[0] == '~')
396     {
397       fm = getenv ("HOME");
398       if (fm == NULL)
399         {
400           LOG (GNUNET_ERROR_TYPE_WARNING,
401                _
402                ("Failed to expand `$HOME': environment variable `HOME' not set"));
403           return NULL;
404         }
405       fm = GNUNET_strdup (fm);
406       /* do not copy '~' */
407       fil_ptr = fil + 1;
408
409       /* skip over dir seperator to be consistent */
410       if (fil_ptr[0] == DIR_SEPARATOR)
411         fil_ptr++;
412     }
413   else
414     {
415       /* relative path */
416       fil_ptr = fil;
417       len = 512;
418       fm = NULL;
419       while (1)
420         {
421           buffer = GNUNET_malloc (len);
422           if (getcwd (buffer, len) != NULL)
423             {
424               fm = buffer;
425               break;
426             }
427           if ((errno == ERANGE) && (len < 1024 * 1024 * 4))
428             {
429               len *= 2;
430               GNUNET_free (buffer);
431               continue;
432             }
433           GNUNET_free (buffer);
434           break;
435         }
436       if (fm == NULL)
437         {
438           LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, "getcwd");
439           buffer = getenv ("PWD");      /* alternative */
440           if (buffer != NULL)
441             fm = GNUNET_strdup (buffer);
442         }
443       if (fm == NULL)
444         fm = GNUNET_strdup ("./");      /* give up */
445     }
446   n = strlen (fm) + 1 + strlen (fil_ptr) + 1;
447   buffer = GNUNET_malloc (n);
448   GNUNET_snprintf (buffer, n, "%s%s%s", fm,
449                    (fm[strlen (fm) - 1] ==
450                     DIR_SEPARATOR) ? "" : DIR_SEPARATOR_STR, fil_ptr);
451   GNUNET_free (fm);
452   return buffer;
453 #else
454   fn = GNUNET_malloc (MAX_PATH + 1);
455
456   if ((lRet = plibc_conv_to_win_path (fil, fn)) != ERROR_SUCCESS)
457     {
458       SetErrnoFromWinError (lRet);
459       LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, "plibc_conv_to_win_path");
460       return NULL;
461     }
462   /* is the path relative? */
463   if ((strncmp (fn + 1, ":\\", 2) != 0) && (strncmp (fn, "\\\\", 2) != 0))
464     {
465       char szCurDir[MAX_PATH + 1];
466
467       lRet = GetCurrentDirectory (MAX_PATH + 1, szCurDir);
468       if (lRet + strlen (fn) + 1 > (MAX_PATH + 1))
469         {
470           SetErrnoFromWinError (ERROR_BUFFER_OVERFLOW);
471           LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, "GetCurrentDirectory");
472           return NULL;
473         }
474       buffer = GNUNET_malloc (MAX_PATH + 1);
475       GNUNET_snprintf (buffer, MAX_PATH + 1, "%s\\%s", szCurDir, fn);
476       GNUNET_free (fn);
477       fn = buffer;
478     }
479
480   return fn;
481 #endif
482 }
483
484
485 /**
486  * Give relative time in human-readable fancy format.
487  *
488  * @param delta time in milli seconds
489  * @return time as human-readable string
490  */
491 char *
492 GNUNET_STRINGS_relative_time_to_string (struct GNUNET_TIME_Relative delta)
493 {
494   const char *unit = _( /* time unit */ "ms");
495   char *ret;
496   uint64_t dval = delta.rel_value;
497
498   if (delta.rel_value == GNUNET_TIME_UNIT_FOREVER_REL.rel_value)
499     return GNUNET_strdup (_("eternity"));
500   if (dval > 5 * 1000)
501     {
502       dval = dval / 1000;
503       unit = _( /* time unit */ "s");
504       if (dval > 5 * 60)
505         {
506           dval = dval / 60;
507           unit = _( /* time unit */ "m");
508           if (dval > 5 * 60)
509             {
510               dval = dval / 60;
511               unit = _( /* time unit */ "h");
512               if (dval > 5 * 24)
513                 {
514                   dval = dval / 24;
515                   unit = _( /* time unit */ " days");
516                 }
517             }
518         }
519     }
520   GNUNET_asprintf (&ret, "%llu %s", dval, unit);
521   return ret;
522 }
523
524
525 /**
526  * "man ctime_r", except for GNUnet time; also, unlike ctime, the
527  * return value does not include the newline character.
528  *
529  * @param t time to convert
530  * @return absolute time in human-readable format
531  */
532 char *
533 GNUNET_STRINGS_absolute_time_to_string (struct GNUNET_TIME_Absolute t)
534 {
535   time_t tt;
536   char *ret;
537
538   if (t.abs_value == GNUNET_TIME_UNIT_FOREVER_ABS.abs_value)
539     return GNUNET_strdup (_("end of time"));
540   tt = t.abs_value / 1000;
541 #ifdef ctime_r
542   ret = ctime_r (&tt, GNUNET_malloc (32));
543 #else
544   ret = GNUNET_strdup (ctime (&tt));
545 #endif
546   ret[strlen (ret) - 1] = '\0';
547   return ret;
548 }
549
550
551
552 /* end of strings.c */