Coverage Report

Created: 2026-08-25 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libzip/lib/zip_source_file_stdio_named.c
Line
Count
Source
1
/*
2
  zip_source_file_stdio_named.c -- source for stdio file opened by name
3
  Copyright (C) 1999-2024 Dieter Baron and Thomas Klausner
4
5
  This file is part of libzip, a library to manipulate ZIP archives.
6
  The authors can be contacted at <info@libzip.org>
7
8
  Redistribution and use in source and binary forms, with or without
9
  modification, are permitted provided that the following conditions
10
  are met:
11
  1. Redistributions of source code must retain the above copyright
12
     notice, this list of conditions and the following disclaimer.
13
  2. Redistributions in binary form must reproduce the above copyright
14
     notice, this list of conditions and the following disclaimer in
15
     the documentation and/or other materials provided with the
16
     distribution.
17
  3. The names of the authors may not be used to endorse or promote
18
     products derived from this software without specific prior
19
     written permission.
20
21
  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
22
  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
25
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
27
  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
29
  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30
  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
31
  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
*/
33
34
#include "zipint.h"
35
36
#include "zip_source_file.h"
37
#include "zip_source_file_stdio.h"
38
39
#include <fcntl.h>
40
#include <stdlib.h>
41
#include <sys/stat.h>
42
#ifdef HAVE_UNISTD_H
43
#include <unistd.h>
44
#endif
45
46
#ifdef HAVE_CLONEFILE
47
#include <sys/attr.h>
48
#include <sys/clonefile.h>
49
#define CAN_CLONE
50
#endif
51
#ifdef HAVE_FICLONERANGE
52
#include <linux/fs.h>
53
#include <sys/ioctl.h>
54
#define CAN_CLONE
55
#endif
56
57
static bool copy_permissions(zip_source_file_context_t *ctx);
58
static int create_temp_file(zip_source_file_context_t *ctx, bool create_file);
59
60
static zip_int64_t _zip_stdio_op_commit_write(zip_source_file_context_t *ctx);
61
static zip_int64_t _zip_stdio_op_create_temp_output(zip_source_file_context_t *ctx);
62
#ifdef CAN_CLONE
63
static zip_int64_t _zip_stdio_op_create_temp_output_cloning(zip_source_file_context_t *ctx, zip_uint64_t offset);
64
#endif
65
static bool _zip_stdio_op_open(zip_source_file_context_t *ctx);
66
static zip_int64_t _zip_stdio_op_remove(zip_source_file_context_t *ctx);
67
static void _zip_stdio_op_rollback_write(zip_source_file_context_t *ctx);
68
static char *_zip_stdio_op_strdup(zip_source_file_context_t *ctx, const char *string);
69
static zip_int64_t _zip_stdio_op_write(zip_source_file_context_t *ctx, const void *data, zip_uint64_t len);
70
static FILE *_zip_fopen_close_on_exec(const char *name, bool writeable);
71
72
/* clang-format off */
73
static zip_source_file_operations_t ops_stdio_named = {
74
    _zip_stdio_op_close,
75
    _zip_stdio_op_commit_write,
76
    _zip_stdio_op_create_temp_output,
77
#ifdef CAN_CLONE
78
    _zip_stdio_op_create_temp_output_cloning,
79
#else
80
    NULL,
81
#endif
82
    _zip_stdio_op_open,
83
    _zip_stdio_op_read,
84
    _zip_stdio_op_remove,
85
    _zip_stdio_op_rollback_write,
86
    _zip_stdio_op_seek,
87
    _zip_stdio_op_stat,
88
    _zip_stdio_op_strdup,
89
    _zip_stdio_op_tell,
90
    _zip_stdio_op_write
91
};
92
/* clang-format on */
93
94
0
ZIP_EXTERN zip_source_t *zip_source_file(zip_t *za, const char *fname, zip_uint64_t start, zip_int64_t len) {
95
0
    if (za == NULL) {
96
0
        return NULL;
97
0
    }
98
99
0
    return zip_source_file_create(fname, start, len, &za->error);
100
0
}
101
102
103
9.04k
ZIP_EXTERN zip_source_t *zip_source_file_create(const char *fname, zip_uint64_t start, zip_int64_t length, zip_error_t *error) {
104
9.04k
    if (fname == NULL || length < ZIP_LENGTH_UNCHECKED) {
105
0
        zip_error_set(error, ZIP_ER_INVAL, 0);
106
0
        return NULL;
107
0
    }
108
109
9.04k
    return zip_source_file_common_new(fname, NULL, start, length, NULL, &ops_stdio_named, NULL, error);
110
9.04k
}
111
112
113
0
static zip_int64_t _zip_stdio_op_commit_write(zip_source_file_context_t *ctx) {
114
0
    if (fclose(ctx->fout) < 0) {
115
0
        zip_error_set(&ctx->error, ZIP_ER_WRITE, errno);
116
0
        return -1;
117
0
    }
118
0
    if (!ctx->temp_output_created) {
119
0
        if (!copy_permissions(ctx)) {
120
0
            return -1;
121
0
        }
122
0
    }
123
0
    if (rename(ctx->tmpname, ctx->fname) < 0) {
124
0
        zip_error_set(&ctx->error, ZIP_ER_RENAME, errno);
125
0
        return -1;
126
0
    }
127
128
0
    return 0;
129
0
}
130
131
132
0
static zip_int64_t _zip_stdio_op_create_temp_output(zip_source_file_context_t *ctx) {
133
0
    int fd = create_temp_file(ctx, true);
134
135
0
    if (fd < 0) {
136
0
        return -1;
137
0
    }
138
139
0
    if ((ctx->fout = fdopen(fd, "r+b")) == NULL) {
140
0
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
141
0
        close(fd);
142
0
        (void)remove(ctx->tmpname);
143
0
        free(ctx->tmpname);
144
0
        ctx->tmpname = NULL;
145
0
        return -1;
146
0
    }
147
148
0
    return 0;
149
0
}
150
151
#ifdef CAN_CLONE
152
0
static zip_int64_t _zip_stdio_op_create_temp_output_cloning(zip_source_file_context_t *ctx, zip_uint64_t offset) {
153
0
    FILE *tfp;
154
155
0
    if (offset > ZIP_OFF_MAX) {
156
0
        zip_error_set(&ctx->error, ZIP_ER_SEEK, E2BIG);
157
0
        return -1;
158
0
    }
159
160
#ifdef HAVE_CLONEFILE
161
    /* clonefile insists on creating the file, so just create a name */
162
    if (create_temp_file(ctx, false) < 0) {
163
        return -1;
164
    }
165
166
    if (clonefile(ctx->fname, ctx->tmpname, 0) < 0) {
167
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
168
        free(ctx->tmpname);
169
        ctx->tmpname = NULL;
170
        return -1;
171
    }
172
    if ((tfp = _zip_fopen_close_on_exec(ctx->tmpname, true)) == NULL) {
173
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
174
        (void)remove(ctx->tmpname);
175
        free(ctx->tmpname);
176
        ctx->tmpname = NULL;
177
        return -1;
178
    }
179
#else
180
0
    {
181
0
        int fd;
182
0
        struct file_clone_range range;
183
0
        zip_os_stat_t st;
184
185
0
        if (zip_os_fstat(fileno(ctx->f), &st) < 0) {
186
0
            zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
187
0
            return -1;
188
0
        }
189
190
0
        if ((fd = create_temp_file(ctx, true)) < 0) {
191
0
            return -1;
192
0
        }
193
194
0
        range.src_fd = fileno(ctx->f);
195
0
        range.src_offset = 0;
196
0
        range.src_length = ((offset + st.st_blksize - 1) / st.st_blksize) * st.st_blksize;
197
0
        if (range.src_length > st.st_size) {
198
0
            range.src_length = 0;
199
0
        }
200
0
        range.dest_offset = 0;
201
0
        if (ioctl(fd, FICLONERANGE, &range) < 0) {
202
0
            zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
203
0
            (void)close(fd);
204
0
            (void)remove(ctx->tmpname);
205
0
            free(ctx->tmpname);
206
0
            ctx->tmpname = NULL;
207
0
            return -1;
208
0
        }
209
210
0
        if ((tfp = fdopen(fd, "r+b")) == NULL) {
211
0
            zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
212
0
            (void)close(fd);
213
0
            (void)remove(ctx->tmpname);
214
0
            free(ctx->tmpname);
215
0
            ctx->tmpname = NULL;
216
0
            return -1;
217
0
        }
218
0
    }
219
0
#endif
220
221
0
    if (ftruncate(fileno(tfp), (off_t)offset) < 0) {
222
0
        (void)fclose(tfp);
223
0
        (void)remove(ctx->tmpname);
224
0
        free(ctx->tmpname);
225
0
        ctx->tmpname = NULL;
226
0
        return -1;
227
0
    }
228
0
    if (zip_os_fseek(tfp, (zip_off_t)offset, SEEK_SET) < 0) {
229
0
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
230
0
        (void)fclose(tfp);
231
0
        (void)remove(ctx->tmpname);
232
0
        free(ctx->tmpname);
233
0
        ctx->tmpname = NULL;
234
0
        return -1;
235
0
    }
236
237
0
    ctx->fout = tfp;
238
239
0
    return 0;
240
0
}
241
#endif
242
243
9.04k
static bool _zip_stdio_op_open(zip_source_file_context_t *ctx) {
244
9.04k
    if ((ctx->f = _zip_fopen_close_on_exec(ctx->fname, false)) == NULL) {
245
0
        zip_error_set(&ctx->error, ZIP_ER_OPEN, errno);
246
0
        return false;
247
0
    }
248
9.04k
    return true;
249
9.04k
}
250
251
252
0
static zip_int64_t _zip_stdio_op_remove(zip_source_file_context_t *ctx) {
253
0
    if (remove(ctx->fname) < 0) {
254
0
        zip_error_set(&ctx->error, ZIP_ER_REMOVE, errno);
255
0
        return -1;
256
0
    }
257
0
    return 0;
258
0
}
259
260
261
0
static void _zip_stdio_op_rollback_write(zip_source_file_context_t *ctx) {
262
0
    if (ctx->fout) {
263
0
        fclose(ctx->fout);
264
0
    }
265
0
    (void)remove(ctx->tmpname);
266
0
}
267
268
9.04k
static char *_zip_stdio_op_strdup(zip_source_file_context_t *ctx, const char *string) {
269
9.04k
    return strdup(string);
270
9.04k
}
271
272
273
0
static zip_int64_t _zip_stdio_op_write(zip_source_file_context_t *ctx, const void *data, zip_uint64_t len) {
274
0
    size_t ret;
275
276
0
    clearerr((FILE *)ctx->fout);
277
0
    ret = fwrite(data, 1, len, (FILE *)ctx->fout);
278
0
    if (ret != len || ferror((FILE *)ctx->fout)) {
279
0
        zip_error_set(&ctx->error, ZIP_ER_WRITE, errno);
280
0
        return -1;
281
0
    }
282
283
0
    return (zip_int64_t)ret;
284
0
}
285
286
287
0
static int create_temp_file(zip_source_file_context_t *ctx, bool create_file) {
288
0
    char *temp;
289
0
    mode_t mode;
290
0
    zip_os_stat_t st;
291
0
    int fd = 0;
292
0
    char *start, *end;
293
294
0
    if (zip_os_stat(ctx->fname, &st) == 0) {
295
        /* If the file already exists, we copy permissions on commit, so err on the side of caution. */
296
0
        ctx->temp_output_created = false;
297
0
        mode = 0600;
298
0
    }
299
0
    else {
300
        /* If we create a new file, we want 0666 with umask applied. Since we can't read the current umask, we can't apply that mode on commit, so we set it here instead. */
301
0
        ctx->temp_output_created = true;
302
0
        mode = 0666;
303
0
    }
304
305
0
    size_t temp_size = strlen(ctx->fname) + 13;
306
0
    if ((temp = (char *)malloc(temp_size)) == NULL) {
307
0
        zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0);
308
0
        return -1;
309
0
    }
