File: str.c

package info (click to toggle)
charliecloud 0.43-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,116 kB
  • sloc: python: 6,021; sh: 4,284; ansic: 3,863; makefile: 598
file content (324 lines) | stat: -rw-r--r-- 9,808 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
/* Copyright © Triad National Security, LLC, and others. */

#define _GNU_SOURCE
#include "config.h"

#include <ctype.h>
#include <string.h>

#include "all.h"


/** Functions **/

/* Serialize the null-terminated vector of arguments argv and return the
   result as a newly allocated string. The purpose is to provide a
   human-readable reconstruction of a command line where each argument can
   also be recovered byte-for-byte; see ch-run(1) for details. */
char *argv_to_string(char **argv)
{
   char *s = NULL;

   for (size_t i = 0; argv[i] != NULL; i++) {
      char *argv_;
      bool quote_p = false;

      // Max length is escape every char plus two quotes and terminating zero.
      // Initialize to zeroes so we don’t have to terminate string later.
      argv_ = malloc_zeroed(2 * strlen(argv[i]) + 3, false);

      // Copy to new string, escaping as we go. Note lots of fall-through. I'm
      // not sure where this list of shell meta-characters came from; I just
      // had it on hand already from when we were deciding on the image
      // reference transformation for filesystem paths.
      for (size_t ji = 0, jo = 0; argv[i][ji] != 0; ji++) {
         char c = argv[i][ji];
         if (isspace(c) || !isascii(c) || !isprint(c))
            quote_p = true;
         switch (c) {
         case '!':   // history expansion
         case '"':   // string delimiter
         case '$':   // variable expansion
         case '\\':  // escape character
         case '`':   // output expansion
            argv_[jo++] = '\\';
         case '#':   // comment
         case '%':   // job ID
         case '&':   // job control
         case '\'':  // string delimiter
         case '(':   // subshell grouping
         case ')':   // subshell grouping
         case '*':   // globbing
         case ';':   // command separator
         case '<':   // redirect
         case '=':   // globbing
         case '>':   // redirect
         case '?':   // globbing
         case '[':   // globbing
         case ']':   // globbing
         case '^':   // command “quick substitution”
         case '{':   // command grouping
         case '|':   // pipe
         case '}':   // command grouping
         case '~':   // home directory expansion
            quote_p = true;
         default:
            argv_[jo++] = c;
            break;
         }
      }

      s = cats(5, s, i == 0 ? "" : " ",
               quote_p ? "\"" : "", argv_, quote_p ? "\"" : "");
   }

   return s;
}

/* Return a snprintf(3)-formatted string in a newly allocated buffer of
   appropriate length. Exit on error.

   This function formats the string twice: Once to figure out how long the
   formatted string is, and again to actually format the string. I’m not aware
   of a better way to compute string length. (musl does it the same way; glibc
   was too complicated for my patience in figuring it out.)

   An alternative would be to allocate a small buffer, try that, and if it’s
   too small re-allocate and format again. For strings that fit, this would
   save a formatting cycle at the cost of wasted memory and more code paths.
   That didn’t seem like the right trade-off, esp. since short strings should
   be the fastest to format. */
char *asprintf_ch(const char *fmt, ...)
{
   va_list ap;
   char *str;

   va_start(ap, fmt);
   str = vasprintf_ch(fmt, ap);
   va_end(ap);

   return str;
}

/* Return bool b as a string. */
const char *bool_to_string(bool b)
{
   return (b ? "yes" : "no");
}

/* Iterate through buffer “buf” of size “s” consisting of null-terminated
   strings and return the number of strings in it. Key assumptions:

      1. The buffer has been initialized to zero, i.e. all bytes that have not
         been explicitly set are null.

      2. All strings have been appended to the buffer in full without
         truncation, including their null terminator.

      3. The buffer contains no empty strings.

   These assumptions are consistent with the construction of the “warnings”
   shared memory buffer, which is the main justification for this function.
   Note that under these assumptions, the final byte in the buffer is
   guaranteed to be null. */
int buf_strings_count(char *buf, size_t size)
{
   int count = 0;

   if (buf[0] != '\0') {
      for (size_t i = 0; i < size; i++)
         if (buf[i] == '\0') {                     // found string terminator
            count++;
            if (i < size - 1 && buf[i+1] == '\0')  // two term. in a row; done
               break;
         }
   }

   return count;
}

/* Concatenate strings a and b into a newly-allocated buffer and return a
   pointer to this buffer. */
char *cat(const char *a, const char *b)
{
   return cats(2, a, b);
}

/* Concatenate argc strings into a newly allocated buffer and return a pointer
   to this buffer. If argc is zero, return the empty string. NULL pointers are
   treated as empty strings. */
