xref: /freebsd/usr.bin/truncate/truncate.c (revision cab6a39d7b343596a5823e65c0f7b426551ec22d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2000 Sheldon Hearn <sheldonh@FreeBSD.org>.
5  * All rights reserved.
6  *
7  * Copyright (c) 2021 The FreeBSD Foundation
8  *
9  * Portions of this software were developed by Ka Ho Ng <khng@FreeBSD.org>
10  * under sponsorship from the FreeBSD Foundation.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  */
34 
35 static const char rcsid[] =
36     "$FreeBSD$";
37 
38 #include <sys/stat.h>
39 
40 #include <ctype.h>
41 #include <err.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 
48 #include <libutil.h>
49 
50 static void	usage(void);
51 
52 int
53 main(int argc, char **argv)
54 {
55 	struct stat sb;
56 	mode_t omode;
57 	off_t oflow, rsize, sz, tsize, round, off, len;
58 	uint64_t usz;
59 	int ch, error, fd, oflags, r;
60 	int do_dealloc;
61 	int do_truncate;
62 	int no_create;
63 	int do_relative;
64 	int do_round;
65 	int do_refer;
66 	int got_size;
67 	char *fname, *rname;
68 	struct spacectl_range sr;
69 
70 	fd = -1;
71 	rsize = tsize = sz = off = 0;
72 	len = -1;
73 	do_dealloc = no_create = do_relative = do_round = do_refer =
74 	    got_size = 0;
75 	do_truncate = 1;
76 	error = r = 0;
77 	rname = NULL;
78 	while ((ch = getopt(argc, argv, "cdr:s:o:l:")) != -1)
79 		switch (ch) {
80 		case 'c':
81 			no_create = 1;
82 			break;
83 		case 'd':
84 			do_dealloc = 1;
85 			do_truncate = 0;
86 			break;
87 		case 'r':
88 			do_refer = 1;
89 			rname = optarg;
90 			break;
91 		case 's':
92 			if (*optarg == '+' || *optarg == '-') {
93 				do_relative = 1;
94 			} else if (*optarg == '%' || *optarg == '/') {
95 				do_round = 1;
96 			}
97 			if (expand_number(do_relative || do_round ?
98 			    optarg + 1 : optarg,
99 			    &usz) == -1 || (off_t)usz < 0)
100 				errx(EXIT_FAILURE,
101 				    "invalid size argument `%s'", optarg);
102 
103 			sz = (*optarg == '-' || *optarg == '/') ?
104 				-(off_t)usz : (off_t)usz;
105 			got_size = 1;
106 			break;
107 		case 'o':
108 			if (expand_number(optarg, &usz) == -1 ||
109 			    (off_t)usz < 0)
110 				errx(EXIT_FAILURE,
111 				    "invalid offset argument `%s'", optarg);
112 
113 			off = usz;
114 			break;
115 		case 'l':
116 			if (expand_number(optarg, &usz) == -1 ||
117 			    (off_t)usz <= 0)
118 				errx(EXIT_FAILURE,
119 				    "invalid length argument `%s'", optarg);
120 
121 			len = usz;
122 			break;
123 		default:
124 			usage();
125 			/* NOTREACHED */
126 		}
127 
128 	argv += optind;
129 	argc -= optind;
130 
131 	/*
132 	 * Exactly one of do_refer, got_size or do_dealloc must be specified.
133 	 * Since do_relative implies got_size, do_relative, do_refer and
134 	 * do_dealloc are also mutually exclusive.  If do_dealloc is specified,
135 	 * the length argument must be set.  See usage() for allowed
136 	 * invocations.
137 	 */
138 	if (argc < 1 || do_refer + got_size + do_dealloc != 1 ||
139 	    (do_dealloc == 1 && len == -1))
140 		usage();
141 	if (do_refer == 1) {
142 		if (stat(rname, &sb) == -1)
143 			err(EXIT_FAILURE, "%s", rname);
144 		tsize = sb.st_size;
145 	} else if (do_relative == 1 || do_round == 1)
146 		rsize = sz;
147 	else if (do_dealloc == 0)
148 		tsize = sz;
149 
150 	if (no_create)
151 		oflags = O_WRONLY;
152 	else
153 		oflags = O_WRONLY | O_CREAT;
154 	omode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
155 
156 	while ((fname = *argv++) != NULL) {
157 		if (fd != -1)
158 			close(fd);
159 		if ((fd = open(fname, oflags, omode)) == -1) {
160 			if (errno != ENOENT) {
161 				warn("%s", fname);
162 				error++;
163 			}
164 			continue;
165 		}
166 		if (do_relative == 1) {
167 			if (fstat(fd, &sb) == -1) {
168 				warn("%s", fname);
169 				error++;
170 				continue;
171 			}
172 			oflow = sb.st_size + rsize;
173 			if (oflow < (sb.st_size + rsize)) {
174 				errno = EFBIG;
175 				warn("%s", fname);
176 				error++;
177 				continue;
178 			}
179 			tsize = oflow;
180 		}
181 		if (do_round == 1) {
182 			if (fstat(fd, &sb) == -1) {
183 				warn("%s", fname);
184 				error++;
185 				continue;
186 			}
187 			sz = rsize;
188 			if (sz < 0)
189 				sz = -sz;
190 			if (sb.st_size % sz) {
191 				round = sb.st_size / sz;
192 				if (round != sz && rsize < 0)
193 					round--;
194 				else if (rsize > 0)
195 					round++;
196 				tsize = (round < 0 ? 0 : round) * sz;
197 			} else
198 				tsize = sb.st_size;
199 		}
200 		if (tsize < 0)
201 			tsize = 0;
202 
203 		if (do_dealloc == 1) {
204 			sr.r_offset = off;
205 			sr.r_len = len;
206 			r = fspacectl(fd, SPACECTL_DEALLOC, &sr, 0, &sr);
207 		}
208 		if (do_truncate == 1)
209 			r = ftruncate(fd, tsize);
210 		if (r == -1) {
211 			warn("%s", fname);
212 			error++;
213 		}
214 	}
215 	if (fd != -1)
216 		close(fd);
217 
218 	return (error ? EXIT_FAILURE : EXIT_SUCCESS);
219 }
220 
221 static void
222 usage(void)
223 {
224 	fprintf(stderr, "%s\n%s\n%s\n",
225 	    "usage: truncate [-c] -s [+|-|%|/]size[K|k|M|m|G|g|T|t] file ...",
226 	    "       truncate [-c] -r rfile file ...",
227 	    "       truncate [-c] -d [-o offset[K|k|M|m|G|g|T|t]] -l length[K|k|M|m|G|g|T|t] file ...");
228 	exit(EXIT_FAILURE);
229 }
230