1/*
2 * Copyright © 2016-2019 Sören Tempel
3 *
4 * This program is free software: you can redistribute it and/or
5 * modify it under the terms of the GNU Affero General Public
6 * License as published by the Free Software Foundation, either
7 * version 3 of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <http://www.gnu.org/licenses/>.
17 */
18
19#include <assert.h>
20#include <errno.h>
21#include <fcntl.h>
22#include <pthread.h>
23#include <semaphore.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <unistd.h>
28
29#include <sys/mman.h>
30#include <sys/stat.h>
31#include <sys/types.h>
32
33#include "util.h"
34
35/**
36 * Returns a string which 'marks' the given position in the given
37 * string. Meaning it returns a string which is excactly as long as the
38 * first 'pos' characters of the given string when printed to a terminal
39 * (tab characters will be retained. The last character (ignoring the
40 * null byte) of this returned string is '^' and will thus point to the
41 * character at 'pos' in the original string if the two string are
42 * printed to a terminal seperated by a newline character.
43 *
44 * @pre The length of the given string must be equal or greater than pos.
45 * @param pos Position of the character in the given string the
46 * returned string should point to. The first element is at
47 * position 0 not at position 1.
48 * @param str String to create marker for.
49 * @returns Marker for the given string as described above.
50 */
51char *
52mark(size_t pos, char *str)
53{
54 size_t i;
55 char *res;
56
57 /* +2 for \0 and ^ character */
58 res = emalloc(pos + 2);
59
60 for (i = 0; i < pos; i++) {
61 if ((res[i] = str[i]) != '\t')
62 res[i] = ' ';
63 }
64
65 res[pos] = '^';
66 res[++pos] = '\0';
67
68 return res;
69}
70
71/**
72 * Returns the content of the line with the given line number.
73 *
74 * @param str Input string containing line.
75 * @param line Line number.
76 * @return A pointer to a dynamically allocated string containing
77 * the content of the given line if it exists. NULL if it doesn't.
78 */
79char *
80linenum(char *str, unsigned int line)
81{
82 size_t len;
83 char ch, *res;
84 size_t end, prev, newline;
85
86 if (line < 1)
87 return NULL;
88 len = strlen(str);
89
90 newline = prev = 0;
91 for (end = 0; end <= len; end++) {
92 ch = str[end];
93 if (ch != '\n' && ch != '\0')
94 continue;
95
96 if (--line == 0) {
97 prev = newline++;
98 break;
99 } else {
100 newline = end + 1;
101 }
102 }
103
104 if (line != 0)
105 return NULL;
106
107 len = end - prev;
108 if (len == 0) /* \n\0 */
109 return NULL;
110
111 res = estrndup(&str[prev], len);
112 return res;
113}
114
115/**
116 * Returns the index of the last character in the given line.
117 *
118 * @param line Line for which last character should be returned.
119 * @return Index of the last character in the given line.
120 */
121size_t
122endofline(char *line)
123{
124 char *pos;
125
126 if (!(pos = strchr(line, '\n')))
127 pos = strchr(line, '\0');
128 assert(pos != NULL);
129
130 assert(pos - line >= 0);
131 return (size_t)(pos - line);
132}
133
134/**
135 * Compares the first 'n' bytes of two strings (like strncmp(3)).
136 * However, unlike strncmp(3) it returns the position of the
137 * first character that differed.
138 *
139 * @param s1 First string to use for comparison.
140 * @param s2 Second string to use for comparison.
141 * @param n Amount of bytes to compare.
142 * @param res Pointer to an address where the position of
143 * the first character that differed should be stored.
144 * The first element is located at position 0.
145 * @returns An integer less than, equal to, or greater than
146 * zero if s1 (or the first n bytes thereof) is found,
147 * respectively, to be less than, to match, or be greater than s2.
148 */
149int
150xstrncmp(char *s1, char *s2, size_t n, size_t *res)
151{
152 size_t i;
153
154 i = 1;
155 while (*s1 && *s1 == *s2 && i < n) {
156 s1++;
157 s2++;
158 i++;
159 }
160
161 *res = --i;
162 return *s1 - *s2;
163}
164
165/**
166 * Maps the given file to memory and returns the address of the mapped
167 * memory location. If the file is empty no mapping is performed and
168 * zero is returned.
169 *
170 * @param dest Memory location to which the address of the mapping is
171 * written. It is not initialized if zero is returned.
172 * @param fp Path to file which should be read.
173 * @returns Length of the mapped memory location or zero if no mapping
174 * has been performed. If an error occured while trying to read the
175 * given file NULL is returned and errno is set to indicate the error.
176 */
177ssize_t
178readfile(char **dest, char *fp)
179{
180 int fd;
181 off_t len;
182 struct stat st;
183
184 if ((fd = open(fp, O_RDONLY)) == -1)
185 return -1;
186 if (fstat(fd, &st))
187 return -1;
188 if ((len = st.st_size) <= 0)
189 return 0;
190
191 *dest = mmap(NULL, (size_t)len, PROT_READ, MAP_PRIVATE, fd, 0);
192 if (*dest == MAP_FAILED)
193 return -1;
194
195 close(fd);
196 return len;
197}
198
199/**
200 * Calls estrndup(3) but terminates the program EXIT_FAILURE if strndup returned
201 * an error.
202 *
203 * @param s Pointer to a string which should be duplicated.
204 * @param n Amount of bytes to copy.
205 * @returns Pointer to a new string which is a duplicate of the given one.
206 */
207char *
208estrndup(char *s, size_t n)
209{
210 char *r;
211
212 if (!(r = strndup(s, n)))
213 die("strndup failed");
214
215 return r;
216}
217
218/**
219 * Calls malloc(3) but terminates the program with EXIT_FAILURE if malloc
220 * returned an error.
221 *
222 * @param size Amount of memory (in bytes) which should be allocated.
223 * @returns Pointer to the allocated memory.
224 */
225void *
226emalloc(size_t size)
227{
228 void *r;
229
230 if (!(r = malloc(size)))
231 die("malloc failed");
232
233 return r;
234}
235
236/**
237 * Calls realloc(3) but terminates the program with EXIT_FAILURE if realloc
238 * returned an error.
239 *
240 * @param size Amount of memory (in bytes) which should be allocated.
241 * @returns Pointer to the allocated memory.
242 */
243void *
244erealloc(void *ptr, size_t size)
245{
246 void *r;
247
248 if (!(r = realloc(ptr, size)))
249 die("realloc failed");
250
251 return r;
252}
253
254/**
255 * Calls pthread_mutex_lock(3) but terminates the program with
256 * EXIT_FAILURE if pthread_mutex_lock returned an error.
257 *
258 * @param lock Spin lock which should be locked.
259 */
260void
261pthread_mutex_elock(pthread_mutex_t *lock)
262{
263 if ((errno = pthread_mutex_lock(lock)))
264 die("pthread_mutex_lock failed");
265}
266
267/**
268 * Calls pthread_mutex_unlock(3) but terminates the program with
269 * EXIT_FAILURE if pthread_mutex_unlock returned an error.
270 *
271 * @param lock Spin lock which should be unlocked.
272 */
273void
274pthread_mutex_eunlock(pthread_mutex_t *lock)
275{
276 if ((errno = pthread_mutex_unlock(lock)))
277 die("pthread_mutex_unlock failed");
278}
279
280/**
281 * Calls sem_wait(3) but terminates the program with EXIT_FAILURE if sem_wait
282 * returned an error.
283 *
284 * @param sem Semaphore to call sem_wait on.
285 */
286void
287sem_ewait(sem_t *sem)
288{
289 if (sem_wait(sem))
290 die("sem_wait failed");
291}
292
293/**
294 * Calls sem_post(3) but terminates the program with EXIT_FAILURE if sem_post
295 * returned an error.
296 *
297 * @param sem Semaphore to call sem_post on.
298 */
299void
300sem_epost(sem_t *sem)
301{
302 if (sem_post(sem))
303 die("sem_post failed");
304}