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 NORMALFILENAME "file"
45 #define SPARSEFILENAME "sparse_file"
46 #define HARDLINKNAME "hard_link"
47 #define SHORTLINKNAME "short_link"
48 #define LONGLINKNAME "long_link"
49
50 static bool opt_g;
51 static bool opt_v;
52
verbose(const char * fmt,...)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
mknormalfile(const char * filename,mode_t mode)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
mksparsefile(const char * filename,mode_t mode)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 *
mklonglinktarget(const char * dirname,const char * filename)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
mktar(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 normal file */
131 verbose("creating %s", NORMALFILENAME);
132 mknormalfile(NORMALFILENAME, 0644);
133
134 /* create a sparse file */
135 verbose("creating %s", SPARSEFILENAME);
136 mksparsefile(SPARSEFILENAME, 0644);
137 chflags(SPARSEFILENAME, UF_NODUMP);
138
139 /* create a hard link */
140 verbose("link %s %s", SPARSEFILENAME, HARDLINKNAME);
141 if (link(SPARSEFILENAME, HARDLINKNAME) != 0)
142 err(1, "%s", HARDLINKNAME);
143
144 /* create a symbolic link with a short target */
145 verbose("symlink %s %s", SPARSEFILENAME, SHORTLINKNAME);
146 if (symlink(SPARSEFILENAME, SHORTLINKNAME) != 0)
147 err(1, "%s", SHORTLINKNAME);
148
149 /* create a symbolic link with a long target */
150 linktarget = mklonglinktarget(SUBDIRNAME, SPARSEFILENAME);
151 verbose("symlink %s %s", linktarget, LONGLINKNAME);
152 if (symlink(linktarget, LONGLINKNAME) != 0)
153 err(1, "%s", LONGLINKNAME);
154 free(linktarget);
155 }
156
157 static void
usage(void)158 usage(void)
159 {
160
161 fprintf(stderr, "usage: %s [-gv] tarfile\n", PROGNAME);
162 exit(EXIT_FAILURE);
163 }
164
165 int
main(int argc,char * argv[])166 main(int argc, char *argv[])
167 {
168 const char *tarfilename;
169 char *dirname;
170 int opt, wstatus;
171 pid_t pid;
172
173 while ((opt = getopt(argc, argv, "gv")) != -1)
174 switch (opt) {
175 case 'g':
176 opt_g = true;
177 break;
178 case 'v':
179 opt_v = true;
180 break;
181 default:
182 usage();
183 }
184
185 argc -= optind;
186 argv += optind;
187
188 if (argc != 1)
189 usage();
190 tarfilename = *argv;
191
192 if (asprintf(&dirname, "%s%s.XXXXXXXX", _PATH_TMP, PROGNAME) < 0)
193 err(1, "asprintf()");
194 if (mkdtemp(dirname) == NULL)
195 err(1, "%s", dirname);
196 verbose("mkdir %s", dirname);
197
198 /* fork a child to create the files */
199 if ((pid = fork()) < 0)
200 err(1, "fork()");
201 if (pid == 0) {
202 verbose("cd %s", dirname);
203 if (chdir(dirname) != 0)
204 err(1, "%s", dirname);
205 verbose("umask 022");
206 umask(022);
207 mktar();
208 verbose("cd -");
209 exit(0);
210 }
211 if (waitpid(pid, &wstatus, 0) < 0)
212 err(1, "waitpid()");
213 if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0)
214 errx(1, "child failed");
215
216 /* fork a child to create the tarball */
217 if ((pid = fork()) < 0)
218 err(1, "fork()");
219 if (pid == 0) {
220 verbose("creating tarball");
221 execlp(opt_g ? "gtar" : "tar",
222 "tar",
223 "-c",
224 "-f", tarfilename,
225 "-C", dirname,
226 "--posix",
227 "--zstd",
228 #if 0
229 "--options", "zstd:frame-per-file",
230 #endif
231 "./" SUBDIRNAME "/../" 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", SUBDIRNAME);
261 (void)rmdir(SUBDIRNAME);
262 verbose("cd -");
263 exit(0);
264 }
265 if (waitpid(pid, &wstatus, 0) < 0)
266 err(1, "waitpid()");
267 if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0)
268 errx(1, "child failed");
269 verbose("rmdir %s", dirname);
270 (void)rmdir(dirname);
271
272 exit(0);
273 }
274