1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2023 Klara, Inc. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/stat.h> 29 #include <sys/wait.h> 30 31 #include <err.h> 32 #include <fcntl.h> 33 #include <paths.h> 34 #include <stdarg.h> 35 #include <stdbool.h> 36 #include <stdlib.h> 37 #include <stdio.h> 38 #include <string.h> 39 #include <unistd.h> 40 41 #define PROGNAME "mktar" 42 43 #define SUBDIRNAME "directory" 44 #define EMPTYDIRNAME "empty" 45 #define NORMALFILENAME "file" 46 #define SPARSEFILENAME "sparse_file" 47 #define HARDLINKNAME "hard_link" 48 #define SHORTLINKNAME "short_link" 49 #define LONGLINKNAME "long_link" 50 51 static bool opt_v; 52 53 static void verbose(const char *fmt, ...) 54 { 55 va_list ap; 56 57 if (!opt_v) 58 return; 59 fprintf(stderr, "%s: ", PROGNAME); 60 va_start(ap, fmt); 61 vfprintf(stderr, fmt, ap); 62 va_end(ap); 63 fprintf(stderr, "\n"); 64 } 65 66 static void 67 mknormalfile(const char *filename, mode_t mode) 68 { 69 char buf[512]; 70 ssize_t res; 71 int fd; 72 73 if ((fd = open(filename, O_RDWR|O_CREAT|O_EXCL, mode)) < 0) 74 err(1, "%s", filename); 75 for (unsigned int i = 0; i < sizeof(buf); i++) 76 buf[i] = 32 + i % 64; 77 res = write(fd, buf, sizeof(buf)); 78 if (res < 0) 79 err(1, "%s", filename); 80 if (res != sizeof(buf)) 81 errx(1, "%s: short write", filename); 82 close(fd); 83 } 84 85 static void 86 mksparsefile(const char *filename, mode_t mode) 87 { 88 char buf[511]; 89 ssize_t res; 90 int fd; 91 92 if ((fd = open(filename, O_RDWR|O_CREAT|O_EXCL, mode)) < 0) 93 err(1, "%s", filename); 94 for (unsigned int i = 33; i <= 126; i++) { 95 memset(buf, i, sizeof(buf)); 96 if (lseek(fd, 1048576LU * (i - 32), SEEK_SET) < 0) 97 err(1, "%s", filename); 98 res = write(fd, buf, sizeof(buf)); 99 if (res < 0) 100 err(1, "%s", filename); 101 if (res != sizeof(buf)) 102 errx(1, "%s: short write", filename); 103 } 104 close(fd); 105 } 106 107 static char * 108 mklonglinktarget(const char *dirname, const char *filename) 109 { 110 char *piece, *target; 111 112 if (asprintf(&piece, "%1$s/../%1$s/../%1$s/../%1$s/../", dirname) < 0) 113 err(1, "asprintf()"); 114 if (asprintf(&target, "%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%2$s", piece, filename) < 0) 115 err(1, "asprintf()"); 116 free(piece); 117 return target; 118 } 119 120 static void 121 mktar(void) 122 { 123 char *linktarget; 124 125 /* create a subdirectory */ 126 verbose("mkdir %s", SUBDIRNAME); 127 if (mkdir(SUBDIRNAME, 0755) != 0) 128 err(1, "%s", SUBDIRNAME); 129 130 /* create a second subdirectory which will remain empty */ 131 verbose("mkdir %s", EMPTYDIRNAME); 132 if (mkdir(EMPTYDIRNAME, 0755) != 0) 133 err(1, "%s", EMPTYDIRNAME); 134 135 /* create a normal file */ 136 verbose("creating %s", NORMALFILENAME); 137 mknormalfile(NORMALFILENAME, 0644); 138 139 /* create a sparse file */ 140 verbose("creating %s", SPARSEFILENAME); 141 mksparsefile(SPARSEFILENAME, 0644); 142 chflags(SPARSEFILENAME, UF_NODUMP); 143 144 /* create a hard link */ 145 verbose("link %s %s", SPARSEFILENAME, HARDLINKNAME); 146 if (link(SPARSEFILENAME, HARDLINKNAME) != 0) 147 err(1, "%s", HARDLINKNAME); 148 149 /* create a symbolic link with a short target */ 150 verbose("symlink %s %s", SPARSEFILENAME, SHORTLINKNAME); 151 if (symlink(SPARSEFILENAME, SHORTLINKNAME) != 0) 152 err(1, "%s", SHORTLINKNAME); 153 154 /* create a symbolic link with a long target */ 155 linktarget = mklonglinktarget(SUBDIRNAME, SPARSEFILENAME); 156 verbose("symlink %s %s", linktarget, LONGLINKNAME); 157 if (symlink(linktarget, LONGLINKNAME) != 0) 158 err(1, "%s", LONGLINKNAME); 159 free(linktarget); 160 } 161 162 static void 163 usage(void) 164 { 165 166 fprintf(stderr, "usage: %s [-v] tarfile\n", PROGNAME); 167 exit(EXIT_FAILURE); 168 } 169 170 int 171 main(int argc, char *argv[]) 172 { 173 const char *tarfilename; 174 char *dirname; 175 int opt, wstatus; 176 pid_t pid; 177 178 while ((opt = getopt(argc, argv, "v")) != -1) 179 switch (opt) { 180 case 'v': 181 opt_v = true; 182 break; 183 default: 184 usage(); 185 } 186 187 argc -= optind; 188 argv += optind; 189 190 if (argc != 1) 191 usage(); 192 tarfilename = *argv; 193 194 if (asprintf(&dirname, "%s%s.XXXXXXXX", _PATH_TMP, PROGNAME) < 0) 195 err(1, "asprintf()"); 196 if (mkdtemp(dirname) == NULL) 197 err(1, "%s", dirname); 198 verbose("mkdir %s", dirname); 199 200 /* fork a child to create the files */ 201 if ((pid = fork()) < 0) 202 err(1, "fork()"); 203 if (pid == 0) { 204 verbose("cd %s", dirname); 205 if (chdir(dirname) != 0) 206 err(1, "%s", dirname); 207 verbose("umask 022"); 208 umask(022); 209 mktar(); 210 verbose("cd -"); 211 exit(0); 212 } 213 if (waitpid(pid, &wstatus, 0) < 0) 214 err(1, "waitpid()"); 215 if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) 216 errx(1, "child failed"); 217 218 /* fork a child to create the tarball */ 219 if ((pid = fork()) < 0) 220 err(1, "fork()"); 221 if (pid == 0) { 222 verbose("creating tarball"); 223 execlp("tar", "tar", 224 "-c", 225 "-f", tarfilename, 226 "-C", dirname, 227 "--zstd", 228 #if 0 229 "--options", "zstd:frame-per-file", 230 #endif 231 "./" EMPTYDIRNAME "/../" NORMALFILENAME, 232 "./" SPARSEFILENAME, 233 "./" HARDLINKNAME, 234 "./" SHORTLINKNAME, 235 "./" SUBDIRNAME, 236 "./" LONGLINKNAME, 237 NULL); 238 err(1, "execlp()"); 239 } 240 if (waitpid(pid, &wstatus, 0) < 0) 241 err(1, "waitpid()"); 242 if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) 243 errx(1, "child failed"); 244 245 /* fork a child to delete everything */ 246 if ((pid = fork()) < 0) 247 err(1, "fork()"); 248 if (pid == 0) { 249 verbose("cd %s", dirname); 250 if (chdir(dirname) != 0) 251 err(1, "%s", dirname); 252 verbose("rm %s", LONGLINKNAME); 253 (void)unlink(LONGLINKNAME); 254 verbose("rm %s", SHORTLINKNAME); 255 (void)unlink(SHORTLINKNAME); 256 verbose("rm %s", HARDLINKNAME); 257 (void)unlink(HARDLINKNAME); 258 verbose("rm %s", SPARSEFILENAME); 259 (void)unlink(SPARSEFILENAME); 260 verbose("rmdir %s", EMPTYDIRNAME); 261 (void)rmdir(EMPTYDIRNAME); 262 verbose("rmdir %s", SUBDIRNAME); 263 (void)rmdir(SUBDIRNAME); 264 verbose("cd -"); 265 exit(0); 266 } 267 if (waitpid(pid, &wstatus, 0) < 0) 268 err(1, "waitpid()"); 269 if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) 270 errx(1, "child failed"); 271 verbose("rmdir %s", dirname); 272 (void)rmdir(dirname); 273 274 exit(0); 275 } 276