chiark / gitweb /
hush.in: Close extraneous file descriptors when running the command.
[misc] / space.c
CommitLineData
df33ee54
MW
1#include <ctype.h>
2#include <errno.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7#include <sys/types.h>
8#include <sys/stat.h>
9
10#include <fcntl.h>
11#include <getopt.h>
12#include <unistd.h>
13
14enum {
15 OK = 0,
16 BADNESS = 1,
17 TROUBLE = 32
18};
19
20static const char *ego = "<unset>";
21
22static const char *bkp = 0;
23
24static unsigned flags = 0;
25#define F_MIDLINETABS 1u
26#define F_INPLACE 2u
27#define F_CHECK 4u
28#define F_BOGUS 8u
29#define F_UNTABIFY 16u
30#define F_TABIFY 32u
31#define F_VERBOSE 64u
18873429 32#define F_TBLANK 128u
df33ee54
MW
33
34static void usage(FILE *fp)
18873429 35 { fprintf(fp, "Usage: %s [-clmtuv] [-i[BKP]] [FILE...]\n\n", ego); }
df33ee54
MW
36
37static char *augment(const char *name, const char *suffix)
38{
39 size_t n = strlen(name), nn = strlen(suffix);
40 char *p = malloc(n + nn + 1);
41
42 if (!p) {
43 fprintf(stderr, "%s: Out of memory!\n", ego);
44 return (0);
45 }
46 memcpy(p, name, n);
47 memcpy(p + n, suffix, nn + 1);
48 return (p);
49}
50
51static FILE *freshname(const char *name, char **newname, mode_t mode)
52{
53 char buf[16];
54 int i;
55 int fd;
56 FILE *fp;
57 char *n;
58
59 for (i = 0; i < 32767; i++) {
60 sprintf(buf, ".new%d", i);
61 if ((n = augment(name, buf)) == 0)
62 goto fail_0;
63 if ((fd = open(n, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) {
64 if (errno == EEXIST) {
65 free(n);
66 continue;
67 }
68 fprintf(stderr, "%s: Can't create new file for `%s': %s\n",
69 ego, name, strerror(errno));
70 goto fail_1;
71 }
72 goto win;
73 }
74 fprintf(stderr, "%s: Can't find new file to update `%s'\n", ego, name);
75 goto fail_1;
76
77win:
78 if (chmod(n, mode)) {
79 fprintf(stderr, "%s: Can't set permissions on `%s': %s\n",
80 ego, n, strerror(errno));
81 goto fail_2;
82 }
83 if ((fp = fdopen(fd, "w")) == 0) {
84 fprintf(stderr, "%s: fdopen on `%s' failed: %s\n",
85 ego, n, strerror(errno));
86 goto fail_2;
87 }
88 *newname = n;
89 return (fp);
90
91fail_2:
92 close(fd);
93fail_1:
94 free(n);
95fail_0:
96 return (0);
97}
98
eb5e380c 99struct buf {
df33ee54
MW
100 char *b;
101 size_t n;
102 size_t sz;
eb5e380c 103};
df33ee54
MW
104#define BUF_INIT { 0, 0, 0 }
105
eb5e380c 106static void reset(struct buf *b) { b->n = 0; }
df33ee54 107
eb5e380c 108static int put(struct buf *b, int ch)
df33ee54
MW
109{
110 size_t w;
111
112 if (b->n >= b->sz) {
113 if (!b->sz) {
114 w = 64;
115 b->b = malloc(w);
116 } else {
117 w = b->sz * 2;
118 b->b = realloc(b->b, w);
119 }
120 if (!b->b) {
121 fprintf(stderr, "%s: Not enough memory for buffer!\n", ego);
122 return (-1);
123 }
124 b->sz = w;
125 }
126 b->b[b->n++] = ch;
127 return (0);
128}
129
130#define TABSTOP(n) (((n) + 8u) & ~7u)
131
132static int space(const char *name)
133{
eb5e380c 134 static struct buf b = BUF_INIT;
df33ee54
MW
135 FILE *fin, *fout = stdout;
136 char *newname = 0, *oldname = 0;
137 int rc = TROUBLE, status = OK;
138 int last = '\n';
18873429
MW
139 unsigned nsp = 0, nwsp = 0, nnl = 0;
140 unsigned hpos = 0, ohpos = 0, nhpos = 0, nl = 1;
df33ee54
MW
141 unsigned i;
142#define f_newline 1u
143#define f_warnspacetab 2u
144#define f_tabify 4u
145#define f_warntabs 8u
146#define f_warnspaces 16u
147#define f_tab 32u
148#define f_bad 64u
149#define f_forced 128u
18873429
MW
150#define f_begin 256u
151 unsigned f = f_newline | f_begin | (flags & F_TABIFY ? f_tabify : 0);
df33ee54
MW
152 int ch;
153
154 if (strcmp(name, "-") == 0) {
155 if (flags & F_INPLACE) {
156 fprintf(stderr, "%s: Can't modify stdin in-place.\n", ego);
157 goto done_0;
158 }
159 fin = stdin;
160 } else {
161 if ((fin = fopen(name, "r")) == 0) {
162 fprintf(stderr, "%s: Failed to open file `%s': %s.\n",
163 ego, name, strerror(errno));
164 goto done_0;
165 }
166 else if (flags & F_INPLACE) {
167 struct stat st;
168 if (stat(name, &st)) {
169 fprintf(stderr, "%s: Can't stat `%s': %s.\n",
170 ego, name, strerror(errno));
171 goto done_1;
172 }
173 if ((fout = freshname(name, &newname, st.st_mode)) == 0)
174 goto done_1;
175 }
176 }
177 if (flags & F_CHECK)
178 fout = 0;
179
180 for (;;) {
181 ch = getc(fin);
182 switch (ch) {
183 case ' ':
184 nsp++; nwsp++; hpos++;
185 if (put(&b, ' ')) goto done_2;
186 break;
187 case '\t':
188 if (flags & F_UNTABIFY) {
f4ed186a 189 if ((flags & F_VERBOSE) && !(f & f_warntabs)) {
df33ee54
MW
190 fprintf(stderr, "%s:%u: found tab\n", name, nl);
191 f |= f_warntabs;
df33ee54 192 }
f4ed186a 193 status = BADNESS;
df33ee54
MW
194 } else if (((flags & F_MIDLINETABS) || (f & f_newline)) && nsp) {
195 if ((flags & F_VERBOSE) && !(f & f_warnspacetab)) {
196 fprintf(stderr, "%s:%u: space followed by tab\n", name, nl);
197 f |= f_warnspacetab;
df33ee54 198 }
f4ed186a 199 status = BADNESS;
df33ee54
MW
200 f |= f_tabify | f_forced;
201 }
202 f |= f_tab;
203 nsp = 0; nwsp++; hpos = TABSTOP(hpos);
204 if (put(&b, '\t')) goto done_2;
205 break;
206 case EOF:
18873429
MW
207 if (!(f & f_begin)) {
208 if (!(f & f_newline)) {
209 if (flags & F_VERBOSE)
210 fprintf(stderr, "%s:%u: file ends in mid-line\n", name, nl);
211 status = BADNESS;
212 nnl = 1;
213 } else if (nnl > 1) {
214 if (flags & F_TBLANK) {
215 if (flags & F_VERBOSE) {
216 fprintf(stderr, "%s:%u: file has trailing blank lines\n",
217 name, nl - nnl + 1);
218 }
219 status = BADNESS;
220 nnl = 1;
221 }
222 }
223 if (fout) while (nnl--) putc('\n', fout);
df33ee54
MW
224 }
225 goto end;
226 case '\n':
227 case '\v':
f4ed186a
MW
228 if (nwsp) {
229 if (flags & F_VERBOSE)
230 fprintf(stderr, "%s:%u: trailing whitespace\n", name, nl);
df33ee54
MW
231 status = BADNESS;
232 }
df33ee54
MW
233 reset(&b);
234 nsp = nwsp = hpos = ohpos = 0; nl++;
18873429
MW
235 if (ch == '\n')
236 nnl++;
237 else if (fout) {
238 while (nnl) { putc('\n', fout); nnl--; }
239 putc(ch, fout);
240 }
df33ee54
MW
241 f |= f_newline;
242 f &= ~(f_tab | f_warnspacetab | f_warntabs | f_warnspaces);
243 if (flags & F_TABIFY)
244 f |= f_tabify;
245 else
246 f &= ~f_tabify;
247 last = '\n';
248 break;
249 default:
18873429 250 if (fout) while (nnl) { putc('\n', fout); nnl--; }
df33ee54
MW
251 if (nwsp) {
252 if (flags & F_UNTABIFY) {
253 if (fout) for (; ohpos < hpos; ohpos++) putc(' ', fout);
254 } else if ((f & f_tabify) &&
255 ((hpos - ohpos >= (last == '.' || last == ':' ?
256 3 : 2)) ||
257 (f & (f_tab | f_newline)))) {
258 i = 0;
259 for (;;) {
260 nhpos = TABSTOP(ohpos);
261 if (nhpos > hpos) break;
262 if (fout) putc('\t', fout);
263 if ((flags & F_VERBOSE) && (flags & F_TABIFY) &&
264 i < b.n && b.b[i] != '\t' &&
265 !(f & (f_warnspaces | f_forced))) {
266 fprintf(stderr, "%s:%u: spaces could be turned into tabs\n",
267 name, nl);
268 f |= f_warnspaces;
269 }
270 ohpos = nhpos;
271 i++;
272 }
273 if (fout)
274 for (; ohpos < hpos; ohpos++) putc(' ', fout);
275 } else if (fout)
276 for (i = 0; i < b.n; i++) putc(b.b[i], fout);
277 }
278 reset(&b);
279 f &= ~(f_newline | f_tab | f_forced);
280 if (!(flags & F_TABIFY) || !(flags & F_MIDLINETABS)) f &= ~f_tabify;
281 nwsp = nsp = 0;
282 hpos++; ohpos = hpos;
283 if (fout) putc(ch, fout);
284 if (ch != '"' && ch != '\'')
285 last = ch;
286 break;
287 }
18873429 288 f &= ~f_begin;
df33ee54
MW
289 }
290end:;
291
292 if (ferror(fin)) {
293 fprintf(stderr, "%s: Error reading `%s': %s\n",
294 ego, name, strerror(errno));
295 goto done_2;
296 }
297
298 if (fout) {
299 if (fflush(fout) || ferror(fout)) f |= f_bad;
300 if (fout != stdout && fclose(fout)) f |= f_bad;
301 fout = 0;
302 if (f & f_bad) {
303 fprintf(stderr, "%s: Error writing `%s': %s\n",
304 ego, newname, strerror(errno));
305 goto done_2;
306 }
307 }
308
309 if (flags & F_INPLACE) {
310 if (bkp) {
311 if ((oldname = augment(name, bkp)) == 0)
312 goto done_2;
313 if (rename(name, oldname)) {
314 fprintf(stderr, "%s: Failed to back up `%s' as `%s': %s\n",
315 ego, name, oldname, strerror(errno));
316 goto done_2;
317 }
318 }
319 if (rename(newname, name)) {
320 if (oldname) rename(oldname, name);
321 fprintf(stderr, "%s: Failed to install `%s' as `%s': %s\n",
322 ego, newname, name, strerror(errno));
323 goto done_2;
324 }
325 }
326
327 rc = status;
328
329done_2:
330 if (oldname) free(oldname);
331 if (newname) {
332 remove(newname);
333 free(newname);
334 }
335done_1:
336 if (fout && fout != stdout) fclose(fout);
337 fclose(fin);
338done_0:
339 return (rc);
340}
341
342static int manysetp(unsigned f) { return (!!(f & (f - 1))); }
343
344int main(int argc, char *argv[])
345{
346 int i;
347 int rc = OK, st;
348
349 if ((ego = strrchr(argv[0], '/')) == 0)
350 ego = argv[0];
351 else
352 ego++;
353
354 for (;;) {
18873429 355 if ((i = getopt(argc, argv, "h" "clmtuv" "i::")) < 0)
df33ee54
MW
356 break;
357 switch (i) {
358 case 'h':
359 printf("%s -- remove extraneous spaces from files\n\n", ego);
360 usage(stdout);
361 fputs("Options:\n\
362 -h Print this help text\n\
363 -c Check files for badness, but don't produce other output\n\
18873429 364 -l Check for, and/or remove, trailing blank lines\n\
df33ee54
MW
365 -m Fix spaces followed by tabs in mid-line\n\
366 -t Tabify file completely\n\
367 -u Untabify file completely\n\
368 -i[BKP] Modify files in place; leave FILEBKP as copy of old FILE\n\
369", stdout);
370 exit(0);
371 case 'i':
372 bkp = optarg;
373 flags |= F_INPLACE;
374 break;
375 case 'm':
376 flags |= F_MIDLINETABS;
377 break;
378 case 'c':
379 flags |= F_CHECK;
380 break;
18873429
MW
381 case 'l':
382 flags |= F_TBLANK;
383 break;
df33ee54
MW
384 case 't':
385 flags |= F_TABIFY;
386 break;
387 case 'u':
388 flags |= F_UNTABIFY;
389 break;
390 case 'v':
391 flags |= F_VERBOSE;
392 break;
393 default:
394 flags |= F_BOGUS;
395 break;
396 }
397 }
398 if (flags & F_BOGUS) {
399 usage(stderr);
400 exit(TROUBLE);
401 }
402 if (manysetp(flags & (F_CHECK | F_INPLACE))) {
403 fprintf(stderr, "%s: Options -c and -i are mutually exclusive.\n", ego);
404 exit(TROUBLE);
405 }
406 if (manysetp(flags & (F_TABIFY | F_UNTABIFY))) {
407 fprintf(stderr, "%s: Options -t and -u are mutually exclusive.\n", ego);
408 exit(TROUBLE);
409 }
410
411 if (optind == argc) {
412 if (isatty(0)) {
413 fprintf(stderr, "%s: No options given and stdin is a terminal.\n",
414 ego);
415 exit(TROUBLE);
416 }
417 rc = space("-");
418 } else for (i = optind; i < argc; i++) {
419 st = space(argv[i]);
420 if (st > rc) rc = st;
421 }
422 if (rc == BADNESS && !(flags & F_CHECK))
423 rc = OK;
424 return (rc);
425}