310
0
    snprintf_s(temp, temp_size, "%s.XXXXXX.part", ctx->fname);
311
0
    end = temp + strlen(temp) - 5;
312
0
    start = end - 6;
313
314
0
    for (;;) {
315
0
        zip_uint32_t value = zip_random_uint32();
316
0
        char *xs = start;
317
318
0
        while (xs < end) {
319
0
            char digit = value % 36;
320
0
            if (digit < 10) {
321
0
                *(xs++) = digit + '0';
322
0
            }
323
0
            else {
324
0
                *(xs++) = digit - 10 + 'a';
325
0
            }
326
0
            value /= 36;
327
0
        }
328
329
0
        if (create_file) {
330
0
            if ((fd = open(temp, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, mode)) >= 0) {
331
0
                break;
332
0
            }
333
0
            if (errno != EEXIST) {
334
0
                zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
335
0
                free(temp);
336
0
                return -1;
337
0
            }
338
0
        }
339
0
        else {
340
0
            if (zip_os_stat(temp, &st) < 0) {
341
0
                if (errno == ENOENT) {
342
0
                    break;
343
0
                }
344
0
                else {
345
0
                    zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
346
0
                    free(temp);
347
0
                    return -1;
348
0
                }
349
0
            }
350
0
        }
351
0
    }
