Coverage Report

Created: 2026-04-01 07:49

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 int create_temp_file(zip_source_file_context_t *ctx, bool create_file);
58
59
static zip_int64_t _zip_stdio_op_commit_write(zip_source_file_context_t *ctx);
60
static zip_int64_t _zip_stdio_op_create_temp_output(zip_source_file_context_t *ctx);
61
#ifdef CAN_CLONE
62
static zip_int64_t _zip_stdio_op_create_temp_output_cloning(zip_source_file_context_t *ctx, zip_uint64_t offset);
63
#endif
64
static bool _zip_stdio_op_open(zip_source_file_context_t *ctx);
65
static zip_int64_t _zip_stdio_op_remove(zip_source_file_context_t *ctx);
66
static void _zip_stdio_op_rollback_write(zip_source_file_context_t *ctx);
67
static char *_zip_stdio_op_strdup(zip_source_file_context_t *ctx, const char *string);
68
static zip_int64_t _zip_stdio_op_write(zip_source_file_context_t *ctx, const void *data, zip_uint64_t len);
69
static FILE *_zip_fopen_close_on_exec(const char *name, bool writeable);
70
71
/* clang-format off */
72
static zip_source_file_operations_t ops_stdio_named = {
73
    _zip_stdio_op_close,
74
    _zip_stdio_op_commit_write,
75
    _zip_stdio_op_create_temp_output,
76
#ifdef CAN_CLONE
77
    _zip_stdio_op_create_temp_output_cloning,
78
#else
79
    NULL,
80
#endif
81
    _zip_stdio_op_open,
82
    _zip_stdio_op_read,
83
    _zip_stdio_op_remove,
84
    _zip_stdio_op_rollback_write,
85
    _zip_stdio_op_seek,
86
    _zip_stdio_op_stat,
87
    _zip_stdio_op_strdup,
88
    _zip_stdio_op_tell,
89
    _zip_stdio_op_write
90
};
91
/* clang-format on */
92
93
0
ZIP_EXTERN zip_source_t *zip_source_file(zip_t *za, const char *fname, zip_uint64_t start, zip_int64_t len) {
94
0
    if (za == NULL) {
95
0
        return NULL;
96
0
    }
97
98
0
    return zip_source_file_create(fname, start, len, &za->error);
99
0
}
100
101
102
23
ZIP_EXTERN zip_source_t *zip_source_file_create(const char *fname, zip_uint64_t start, zip_int64_t length, zip_error_t *error) {
103
23
    if (fname == NULL || length < ZIP_LENGTH_UNCHECKED) {
104
0
        zip_error_set(error, ZIP_ER_INVAL, 0);
105
0
        return NULL;
106
0
    }
107
108
23
    return zip_source_file_common_new(fname, NULL, start, length, NULL, &ops_stdio_named, NULL, error);
109
23
}
110
111
112
0
static zip_int64_t _zip_stdio_op_commit_write(zip_source_file_context_t *ctx) {
113
0
    if (fclose(ctx->fout) < 0) {
114
0
        zip_error_set(&ctx->error, ZIP_ER_WRITE, errno);
115
0
        return -1;
116
0
    }
117
0
    if (rename(ctx->tmpname, ctx->fname) < 0) {
118
0
        zip_error_set(&ctx->error, ZIP_ER_RENAME, errno);
119
0
        return -1;
120
0
    }
121
122
0
    return 0;
123
0
}
124
125
126
0
static zip_int64_t _zip_stdio_op_create_temp_output(zip_source_file_context_t *ctx) {
127
0
    int fd = create_temp_file(ctx, true);
128
129
0
    if (fd < 0) {
130
0
        return -1;
131
0
    }
132
133
0
    if ((ctx->fout = fdopen(fd, "r+b")) == NULL) {
134
0
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
135
0
        close(fd);
136
0
        (void)remove(ctx->tmpname);
137
0
        free(ctx->tmpname);
138
0
        ctx->tmpname = NULL;
139
0
        return -1;
140
0
    }
141
142
0
    return 0;
143
0
}
144
145
#ifdef CAN_CLONE
146
0
static zip_int64_t _zip_stdio_op_create_temp_output_cloning(zip_source_file_context_t *ctx, zip_uint64_t offset) {
147
0
    FILE *tfp;
148
149
0
    if (offset > ZIP_OFF_MAX) {
150
0
        zip_error_set(&ctx->error, ZIP_ER_SEEK, E2BIG);
151
0
        return -1;
152
0
    }
153
154
#ifdef HAVE_CLONEFILE
155
    /* clonefile insists on creating the file, so just create a name */
156
    if (create_temp_file(ctx, false) < 0) {
157
        return -1;
158
    }
159
160
    if (clonefile(ctx->fname, ctx->tmpname, 0) < 0) {
161
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
162
        free(ctx->tmpname);
163
        ctx->tmpname = NULL;
164
        return -1;
165
    }
166
    if ((tfp = _zip_fopen_close_on_exec(ctx->tmpname, true)) == NULL) {
167
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
168
        (void)remove(ctx->tmpname);
169
        free(ctx->tmpname);
170
        ctx->tmpname = NULL;
171
        return -1;
172
    }
173
#else
174
0
    {
175
0
        int fd;
176
0
        struct file_clone_range range;
177
0
        zip_os_stat_t st;
178
179
0
        if (zip_os_fstat(fileno(ctx->f), &st) < 0) {
180
0
            zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
181
0
            return -1;
182
0
        }
183
184
0
        if ((fd = create_temp_file(ctx, true)) < 0) {
185
0
            return -1;
186
0
        }
187
188
0
        range.src_fd = fileno(ctx->f);
189
0
        range.src_offset = 0;
190
0
        range.src_length = ((offset + st.st_blksize - 1) / st.st_blksize) * st.st_blksize;
191
0
        if (range.src_length > st.st_size) {
192
0
            range.src_length = 0;
193
0
        }
194
0
        range.dest_offset = 0;
195
0
        if (ioctl(fd, FICLONERANGE, &range) < 0) {
196
0
            zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
197
0
            (void)close(fd);
198
0
            (void)remove(ctx->tmpname);
199
0
            free(ctx->tmpname);
200
0
            ctx->tmpname = NULL;
201
0
            return -1;
202
0
        }
203
204
0
        if ((tfp = fdopen(fd, "r+b")) == NULL) {
205
0
            zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
206
0
            (void)close(fd);
207
0
            (void)remove(ctx->tmpname);
208
0
            free(ctx->tmpname);
209
0
            ctx->tmpname = NULL;
210
0
            return -1;
211
0
        }
212
0
    }
213
0
#endif
214
215
0
    if (ftruncate(fileno(tfp), (off_t)offset) < 0) {
216
0
        (void)fclose(tfp);
217
0
        (void)remove(ctx->tmpname);
218
0
        free(ctx->tmpname);
219
0
        ctx->tmpname = NULL;
220
0
        return -1;
221
0
    }
222
0
    if (zip_os_fseek(tfp, (zip_off_t)offset, SEEK_SET) < 0) {
223
0
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
224
0
        (void)fclose(tfp);
225
0
        (void)remove(ctx->tmpname);
226
0
        free(ctx->tmpname);
227
0
        ctx->tmpname = NULL;
228
0
        return -1;
229
0
    }
230
231
0
    ctx->fout = tfp;
232
233
0
    return 0;
234
0
}
235
#endif
236
237
0
static bool _zip_stdio_op_open(zip_source_file_context_t *ctx) {
238
0
    if ((ctx->f = _zip_fopen_close_on_exec(ctx->fname, false)) == NULL) {
239
0
        zip_error_set(&ctx->error, ZIP_ER_OPEN, errno);
240
0
        return false;
241
0
    }
242
0
    return true;
243
0
}
244
245
246
0
static zip_int64_t _zip_stdio_op_remove(zip_source_file_context_t *ctx) {
247
0
    if (remove(ctx->fname) < 0) {
248
0
        zip_error_set(&ctx->error, ZIP_ER_REMOVE, errno);
249
0
        return -1;
250
0
    }
251
0
    return 0;
252
0
}
253
254
255
0
static void _zip_stdio_op_rollback_write(zip_source_file_context_t *ctx) {
256
0
    if (ctx->fout) {
257
0
        fclose(ctx->fout);
258
0
    }
259
0
    (void)remove(ctx->tmpname);
260
0
}
261
262
23
static char *_zip_stdio_op_strdup(zip_source_file_context_t *ctx, const char *string) {
263
23
    return strdup(string);
264
23
}
265
266
267
0
static zip_int64_t _zip_stdio_op_write(zip_source_file_context_t *ctx, const void *data, zip_uint64_t len) {
268
0
    size_t ret;
269
270
0
    clearerr((FILE *)ctx->fout);
271
0
    ret = fwrite(data, 1, len, (FILE *)ctx->fout);
272
0
    if (ret != len || ferror((FILE *)ctx->fout)) {
273
0
        zip_error_set(&ctx->error, ZIP_ER_WRITE, errno);
274
0
        return -1;
275
0
    }
276
277
0
    return (zip_int64_t)ret;
278
0
}
279
280
281
0
static int create_temp_file(zip_source_file_context_t *ctx, bool create_file) {
282
0
    char *temp;
283
0
    int mode;
284
0
    zip_os_stat_t st;
285
0
    int fd = 0;
286
0
    char *start, *end;
287
288
0
    if (zip_os_stat(ctx->fname, &st) == 0) {
289
0
        mode = st.st_mode;
290
0
    }
291
0
    else {
292
0
        mode = -1;
293
0
    }
294
295
0
    size_t temp_size = strlen(ctx->fname) + 13;
296
0
    if ((temp = (char *)malloc(temp_size)) == NULL) {
297
0
        zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0);
298
0
        return -1;
299
0
    }
