1 /* 2 * Copy a file into a filesystem. 3 * 4 * Copyright (C) 2019, 2022, 2024 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA 20 */ 21 22 #include <stdio.h> 23 #include <string.h> 24 25 #include <sys/stat.h> 26 27 #include <e2access/path.h> 28 #include <fsclient/client.h> 29 #include <systypes/fcntl.h> 30 31 #include "file.h" 32 #include "ops.h" 33 #include "session.h" 34 35 36 37 /* Copy buffer size. */ 38 39 static int BUFSIZE = 4096; 40 41 42 43 /* Alternative metadata set by options. */ 44 45 extern struct metadata md; 46 47 48 49 /* Copy a file into the filesystem image. */ 50 51 static int _copy_file_in(const char *source, const char *target, flags_t flags) 52 { 53 int retval = 0; 54 55 /* Copying details. */ 56 57 FILE *fp; 58 file_t *target_fp; 59 char buf[BUFSIZE]; 60 size_t got; 61 offset_t written; 62 63 /* Open a file in the target directory. */ 64 65 target_fp = client_open(target, flags); 66 67 if (target_fp == NULL) 68 return 1; 69 70 /* Open the file in the source directory. */ 71 72 fp = fopen(source, "r"); 73 74 /* Copy the file content. */ 75 76 if (fp != NULL) 77 { 78 while ((got = fread(buf, sizeof(char), BUFSIZE, fp))) 79 { 80 while (got) 81 { 82 written = client_write(target_fp, buf, got); 83 84 if (!written) 85 { 86 retval = 1; 87 goto close_files; 88 } 89 got -= written; 90 } 91 } 92 } 93 94 close_files: 95 fclose(fp); 96 client_close(target_fp); 97 98 return retval; 99 } 100 101 /* Copy a source file from the external environment into the filesystem. */ 102 103 static int _copy_in(const char *source, const char *target, 104 const char *basename) 105 { 106 /* Obtain basename and eventual target path. */ 107 108 const char *source_basename = path_basename(source); 109 char target_plus_basename[strlen(target) + 1 + 110 (basename != NULL ? strlen(basename) 111 : strlen(source_basename)) + 1]; 112 const char *target_path; 113 114 /* Target file properties. */ 115 116 flags_t flags; 117 struct stat st; 118 long err; 119 120 /* By default, treat the target as a new object. */ 121 122 int target_is_new = 1; 123 124 /* Without a basename, the target exists and is either a directory, into 125 which the source file shall be copied, or it is a file that shall be 126 overwritten. */ 127 128 if (basename == NULL) 129 { 130 err = client_stat(target, &st); 131 132 if (err) 133 { 134 fprintf(stderr, "Could not stat target: %s\n", target); 135 return 1; 136 } 137 138 target_is_new = S_ISDIR(st.st_mode); 139 140 if (target_is_new) 141 basename = path_basename(source); 142 } 143 144 /* Obtain the target path. */ 145 146 if (basename != NULL) 147 { 148 /* At the top level, omit the directory. */ 149 150 if (strlen(target)) 151 { 152 sprintf(target_plus_basename, "%s/%s", target, basename); 153 target_path = target_plus_basename; 154 } 155 else 156 target_path = basename; 157 } 158 else 159 target_path = target; 160 161 /* Directories are created with the same metadata. */ 162 163 if (isdir(source)) 164 { 165 if (!target_is_new) 166 { 167 fprintf(stderr, "Directory cannot be copied as it already exists: %s\n", target_path); 168 return 1; 169 } 170 171 err = client_stat(source, &st); 172 173 if (err) 174 { 175 fprintf(stderr, "Could not stat source: %s\n", source); 176 return 1; 177 } 178 179 if (client_mkdir(target_path, st.st_mode & ~md.mask)) 180 return 1; 181 } 182 183 /* Files are copied. */ 184 185 else if (isfile(source)) 186 { 187 flags = O_WRONLY; 188 189 /* Obtain the inode for the target file. */ 190 191 if (target_is_new) 192 flags |= O_CREAT; 193 194 /* NOTE: Overwrite/update metadata where appropriate. */ 195 196 if (_copy_file_in(source, target_path, flags)) 197 { 198 fprintf(stderr, "Failed to copy file: %s\n", source); 199 return 1; 200 } 201 } 202 203 return 0; 204 } 205 206 /* Copy source files from the external environment into the filesystem image. */ 207 208 int copy_in(int argc, char *argv[]) 209 { 210 int i; 211 212 /* Target filename details. */ 213 214 char *target = argv[argc - 1]; 215 char *basename; 216 struct stat st; 217 218 /* Locate the target and test whether it is a file or a directory. */ 219 220 long err = client_stat(target, &st); 221 222 /* Only a non-existent file in an existing directory is permitted. */ 223 224 if (err == -L4_ENOENT) 225 { 226 /* Split the path, making target the parent directory. */ 227 228 basename = path_split(target); 229 230 /* Set the parent directory to the empty string if absent. */ 231 232 if (basename == target) 233 target = basename + strlen(basename); 234 235 err = client_stat(target, &st); 236 237 if (err) 238 { 239 fprintf(stderr, "Could not stat target parent: %s\n", target); 240 return 1; 241 } 242 243 if (!S_ISDIR(st.st_mode)) 244 { 245 fprintf(stderr, "Target parent is not directory: %s\n", target); 246 return 1; 247 } 248 } 249 else if (err) 250 { 251 fprintf(stderr, "Could not stat target: %s\n", target); 252 return 1; 253 } 254 255 /* An existing object can be a file or directory. */ 256 257 else 258 { 259 basename = NULL; 260 261 /* Only permit a target file when one source file is given. */ 262 263 if (!S_ISDIR(st.st_mode) && (argc > 2)) 264 { 265 fprintf(stderr, "Target can only be a file when copying a single file: %s\n", target); 266 return 1; 267 } 268 } 269 270 /* Copy each source object to the target directory. */ 271 272 for (i = 0; i < argc - 1; i++) 273 if (_copy_in(argv[i], target, basename)) 274 return 1; 275 276 return 0; 277 } 278 279 /* vim: tabstop=4 expandtab shiftwidth=4 280 */