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