300
0
    snprintf_s(temp, temp_size, "%s.XXXXXX.part", ctx->fname);
301
0
    end = temp + strlen(temp) - 5;
302
0
    start = end - 6;
303
304
0
    for (;;) {
305
0
        zip_uint32_t value = zip_random_uint32();
306
0
        char *xs = start;
307
308
0
        while (xs < end) {
309
0
            char digit = value % 36;
310
0
            if (digit < 10) {
311
0
                *(xs++) = digit + '0';
312
0
            }
313
0
            else {
314
0
                *(xs++) = digit - 10 + 'a';
315
0
            }
316
0
            value /= 36;
317
0
        }
318
319
0
        if (create_file) {
320
0
            if ((fd = open(temp, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, mode == -1 ? 0666 : (mode_t)mode)) >= 0) {
321
0
                if (mode != -1) {
322
                    /* open() honors umask(), which we don't want in this case */
323
0
#ifdef HAVE_FCHMOD
324
0
                    (void)fchmod(fd, (mode_t)mode);
325
#else
326
                    (void)chmod(temp, (mode_t)mode);
327
#endif
328
0
                }
329
0
                break;
330
0
            }
331
0
            if (errno != EEXIST) {
332
0
                zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
333
0
                free(temp);
334
0
                return -1;
335
0
            }
336
0
        }
337
0
        else {
338
0
            if (zip_os_stat(temp, &st) < 0) {
339
0
                if (errno == ENOENT) {
340
0
                    break;
341
0
                }
342
0
                else {
343
0
                    zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
344
0
                    free(temp);
345
0
                    return -1;
346
0
                }
347
0
            }
348
0
        }
349
0
    }
350
351
0
    ctx->tmpname = temp;
352
353
0
    return fd; /* initialized to 0 if !create_file */
354
0
}
355
356
357
/*
358
 * fopen replacement that sets the close-on-exec flag
359
 * some implementations support an fopen 'e' flag for that,
360
 * but e.g. macOS doesn't.
361
 */
362
0
static FILE *_zip_fopen_close_on_exec(const char *name, bool writeable) {
363
0
    int fd;
364
0
    int flags;
365
0
    FILE *fp;
366
367
0
    flags = O_CLOEXEC;
368
0
    if (writeable) {
369
0
        flags |= O_RDWR;
370
0
    }
371
0
    else {
372
0
        flags |= O_RDONLY;
373
0
    }
374
375
    /* mode argument needed on Windows */
376
0
    if ((fd = open(name, flags, 0666)) < 0) {
377
0
        return NULL;
378
0
    }
379
0
    if ((fp = fdopen(fd, writeable ? "r+b" : "rb")) == NULL) {
380
0
        return NULL;
381
0
    }
382
0
    return fp;
383
0
}