Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/atf/atf-c/utils.c
39535 views
1
/* Copyright (c) 2010 The NetBSD Foundation, Inc.
2
* All rights reserved.
3
*
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions
6
* are met:
7
* 1. Redistributions of source code must retain the above copyright
8
* notice, this list of conditions and the following disclaimer.
9
* 2. Redistributions in binary form must reproduce the above copyright
10
* notice, this list of conditions and the following disclaimer in the
11
* documentation and/or other materials provided with the distribution.
12
*
13
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
14
* CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
15
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17
* IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
18
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
20
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
22
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
25
26
#include "atf-c/utils.h"
27
28
#include <sys/stat.h>
29
#include <sys/wait.h>
30
31
#include <err.h>
32
#include <errno.h>
33
#include <fcntl.h>
34
#include <regex.h>
35
#include <stdio.h>
36
#include <stdlib.h>
37
#include <string.h>
38
#include <unistd.h>
39
40
#include <atf-c.h>
41
42
#include "atf-c/detail/dynstr.h"
43
44
/* No prototype in header for this one, it's a little sketchy (internal). */
45
void atf_tc_set_resultsfile(const char *);
46
47
/** Allocate a filename to be used by atf_utils_{fork,wait}.
48
*
49
* In case of a failure, marks the calling test as failed when in_parent is
50
* true, else terminates execution.
51
*
52
* \param [out] name String to contain the generated file.
53
* \param pid PID of the process that will write to the file.
54
* \param suffix Either "out" or "err".
55
* \param in_parent If true, fail with atf_tc_fail; else use err(3). */
56
static void
57
init_out_filename(atf_dynstr_t *name, const pid_t pid, const char *suffix,
58
const bool in_parent)
59
{
60
atf_error_t error;
61
62
error = atf_dynstr_init_fmt(name, "atf_utils_fork_%d_%s.txt",
63
(int)pid, suffix);
64
if (atf_is_error(error)) {
65
char buffer[1024];
66
atf_error_format(error, buffer, sizeof(buffer));
67
if (in_parent) {
68
atf_tc_fail("Failed to create output file: %s", buffer);
69
} else {
70
err(EXIT_FAILURE, "Failed to create output file: %s", buffer);
71
}
72
}
73
}
74
75
/** Searches for a regexp in a string.
76
*
77
* \param regex The regexp to look for.
78
* \param str The string in which to look for the expression.
79
*
80
* \return True if there is a match; false otherwise. */
81
static
82
bool
83
grep_string(const char *regex, const char *str)
84
{
85
int res;
86
regex_t preg;
87
88
printf("Looking for '%s' in '%s'\n", regex, str);
89
ATF_REQUIRE(regcomp(&preg, regex, REG_EXTENDED) == 0);
90
91
res = regexec(&preg, str, 0, NULL, 0);
92
ATF_REQUIRE(res == 0 || res == REG_NOMATCH);
93
94
regfree(&preg);
95
96
return res == 0;
97
}
98
99
/** Prints the contents of a file to stdout.
100
*
101
* \param name The name of the file to be printed.
102
* \param prefix An string to be prepended to every line of the printed
103
* file. */
104
void
105
atf_utils_cat_file(const char *name, const char *prefix)
106
{
107
const int fd = open(name, O_RDONLY);
108
ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name);
109
110
char buffer[1024];
111
ssize_t count;
112
bool continued = false;
113
while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
114
buffer[count] = '\0';
115
116
if (!continued)
117
printf("%s", prefix);
118
119
char *iter = buffer;
120
char *end;
121
while ((end = strchr(iter, '\n')) != NULL) {
122
*end = '\0';
123
printf("%s\n", iter);
124
125
iter = end + 1;
126
if (iter != buffer + count)
127
printf("%s", prefix);
128
else
129
continued = false;
130
}
131
if (iter < buffer + count) {
132
printf("%s", iter);
133
continued = true;
134
}
135
}
136
ATF_REQUIRE(count == 0);
137
}
138
139
/** Compares a file against the given golden contents.
140
*
141
* \param name Name of the file to be compared.
142
* \param contents Expected contents of the file.
143
*
144
* \return True if the file matches the contents; false otherwise. */
145
bool
146
atf_utils_compare_file(const char *name, const char *contents)
147
{
148
const int fd = open(name, O_RDONLY);
149
ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name);
150
151
const char *pos = contents;
152
ssize_t remaining = strlen(contents);
153
154
char buffer[1024];
155
ssize_t count;
156
while ((count = read(fd, buffer, sizeof(buffer))) > 0 &&
157
count <= remaining) {
158
if (memcmp(pos, buffer, count) != 0) {
159
close(fd);
160
return false;
161
}
162
remaining -= count;
163
pos += count;
164
}
165
close(fd);
166
return count == 0 && remaining == 0;
167
}
168
169
/** Copies a file.
170
*
171
* \param source Path to the source file.
172
* \param destination Path to the destination file. */
173
void
174
atf_utils_copy_file(const char *source, const char *destination)
175
{
176
const int input = open(source, O_RDONLY);
177
ATF_REQUIRE_MSG(input != -1, "Failed to open source file during "
178
"copy (%s)", source);
179
180
const int output = open(destination, O_WRONLY | O_CREAT | O_TRUNC, 0777);
181
ATF_REQUIRE_MSG(output != -1, "Failed to open destination file during "
182
"copy (%s)", destination);
183
184
char buffer[1024];
185
ssize_t length;
186
while ((length = read(input, buffer, sizeof(buffer))) > 0)
187
ATF_REQUIRE_MSG(write(output, buffer, length) == length,
188
"Failed to write to %s during copy", destination);
189
ATF_REQUIRE_MSG(length != -1, "Failed to read from %s during copy", source);
190
191
struct stat sb;
192
ATF_REQUIRE_MSG(fstat(input, &sb) != -1,
193
"Failed to stat source file %s during copy", source);
194
ATF_REQUIRE_MSG(fchmod(output, sb.st_mode) != -1,
195
"Failed to chmod destination file %s during copy",
196
destination);
197
198
close(output);
199
close(input);
200
}
201
202
/** Creates a file.
203
*
204
* \param name Name of the file to create.
205
* \param contents Text to write into the created file.
206
* \param ... Positional parameters to the contents. */
207
void
208
atf_utils_create_file(const char *name, const char *contents, ...)
209
{
210
va_list ap;
211
atf_dynstr_t formatted;
212
atf_error_t error;
213
214
va_start(ap, contents);
215
error = atf_dynstr_init_ap(&formatted, contents, ap);
216
va_end(ap);
217
ATF_REQUIRE(!atf_is_error(error));
218
219
const int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
220
ATF_REQUIRE_MSG(fd != -1, "Cannot create file %s", name);
221
ATF_REQUIRE(write(fd, atf_dynstr_cstring(&formatted),
222
atf_dynstr_length(&formatted)) != -1);
223
close(fd);
224
225
atf_dynstr_fini(&formatted);
226
}
227
228
/** Checks if a file exists.
229
*
230
* \param path Location of the file to check for.
231
*
232
* \return True if the file exists, false otherwise. */
233
bool
234
atf_utils_file_exists(const char *path)
235
{
236
const int ret = access(path, F_OK);
237
if (ret == -1) {
238
if (errno != ENOENT)
239
atf_tc_fail("Failed to check the existence of %s: %s", path,
240
strerror(errno));
241
else
242
return false;
243
} else
244
return true;
245
}
246
247
/** Spawns a subprocess and redirects its output to files.
248
*
249
* Use the atf_utils_wait() function to wait for the completion of the spawned
250
* subprocess and validate its exit conditions.
251
*
252
* \return 0 in the new child; the PID of the new child in the parent. Does
253
* not return in error conditions. */
254
pid_t
255
atf_utils_fork(void)
256
{
257
const pid_t pid = fork();
258
if (pid == -1)
259
atf_tc_fail("fork failed");
260
261
if (pid == 0) {
262
atf_dynstr_t out_name;
263
init_out_filename(&out_name, getpid(), "out", false);
264
265
atf_dynstr_t err_name;
266
init_out_filename(&err_name, getpid(), "err", false);
267
268
atf_utils_redirect(STDOUT_FILENO, atf_dynstr_cstring(&out_name));
269
atf_utils_redirect(STDERR_FILENO, atf_dynstr_cstring(&err_name));
270
271
atf_dynstr_fini(&err_name);
272
atf_dynstr_fini(&out_name);
273
}
274
return pid;
275
}
276
277
void
278
atf_utils_reset_resultsfile(void)
279
{
280
281
atf_tc_set_resultsfile("/dev/null");
282
}
283
284
/** Frees an dynamically-allocated "argv" array.
285
*
286
* \param argv A dynamically-allocated array of dynamically-allocated
287
* strings. */
288
void
289
atf_utils_free_charpp(char **argv)
290
{
291
char **ptr;
292
293
for (ptr = argv; *ptr != NULL; ptr++)
294
free(*ptr);
295
296
free(argv);
297
}
298
299
/** Searches for a regexp in a file.
300
*
301
* \param regex The regexp to look for.
302
* \param file The file in which to look for the expression.
303
* \param ... Positional parameters to the regex.
304
*
305
* \return True if there is a match; false otherwise. */
306
bool
307
atf_utils_grep_file(const char *regex, const char *file, ...)
308
{
309
int fd;
310
va_list ap;
311
atf_dynstr_t formatted;
312
atf_error_t error;
313
314
va_start(ap, file);
315
error = atf_dynstr_init_ap(&formatted, regex, ap);
316
va_end(ap);
317
ATF_REQUIRE(!atf_is_error(error));
318
319
ATF_REQUIRE((fd = open(file, O_RDONLY)) != -1);
320
bool found = false;
321
char *line = NULL;
322
while (!found && (line = atf_utils_readline(fd)) != NULL) {
323
found = grep_string(atf_dynstr_cstring(&formatted), line);
324
free(line);
325
}
326
close(fd);
327
328
atf_dynstr_fini(&formatted);
329
330
return found;
331
}
332
333
/** Searches for a regexp in a string.
334
*
335
* \param regex The regexp to look for.
336
* \param str The string in which to look for the expression.
337
* \param ... Positional parameters to the regex.
338
*
339
* \return True if there is a match; false otherwise. */
340
bool
341
atf_utils_grep_string(const char *regex, const char *str, ...)
342
{
343
bool res;
344
va_list ap;
345
atf_dynstr_t formatted;
346
atf_error_t error;
347
348
va_start(ap, str);
349
error = atf_dynstr_init_ap(&formatted, regex, ap);
350
va_end(ap);
351
ATF_REQUIRE(!atf_is_error(error));
352
353
res = grep_string(atf_dynstr_cstring(&formatted), str);
354
355
atf_dynstr_fini(&formatted);
356
357
return res;
358
}
359
360
/** Reads a line of arbitrary length.
361
*
362
* \param fd The descriptor from which to read the line.
363
*
364
* \return A pointer to the read line, which must be released with free(), or
365
* NULL if there was nothing to read from the file. */
366
char *
367
atf_utils_readline(const int fd)
368
{
369
char ch;
370
ssize_t cnt;
371
atf_dynstr_t temp;
372
atf_error_t error;
373
374
error = atf_dynstr_init(&temp);
375
ATF_REQUIRE(!atf_is_error(error));
376
377
while ((cnt = read(fd, &ch, sizeof(ch))) == sizeof(ch) &&
378
ch != '\n') {
379
error = atf_dynstr_append_fmt(&temp, "%c", ch);
380
ATF_REQUIRE(!atf_is_error(error));
381
}
382
ATF_REQUIRE(cnt != -1);
383
384
if (cnt == 0 && atf_dynstr_length(&temp) == 0) {
385
atf_dynstr_fini(&temp);
386
return NULL;
387
} else
388
return atf_dynstr_fini_disown(&temp);
389
}
390
391
/** Redirects a file descriptor to a file.
392
*
393
* \param target_fd The file descriptor to be replaced.
394
* \param name The name of the file to direct the descriptor to.
395
*
396
* \pre Should only be called from the process spawned by fork_for_testing
397
* because this exits uncontrolledly.
398
* \post Terminates execution if the redirection fails. */
399
void
400
atf_utils_redirect(const int target_fd, const char *name)
401
{
402
if (target_fd == STDOUT_FILENO)
403
fflush(stdout);
404
else if (target_fd == STDERR_FILENO)
405
fflush(stderr);
406
407
const int new_fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
408
if (new_fd == -1)
409
err(EXIT_FAILURE, "Cannot create %s", name);
410
if (new_fd != target_fd) {
411
if (dup2(new_fd, target_fd) == -1)
412
err(EXIT_FAILURE, "Cannot redirect to fd %d", target_fd);
413
}
414
close(new_fd);
415
}
416
417
/** Waits for a subprocess and validates its exit condition.
418
*
419
* \param pid The process to be waited for. Must have been started by
420
* testutils_fork().
421
* \param exitstatus Expected exit status.
422
* \param expout Expected contents of stdout.
423
* \param experr Expected contents of stderr. */
424
void
425
atf_utils_wait(const pid_t pid, const int exitstatus, const char *expout,
426
const char *experr)
427
{
428
int status;
429
ATF_REQUIRE(waitpid(pid, &status, 0) != -1);
430
431
atf_dynstr_t out_name;
432
init_out_filename(&out_name, pid, "out", true);
433
434
atf_dynstr_t err_name;
435
init_out_filename(&err_name, pid, "err", true);
436
437
atf_utils_cat_file(atf_dynstr_cstring(&out_name), "subprocess stdout: ");
438
atf_utils_cat_file(atf_dynstr_cstring(&err_name), "subprocess stderr: ");
439
440
ATF_REQUIRE(WIFEXITED(status));
441
ATF_REQUIRE_EQ(exitstatus, WEXITSTATUS(status));
442
443
const char *save_prefix = "save:";
444
const size_t save_prefix_length = strlen(save_prefix);
445
446
if (strlen(expout) > save_prefix_length &&
447
strncmp(expout, save_prefix, save_prefix_length) == 0) {
448
atf_utils_copy_file(atf_dynstr_cstring(&out_name),
449
expout + save_prefix_length);
450
} else {
451
ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&out_name),
452
expout));
453
}
454
455
if (strlen(experr) > save_prefix_length &&
456
strncmp(experr, save_prefix, save_prefix_length) == 0) {
457
atf_utils_copy_file(atf_dynstr_cstring(&err_name),
458
experr + save_prefix_length);
459
} else {
460
ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&err_name),
461
experr));
462
}
463
464
ATF_REQUIRE(unlink(atf_dynstr_cstring(&out_name)) != -1);
465
ATF_REQUIRE(unlink(atf_dynstr_cstring(&err_name)) != -1);
466
}
467
468