Coverage Report

Created: 2026-05-30 06:16

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
5.59k
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
5.59k
    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
5.59k
    return zip_source_file_common_new(fname, NULL, start, length, NULL, &ops_stdio_named, NULL, error);
109
5.59k
}
110
111
112
1.03k
static zip_int64_t _zip_stdio_op_commit_write(zip_source_file_context_t *ctx) {
113
1.03k
    if (fclose(ctx->fout) < 0) {
114
0
        zip_error_set(&ctx->error, ZIP_ER_WRITE, errno);
115
0
        return -1;
116
0
    }
117
1.03k
    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
1.03k
    return 0;
123
1.03k
}
124
125
126
1.03k
static zip_int64_t _zip_stdio_op_create_temp_output(zip_source_file_context_t *ctx) {
127
1.03k
    int fd = create_temp_file(ctx, true);
128
129
1.03k
    if (fd < 0) {
130
0
        return -1;
131
0
    }
132
133
1.03k
    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
1.03k
    return 0;
143
1.03k
}
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
4.56k
static bool _zip_stdio_op_open(zip_source_file_context_t *ctx) {
238
4.56k
    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
4.56k
    return true;
243
4.56k
}
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
5.59k
static char *_zip_stdio_op_strdup(zip_source_file_context_t *ctx, const char *string) {
263
5.59k
    return strdup(string);
264
5.59k
}
265
266
267
32.0k
static zip_int64_t _zip_stdio_op_write(zip_source_file_context_t *ctx, const void *data, zip_uint64_t len) {
268
32.0k
    size_t ret;
269
270
32.0k
    clearerr((FILE *)ctx->fout);
271
32.0k
    ret = fwrite(data, 1, len, (FILE *)ctx->fout);
272
32.0k
    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
32.0k
    return (zip_int64_t)ret;
278
32.0k
}
279
280
281
1.03k
static int create_temp_file(zip_source_file_context_t *ctx, bool create_file) {
282
1.03k
    char *temp;
283
1.03k
    int mode;
284
1.03k
    zip_os_stat_t st;
285
1.03k
    int fd = 0;
286
1.03k
    char *start, *end;
287
288
1.03k
    if (zip_os_stat(ctx->fname, &st) == 0) {
289
0
        mode = st.st_mode;
290
0
    }
291
1.03k
    else {
292
1.03k
        mode = -1;
293
1.03k
    }
294
295
1.03k
    size_t temp_size = strlen(ctx->fname) + 13;
296
1.03k
    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
1.03k
    snprintf_s(temp, temp_size, "%s.XXXXXX.part", ctx->fname);
301
1.03k
    end = temp + strlen(temp) - 5;
302
1.03k
    start = end - 6;
303
304
1.03k
    for (;;) {
305
1.03k
        zip_uint32_t value = zip_random_uint32();
306
1.03k
        char *xs = start;
307
308
7.21k
        while (xs < end) {
309
6.18k
            char digit = value % 36;
310
6.18k
            if (digit < 10) {
311
1.75k
                *(xs++) = digit + '0';
312
1.75k
            }
313
4.42k
            else {
314
4.42k
                *(xs++) = digit - 10 + 'a';
315
4.42k
            }
316
6.18k
            value /= 36;
317
6.18k
        }
318
319
1.03k
        if (create_file) {
320
1.03k
            if ((fd = open(temp, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, mode == -1 ? 0666 : (mode_t)mode)) >= 0) {
321
1.03k
                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
1.03k
                break;
330
1.03k
            }
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
1.03k
    }
350
351
1.03k
    ctx->tmpname = temp;
352
353
1.03k
    return fd; /* initialized to 0 if !create_file */
354
1.03k
}
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
4.56k
static FILE *_zip_fopen_close_on_exec(const char *name, bool writeable) {
363
4.56k
    int fd;
364
4.56k
    int flags;
365
4.56k
    FILE *fp;
366
367
4.56k
    flags = O_CLOEXEC;
368
4.56k
    if (writeable) {
369
0
        flags |= O_RDWR;
370
0
    }
371
4.56k
    else {
372
4.56k
        flags |= O_RDONLY;
373
4.56k
    }
374
375
    /* mode argument needed on Windows */
376
4.56k
    if ((fd = open(name, flags, 0666)) < 0) {
377
0
        return NULL;
378
0
    }
379
4.56k
    if ((fp = fdopen(fd, writeable ? "r+b" : "rb")) == NULL) {
380
0
        return NULL;
381
0
    }
382
4.56k
    return fp;
383
4.56k
}