tfortune-1.0.1.
[tfortune.git] / util.c
1 /* SPDX-License-Identifier: GPL-3.0-only */
2
3 #include "tf.h"
4
5 DEFINE_TF_ERRLIST;
6
7 int atoi64(const char *str, int64_t *value)
8 {
9 char *endptr;
10 long long tmp;
11
12 errno = 0; /* To distinguish success/failure after call */
13 tmp = strtoll(str, &endptr, 10);
14 if (errno == ERANGE && (tmp == LLONG_MAX || tmp == LLONG_MIN))
15 return -E_ATOI_OVERFLOW;
16 /*
17 * If there were no digits at all, strtoll() stores the original value
18 * of str in *endptr.
19 */
20 if (endptr == str)
21 return -E_ATOI_NO_DIGITS;
22 /*
23 * The implementation may also set errno and return 0 in case no
24 * conversion was performed.
25 */
26 if (errno != 0 && tmp == 0)
27 return -E_ATOI_NO_DIGITS;
28 if (*endptr != '\0') /* Further characters after number */
29 return -E_ATOI_JUNK_AT_END;
30 *value = tmp;
31 return 1;
32 }
33
34 __attribute__ ((warn_unused_result))
35 void *xrealloc(void *p, size_t size)
36 {
37 /*
38 * No need to check for NULL pointers: If p is NULL, the call
39 * to realloc is equivalent to malloc(size)
40 */
41 assert(size);
42 if (!(p = realloc(p, size))) {
43 EMERG_LOG("realloc failed (size = %zu), aborting\n", size);
44 exit(EXIT_FAILURE);
45 }
46 return p;
47 }
48
49 __attribute__ ((warn_unused_result))
50 void *xmalloc(size_t size)
51 {
52 return xrealloc(NULL, size);
53 }
54
55 __attribute__ ((warn_unused_result))
56 void *xcalloc(size_t size)
57 {
58 void *p = xmalloc(size);
59 memset(p, 0, size);
60 return p;
61 }
62
63 __attribute__ ((warn_unused_result))
64 char *xstrdup(const char *str)
65 {
66 char *p = strdup(str);
67
68 if (p)
69 return p;
70 EMERG_LOG("strdup failed, aborting\n");
71 exit(EXIT_FAILURE);
72 }
73
74 /*
75 * Get the home directory of the current user. Returns a dynamically allocated
76 * string that must be freed by the caller.
77 */
78 __attribute__ ((warn_unused_result))
79 char *get_homedir(void)
80 {
81 struct passwd *pw = getpwuid(getuid());
82
83 if (!pw) {
84 EMERG_LOG("could not get home directory: %s\n",
85 strerror(errno));
86 exit(EXIT_FAILURE);
87 }
88 return xstrdup(pw->pw_dir);
89 }
90
91 /*
92 * Print a formated message to a dynamically allocated string.
93 *
94 * This function is similar to vasprintf(), a GNU extension which is not in C
95 * or POSIX. It allocates a string large enough to hold the output including
96 * the terminating null byte. The allocated string is returned via the first
97 * argument and must be freed by the caller. However, unlike vasprintf(), this
98 * function calls exit() if insufficient memory is available, while vasprintf()
99 * returns -1 in this case.
100 *
101 * It returns the number of bytes written, not including the terminating \p
102 * NULL character.
103 */
104 __attribute__ ((format (printf, 2, 0)))
105 unsigned xvasprintf(char **result, const char *fmt, va_list ap)
106 {
107 int ret;
108 size_t size = 150;
109 va_list aq;
110
111 *result = xmalloc(size + 1);
112 va_copy(aq, ap);
113 ret = vsnprintf(*result, size, fmt, aq);
114 va_end(aq);
115 assert(ret >= 0);
116 if ((size_t)ret < size)
117 return ret;
118 size = ret + 1;
119 *result = xrealloc(*result, size);
120 va_copy(aq, ap);
121 ret = vsnprintf(*result, size, fmt, aq);
122 va_end(aq);
123 assert(ret >= 0 && (size_t)ret < size);
124 return ret;
125 }
126
127 __attribute__ ((format (printf, 2, 3)))
128 /* Print to a dynamically allocated string, variable number of arguments. */
129 unsigned xasprintf(char **result, const char *fmt, ...)
130 {
131 va_list ap;
132 unsigned ret;
133
134 va_start(ap, fmt);
135 ret = xvasprintf(result, fmt, ap);
136 va_end(ap);
137 return ret;
138 }
139
140 /*
141 * Compile a regular expression.
142 *
143 * This simple wrapper calls regcomp(3) and logs a message on errors.
144 */
145 int xregcomp(regex_t *preg, const char *regex, int cflags)
146 {
147 char *buf;
148 size_t size;
149 int ret = regcomp(preg, regex, cflags);
150
151 if (ret == 0)
152 return 1;
153 size = regerror(ret, preg, NULL, 0);
154 buf = xmalloc(size);
155 regerror(ret, preg, buf, size);
156 ERROR_LOG("%s\n", buf);
157 free(buf);
158 return -E_REGEX;
159 }
160
161 /* Write input from fd to dynamically allocated buffer. */
162 int fd2buf(int fd, struct iovec *result)
163 {
164 const size_t max_size = 32 * 1024;
165 size_t loaded = 0;
166 int ret;
167 struct iovec iov;
168
169 iov.iov_len = 100; /* guess that's sufficient */
170 iov.iov_base = xmalloc(iov.iov_len);
171 for (;;) {
172 ret = read(fd, iov.iov_base + loaded, iov.iov_len - loaded);
173 if (ret < 0)
174 ret = -ERRNO_TO_TF_ERROR(errno);
175 if (ret <= 0)
176 break;
177 loaded += ret;
178 if (loaded >= iov.iov_len) {
179 iov.iov_len *= 2;
180 ret = -ERRNO_TO_TF_ERROR(EOVERFLOW);
181 if (iov.iov_len >= max_size)
182 break;
183 iov.iov_base = xrealloc(iov.iov_base, iov.iov_len);
184 }
185 };
186 if (ret < 0) {
187 free(iov.iov_base);
188 result->iov_base = NULL;
189 result->iov_len = 0;
190 return ret;
191 }
192 iov.iov_len = loaded;
193 iov.iov_base = xrealloc(iov.iov_base, loaded + 1);
194 *((char *)iov.iov_base + loaded) = '\0';
195 *result = iov;
196 return 1;
197 }
198
199 __attribute__ ((format (printf, 2, 3)))
200 void tf_log(int ll, const char* fmt,...)
201 {
202 va_list argp;
203 if (ll < loglevel_arg_val)
204 return;
205 va_start(argp, fmt);
206 vfprintf(stderr, fmt, argp);
207 va_end(argp);
208 }
209
210 struct regfile_iter {
211 DIR *dir;
212 int dfd;
213 struct dirent *entry;
214 struct stat stat;
215 };
216
217 void regfile_iter_next(struct regfile_iter *iter)
218 {
219 for (;;) {
220 iter->entry = readdir(iter->dir);
221 if (!iter->entry)
222 return;
223 if (!strcmp(iter->entry->d_name, "."))
224 continue;
225 if (!strcmp(iter->entry->d_name, ".."))
226 continue;
227 if (fstatat(iter->dfd, iter->entry->d_name, &iter->stat, 0) == -1)
228 continue;
229 if (!S_ISREG(iter->stat.st_mode))
230 continue;
231 if (iter->stat.st_size == 0) /* skip over empty files */
232 continue;
233 return;
234 }
235 }
236
237 void regfile_iter_new(const char *dirname, struct regfile_iter **result)
238 {
239 struct regfile_iter *iter;
240 DIR *dir = opendir(dirname);
241
242 if (!dir) {
243 NOTICE_LOG("opendir %s: %s\n", dirname, strerror(errno));
244 *result = NULL;
245 return;
246 }
247 iter = xmalloc(sizeof(*iter));
248 iter->dir = dir;
249 iter->dfd = dirfd(iter->dir);
250 assert(iter->dfd >= 0);
251 regfile_iter_next(iter);
252 *result = iter;
253 }
254
255 static void *xmmap(size_t sz, int fd, const char *path)
256 {
257 void *result = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0);
258
259 if (result == MAP_FAILED) {
260 EMERG_LOG("mmap %s: %s\n", path, strerror(errno));
261 exit(EXIT_FAILURE);
262 }
263 return result;
264 }
265
266 void mmap_file(const char *path, struct iovec *iov)
267 {
268 int fd, ret;
269
270 ret = open(path, 0);
271 if (ret < 0) {
272 EMERG_LOG("could not open %s: %s\n", path, strerror(errno));
273 exit(EXIT_FAILURE);
274 }
275 fd = ret;
276 iov->iov_len = lseek(fd, 0, SEEK_END);
277 if (iov->iov_len == (size_t)(off_t)-1) {
278 EMERG_LOG("lseek %s: %s\n", path, strerror(errno));
279 exit(EXIT_FAILURE);
280 }
281 iov->iov_base = xmmap(iov->iov_len, fd, path);
282 close(fd);
283 }
284
285 bool regfile_iter_map(const struct regfile_iter *iter, struct iovec *result)
286 {
287 int ret, fd;
288 void *map;
289 const char *path;
290
291 if (!iter || !iter->entry)
292 return false;
293 path = iter->entry->d_name;
294 ret = openat(iter->dfd, path, O_RDONLY, 0);
295 if (ret < 0) {
296 EMERG_LOG("could not open %s: %s\n", path, strerror(errno));
297 exit(EXIT_FAILURE);
298 }
299 fd = ret;
300 map = xmmap(iter->stat.st_size, fd, path);
301 close(fd);
302 result->iov_len = iter->stat.st_size;
303 result->iov_base = map;
304 return true;
305 }
306
307 const char *regfile_iter_basename(const struct regfile_iter *iter)
308 {
309 if (!iter || !iter->entry)
310 return NULL;
311 return iter->entry->d_name;
312 }
313
314 const struct stat *regfile_iter_stat(const struct regfile_iter *iter)
315 {
316 return iter? &iter->stat : NULL;
317 }
318
319 void regfile_iter_free(struct regfile_iter *iter)
320 {
321 if (!iter)
322 return;
323 closedir(iter->dir);
324 free(iter);
325 }