1 /* 2 * Copy a file into a filesystem. 3 * 4 * Copyright (C) 2019, 2022 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 24 #include <sys/stat.h> 25 26 #include <ext2fs/ext2fs.h> 27 28 #include "file.h" 29 #include "image.h" 30 #include "path.h" 31 #include "session.h" 32 33 34 35 /* Copy buffer size. */ 36 37 static int BUFSIZE = 4096; 38 39 40 41 /* Alternative metadata set by options. */ 42 43 extern struct metadata md; 44 45 46 47 /* Copy a file into the filesystem image. */ 48 49 static int _copy_file_in(const char *filename, ext2_filsys fs, 50 ext2_ino_t ino_file, int flags) 51 { 52 int retval = 0; 53 ext2_file_t file; 54 55 /* Copying details. */ 56 57 FILE *fp; 58 char buf[BUFSIZE]; 59 size_t got; 60 unsigned int written; 61 62 /* Open a file in the target directory. */ 63 64 if (ext2fs_file_open(fs, ino_file, flags, &file)) 65 return 1; 66 67 /* Truncate the file, if overwriting. */ 68 69 if (!(flags & EXT2_FILE_CREATE)) 70 ext2fs_file_set_size2(file, 0); 71 72 /* Open the file in the source directory. */ 73 74 fp = fopen(filename, "r"); 75 76 /* Copy the file content. */ 77 78 if (fp != NULL) 79 { 80 while (got = fread(buf, sizeof(char), BUFSIZE, fp)) 81 { 82 while (got) 83 { 84 if (ext2fs_file_write(file, buf, got, &written)) 85 { 86 retval = 1; 87 goto close_files; 88 } 89 got -= written; 90 } 91 } 92 } 93 94 close_files: 95 fclose(fp); 96 ext2fs_file_flush(file); 97 ext2fs_file_close(file); 98 99 return retval; 100 } 101 102 /* Make a new object using the given function. */ 103 104 static int _make_object(ext2_filsys fs, const char *filename, 105 ext2_ino_t ino_parent, const char *basename, 106 ext2_ino_t *ino_target, 107 errcode_t (*fn)(ext2_filsys, ext2_ino_t, const char *, 108 __u16, __u16, __u16, ext2_ino_t *)) 109 { 110 errcode_t retval; 111 struct stat st; 112 113 /* Obtain the metadata. */ 114 115 if (lstat(filename, &st)) 116 { 117 fprintf(stderr, "Failed to read object metadata: %s\n", filename); 118 return 1; 119 } 120 121 retval = fn(fs, ino_parent, basename, st.st_mode & ~md.mask, 122 md.have_uid ? md.uid : st.st_uid, 123 md.have_gid ? md.gid : st.st_gid, 124 ino_target); 125 126 if (retval) 127 { 128 fprintf(stderr, "Failed to create object: %s\n", filename); 129 return 1; 130 } 131 132 return 0; 133 } 134 135 /* Copy a source file from the external environment into the filesystem. */ 136 137 static int _copy_in(ext2_filsys fs, const char *filename, ext2_ino_t ino_target, 138 const char *basename) 139 { 140 errcode_t retval; 141 int flags; 142 143 /* By default, treat the target as a new object. */ 144 145 ext2_ino_t ino_parent = ino_target; 146 int target_is_new = 1; 147 148 /* Source file details. */ 149 150 struct stat st; 151 152 /* Without a basename, the target exists and is either a directory, into 153 which the source file shall be copied, or it is a file that shall be 154 overwritten (for which the basename is irrelevant). */ 155 156 if (basename == NULL) 157 { 158 basename = path_basename(filename); 159 target_is_new = image_isdir_by_inode(fs, ino_target); 160 } 161 162 /* Directories are created with the same metadata. */ 163 164 if (isdir(filename)) 165 { 166 if (!target_is_new) 167 { 168 fprintf(stderr, "Directory cannot be copied as it already exists: %s\n", filename); 169 return 1; 170 } 171 172 if (_make_object(fs, filename, ino_parent, basename, &ino_target, 173 image_make_dir)) 174 return 1; 175 } 176 177 /* Files are copied. */ 178 179 else if (isfile(filename)) 180 { 181 flags = EXT2_FILE_WRITE; 182 183 /* Obtain the inode for the target file. */ 184 185 if (target_is_new) 186 { 187 if (_make_object(fs, filename, ino_parent, basename, &ino_target, 188 image_create_file)) 189 return 1; 190 191 flags |= EXT2_FILE_CREATE; 192 } 193 194 /* NOTE: Overwrite/update metadata where appropriate. */ 195 196 if (_copy_file_in(filename, fs, ino_target, flags)) 197 { 198 fprintf(stderr, "Failed to copy file: %s\n", filename); 199 return 1; 200 } 201 } 202 } 203 204 /* Copy source files from the external environment into the filesystem image. */ 205 206 int copy_in(ext2_filsys fs, int argc, char *argv[]) 207 { 208 errcode_t retval; 209 int i; 210 211 /* Target filename details. */ 212 213 const char *target = argv[argc - 1]; 214 const char *target_remaining = target; 215 const char *basename; 216 217 /* Target file and directory details. */ 218 219 int target_is_file; 220 ext2_ino_t ino_target; 221 222 /* Locate the target and test whether it is a file or a directory. */ 223 224 if (image_resolve_by_path(fs, &target_remaining, &ino_target)) 225 { 226 /* Only a non-existent file in an existing directory is permitted. */ 227 228 if (!image_isdir_by_inode(fs, ino_target) || !path_is_leafname(target_remaining)) 229 { 230 fprintf(stderr, "Target not found: %s\n", target); 231 return 1; 232 } 233 234 /* Any absent target leaves the basename remaining. 235 The resolved target is the parent directory. */ 236 237 target_is_file = 0; 238 basename = target_remaining; 239 } 240 else 241 { 242 /* Any present target can be a file or directory. 243 The resolved target is the actual target. */ 244 245 target_is_file = image_isfile_by_inode(fs, ino_target); 246 basename = NULL; 247 } 248 249 /* Only permit a target file when one source file is given. */ 250 251 if (target_is_file) 252 { 253 if (argc > 2) 254 { 255 fprintf(stderr, "Target can only be a file when copying a single file: %s\n", target); 256 return 1; 257 } 258 } 259 else if (!image_isdir_by_inode(fs, ino_target)) 260 { 261 fprintf(stderr, "Target is not a directory: %s\n", target); 262 return 1; 263 } 264 265 /* Copy each source object to the target directory. */ 266 267 for (i = 0; i < argc - 1; i++) 268 if (_copy_in(fs, argv[i], ino_target, basename)) 269 return 1; 270 271 return 0; 272 } 273 274 /* vim: tabstop=4 expandtab shiftwidth=4 275 */