1#include "config.h"
2
3#if HAVE_RECALLOCARRAY
4
5int dummy;
6
7#else
8
9/*	$Id: compat_recallocarray.c,v 1.1 2017/06/12 19:05:47 schwarze Exp $ */
10/*	$OpenBSD: malloc.c,v 1.225 2017/05/13 07:11:29 otto Exp $ */
11/*
12 * Copyright (c) 2017 Otto Moerbeek <otto@drijf.net>
13 *
14 * Permission to use, copy, modify, and distribute this software for any
15 * purpose with or without fee is hereby granted, provided that the above
16 * copyright notice and this permission notice appear in all copies.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
19 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
20 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
21 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
22 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
23 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
24 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 */
26
27#include <sys/types.h>
28#include <errno.h>
29#include <stdint.h>
30#include <stdlib.h>
31#include <string.h>
32
33/*
34 * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
35 * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
36 */
37#define MUL_NO_OVERFLOW	((size_t)1 << (sizeof(size_t) * 4))
38
39/*
40 * Even though specified in POSIX, the PAGESIZE and PAGE_SIZE
41 * macros have very poor portability.  Since we only use this
42 * to avoid free() overhead for small shrinking, simply pick
43 * an arbitrary number.
44 */
45#define MALLOC_PAGESIZE         (1UL << 12)
46
47
48void *
49recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size)
50{
51	size_t oldsize, newsize;
52	void *newptr;
53
54	if (ptr == NULL)
55		return calloc(newnmemb, size);
56
57	if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
58	    newnmemb > 0 && SIZE_MAX / newnmemb < size) {
59		errno = ENOMEM;
60		return NULL;
61	}
62	newsize = newnmemb * size;
63
64	if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
65	    oldnmemb > 0 && SIZE_MAX / oldnmemb < size) {
66		errno = EINVAL;
67		return NULL;
68	}
69	oldsize = oldnmemb * size;
70
71	/*
72	 * Don't bother too much if we're shrinking just a bit,
73	 * we do not shrink for series of small steps, oh well.
74	 */
75	if (newsize <= oldsize) {
76		size_t d = oldsize - newsize;
77
78		if (d < oldsize / 2 && d < MALLOC_PAGESIZE) {
79			memset((char *)ptr + newsize, 0, d);
80			return ptr;
81		}
82	}
83
84	newptr = malloc(newsize);
85	if (newptr == NULL)
86		return NULL;
87
88	if (newsize > oldsize) {
89		memcpy(newptr, ptr, oldsize);
90		memset((char *)newptr + oldsize, 0, newsize - oldsize);
91	} else
92		memcpy(newptr, ptr, newsize);
93
94	/*
95	 * At this point, the OpenBSD implementation calls
96	 * explicit_bzero() on the old memory before it is
97	 * freed.  Since explicit_bzero() is hard to implement
98	 * portably and we don't handle confidential data in
99	 * mandoc in the first place, simply free the memory
100	 * without clearing it.
101	 */
102
103	free(ptr);
104
105	return newptr;
106}
107
108#endif /* !HAVE_RECALLOCARRAY */
109