about summary refs log tree commit diff
path: root/src/libnm-std-aux/nm-std-utils.c
blob: ef0e445d7696442a0c4b5fd9a7690c8f8290a1e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "nm-default-std.h"

#include "nm-std-utils.h"

#include <assert.h>
#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <net/if.h>
#include <pwd.h>
#include <stdint.h>
#include <sys/types.h>

/*****************************************************************************/

NM_STATIC_ASSERT(NM_IFNAMSIZ == IFNAMSIZ);

/*****************************************************************************/
size_t
nm_utils_get_next_realloc_size(bool true_realloc, size_t requested)
{
    size_t n, x;

    /* https://doc.qt.io/qt-5/containers.html#growth-strategies */

    if (requested <= 40) {
        /* small allocations. Increase in small steps of 8 bytes.
         *
         * We get thus sizes of 8, 16, 32, 40. */
        if (requested <= 8)
            return 8;
        if (requested <= 16)
            return 16;
        if (requested <= 32)
            return 32;

        /* The return values for < 104 are essentially hard-coded, and the choice here is
         * made without very strong reasons.
         *
         * We want to stay 24 bytes below the power-of-two border 64. Hence, return 40 here.
         * However, the next step then is already 104 (128 - 24). It's a larger gap than in
         * the steps before.
         *
         * It's not clear whether some of the steps should be adjusted (or how exactly). */
        return 40;
    }

    if (requested <= 0x2000u - 24u || NM_UNLIKELY(!true_realloc)) {
        /* mid sized allocations. Return next power of two, minus 24 bytes extra space
         * at the beginning.
         * That means, we double the size as we grow.
         *
         * With !true_realloc, it means that the caller does not intend to call
         * realloc() but instead clone the buffer. This is for example the case, when we
         * want to nm_explicit_bzero() the old buffer. In that case we really want to grow
         * the buffer exponentially every time and not increment in page sizes of 4K (below).
         *
         * We get thus sizes of 104, 232, 488, 1000, 2024, 4072, 8168... */

        if (NM_UNLIKELY(requested > SIZE_MAX / 2u - 24u))
            goto out_huge;

        x = requested + 24u;
        n = 128u;
        while (n < x) {
            n <<= 1;
            nm_assert(n > 128u);
        }

        nm_assert(n > 24u && n - 24u >= requested);
        return n - 24u;
    }

    if (NM_UNLIKELY(requested > SIZE_MAX - 0x1000u - 24u)) {
        /* overflow happened. */
        goto out_huge;
    }

    /* For large allocations (with !true_realloc) we allocate memory in chunks of
     * 4K (- 24 bytes extra), assuming that the memory gets mmapped and thus
     * realloc() is efficient by just reordering pages. */
    n = ((requested + (0x0FFFu + 24u)) & ~((size_t) 0x0FFFu)) - 24u;
    nm_assert(n >= requested);
    return n;

out_huge:
    if (sizeof(size_t) > 4u) {
        /* on s390x (64 bit), gcc with LTO can complain that the size argument to
         * malloc must not be larger than 9223372036854775807.
         *
         * Work around that by returning SSIZE_MAX. It should be plenty still! */
        assert(requested <= (size_t) SSIZE_MAX);
        return (size_t) SSIZE_MAX;
    }
    return SIZE_MAX;
}

/*****************************************************************************/

bool
nm_utils_set_effective_user(const char *user, char *errbuf, size_t errbuf_len)
{
    struct passwd *pwentry;
    int            errsv;
    char           error[1024];

    errno   = 0;
    pwentry = getpwnam(user);
    if (!pwentry) {
        errsv = errno;
        if (errsv == 0) {
            snprintf(errbuf, errbuf_len, "user not found");
        } else {
            snprintf(errbuf,
                     errbuf_len,
                     "error getting user entry: %d (%s)\n",
                     errsv,
                     _nm_strerror_r(errsv, error, sizeof(error)));
        }
        return false;
    }

    if (setgid(pwentry->pw_gid) != 0) {
        errsv = errno;
        snprintf(errbuf,
                 errbuf_len,
                 "failed to change group to %u: %d (%s)\n",
                 pwentry->pw_gid,
                 errsv,
                 _nm_strerror_r(errsv, error, sizeof(error)));
        return false;
    }

    if (initgroups(user, pwentry->pw_gid) != 0) {
        errsv = errno;
        snprintf(errbuf,
                 errbuf_len,
                 "failed to reset supplementary group list to %u: %d (%s)\n",
                 pwentry->pw_gid,
                 errsv,
                 _nm_strerror_r(errsv, error, sizeof(error)));
        return false;
    }

    if (setuid(pwentry->pw_uid) != 0) {
        errsv = errno;
        snprintf(errbuf,
                 errbuf_len,
                 "failed to change user to %u: %d (%s)\n",
                 pwentry->pw_uid,
                 errsv,
                 _nm_strerror_r(errsv, error, sizeof(error)));
        return false;
    }

    return true;
}

/*****************************************************************************/

bool
nm_utils_read_file_to_stdout(const char *filename, char *errbuf, size_t errbuf_len)
{
    nm_auto_close int fd = -1;
    char              buffer[4096];
    char              error[1024];
    ssize_t           bytes_read;
    int               errsv;

    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        errsv = errno;
        snprintf(errbuf,
                 errbuf_len,
                 "error opening the file: %d (%s)",
                 errsv,
                 _nm_strerror_r(errsv, error, sizeof(error)));
        return false;
    }

    while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
        if (fwrite(buffer, 1, bytes_read, stdout) != (size_t) bytes_read) {
            errsv = errno;
            snprintf(errbuf,
                     errbuf_len,
                     "error writing to stdout: %d (%s)",
                     errsv,
                     _nm_strerror_r(errsv, error, sizeof(error)));
            return false;
        }
    }

    if (bytes_read < 0) {
        errsv = errno;
        snprintf(errbuf,
                 errbuf_len,
                 "error reading the file: %d (%s)",
                 errsv,
                 _nm_strerror_r(errsv, error, sizeof(error)));
        return false;
    }

    return true;
}

/*****************************************************************************/

/**
 * _nm_strerror_r:
 * @errsv: the errno passed to strerror_r()
 * @buf: the string buffer, must be non-null
 * @buf_size: the size of the buffer, must be positive.
 *
 * A wrapper around strerror_r(). Does little else, aside clearing up the
 * confusion about the different versions of the function.
 *
 * errno is preserved.
 *
 * Returns: the error string. This is either a static strong or @buf. It
 *   is not guaranteed to be @buf.
 */
const char *
_nm_strerror_r(int errsv, char *buf, size_t buf_size)
{
    NM_AUTO_PROTECT_ERRNO(errsv2);
    char *buf2;

    nm_assert(buf);
    nm_assert(buf_size > 0);

#if (!defined(__GLIBC__) && !defined(__UCLIBC__)) || ((_POSIX_C_SOURCE >= 200112L) && !_GNU_SOURCE)
    /* XSI-compliant */
    if (strerror_r(errsv, buf, buf_size) != 0) {
        snprintf(buf, buf_size, "Unspecified errno %d", errsv);
    }
    buf2 = buf;
#else
    /* GNU-specific */
    buf2 = strerror_r(errsv, buf, buf_size);
#endif

    nm_assert(buf2);
    return buf2;
}