summaryrefslogtreecommitdiff
path: root/src/copy_file.c
blob: 0db52630f7507efabd464fac2ab01e1a2f54eeec (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
/*
 * SPDX-License-Identifier: ISC
 *
 * Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * This is an open source non-commercial project. Dear PVS-Studio, please check it.
 * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
 */

#include <config.h>

#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#include "sudo.h"

/*
 * Extend the given fd to the specified size in bytes.
 * We do this to allocate disk space up-front before overwriting
 * the original file with the temporary.  Otherwise, we could
 * we run out of disk space after truncating the original file.
 */
static int
sudo_extend_file(int fd, const char *name, off_t new_size)
{
    off_t old_size, size;
    ssize_t nwritten;
    char zeroes[BUFSIZ] = { '\0' };
    debug_decl(sudo_extend_file, SUDO_DEBUG_UTIL);

    if ((old_size = lseek(fd, 0, SEEK_END)) == -1) {
	sudo_warn("lseek");
	debug_return_int(-1);
    }
    sudo_debug_printf(SUDO_DEBUG_INFO, "%s: extending %s from %lld to %lld",
	__func__, name, (long long)old_size, (long long)new_size);

    for (size = old_size; size < new_size; size += nwritten) {
	size_t len = new_size - size;
	if (len > sizeof(zeroes))
	    len = sizeof(zeroes);
	nwritten = write(fd, zeroes, len);
	if (nwritten == -1) {
	    int serrno = errno;
	    if (ftruncate(fd, old_size) == -1) {
		sudo_debug_printf(
		    SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
		    "unable to truncate %s to %lld", name, (long long)old_size);
	    }
	    errno = serrno;
	    debug_return_int(-1);
	}
    }
    if (lseek(fd, 0, SEEK_SET) == -1) {
	sudo_warn("lseek");
	debug_return_int(-1);
    }

    debug_return_int(0);
}

/*
 * Copy the contents of src_fd into dst_fd.
 * Returns 0 on success or -1 on error.
 */
int
sudo_copy_file(const char *src, int src_fd, off_t src_len, const char *dst,
    int dst_fd, off_t dst_len)
{
    char buf[BUFSIZ];
    ssize_t nwritten, nread;
    debug_decl(sudo_copy_file, SUDO_DEBUG_UTIL);

    /* Extend the file to the new size if larger before copying. */
    if (dst_len > 0 && src_len > dst_len) {
	if (sudo_extend_file(dst_fd, dst, src_len) == -1)
	    goto write_error;
    }

    /* Overwrite the old file with the new contents. */
    while ((nread = read(src_fd, buf, sizeof(buf))) > 0) {
	ssize_t off = 0;
	do {
	    nwritten = write(dst_fd, buf + off, nread - off);
	    if (nwritten == -1)
		goto write_error;
	    off += nwritten;
	} while (nread > off);
    }
    if (nread == 0) {
	/* success, read to EOF */
	if (src_len < dst_len) {
	    /* We don't open with O_TRUNC so must truncate manually. */
	    if (ftruncate(dst_fd, src_len) == -1) {
		sudo_debug_printf(
		    SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
		    "unable to truncate %s to %lld", dst, (long long)src_len);
		goto write_error;
	    }
	}
	debug_return_int(0);
    } else if (nread < 0) {
	sudo_warn(U_("unable to read from %s"), src);
	debug_return_int(-1);
    } else {
write_error:
	sudo_warn(U_("unable to write to %s"), dst);
	debug_return_int(-1);
    }
}