352
353
0
    ctx->tmpname = temp;
354
355
0
    return fd; /* initialized to 0 if !create_file */
356
0
}
357
358
359
/*
360
 * fopen replacement that sets the close-on-exec flag
361
 * some implementations support an fopen 'e' flag for that,
362
 * but e.g. macOS doesn't.
363
 */
364
9.04k
static FILE *_zip_fopen_close_on_exec(const char *name, bool writeable) {
365
9.04k
    int fd;
366
9.04k
    int flags;
367
9.04k
    FILE *fp;
368
369
9.04k
    flags = O_CLOEXEC;
370
9.04k
    if (writeable) {
371
0
        flags |= O_RDWR;
372
0
    }
373
9.04k
    else {
374
9.04k
        flags |= O_RDONLY;
375
9.04k
    }
376
377
    /* mode argument needed on Windows */
378
9.04k
    if ((fd = open(name, flags, 0666)) < 0) {
379
0
        return NULL;
380
0
    }
381
9.04k
    if ((fp = fdopen(fd, writeable ? "r+b" : "rb")) == NULL) {
382
0
        return NULL;
383
0
    }
384
9.04k
    return fp;
385
9.04k
}
386
387
0
static bool copy_permissions(zip_source_file_context_t *ctx) {
388
0
    zip_os_stat_t st;
389
390
0
    if (zip_os_stat(ctx->fname, &st) < 0) {
391
0
        zip_error_set(&ctx->error, ZIP_ER_RENAME, errno);
392
0
        return false;
393
0
    }
394
    /* TODO: copy ACLs */
395
0
    if (chmod(ctx->tmpname, st.st_mode) < 0) {
396
0
        zip_error_set(&ctx->error, ZIP_ER_RENAME, errno);
397
0
        return false;
398
0
    }
399
0
    return true;
400
0
}