a95981690be1f10b95f2f0bf2687937379d2e719
[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
36 /**
37  * Fill a buffer of the given size with
38  * count 0-terminated strings (given as varargs).
39  * If "buffer" is NULL, only compute the amount of
40  * space required (sum of "strlen(arg)+1").
41  *
42  * Unlike using "snprintf" with "%s", this function
43  * will add 0-terminators after each string.  The
44  * "GNUNET_string_buffer_tokenize" function can be
45  * used to parse the buffer back into individual
46  * strings.
47  *
48  * @param buffer the buffer to fill with strings, can
49  *               be NULL in which case only the necessary
50  *               amount of space will be calculated
51  * @param size number of bytes available in buffer
52  * @param count number of strings that follow
53  * @param ... count 0-terminated strings to copy to buffer
54  * @return number of bytes written to the buffer
55  *         (or number of bytes that would have been written)
56  */
57 size_t
58 GNUNET_STRINGS_buffer_fill (char *buffer, size_t size, unsigned int count, ...)
59 {
60   size_t needed;
61   size_t slen;
62   const char *s;
63   va_list ap;
64
65   needed = 0;
66   va_start (ap, count);
67   while (count > 0)
68   {
69     s = va_arg (ap, const char *);
70
71     slen = strlen (s) + 1;
72     if (buffer != NULL)
73     {
74       GNUNET_assert (needed + slen <= size);
75       memcpy (&buffer[needed], s, slen);
76     }
77     needed += slen;
78     count--;
79   }
80   va_end (ap);
81   return needed;
82 }
83
84
85 /**
86  * Given a buffer of a given size, find "count"
87  * 0-terminated strings in the buffer and assign
88  * the count (varargs) of type "const char**" to the
89  * locations of the respective strings in the
90  * buffer.
91  *
92  * @param buffer the buffer to parse
93  * @param size size of the buffer
94  * @param count number of strings to locate
95  * @return offset of the character after the last 0-termination
96  *         in the buffer, or 0 on error.
97  */
98 unsigned int
99 GNUNET_STRINGS_buffer_tokenize (const char *buffer,
100                                 size_t size, unsigned int count, ...)
101 {
102   unsigned int start;
103   unsigned int needed;
104   const char **r;
105   va_list ap;
106
107   needed = 0;
108   va_start (ap, count);
109   while (count > 0)
110   {
111     r = va_arg (ap, const char **);
112
113     start = needed;
114     while ((needed < size) && (buffer[needed] != '\0'))
115       needed++;
116     if (needed == size)
117     {
118       va_end (ap);
119       return 0;                 /* error */
120     }
121     *r = &buffer[start];
122     needed++;                   /* skip 0-termination */
123     count--;
124   }
125   va_end (ap);
126   return needed;
127 }
128
129
130 /**
131  * Convert a given filesize into a fancy human-readable format.
132  *
133  * @param size number of bytes
134  * @return fancy representation of the size (possibly rounded) for humans
135  */
136 char *
137 GNUNET_STRINGS_byte_size_fancy (unsigned long long size)
138 {
139   const char *unit = _( /* size unit */ "b");
140   char *ret;
141
142   if (size > 5 * 1024)
143   {
144     size = size / 1024;
145     unit = _( /* size unit */ "KiB");
146     if (size > 5 * 1024)
147     {
148       size = size / 1024;
149       unit = _( /* size unit */ "MiB");
150       if (size > 5 * 1024)
151       {
152         size = size / 1024;
153         unit = _( /* size unit */ "GiB");
154         if (size > 5 * 1024)
155         {
156           size = size / 1024;
157           unit = _( /* size unit */ "TiB");
158         }
159       }
160     }
161   }
162   ret = GNUNET_malloc (32);
163   GNUNET_snprintf (ret, 32, "%llu %s", size, unit);
164   return ret;
165 }
166
167
168 /**
169  * Convert the len characters long character sequence
170  * given in input that is in the given charset
171  * to UTF-8.
172  * @return the converted string (0-terminated),
173  *  if conversion fails, a copy of the orignal
174  *  string is returned.
175  */
176 char *
177 GNUNET_STRINGS_to_utf8 (const char *input, size_t len, const char *charset)
178 {
179   char *ret;
180
181 #if ENABLE_NLS && HAVE_ICONV
182   size_t tmpSize;
183   size_t finSize;
184   char *tmp;
185   char *itmp;
186   iconv_t cd;
187
188   cd = iconv_open ("UTF-8", charset);
189   if (cd == (iconv_t) - 1)
190   {
191     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "iconv_open");
192     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
193                 _("Character set requested was `%s'\n"), charset);
194     ret = GNUNET_malloc (len + 1);
195     memcpy (ret, input, len);
196     ret[len] = '\0';
197     return ret;
198   }
199   tmpSize = 3 * len + 4;
200   tmp = GNUNET_malloc (tmpSize);
201   itmp = tmp;
202   finSize = tmpSize;
203   if (iconv (cd,
204 #if FREEBSD || DARWIN || WINDOWS
205              (const char **) &input,
206 #else
207              (char **) &input,
208 #endif
209              &len, &itmp, &finSize) == SIZE_MAX)
210   {
211     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "iconv");
212     iconv_close (cd);
213     GNUNET_free (tmp);
214     ret = GNUNET_malloc (len + 1);
215     memcpy (ret, input, len);
216     ret[len] = '\0';
217     return ret;
218   }
219   ret = GNUNET_malloc (tmpSize - finSize + 1);
220   memcpy (ret, tmp, tmpSize - finSize);
221   ret[tmpSize - finSize] = '\0';
222   GNUNET_free (tmp);
223   if (0 != iconv_close (cd))
224     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "iconv_close");
225   return ret;
226 #else
227   ret = GNUNET_malloc (len + 1);
228   memcpy (ret, input, len);
229   ret[len] = '\0';
230   return ret;
231 #endif
232 }
233
234
235 /**
236  * Complete filename (a la shell) from abbrevition.
237  * @param fil the name of the file, may contain ~/ or
238  *        be relative to the current directory
239  * @returns the full file name,
240  *          NULL is returned on error
241  */
242 char *
243 GNUNET_STRINGS_filename_expand (const char *fil)
244 {
245   char *buffer;
246
247 #ifndef MINGW
248   size_t len;
249   size_t n;
250   char *fm;
251   const char *fil_ptr;
252 #else
253   char *fn;
254   long lRet;
255 #endif
256
257   if (fil == NULL)
258     return NULL;
259
260 #ifndef MINGW
261   if (fil[0] == DIR_SEPARATOR)
262     /* absolute path, just copy */
263     return GNUNET_strdup (fil);
264   if (fil[0] == '~')
265   {
266     fm = getenv ("HOME");
267     if (fm == NULL)
268     {
269       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
270                   _
271                   ("Failed to expand `$HOME': environment variable `HOME' not set"));
272       return NULL;
273     }
274     fm = GNUNET_strdup (fm);
275     /* do not copy '~' */
276     fil_ptr = fil + 1;
277
278     /* skip over dir seperator to be consistent */
279     if (fil_ptr[0] == DIR_SEPARATOR)
280       fil_ptr++;
281   }
282   else
283   {
284     /* relative path */
285     fil_ptr = fil;
286     len = 512;
287     fm = NULL;
288     while (1)
289     {
290       buffer = GNUNET_malloc (len);
291       if (getcwd (buffer, len) != NULL)
292       {
293         fm = buffer;
294         break;
295       }
296       if ((errno == ERANGE) && (len < 1024 * 1024 * 4))
297       {
298         len *= 2;
299         GNUNET_free (buffer);
300         continue;
301       }
302       GNUNET_free (buffer);
303       break;
304     }
305     if (fm == NULL)
306     {
307       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "getcwd");
308       buffer = getenv ("PWD");  /* alternative */
309       if (buffer != NULL)
310         fm = GNUNET_strdup (buffer);
311     }
312     if (fm == NULL)
313       fm = GNUNET_strdup ("./");        /* give up */
314   }
315   n = strlen (fm) + 1 + strlen (fil_ptr) + 1;
316   buffer = GNUNET_malloc (n);
317   GNUNET_snprintf (buffer, n, "%s%s%s",
318                    fm,
319                    (fm[strlen (fm) - 1] ==
320                     DIR_SEPARATOR) ? "" : DIR_SEPARATOR_STR, fil_ptr);
321   GNUNET_free (fm);
322   return buffer;
323 #else
324   fn = GNUNET_malloc (MAX_PATH + 1);
325
326   if ((lRet = plibc_conv_to_win_path (fil, fn)) != ERROR_SUCCESS)
327   {
328     SetErrnoFromWinError (lRet);
329     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "plibc_conv_to_win_path");
330     return NULL;
331   }
332   /* is the path relative? */
333   if ((strncmp (fn + 1, ":\\", 2) != 0) && (strncmp (fn, "\\\\", 2) != 0))
334   {
335     char szCurDir[MAX_PATH + 1];
336
337     lRet = GetCurrentDirectory (MAX_PATH + 1, szCurDir);
338     if (lRet + strlen (fn) + 1 > (MAX_PATH + 1))
339     {
340       SetErrnoFromWinError (ERROR_BUFFER_OVERFLOW);
341       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "GetCurrentDirectory");
342       return NULL;
343     }
344     buffer = GNUNET_malloc (MAX_PATH + 1);
345     GNUNET_snprintf (buffer, MAX_PATH + 1, "%s\\%s", szCurDir, fn);
346     GNUNET_free (fn);
347     fn = buffer;
348   }
349
350   return fn;
351 #endif
352 }
353
354
355 /**
356  * Give relative time in human-readable fancy format.
357  *
358  * @param delta time in milli seconds
359  * @return time as human-readable string
360  */
361 char *
362 GNUNET_STRINGS_relative_time_to_string (struct GNUNET_TIME_Relative delta)
363 {
364   const char *unit = _( /* time unit */ "ms");
365   char *ret;
366   uint64_t dval = delta.rel_value;
367
368   if (delta.rel_value == GNUNET_TIME_UNIT_FOREVER_REL.rel_value)
369     return GNUNET_strdup (_("eternity"));
370   if (dval > 5 * 1000)
371   {
372     dval = dval / 1000;
373     unit = _( /* time unit */ "s");
374     if (dval > 5 * 60)
375     {
376       dval = dval / 60;
377       unit = _( /* time unit */ "m");
378       if (dval > 5 * 60)
379       {
380         dval = dval / 60;
381         unit = _( /* time unit */ "h");
382         if (dval > 5 * 24)
383         {
384           dval = dval / 24;
385           unit = _( /* time unit */ " days");
386         }
387       }
388     }
389   }
390   GNUNET_asprintf (&ret, "%llu %s", dval, unit);
391   return ret;
392 }
393
394
395 /**
396  * "man ctime_r", except for GNUnet time; also, unlike ctime, the
397  * return value does not include the newline character.
398  *
399  * @param t time to convert
400  * @return absolute time in human-readable format
401  */
402 char *
403 GNUNET_STRINGS_absolute_time_to_string (struct GNUNET_TIME_Absolute t)
404 {
405   time_t tt;
406   char *ret;
407
408   if (t.abs_value == GNUNET_TIME_UNIT_FOREVER_ABS.abs_value)
409     return GNUNET_strdup (_("end of time"));
410   tt = t.abs_value / 1000;
411 #ifdef ctime_r
412   ret = ctime_r (&tt, GNUNET_malloc (32));
413 #else
414   ret = GNUNET_strdup (ctime (&tt));
415 #endif
416   ret[strlen (ret) - 1] = '\0';
417   return ret;
418 }
419
420
421
422 /* end of strings.c */