| 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); |
| 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 | } |
| 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: |
| 250 | if (fout) while (nnl) { putc('\n', fout); nnl--; } |
| 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 | } |
| 288 | f &= ~f_begin; |
| 289 | } |
| 290 | end:; |
| 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 | |
| 329 | done_2: |
| 330 | if (oldname) free(oldname); |
| 331 | if (newname) { |
| 332 | remove(newname); |
| 333 | free(newname); |
| 334 | } |
| 335 | done_1: |
| 336 | if (fout && fout != stdout) fclose(fout); |
| 337 | fclose(fin); |
| 338 | done_0: |
| 339 | return (rc); |
| 340 | } |
| 341 | |
| 342 | static int manysetp(unsigned f) { return (!!(f & (f - 1))); } |
| 343 | |
| 344 | int 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 (;;) { |
| 355 | if ((i = getopt(argc, argv, "h" "clmtuv" "i::")) < 0) |
| 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\ |
| 364 | -l Check for, and/or remove, trailing blank lines\n\ |
| 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; |
| 381 | case 'l': |
| 382 | flags |= F_TBLANK; |
| 383 | break; |
| 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 | } |