| 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_CHECK) && !(f & f_warntabs)) { |
| 187 | fprintf(stderr, "%s:%u: found tab\n", name, nl); |
| 188 | f |= f_warntabs; |
| 189 | status = BADNESS; |
| 190 | } |
| 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 | status = BADNESS; |
| 196 | } |
| 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 && (flags & F_VERBOSE)) { |
| 214 | fprintf(stderr, "%s:%u: trailing whitespace\n", name, nl); |
| 215 | status = BADNESS; |
| 216 | } |
| 217 | if (fout) putc('\n', fout); |
| 218 | reset(&b); |
| 219 | nsp = nwsp = hpos = ohpos = 0; nl++; |
| 220 | f |= f_newline; |
| 221 | f &= ~(f_tab | f_warnspacetab | f_warntabs | f_warnspaces); |
| 222 | if (flags & F_TABIFY) |
| 223 | f |= f_tabify; |
| 224 | else |
| 225 | f &= ~f_tabify; |
| 226 | last = '\n'; |
| 227 | break; |
| 228 | default: |
| 229 | if (nwsp) { |
| 230 | if (flags & F_UNTABIFY) { |
| 231 | if (fout) for (; ohpos < hpos; ohpos++) putc(' ', fout); |
| 232 | } else if ((f & f_tabify) && |
| 233 | ((hpos - ohpos >= (last == '.' || last == ':' ? |
| 234 | 3 : 2)) || |
| 235 | (f & (f_tab | f_newline)))) { |
| 236 | i = 0; |
| 237 | for (;;) { |
| 238 | nhpos = TABSTOP(ohpos); |
| 239 | if (nhpos > hpos) break; |
| 240 | if (fout) putc('\t', fout); |
| 241 | if ((flags & F_VERBOSE) && (flags & F_TABIFY) && |
| 242 | i < b.n && b.b[i] != '\t' && |
| 243 | !(f & (f_warnspaces | f_forced))) { |
| 244 | fprintf(stderr, "%s:%u: spaces could be turned into tabs\n", |
| 245 | name, nl); |
| 246 | f |= f_warnspaces; |
| 247 | } |
| 248 | ohpos = nhpos; |
| 249 | i++; |
| 250 | } |
| 251 | if (fout) |
| 252 | for (; ohpos < hpos; ohpos++) putc(' ', fout); |
| 253 | } else if (fout) |
| 254 | for (i = 0; i < b.n; i++) putc(b.b[i], fout); |
| 255 | } |
| 256 | reset(&b); |
| 257 | f &= ~(f_newline | f_tab | f_forced); |
| 258 | if (!(flags & F_TABIFY) || !(flags & F_MIDLINETABS)) f &= ~f_tabify; |
| 259 | nwsp = nsp = 0; |
| 260 | hpos++; ohpos = hpos; |
| 261 | if (fout) putc(ch, fout); |
| 262 | if (ch != '"' && ch != '\'') |
| 263 | last = ch; |
| 264 | break; |
| 265 | } |
| 266 | } |
| 267 | end:; |
| 268 | |
| 269 | if (ferror(fin)) { |
| 270 | fprintf(stderr, "%s: Error reading `%s': %s\n", |
| 271 | ego, name, strerror(errno)); |
| 272 | goto done_2; |
| 273 | } |
| 274 | |
| 275 | if (fout) { |
| 276 | if (fflush(fout) || ferror(fout)) f |= f_bad; |
| 277 | if (fout != stdout && fclose(fout)) f |= f_bad; |
| 278 | fout = 0; |
| 279 | if (f & f_bad) { |
| 280 | fprintf(stderr, "%s: Error writing `%s': %s\n", |
| 281 | ego, newname, strerror(errno)); |
| 282 | goto done_2; |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | if (flags & F_INPLACE) { |
| 287 | if (bkp) { |
| 288 | if ((oldname = augment(name, bkp)) == 0) |
| 289 | goto done_2; |
| 290 | if (rename(name, oldname)) { |
| 291 | fprintf(stderr, "%s: Failed to back up `%s' as `%s': %s\n", |
| 292 | ego, name, oldname, strerror(errno)); |
| 293 | goto done_2; |
| 294 | } |
| 295 | } |
| 296 | if (rename(newname, name)) { |
| 297 | if (oldname) rename(oldname, name); |
| 298 | fprintf(stderr, "%s: Failed to install `%s' as `%s': %s\n", |
| 299 | ego, newname, name, strerror(errno)); |
| 300 | goto done_2; |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | rc = status; |
| 305 | |
| 306 | done_2: |
| 307 | if (oldname) free(oldname); |
| 308 | if (newname) { |
| 309 | remove(newname); |
| 310 | free(newname); |
| 311 | } |
| 312 | done_1: |
| 313 | if (fout && fout != stdout) fclose(fout); |
| 314 | fclose(fin); |
| 315 | done_0: |
| 316 | return (rc); |
| 317 | } |
| 318 | |
| 319 | static int manysetp(unsigned f) { return (!!(f & (f - 1))); } |
| 320 | |
| 321 | int main(int argc, char *argv[]) |
| 322 | { |
| 323 | int i; |
| 324 | int rc = OK, st; |
| 325 | |
| 326 | if ((ego = strrchr(argv[0], '/')) == 0) |
| 327 | ego = argv[0]; |
| 328 | else |
| 329 | ego++; |
| 330 | |
| 331 | for (;;) { |
| 332 | if ((i = getopt(argc, argv, "h" "cmtuv" "i::")) < 0) |
| 333 | break; |
| 334 | switch (i) { |
| 335 | case 'h': |
| 336 | printf("%s -- remove extraneous spaces from files\n\n", ego); |
| 337 | usage(stdout); |
| 338 | fputs("Options:\n\ |
| 339 | -h Print this help text\n\ |
| 340 | -c Check files for badness, but don't produce other output\n\ |
| 341 | -m Fix spaces followed by tabs in mid-line\n\ |
| 342 | -t Tabify file completely\n\ |
| 343 | -u Untabify file completely\n\ |
| 344 | -i[BKP] Modify files in place; leave FILEBKP as copy of old FILE\n\ |
| 345 | ", stdout); |
| 346 | exit(0); |
| 347 | case 'i': |
| 348 | bkp = optarg; |
| 349 | flags |= F_INPLACE; |
| 350 | break; |
| 351 | case 'm': |
| 352 | flags |= F_MIDLINETABS; |
| 353 | break; |
| 354 | case 'c': |
| 355 | flags |= F_CHECK; |
| 356 | break; |
| 357 | case 't': |
| 358 | flags |= F_TABIFY; |
| 359 | break; |
| 360 | case 'u': |
| 361 | flags |= F_UNTABIFY; |
| 362 | break; |
| 363 | case 'v': |
| 364 | flags |= F_VERBOSE; |
| 365 | break; |
| 366 | default: |
| 367 | flags |= F_BOGUS; |
| 368 | break; |
| 369 | } |
| 370 | } |
| 371 | if (flags & F_BOGUS) { |
| 372 | usage(stderr); |
| 373 | exit(TROUBLE); |
| 374 | } |
| 375 | if (manysetp(flags & (F_CHECK | F_INPLACE))) { |
| 376 | fprintf(stderr, "%s: Options -c and -i are mutually exclusive.\n", ego); |
| 377 | exit(TROUBLE); |
| 378 | } |
| 379 | if (manysetp(flags & (F_TABIFY | F_UNTABIFY))) { |
| 380 | fprintf(stderr, "%s: Options -t and -u are mutually exclusive.\n", ego); |
| 381 | exit(TROUBLE); |
| 382 | } |
| 383 | |
| 384 | if (optind == argc) { |
| 385 | if (isatty(0)) { |
| 386 | fprintf(stderr, "%s: No options given and stdin is a terminal.\n", |
| 387 | ego); |
| 388 | exit(TROUBLE); |
| 389 | } |
| 390 | rc = space("-"); |
| 391 | } else for (i = optind; i < argc; i++) { |
| 392 | st = space(argv[i]); |
| 393 | if (st > rc) rc = st; |
| 394 | } |
| 395 | if (rc == BADNESS && !(flags & F_CHECK)) |
| 396 | rc = OK; |
| 397 | return (rc); |
| 398 | } |