char *cats(size_t argc, ...)
{
   char *ret, *next;
   size_t ret_len;
   char **argv;
   size_t *argv_lens;
   va_list ap;

   argv = malloc_ch(argc * sizeof(char *), true);
   argv_lens = malloc_ch(argc * sizeof(size_t), false);

   // compute buffer size and convert NULLs to empty string
   va_start(ap, argc);
   ret_len = 1;  // for terminator
   for (int i = 0; i < argc; i++)
   {
      char *arg = va_arg(ap, char *);
      if (arg == NULL) {
         argv[i] = "";
         argv_lens[i] = 0;
      } else {
         argv[i] = arg;
         argv_lens[i] = strlen(arg);
      }
      ret_len += argv_lens[i];
   }
   va_end(ap);

   // copy strings
   ret = malloc_ch(ret_len, false);
   next = ret;
   for (int i = 0; i < argc; i++) {
      memcpy(next, argv[i], argv_lens[i]);
      next += argv_lens[i];
   }
   ret[ret_len-1] = '\0';

   return ret;
}

/* Return a string describing the given errno_ in a nerdly fashion, i.e.,
   numeric value and also C constant name if available. */
char *errno_nerd_str(int errno_)
{
   char *ret;

   ret = asprintf_ch("%d", errno_);
#ifdef HAVE_STRERRORNAME_NP
   ret = cats(3, ret, " ", strerrorname_np(errno_));
#endif

   return ret;
}

/** Return a copy of @p s with all instances of @p old replaced with @p new. */
char *replace_char(const char *s, char old, char new)
{
   char *ret = strdup_ch(s);

   for (int i = 0; ret[i] != '\0'; i++)
      if (ret[i] == old)
         ret[i] = new;

   return ret;
}

/** Split a string on the first occurrence of a delimiter byte.

    @param a[out]   The part of @p str before the first occurrence of @p del,
                    or all of @p str if @p del is not present.

    @param b[out]   The part of @p str after the first occurrence of @p del,
                    or @c NULL if @p del is not present.

    @param str[in]  String to split. May not be @c NULL. Can be the same
                    buffer as @p a or @p b, if its value isn’t needed after
                    the call.

    @param del[in]  Delimiter to split on.

    @p a and @p b point into a newly allocated buffer, and @p str is left
    unchanged. This is the *same* buffer, so the parts can be re-joined by
    setting @c *(*b-1) to @p del. The point here is to provide an easier
    wrapper for @c strsep(3). */
void split(char **a, char **b, const char *str, char del)
{
   char delstr[2] = { del, 0 };
   T__ (str != NULL);
   *b = strdup_ch(str);
   *a = strsep(b, delstr);
}

/* Return a copy of s in a newly allocated, pointerless buffer. Cannot fail.

   Note: Unlike strdup(3), strdup_ch() is only needed if you need to actually
   modify the copy. It should not be used to simplify memory management.

   Implemented in terms of a memory copy so we don’t need to care about which
   strdup(3) is being used (libc or libgc). */
char *strdup_ch(const char *s)
{
   char *dst;
   size_t buf_sz = strlen(s) + 1;

   dst = malloc_ch(buf_sz, false);
   memcpy(dst, s, buf_sz);
   return dst;
}

/** Test equality of two strings.

      @param a,b  Strings to compare. May not be @c NULL.

    @returns True if @p a and @p b are equal, false otherwise.

    TL;DR: I got burned by @c strcmp(3)’s confusing inverted return value one
    too many times */
bool streq(const char *a, const char *b)
{
   T__ (a && b);

#undef strcmp
   return !strcmp(a, b);
#define strcmp strcmp_error
}

/** Test equality of first @p n bytes of two strings.

      @param a,b  Strings to compare. May not be @c NULL.

      @param n    Maximum number of bytes to compare.

    @returns True if @a and @p are equal through the first @p n bytes, false otherwise. If no bytes are compared, i.e. @p n is zero, return true.

    This is equivalent to @c streq() if either input’s length is less than or equal to @n bytes. */
bool streqn(const char *a, const char *b, size_t n)
{
   T__ (a && b);

#undef strncmp
   return !strncmp(a, b, n);
#define strncmp strncmp_error
}

/* Append null-terminated string “str” to the memory buffer “offset” bytes
   after from the address pointed to by “addr”. Buffer length is “size” bytes.
   Return the number of bytes written. If there isn’t enough room for the
   string, do nothing and return zero. */
size_t string_append(char *addr, char *str, size_t size, size_t offset)
{
   size_t written = strlen(str) + 1;

   if (size > (offset + written - 1))  // there is space
      memcpy(addr + offset, str, written);

   return written;
}

/* Like asprintf_ch(), but takes and consumes a va_list pointer. */
char *vasprintf_ch(const char *fmt, va_list ap)
{
   va_list ap2;
   int str_len;
   char *str; // = malloc_ch(1024, false);

   va_copy(ap2, ap);

   T_e (0 <= (str_len = vsnprintf(NULL, 0, fmt, ap)));
   str = malloc_ch(str_len + 1, false);
   T_e (str_len == vsnprintf(str, str_len + 1, fmt, ap2));

   va_end(ap2);

   return str;
}