chiark / gitweb /
debian/control: Include `prlimit' and `x86-model' in the metapackage.
[misc] / space.c
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
14 enum {
15   OK = 0,
16   BADNESS = 1,
17   TROUBLE = 32
18 };
19
20 static const char *ego = "<unset>";
21
22 static const char *bkp = 0;
23
24 static 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
32 #define F_TBLANK 128u
33
34 static void usage(FILE *fp)
35   { fprintf(fp, "Usage: %s [-clmtuv] [-i[BKP]] [FILE...]\n\n", ego); }
36
37 static 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
51 static 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
77 win:
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
91 fail_2:
92   close(fd);
93 fail_1:
94   free(n);
95 fail_0:
96   return (0);
97 }
98
99 struct buf {
100   char *b;
101   size_t n;
102   size_t sz;
103 };
104 #define BUF_INIT { 0, 0, 0 }
105
106 static void reset(struct buf *b) { b->n = 0; }
107
108 static int put(struct buf *b, int ch)
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
132 static int space(const char *name)
133 {
134   static struct buf b = BUF_INIT;
135   FILE *fin, *fout = stdout;
136   char *newname = 0, *oldname = 0;
137   int rc = TROUBLE, status = OK;
138   int last = '\n';
139   unsigned nsp = 0, nwsp = 0, nnl = 0;
140   unsigned hpos = 0, ohpos = 0, nhpos = 0, nl = 1;
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
150 #define f_begin 256u
151   unsigned f = f_newline | f_begin | (flags & F_TABIFY ? f_tabify : 0);
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) {
189           if ((flags & F_VERBOSE) && !(f & f_warntabs)) {
190             fprintf(stderr, "%s:%u: found tab\n", name, nl);
191             f |= f_warntabs;
192           }
193           status = BADNESS;
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;
198           }
199           status = BADNESS;
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:
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); else nnl = 0;
224         }
225         goto end;
226       case '\n':
227       case '\v':
228         if (nwsp) {
229           if (flags & F_VERBOSE)
230             fprintf(stderr, "%s:%u: trailing whitespace\n", name, nl);
231           status = BADNESS;
232         }
233         reset(&b);
234         nsp = nwsp = hpos = ohpos = 0; nl++;
235         if (ch == '\n')
236           nnl++;
237         else if (fout) {
238           while (nnl) { putc('\n', fout); nnl--; }
239           putc(ch, fout);
240         } else
241           nnl = 0;
242         f |= f_newline;
243         f &= ~(f_tab | f_warnspacetab | f_warntabs | f_warnspaces);
244         if (flags & F_TABIFY)
245           f |= f_tabify;
246         else
247           f &= ~f_tabify;
248         last = '\n';
249         break;
250       default:
251         if (fout) while (nnl) { putc('\n', fout); nnl--; } else nnl = 0;
252         if (nwsp) {
253           if (flags & F_UNTABIFY) {
254             if (fout) for (; ohpos < hpos; ohpos++) putc(' ', fout);
255           } else if ((f & f_tabify) &&
256                      ((hpos - ohpos >= (last == '.' || last == ':' ?
257                                           3 : 2)) ||
258                       (f & (f_tab | f_newline)))) {
259             i = 0;
260             for (;;) {
261               nhpos = TABSTOP(ohpos);
262               if (nhpos > hpos) break;
263               if (fout) putc('\t', fout);
264               if ((flags & F_VERBOSE) && (flags & F_TABIFY) &&
265                   i < b.n && b.b[i] != '\t' &&
266                   !(f & (f_warnspaces | f_forced))) {
267                 fprintf(stderr, "%s:%u: spaces could be turned into tabs\n",
268                         name, nl);
269                 f |= f_warnspaces;
270               }
271               ohpos = nhpos;
272               i++;
273             }
274             if (fout)
275               for (; ohpos < hpos; ohpos++) putc(' ', fout);
276           } else if (fout)
277             for (i = 0; i < b.n; i++) putc(b.b[i], fout);
278         }
279         reset(&b);
280         f &= ~(f_newline | f_tab | f_forced);
281         if (!(flags & F_TABIFY) || !(flags & F_MIDLINETABS)) f &= ~f_tabify;
282         nwsp = nsp = 0;
283         hpos++; ohpos = hpos;
284         if (fout) putc(ch, fout);
285         if (ch != '"' && ch != '\'')
286           last = ch;
287         break;
288     }
289     f &= ~f_begin;
290   }
291 end:;
292
293   if (ferror(fin)) {
294     fprintf(stderr, "%s: Error reading `%s': %s\n",
295             ego, name, strerror(errno));
296     goto done_2;
297   }
298
299   if (fout) {
300     if (fflush(fout) || ferror(fout)) f |= f_bad;
301     if (fout != stdout && fclose(fout)) f |= f_bad;
302     fout = 0;
303     if (f & f_bad) {
304       fprintf(stderr, "%s: Error writing `%s': %s\n",
305               ego, newname, strerror(errno));
306       goto done_2;
307     }
308   }
309
310   if (flags & F_INPLACE) {
311     if (bkp) {
312       if ((oldname = augment(name, bkp)) == 0)
313         goto done_2;
314       if (rename(name, oldname)) {
315         fprintf(stderr, "%s: Failed to back up `%s' as `%s': %s\n",
316                 ego, name, oldname, strerror(errno));
317         goto done_2;
318       }
319     }
320     if (rename(newname, name)) {
321       if (oldname) rename(oldname, name);
322       fprintf(stderr, "%s: Failed to install `%s' as `%s': %s\n",
323               ego, newname, name, strerror(errno));
324       goto done_2;
325     }
326   }
327
328   rc = status;
329
330 done_2:
331   if (oldname) free(oldname);
332   if (newname) {
333     remove(newname);
334     free(newname);
335   }
336 done_1:
337   if (fout && fout != stdout) fclose(fout);
338   fclose(fin);
339 done_0:
340   return (rc);
341 }
342
343 static int manysetp(unsigned f) { return (!!(f & (f - 1))); }
344
345 int main(int argc, char *argv[])
346 {
347   int i;
348   int rc = OK, st;
349
350   if ((ego = strrchr(argv[0], '/')) == 0)
351     ego = argv[0];
352   else
353     ego++;
354
355   for (;;) {
356     if ((i = getopt(argc, argv, "h" "clmtuv" "i::")) < 0)
357       break;
358     switch (i) {
359       case 'h':
360         printf("%s -- remove extraneous spaces from files\n\n", ego);
361         usage(stdout);
362         fputs("Options:\n\
363   -h            Print this help text\n\
364   -c            Check files for badness, but don't produce other output\n\
365   -l            Check for, and/or remove, trailing blank lines\n\
366   -m            Fix spaces followed by tabs in mid-line\n\
367   -t            Tabify file completely\n\
368   -u            Untabify file completely\n\
369   -v            Report verbose messages\n\
370   -i[BKP]       Modify files in place; leave FILEBKP as copy of old FILE\n\
371 ", stdout);
372         exit(0);
373       case 'i':
374         bkp = optarg;
375         flags |= F_INPLACE;
376         break;
377       case 'm':
378         flags |= F_MIDLINETABS;
379         break;
380       case 'c':
381         flags |= F_CHECK;
382         break;
383       case 'l':
384         flags |= F_TBLANK;
385         break;
386       case 't':
387         flags |= F_TABIFY;
388         break;
389       case 'u':
390         flags |= F_UNTABIFY;
391         break;
392       case 'v':
393         flags |= F_VERBOSE;
394         break;
395       default:
396         flags |= F_BOGUS;
397         break;
398     }
399   }
400   if (flags & F_BOGUS) {
401     usage(stderr);
402     exit(TROUBLE);
403   }
404   if (manysetp(flags & (F_CHECK | F_INPLACE))) {
405     fprintf(stderr, "%s: Options -c and -i are mutually exclusive.\n", ego);
406     exit(TROUBLE);
407   }
408   if (manysetp(flags & (F_TABIFY | F_UNTABIFY))) {
409     fprintf(stderr, "%s: Options -t and -u are mutually exclusive.\n", ego);
410     exit(TROUBLE);
411   }
412
413   if (optind == argc) {
414     if (isatty(0)) {
415       fprintf(stderr, "%s: No options given and stdin is a terminal.\n",
416               ego);
417       exit(TROUBLE);
418     }
419     rc = space("-");
420   } else for (i = optind; i < argc; i++) {
421     st = space(argv[i]);
422     if (st > rc) rc = st;
423   }
424   if (rc == BADNESS && !(flags & F_CHECK))
425     rc = OK;
426   return (rc);
427 }