David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame^] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | #include "util/copyfile.h" |
| 3 | #include "util/namespaces.h" |
| 4 | #include <internal/lib.h> |
| 5 | #include <sys/mman.h> |
| 6 | #include <sys/stat.h> |
| 7 | #include <errno.h> |
| 8 | #include <fcntl.h> |
| 9 | #include <stdio.h> |
| 10 | #include <stdlib.h> |
| 11 | #include <string.h> |
| 12 | #include <unistd.h> |
| 13 | |
| 14 | static int slow_copyfile(const char *from, const char *to, struct nsinfo *nsi) |
| 15 | { |
| 16 | int err = -1; |
| 17 | char *line = NULL; |
| 18 | size_t n; |
| 19 | FILE *from_fp, *to_fp; |
| 20 | struct nscookie nsc; |
| 21 | |
| 22 | nsinfo__mountns_enter(nsi, &nsc); |
| 23 | from_fp = fopen(from, "r"); |
| 24 | nsinfo__mountns_exit(&nsc); |
| 25 | if (from_fp == NULL) |
| 26 | goto out; |
| 27 | |
| 28 | to_fp = fopen(to, "w"); |
| 29 | if (to_fp == NULL) |
| 30 | goto out_fclose_from; |
| 31 | |
| 32 | while (getline(&line, &n, from_fp) > 0) |
| 33 | if (fputs(line, to_fp) == EOF) |
| 34 | goto out_fclose_to; |
| 35 | err = 0; |
| 36 | out_fclose_to: |
| 37 | fclose(to_fp); |
| 38 | free(line); |
| 39 | out_fclose_from: |
| 40 | fclose(from_fp); |
| 41 | out: |
| 42 | return err; |
| 43 | } |
| 44 | |
| 45 | int copyfile_offset(int ifd, loff_t off_in, int ofd, loff_t off_out, u64 size) |
| 46 | { |
| 47 | void *ptr; |
| 48 | loff_t pgoff; |
| 49 | |
| 50 | pgoff = off_in & ~(page_size - 1); |
| 51 | off_in -= pgoff; |
| 52 | |
| 53 | ptr = mmap(NULL, off_in + size, PROT_READ, MAP_PRIVATE, ifd, pgoff); |
| 54 | if (ptr == MAP_FAILED) |
| 55 | return -1; |
| 56 | |
| 57 | while (size) { |
| 58 | ssize_t ret = pwrite(ofd, ptr + off_in, size, off_out); |
| 59 | if (ret < 0 && errno == EINTR) |
| 60 | continue; |
| 61 | if (ret <= 0) |
| 62 | break; |
| 63 | |
| 64 | size -= ret; |
| 65 | off_in += ret; |
| 66 | off_out += ret; |
| 67 | } |
| 68 | munmap(ptr, off_in + size); |
| 69 | |
| 70 | return size ? -1 : 0; |
| 71 | } |
| 72 | |
| 73 | static int copyfile_mode_ns(const char *from, const char *to, mode_t mode, |
| 74 | struct nsinfo *nsi) |
| 75 | { |
| 76 | int fromfd, tofd; |
| 77 | struct stat st; |
| 78 | int err; |
| 79 | char *tmp = NULL, *ptr = NULL; |
| 80 | struct nscookie nsc; |
| 81 | |
| 82 | nsinfo__mountns_enter(nsi, &nsc); |
| 83 | err = stat(from, &st); |
| 84 | nsinfo__mountns_exit(&nsc); |
| 85 | if (err) |
| 86 | goto out; |
| 87 | err = -1; |
| 88 | |
| 89 | /* extra 'x' at the end is to reserve space for '.' */ |
| 90 | if (asprintf(&tmp, "%s.XXXXXXx", to) < 0) { |
| 91 | tmp = NULL; |
| 92 | goto out; |
| 93 | } |
| 94 | ptr = strrchr(tmp, '/'); |
| 95 | if (!ptr) |
| 96 | goto out; |
| 97 | ptr = memmove(ptr + 1, ptr, strlen(ptr) - 1); |
| 98 | *ptr = '.'; |
| 99 | |
| 100 | tofd = mkstemp(tmp); |
| 101 | if (tofd < 0) |
| 102 | goto out; |
| 103 | |
| 104 | if (st.st_size == 0) { /* /proc? do it slowly... */ |
| 105 | err = slow_copyfile(from, tmp, nsi); |
| 106 | if (!err && fchmod(tofd, mode)) |
| 107 | err = -1; |
| 108 | goto out_close_to; |
| 109 | } |
| 110 | |
| 111 | if (fchmod(tofd, mode)) |
| 112 | goto out_close_to; |
| 113 | |
| 114 | nsinfo__mountns_enter(nsi, &nsc); |
| 115 | fromfd = open(from, O_RDONLY); |
| 116 | nsinfo__mountns_exit(&nsc); |
| 117 | if (fromfd < 0) |
| 118 | goto out_close_to; |
| 119 | |
| 120 | err = copyfile_offset(fromfd, 0, tofd, 0, st.st_size); |
| 121 | |
| 122 | close(fromfd); |
| 123 | out_close_to: |
| 124 | close(tofd); |
| 125 | if (!err) |
| 126 | err = link(tmp, to); |
| 127 | unlink(tmp); |
| 128 | out: |
| 129 | free(tmp); |
| 130 | return err; |
| 131 | } |
| 132 | |
| 133 | int copyfile_ns(const char *from, const char *to, struct nsinfo *nsi) |
| 134 | { |
| 135 | return copyfile_mode_ns(from, to, 0755, nsi); |
| 136 | } |
| 137 | |
| 138 | int copyfile_mode(const char *from, const char *to, mode_t mode) |
| 139 | { |
| 140 | return copyfile_mode_ns(from, to, mode, NULL); |
| 141 | } |
| 142 | |
| 143 | int copyfile(const char *from, const char *to) |
| 144 | { |
| 145 | return copyfile_mode(from, to, 0755); |
| 146 | } |