Line data Source code
1 : /*
2 : * lwan - web server
3 : * Copyright (c) 2012 L. A. F. Pereira <l@tia.mat.br>
4 : *
5 : * This program is free software; you can redistribute it and/or
6 : * modify it under the terms of the GNU General Public License
7 : * as published by the Free Software Foundation; either version 2
8 : * of the License, or any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program; if not, write to the Free Software
17 : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 : * USA.
19 : */
20 :
21 : #include <errno.h>
22 : #include <fcntl.h>
23 : #include <libproc.h>
24 : #include <limits.h>
25 : #include <linux/capability.h>
26 : #include <pthread.h>
27 : #include <stdint.h>
28 : #include <stdlib.h>
29 : #include <string.h>
30 : #include <sys/epoll.h>
31 : #include <sys/socket.h>
32 : #include <sys/types.h>
33 : #include <sys/vfs.h>
34 : #include <unistd.h>
35 :
36 : #include "lwan.h"
37 :
38 : #ifndef LWAN_HAVE_MEMPCPY
39 : void *mempcpy(void *dest, const void *src, size_t len)
40 : {
41 : char *p = memcpy(dest, src, len);
42 : return p + len;
43 : }
44 : #endif
45 :
46 : #ifndef LWAN_HAVE_MEMRCHR
47 : void *memrchr(const void *s, int c, size_t n)
48 : {
49 : const char *end = (const char *)s + n + 1;
50 : const char *prev = NULL;
51 :
52 : for (const char *cur = s; cur <= end; prev = cur++) {
53 : cur = (const char *)memchr(cur, c, (size_t)(end - cur));
54 : if (!cur)
55 : break;
56 : }
57 :
58 : return (void *)prev;
59 : }
60 : #endif
61 :
62 : #ifndef LWAN_HAVE_PIPE2
63 : int pipe2(int pipefd[2], int flags)
64 : {
65 : int r;
66 :
67 : r = pipe(pipefd);
68 : if (r < 0)
69 : return r;
70 :
71 : if (fcntl(pipefd[0], F_SETFL, flags) < 0 ||
72 : fcntl(pipefd[1], F_SETFL, flags) < 0) {
73 : int saved_errno = errno;
74 :
75 : close(pipefd[0]);
76 : close(pipefd[1]);
77 :
78 : errno = saved_errno;
79 : return -1;
80 : }
81 :
82 : return 0;
83 : }
84 : #endif
85 :
86 : #ifndef LWAN_HAVE_ACCEPT4
87 : int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags)
88 : {
89 : int fd = accept(sock, addr, addrlen);
90 : int newflags = 0;
91 :
92 : if (fd < 0)
93 : return fd;
94 :
95 : if (flags & SOCK_NONBLOCK) {
96 : newflags |= O_NONBLOCK;
97 : flags &= ~SOCK_NONBLOCK;
98 : }
99 : if (flags & SOCK_CLOEXEC) {
100 : newflags |= O_CLOEXEC;
101 : flags &= ~SOCK_CLOEXEC;
102 : }
103 : if (flags) {
104 : errno = -EINVAL;
105 : return -1;
106 : }
107 :
108 : if (fcntl(fd, F_SETFL, newflags) < 0) {
109 : int saved_errno = errno;
110 :
111 : close(fd);
112 :
113 : errno = saved_errno;
114 : return -1;
115 : }
116 :
117 : return fd;
118 : }
119 : #endif
120 :
121 : #ifndef LWAN_HAVE_CLOCK_GETTIME
122 : int clock_gettime(clockid_t clk_id, struct timespec *ts)
123 : {
124 : switch (clk_id) {
125 : case CLOCK_MONOTONIC:
126 : case CLOCK_MONOTONIC_COARSE:
127 : /* FIXME: time() isn't monotonic */
128 : ts->tv_sec = time(NULL);
129 : ts->tv_nsec = 0;
130 : return 0;
131 : }
132 :
133 : errno = EINVAL;
134 : return -1;
135 : }
136 : #endif
137 :
138 : #if !defined(LWAN_HAVE_EPOLL) && defined(LWAN_HAVE_KQUEUE)
139 : #include <sys/event.h>
140 : #include <sys/time.h>
141 : #include <sys/types.h>
142 :
143 : #include "hash.h"
144 :
145 : int epoll_create1(int flags __attribute__((unused))) { return kqueue(); }
146 :
147 : static int epoll_no_event_marker;
148 :
149 : int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
150 : {
151 : struct kevent ev;
152 :
153 : switch (op) {
154 : case EPOLL_CTL_ADD:
155 : case EPOLL_CTL_MOD: {
156 : int events = 0;
157 : void *udata = event->data.ptr;
158 : int flags = EV_ADD;
159 :
160 : if (event->events & EPOLLIN) {
161 : events = EVFILT_READ;
162 : } else if (event->events & EPOLLOUT) {
163 : events = EVFILT_WRITE;
164 : } else {
165 : events = EVFILT_WRITE;
166 : udata = &epoll_no_event_marker;
167 : }
168 :
169 : if (event->events & EPOLLONESHOT)
170 : flags |= EV_ONESHOT;
171 : if (event->events & EPOLLET)
172 : flags |= EV_CLEAR;
173 :
174 : flags |= EV_ERROR; /* EPOLLERR is always set. */
175 : flags |= EV_EOF; /* EPOLLHUP is always set. */
176 :
177 : EV_SET(&ev, fd, events, flags, 0, 0, udata);
178 : break;
179 : }
180 :
181 : case EPOLL_CTL_DEL:
182 : EV_SET(&ev, fd, 0, EV_DELETE, 0, 0, 0);
183 : break;
184 :
185 : default:
186 : errno = EINVAL;
187 : return -1;
188 : }
189 :
190 : return kevent(epfd, &ev, 1, NULL, 0, NULL);
191 : }
192 :
193 : static struct timespec *to_timespec(struct timespec *t, int ms)
194 : {
195 : if (ms < 0)
196 : return NULL;
197 :
198 : t->tv_sec = ms / 1000;
199 : t->tv_nsec = (ms % 1000) * 1000000;
200 :
201 : return t;
202 : }
203 :
204 : int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
205 : {
206 : struct epoll_event *ev = events;
207 : struct kevent evs[maxevents];
208 : struct timespec tmspec;
209 : struct hash *coalesce;
210 : int i, r;
211 :
212 : coalesce = hash_int_new(NULL, NULL);
213 : if (UNLIKELY(!coalesce))
214 : return -1;
215 :
216 : r = kevent(epfd, NULL, 0, evs, maxevents, to_timespec(&tmspec, timeout));
217 : if (UNLIKELY(r < 0)) {
218 : hash_free(coalesce);
219 : return -1;
220 : }
221 :
222 : for (i = 0; i < r; i++) {
223 : struct kevent *kev = &evs[i];
224 : uint32_t mask = (uint32_t)(uintptr_t)hash_find(
225 : coalesce, (void *)(intptr_t)evs[i].ident);
226 :
227 : if (kev->flags & EV_ERROR)
228 : mask |= EPOLLERR;
229 : if (kev->flags & EV_EOF)
230 : mask |= EPOLLRDHUP;
231 :
232 : if (kev->filter == EVFILT_READ)
233 : mask |= EPOLLIN;
234 : else if (kev->filter == EVFILT_WRITE && evs[i].udata != &epoll_no_event_marker)
235 : mask |= EPOLLOUT;
236 :
237 : hash_add(coalesce, (void *)(intptr_t)evs[i].ident,
238 : (void *)(uintptr_t)mask);
239 : }
240 :
241 : for (i = 0; i < r; i++) {
242 : void *maskptr;
243 :
244 : maskptr = hash_find(coalesce, (void *)(intptr_t)evs[i].ident);
245 : if (maskptr) {
246 : struct kevent *kev = &evs[i];
247 :
248 : if (kev->udata == &epoll_no_event_marker)
249 : continue;
250 :
251 : ev->data.ptr = kev->udata;
252 : ev->events = (uint32_t)(uintptr_t)maskptr;
253 : ev++;
254 : }
255 : }
256 :
257 : hash_free(coalesce);
258 : return (int)(intptr_t)(ev - events);
259 : }
260 : #elif !defined(LWAN_HAVE_EPOLL)
261 : #error epoll() not implemented for this platform
262 : #endif
263 :
264 : #if defined(__linux__) || defined(__CYGWIN__)
265 : #if defined(LWAN_HAVE_GETAUXVAL)
266 : #include <sys/auxv.h>
267 : #endif
268 :
269 460 : int proc_pidpath(pid_t pid, void *buffer, size_t buffersize)
270 : {
271 : ssize_t path_len;
272 :
273 460 : if (getpid() != pid) {
274 0 : errno = EACCES;
275 :
276 0 : return -1;
277 : }
278 :
279 : #if defined(LWAN_HAVE_GETAUXVAL)
280 460 : const char *execfn = (const char *)getauxval(AT_EXECFN);
281 :
282 460 : if (execfn) {
283 460 : size_t len = strlen(execfn);
284 :
285 460 : if (len + 1 < buffersize) {
286 460 : memcpy(buffer, execfn, len + 1);
287 :
288 460 : return 0;
289 : }
290 : }
291 : #endif
292 :
293 0 : path_len = readlink("/proc/self/exe", buffer, buffersize);
294 0 : if (path_len < 0)
295 0 : return -1;
296 :
297 0 : if (path_len < (ssize_t)buffersize) {
298 0 : ((char *)buffer)[path_len] = '\0';
299 :
300 0 : return 0;
301 : }
302 :
303 0 : errno = EOVERFLOW;
304 0 : return -1;
305 : }
306 :
307 : #elif defined(__FreeBSD__)
308 : #include <sys/sysctl.h>
309 :
310 : int proc_pidpath(pid_t pid, void *buffer, size_t buffersize)
311 : {
312 : int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
313 : size_t path_len = buffersize;
314 :
315 : if (getpid() != pid) {
316 : errno = EACCES;
317 :
318 : return -1;
319 : }
320 :
321 : if (sysctl(mib, N_ELEMENTS(mib), buffer, &path_len, NULL, 0) < 0)
322 : return -1;
323 :
324 : return 0;
325 : }
326 : #elif defined(LWAN_HAVE_DLADDR) && !defined(__APPLE__)
327 : #include <dlfcn.h>
328 :
329 : int proc_pidpath(pid_t pid, void *buffer, size_t buffersize)
330 : {
331 : Dl_info info;
332 :
333 : if (getpid() != pid) {
334 : errno = EACCES;
335 : return -1;
336 : }
337 :
338 : extern int main();
339 : if (dladdr(main, &info)) {
340 : if (!info.dli_fname)
341 : goto fallback;
342 :
343 : if (buffersize < PATH_MAX - 1)
344 : goto fallback;
345 :
346 : if (realpath(info.dli_fname, buffer))
347 : return 0;
348 : }
349 :
350 : fallback:
351 : if (strlcpy(buffer, "lwan", buffersize) >= buffersize) {
352 : errno = ENOMEM;
353 : return -1;
354 : }
355 :
356 : return 0;
357 : }
358 : #elif !defined(__APPLE__)
359 : #error proc_pidpath() not implemented for this architecture
360 : #endif
361 :
362 : #if defined(__linux__)
363 :
364 : #if !defined(LWAN_HAVE_GETTID)
365 : #include <sys/syscall.h>
366 :
367 : pid_t gettid(void) { return (pid_t)syscall(SYS_gettid); }
368 : #endif
369 :
370 : #elif defined(__FreeBSD__)
371 : #include <sys/thr.h>
372 :
373 : pid_t gettid(void)
374 : {
375 : long ret;
376 :
377 : thr_self(&ret);
378 :
379 : return (pid_t)ret;
380 : }
381 : #elif defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
382 : #include <sys/syscall.h>
383 :
384 : pid_t gettid(void) { return syscall(SYS_thread_selfid); }
385 : #else
386 : pid_t gettid(void) { return (pid_t)pthread_self(); }
387 : #endif
388 :
389 : #if defined(__APPLE__)
390 : /* NOTE: Although saved UID/GID cannot be set using sysctl(), for the use
391 : * case in Lwan, it's possible to obtain the value and check if they're the
392 : * ones expected -- and abort if it's not. Should be good enough for a
393 : * wrapper like this. */
394 :
395 : #include <sys/sysctl.h>
396 :
397 : static int get_current_proc_info(struct kinfo_proc *kp)
398 : {
399 : int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
400 : size_t len = sizeof(*kp);
401 :
402 : return sysctl(mib, N_ELEMENTS(mib), kp, &len, NULL, 0);
403 : }
404 :
405 : int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid)
406 : {
407 : struct kinfo_proc kp;
408 :
409 : if (!get_current_proc_info(&kp)) {
410 : *ruid = getuid();
411 : *euid = geteuid();
412 : *suid = kp.kp_eproc.e_pcred.p_svuid;
413 :
414 : return 0;
415 : }
416 :
417 : return -1;
418 : }
419 :
420 : int setresuid(uid_t ruid, uid_t euid, uid_t suid __attribute__((unused)))
421 : {
422 : return setreuid(ruid, euid);
423 : }
424 :
425 : int setresgid(gid_t rgid, gid_t egid, gid_t sgid __attribute__((unused)))
426 : {
427 : return setregid(rgid, egid);
428 : }
429 :
430 : int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid)
431 : {
432 : struct kinfo_proc kp;
433 :
434 : if (!get_current_proc_info(&kp)) {
435 : *rgid = getgid();
436 : *egid = getegid();
437 : *sgid = kp.kp_eproc.e_pcred.p_svgid;
438 :
439 : return 0;
440 : }
441 :
442 : return -1;
443 : }
444 : #endif
445 :
446 : #if !defined(LWAN_HAVE_MKOSTEMP)
447 : int mkostemp(char *tmpl, int flags)
448 : {
449 : int fd, fl;
450 :
451 : fd = mkstemp(tmpl);
452 : if (fd < 0)
453 : return -1;
454 :
455 : fl = fcntl(fd, F_GETFD);
456 : if (fl < 0)
457 : goto out;
458 :
459 : if (flags & O_CLOEXEC)
460 : fl |= FD_CLOEXEC;
461 :
462 : if (fcntl(fd, F_SETFD, fl) < 0)
463 : goto out;
464 :
465 : return fd;
466 :
467 : out:
468 : close(fd);
469 : return -1;
470 : }
471 : #endif
472 :
473 : #if !defined(LWAN_HAVE_REALLOCARRAY)
474 : /* $OpenBSD: reallocarray.c,v 1.2 2014/12/08 03:45:00 bcook Exp $ */
475 : /*
476 : * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
477 : *
478 : * Permission to use, copy, modify, and distribute this software for any
479 : * purpose with or without fee is hereby granted, provided that the above
480 : * copyright notice and this permission notice appear in all copies.
481 : *
482 : * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
483 : * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
484 : * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
485 : * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
486 : * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
487 : * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
488 : * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
489 : */
490 :
491 : #include <errno.h>
492 : #include <stdbool.h>
493 : #include <stdint.h>
494 : #include <stdlib.h>
495 : #include <sys/types.h>
496 :
497 : #if !defined(LWAN_HAVE_BUILTIN_MUL_OVERFLOW)
498 : /*
499 : * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
500 : * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
501 : */
502 : #define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
503 :
504 : static inline bool umull_overflow(size_t a, size_t b, size_t *out)
505 : {
506 : if ((a >= MUL_NO_OVERFLOW || b >= MUL_NO_OVERFLOW) && a > 0 &&
507 : SIZE_MAX / a < b)
508 : return true;
509 : *out = a * b;
510 : return false;
511 : }
512 : #else
513 : #define umull_overflow __builtin_mul_overflow
514 : #endif
515 :
516 : void *reallocarray(void *optr, size_t nmemb, size_t size)
517 : {
518 : size_t total_size;
519 : if (UNLIKELY(umull_overflow(nmemb, size, &total_size))) {
520 : errno = ENOMEM;
521 : return NULL;
522 : }
523 : if (UNLIKELY(total_size == 0)) {
524 : free(optr);
525 : return malloc(1);
526 : }
527 : return realloc(optr, total_size);
528 : }
529 : #endif /* LWAN_HAVE_REALLOCARRAY */
530 :
531 : #if !defined(LWAN_HAVE_READAHEAD)
532 : ssize_t readahead(int fd, off_t offset, size_t count)
533 : {
534 : #if defined(LWAN_HAVE_POSIX_FADVISE)
535 : return (ssize_t)posix_fadvise(fd, offset, (off_t)count,
536 : POSIX_FADV_WILLNEED);
537 : #else
538 : (void)fd;
539 : (void)offset;
540 : (void)count;
541 :
542 : return 0;
543 : #endif
544 : }
545 : #endif
546 :
547 : #if !defined(LWAN_HAVE_GET_CURRENT_DIR_NAME)
548 : #include <limits.h>
549 :
550 : char *get_current_dir_name(void)
551 : {
552 : char buffer[PATH_MAX];
553 : char *ret;
554 :
555 : ret = getcwd(buffer, sizeof(buffer));
556 : return strdup(ret ? ret : "/");
557 : }
558 : #endif
559 :
560 : #ifndef __linux__
561 : int capset(struct __user_cap_header_struct *header,
562 : struct __user_cap_data_struct *data)
563 : {
564 : #ifdef __OpenBSD__
565 : if (header->version != _LINUX_CAPABILITY_VERSION_1)
566 : return -EINVAL;
567 : if (header->pid != 0)
568 : return -EINVAL;
569 : if (data->effective == 0 && data->permitted == 0)
570 : return pledge("stdio rpath tmppath inet error", NULL);
571 : #else
572 : (void)header;
573 : (void)data;
574 : #endif
575 :
576 : return 0;
577 : }
578 : #endif
579 :
580 : #if !defined(LWAN_HAVE_FWRITE_UNLOCKED)
581 : size_t fwrite_unlocked(const void *ptr, size_t size, size_t n, FILE *stream)
582 : {
583 : size_t to_write = size * n;
584 : const size_t total_to_write = to_write;
585 :
586 : if (!to_write)
587 : return 0;
588 :
589 : fflush/* _unlocked? */(stream);
590 :
591 : while (to_write) {
592 : ssize_t r = write(fileno(stream), ptr, to_write);
593 : if (r < 0) {
594 : if (errno == EINTR)
595 : continue;
596 : break;
597 : }
598 :
599 : to_write -= (size_t)r;
600 : }
601 :
602 : return (total_to_write - to_write) / size;
603 : }
604 : #endif
605 :
606 : #if !defined(LWAN_HAVE_STATFS)
607 : int statfs(const char *path, struct statfs *buf)
608 : {
609 : (void)path;
610 : (void)buf;
611 :
612 : *errno = ENOSYS;
613 : return -1;
614 : }
615 : #endif
616 :
617 0 : static int lwan_getentropy_fallback(void *buffer, size_t buffer_len)
618 : {
619 : int fd;
620 :
621 0 : fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY);
622 0 : if (fd < 0) {
623 0 : fd = open("/dev/random", O_CLOEXEC | O_RDONLY);
624 0 : if (fd < 0)
625 0 : return -1;
626 : }
627 0 : ssize_t total_read = read(fd, buffer, buffer_len);
628 0 : close(fd);
629 :
630 0 : return total_read == (ssize_t)buffer_len ? 0 : -1;
631 : }
632 :
633 : #if defined(SYS_getrandom)
634 276 : long int lwan_getentropy(void *buffer, size_t buffer_len, int flags)
635 : {
636 276 : long r = syscall(SYS_getrandom, buffer, buffer_len, flags);
637 :
638 276 : if (r < 0)
639 0 : return lwan_getentropy_fallback(buffer, buffer_len);
640 :
641 276 : return r;
642 : }
643 : #elif defined(LWAN_HAVE_GETENTROPY)
644 : long int lwan_getentropy(void *buffer, size_t buffer_len, int flags)
645 : {
646 : (void)flags;
647 :
648 : if (!getentropy(buffer, buffer_len))
649 : return buffer_len;
650 :
651 : return lwan_getentropy_fallback(buffer, buffer_len);
652 : }
653 : #else
654 : long int lwan_getentropy(void *buffer, size_t buffer_len, int flags)
655 : {
656 : (void)flags;
657 : return lwan_getentropy_fallback(buffer, buffer_len);
658 : }
659 : #endif
660 :
661 2382 : static inline int isalpha_neutral(char c)
662 : {
663 : /* Use this instead of isalpha() from ctype.h because they consider
664 : * the current locale. This assumes CHAR_BIT == 8. */
665 : static const unsigned char table[32] = {
666 : 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, 7, 0, 0, 0, 0,
667 : 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
668 : };
669 2382 : unsigned char uc = (unsigned char)c;
670 2382 : return table[uc >> 3] & 1 << (uc & 7);
671 : }
672 :
673 1775 : bool strcaseequal_neutral(const char *a, const char *b)
674 : {
675 3754 : for (;;) {
676 5529 : char ca = *a++;
677 5529 : char cb = *b++;
678 :
679 : /* See which bits are different in either character */
680 5529 : switch (ca ^ cb) {
681 2099 : case 0: /* ca and cb are the same: advance */
682 2099 : if (ca == '\0') {
683 : /* If `ca` is 0 here, then cb must be 0 too, so we don't
684 : * need to check both. */
685 364 : return true;
686 : }
687 1735 : continue;
688 2019 : case 32: /* Only 5th bit is set: advance if either are uppercase
689 : * ASCII characters, but differ in case only */
690 : /* If either is an uppercase ASCII character, then move on */
691 2019 : if (isalpha_neutral(ca) || isalpha_neutral(cb))
692 2019 : continue;
693 : /* Fallthrough */
694 : default:
695 1411 : return false;
696 : }
697 : }
698 : }
699 :
700 : #ifndef NDEBUG
701 92 : __attribute__((constructor)) static void test_strcaseequal_neutral(void)
702 : {
703 92 : assert(strcaseequal_neutral("LWAN", "lwan") == true);
704 92 : assert(strcaseequal_neutral("LwAn", "lWaN") == true);
705 92 : assert(strcaseequal_neutral("SomE-HeaDer", "some-header") == true);
706 :
707 92 : assert(strcaseequal_neutral("SomE-HeaDeP", "some-header") == false);
708 92 : assert(strcaseequal_neutral("LwAN", "lwam") == false);
709 92 : assert(strcaseequal_neutral("LwAn", "lWaM") == false);
710 :
711 : static_assert(CHAR_BIT == 8, "sane CHAR_BIT value");
712 : static_assert('*' == 42, "ASCII character set");
713 : static_assert('0' == 48, "ASCII character set");
714 : static_assert('a' == 97, "ASCII character set");
715 92 : }
716 : #endif
717 :
718 : #ifndef LWAN_HAVE_STPCPY
719 : char *stpncpy(char *restrict dst, const char *restrict src, size_t sz)
720 : {
721 : /* Implementation from the Linux stpcpy(3) man page. */
722 : char *p = mempcpy(dst, src, sz);
723 : *p = 0;
724 : return p;
725 : }
726 :
727 : char *stpcpy(char *restrict dst, const char *restrict src)
728 : {
729 : return stpncpy(dst, src, strlen(src));
730 : }
731 : #endif
|