Sync usage with man page.
[netbsd-mini2440.git] / usr.bin / error / touch.c
blob7aeeae54ee419723db2e158ed03e3f5b3c98c09f
1 /* $NetBSD: touch.c,v 1.21 2009/08/13 05:53:58 dholland Exp $ */
3 /*
4 * Copyright (c) 1980, 1993
5 * The Regents of the University of California. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)touch.c 8.1 (Berkeley) 6/6/93";
36 #endif
37 __RCSID("$NetBSD: touch.c,v 1.21 2009/08/13 05:53:58 dholland Exp $");
38 #endif /* not lint */
40 #include <sys/param.h>
41 #include <sys/stat.h>
42 #include <ctype.h>
43 #include <signal.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include <util.h>
49 #include <stdarg.h>
50 #include "error.h"
51 #include "pathnames.h"
54 * Iterate through errors
56 #define EITERATE(p, fv, i) for (p = fv[i]; p < fv[i+1]; p++)
57 #define ECITERATE(ei, p, lb, errs, nerrs) \
58 for (ei = lb; p = errs[ei],ei < nerrs; ei++)
60 #define FILEITERATE(fi, lb, num) \
61 for (fi = lb; fi <= num; fi++)
63 static int touchstatus = Q_YES;
66 * codes for probethisfile to return
68 #define F_NOTEXIST 1
69 #define F_NOTREAD 2
70 #define F_NOTWRITE 3
71 #define F_TOUCHIT 4
73 static int countfiles(Eptr *);
74 static int nopertain(Eptr **);
75 static void hackfile(const char *, Eptr **, int, int);
76 static boolean preview(const char *, int, Eptr **, int);
77 static int settotouch(const char *);
78 static void diverterrors(const char *, int, Eptr **, int, boolean, int);
79 static int oktotouch(const char *);
80 static void execvarg(int, int *, char ***);
81 static boolean edit(const char *);
82 static void insert(int);
83 static void text(Eptr, boolean);
84 static boolean writetouched(int);
85 static int mustoverwrite(FILE *, FILE *);
86 static int mustwrite(const char *, unsigned, FILE *);
87 static void errorprint(FILE *, Eptr, boolean);
88 static int probethisfile(const char *);
90 void
91 findfiles(int my_nerrors, Eptr *my_errors, int *r_nfiles, Eptr ***r_files)
93 int my_nfiles;
94 Eptr **my_files;
96 const char *name;
97 int ei;
98 int fi;
99 Eptr errorp;
101 my_nfiles = countfiles(my_errors);
103 my_files = Calloc(my_nfiles + 3, sizeof (Eptr*));
104 touchedfiles = Calloc(my_nfiles+3, sizeof(boolean));
106 * Now, partition off the error messages
107 * into those that are synchronization, discarded or
108 * not specific to any file, and those that were
109 * nulled or true errors.
111 my_files[0] = &my_errors[0];
112 ECITERATE(ei, errorp, 0, my_errors, my_nerrors) {
113 if ( ! (NOTSORTABLE(errorp->error_e_class)))
114 break;
117 * Now, and partition off all error messages
118 * for a given file.
120 my_files[1] = &my_errors[ei];
121 touchedfiles[0] = touchedfiles[1] = false;
122 name = "\1";
123 fi = 1;
124 ECITERATE(ei, errorp, ei, my_errors, my_nerrors) {
125 if (errorp->error_e_class == C_NULLED
126 || errorp->error_e_class == C_TRUE) {
127 if (strcmp(errorp->error_text[0], name) != 0) {
128 name = errorp->error_text[0];
129 touchedfiles[fi] = false;
130 my_files[fi] = &my_errors[ei];
131 fi++;
135 my_files[fi] = &my_errors[my_nerrors];
136 *r_nfiles = my_nfiles;
137 *r_files = my_files;
140 static int
141 countfiles(Eptr *errors)
143 const char *name;
144 int ei;
145 Eptr errorp;
146 int my_nfiles;
148 my_nfiles = 0;
149 name = "\1";
150 ECITERATE(ei, errorp, 0, errors, nerrors) {
151 if (SORTABLE(errorp->error_e_class)) {
152 if (strcmp(errorp->error_text[0],name) != 0) {
153 my_nfiles++;
154 name = errorp->error_text[0];
158 return (my_nfiles);
161 const char *class_table[] = {
162 /*C_UNKNOWN 0 */ "Unknown",
163 /*C_IGNORE 1 */ "ignore",
164 /*C_SYNC 2 */ "synchronization",
165 /*C_DISCARD 3 */ "discarded",
166 /*C_NONSPEC 4 */ "non specific",
167 /*C_THISFILE 5 */ "specific to this file",
168 /*C_NULLED 6 */ "nulled",
169 /*C_TRUE 7 */ "true",
170 /*C_DUPL 8 */ "duplicated"
173 int class_count[C_LAST - C_FIRST] = {0};
175 void
176 filenames(int my_nfiles, Eptr **my_files)
178 int fi;
179 const char *sep = " ";
180 int someerrors;
183 * first, simply dump out errors that
184 * don't pertain to any file
186 someerrors = nopertain(my_files);
188 if (my_nfiles) {
189 someerrors++;
190 if (terse)
191 fprintf(stdout, "%d file%s", my_nfiles, plural(my_nfiles));
192 else
193 fprintf(stdout, "%d file%s contain%s errors",
194 my_nfiles, plural(my_nfiles), verbform(my_nfiles));
195 if (!terse) {
196 FILEITERATE(fi, 1, my_nfiles) {
197 fprintf(stdout, "%s\"%s\" (%d)",
198 sep, (*my_files[fi])->error_text[0],
199 (int)(my_files[fi+1] - my_files[fi]));
200 sep = ", ";
203 fprintf(stdout, "\n");
205 if (!someerrors)
206 fprintf(stdout, "No errors.\n");
210 * Dump out errors that don't pertain to any file
212 static int
213 nopertain(Eptr **my_files)
215 int type;
216 int someerrors = 0;
217 Eptr *erpp;
218 Eptr errorp;
220 if (my_files[1] - my_files[0] <= 0)
221 return (0);
222 for (type = C_UNKNOWN; NOTSORTABLE(type); type++) {
223 if (class_count[type] <= 0)
224 continue;
225 if (type > C_SYNC)
226 someerrors++;
227 if (terse) {
228 fprintf(stdout, "\t%d %s errors NOT PRINTED\n",
229 class_count[type], class_table[type]);
230 } else {
231 fprintf(stdout, "\n\t%d %s errors follow\n",
232 class_count[type], class_table[type]);
233 EITERATE(erpp, my_files, 0) {
234 errorp = *erpp;
235 if (errorp->error_e_class == type) {
236 errorprint(stdout, errorp, true);
241 return (someerrors);
244 bool
245 touchfiles(int my_nfiles, Eptr **my_files, int *r_edargc, char ***r_edargv)
247 const char *name;
248 Eptr errorp;
249 int fi;
250 Eptr *erpp;
251 int ntrueerrors;
252 boolean scribbled;
253 int n_pissed_on; /* # of file touched*/
254 int spread;
256 FILEITERATE(fi, 1, my_nfiles) {
257 name = (*my_files[fi])->error_text[0];
258 spread = my_files[fi+1] - my_files[fi];
259 fprintf(stdout, terse
260 ? "\"%s\" has %d error%s, "
261 : "\nFile \"%s\" has %d error%s.\n"
262 , name ,spread ,plural(spread));
264 * First, iterate through all error messages in this file
265 * to see how many of the error messages really will
266 * get inserted into the file.
268 ntrueerrors = 0;
269 EITERATE(erpp, my_files, fi) {
270 errorp = *erpp;
271 if (errorp->error_e_class == C_TRUE)
272 ntrueerrors++;
274 fprintf(stdout, terse
275 ? "insert %d\n"
276 : "\t%d of these errors can be inserted into the file.\n",
277 ntrueerrors);
279 hackfile(name, my_files, fi, ntrueerrors);
281 scribbled = false;
282 n_pissed_on = 0;
283 FILEITERATE(fi, 1, my_nfiles) {
284 scribbled |= touchedfiles[fi];
285 n_pissed_on++;
287 if (scribbled) {
289 * Construct an execv argument
291 execvarg(n_pissed_on, r_edargc, r_edargv);
292 return true;
293 } else {
294 if (!terse)
295 fprintf(stdout, "You didn't touch any files.\n");
296 return false;
300 static void
301 hackfile(const char *name, Eptr **my_files, int ix, int my_nerrors)
303 boolean previewed;
304 int errordest; /* where errors go */
306 if (!oktotouch(name)) {
307 previewed = false;
308 errordest = TOSTDOUT;
309 } else {
310 previewed = preview(name, my_nerrors, my_files, ix);
311 errordest = settotouch(name);
314 if (errordest != TOSTDOUT)
315 touchedfiles[ix] = true;
317 if (previewed && errordest == TOSTDOUT)
318 return;
320 diverterrors(name, errordest, my_files, ix, previewed, my_nerrors);
322 if (errordest == TOTHEFILE) {
324 * overwrite the original file
326 writetouched(1);
330 static boolean
331 preview(const char *name, int my_nerrors, Eptr **my_files, int ix)
333 int back;
334 Eptr *erpp;
336 if (my_nerrors <= 0)
337 return false;
338 back = false;
339 if (query) {
340 switch (inquire(terse
341 ? "Preview? "
342 : "Do you want to preview the errors first? ")) {
343 case Q_YES:
344 case Q_yes:
345 back = true;
346 EITERATE(erpp, my_files, ix) {
347 errorprint(stdout, *erpp, true);
349 if (!terse)
350 fprintf(stdout, "\n");
351 case Q_error:
352 default:
353 break;
356 return (back);
359 static int
360 settotouch(const char *name)
362 int dest = TOSTDOUT;
364 if (query) {
365 switch (inquire(terse
366 ? "Touch? "
367 : "Do you want to touch file \"%s\"? ",
368 name)) {
369 case Q_NO:
370 case Q_no:
371 case Q_error:
372 touchstatus = Q_NO;
373 return (dest);
374 default:
375 touchstatus = Q_YES;
376 break;
380 switch (probethisfile(name)) {
381 case F_NOTREAD:
382 dest = TOSTDOUT;
383 fprintf(stdout, terse
384 ? "\"%s\" unreadable\n"
385 : "File \"%s\" is unreadable\n",
386 name);
387 break;
388 case F_NOTWRITE:
389 dest = TOSTDOUT;
390 fprintf(stdout, terse
391 ? "\"%s\" unwritable\n"
392 : "File \"%s\" is unwritable\n",
393 name);
394 break;
395 case F_NOTEXIST:
396 dest = TOSTDOUT;
397 fprintf(stdout, terse
398 ? "\"%s\" not found\n"
399 : "Can't find file \"%s\" to insert error messages into.\n",
400 name);
401 break;
402 default:
403 dest = edit(name) ? TOSTDOUT : TOTHEFILE;
404 break;
406 return (dest);
409 static void
410 diverterrors(const char *name, int dest, Eptr **my_files, int ix,
411 boolean previewed, int nterrors)
413 int my_nerrors;
414 Eptr *erpp;
415 Eptr errorp;
417 my_nerrors = my_files[ix+1] - my_files[ix];
419 if (my_nerrors != nterrors && !previewed) {
420 fprintf(stdout, terse
421 ? "Uninserted errors\n"
422 : ">>Uninserted errors for file \"%s\" follow.\n",
423 name);
426 EITERATE(erpp, my_files, ix) {
427 errorp = *erpp;
428 if (errorp->error_e_class != C_TRUE) {
429 if (previewed || touchstatus == Q_NO)
430 continue;
431 errorprint(stdout, errorp, true);
432 continue;
434 switch (dest) {
435 case TOSTDOUT:
436 if (previewed || touchstatus == Q_NO)
437 continue;
438 errorprint(stdout,errorp, true);
439 break;
440 case TOTHEFILE:
441 insert(errorp->error_line);
442 text(errorp, false);
443 break;
448 static int
449 oktotouch(const char *filename)
451 const char *src;
452 const char *pat;
453 const char *osrc;
455 pat = suffixlist;
456 if (pat == 0)
457 return (0);
458 if (*pat == '*')
459 return (1);
460 while (*pat++ != '.')
461 continue;
462 --pat; /* point to the period */
464 for (src = &filename[strlen(filename)], --src;
465 src > filename && *src != '.'; --src)
466 continue;
467 if (*src != '.')
468 return (0);
470 for (src++, pat++, osrc = src; *src && *pat; src = osrc, pat++) {
471 for (; *src /* not at end of the source */
472 && *pat /* not off end of pattern */
473 && *pat != '.' /* not off end of sub pattern */
474 && *pat != '*' /* not wild card */
475 && *src == *pat; /* and equal... */
476 src++, pat++)
477 continue;
478 if (*src == 0 && (*pat == 0 || *pat == '.' || *pat == '*'))
479 return (1);
480 if (*src != 0 && *pat == '*')
481 return (1);
482 while (*pat && *pat != '.')
483 pat++;
484 if (!*pat)
485 return (0);
487 return (0);
491 * Construct an execv argument
492 * We need 1 argument for the editor's name
493 * We need 1 argument for the initial search string
494 * We need n_pissed_on arguments for the file names
495 * We need 1 argument that is a null for execv.
496 * The caller fills in the editor's name.
497 * We fill in the initial search string.
498 * We fill in the arguments, and the null.
500 static void
501 execvarg(int n_pissed_on, int *r_argc, char ***r_argv)
503 Eptr p;
504 const char *sep;
505 int fi;
507 sep = NULL;
508 (*r_argv) = Calloc(n_pissed_on + 3, sizeof(char *));
509 (*r_argc) = n_pissed_on + 2;
510 (*r_argv)[1] = Strdup("+1;/###/"); /* XXX leaked */
511 n_pissed_on = 2;
512 if (!terse) {
513 fprintf(stdout, "You touched file(s):");
514 sep = " ";
516 FILEITERATE(fi, 1, nfiles) {
517 if (!touchedfiles[fi])
518 continue;
519 p = *(files[fi]);
520 if (!terse) {
521 fprintf(stdout,"%s\"%s\"", sep, p->error_text[0]);
522 sep = ", ";
524 (*r_argv)[n_pissed_on++] = p->error_text[0];
526 if (!terse)
527 fprintf(stdout, "\n");
528 (*r_argv)[n_pissed_on] = 0;
531 static FILE *o_touchedfile; /* the old file */
532 static FILE *n_touchedfile; /* the new file */
533 static const char *o_name;
534 static char n_name[MAXPATHLEN];
535 static int o_lineno;
536 static int n_lineno;
537 static boolean tempfileopen = false;
540 * open the file; guaranteed to be both readable and writable
541 * Well, if it isn't, then return TRUE if something failed
543 static boolean
544 edit(const char *name)
546 int fd;
547 const char *tmpdir;
549 o_name = name;
550 if ((o_touchedfile = fopen(name, "r")) == NULL) {
551 fprintf(stderr, "%s: Can't open file \"%s\" to touch (read).\n",
552 processname, name);
553 return true;
555 if ((tmpdir = getenv("TMPDIR")) == NULL)
556 tmpdir = _PATH_TMP;
557 (void)snprintf(n_name, sizeof (n_name), "%s/%s", tmpdir, TMPFILE);
558 fd = -1;
559 if ((fd = mkstemp(n_name)) == -1 ||
560 (n_touchedfile = fdopen(fd, "w")) == NULL) {
561 if (fd != -1)
562 close(fd);
563 fprintf(stderr,"%s: Can't open file \"%s\" to touch (write).\n",
564 processname, name);
565 return true;
567 tempfileopen = true;
568 n_lineno = 0;
569 o_lineno = 0;
570 return false;
574 * Position to the line (before, after) the line given by place
576 static char edbuf[BUFSIZ];
578 static void
579 insert(int place)
581 --place; /* always insert messages before the offending line */
582 for (; o_lineno < place; o_lineno++, n_lineno++) {
583 if (fgets(edbuf, BUFSIZ, o_touchedfile) == NULL)
584 return;
585 fputs(edbuf, n_touchedfile);
589 static void
590 text(Eptr p, boolean use_all)
592 int offset = use_all ? 0 : 2;
594 fputs(lang_table[p->error_language].lang_incomment, n_touchedfile);
595 fprintf(n_touchedfile, "%d [%s] ",
596 p->error_line,
597 lang_table[p->error_language].lang_name);
598 wordvprint(n_touchedfile, p->error_lgtext-offset, p->error_text+offset);
599 fputs(lang_table[p->error_language].lang_outcomment, n_touchedfile);
600 n_lineno++;
604 * write the touched file to its temporary copy,
605 * then bring the temporary in over the local file
607 static boolean
608 writetouched(int overwrite)
610 unsigned nread;
611 FILE *localfile;
612 FILE *temp;
613 int botch;
614 int oktorm;
616 botch = 0;
617 oktorm = 1;
618 while ((nread = fread(edbuf, 1, sizeof(edbuf), o_touchedfile)) != 0) {
619 if (nread != fwrite(edbuf, 1, nread, n_touchedfile)) {
621 * Catastrophe in temporary area: file system full?
623 botch = 1;
624 fprintf(stderr,
625 "%s: write failure: No errors inserted in \"%s\"\n",
626 processname, o_name);
629 fclose(n_touchedfile);
630 fclose(o_touchedfile);
633 * Now, copy the temp file back over the original
634 * file, thus preserving links, etc
636 if (botch == 0 && overwrite) {
637 botch = 0;
638 localfile = NULL;
639 temp = NULL;
640 if ((localfile = fopen(o_name, "w")) == NULL) {
641 fprintf(stderr,
642 "%s: Can't open file \"%s\" to overwrite.\n",
643 processname, o_name);
644 botch++;
646 if ((temp = fopen(n_name, "r")) == NULL) {
647 fprintf(stderr, "%s: Can't open file \"%s\" to read.\n",
648 processname, n_name);
649 botch++;
651 if (!botch)
652 oktorm = mustoverwrite(localfile, temp);
653 if (localfile != NULL)
654 fclose(localfile);
655 if (temp != NULL)
656 fclose(temp);
658 if (oktorm == 0) {
659 fprintf(stderr, "%s: Catastrophe: A copy of \"%s\": was saved in \"%s\"\n",
660 processname, o_name, n_name);
661 exit(1);
664 * Kiss the temp file good bye
666 unlink(n_name);
667 tempfileopen = false;
668 return true;
672 * return 1 if the tmpfile can be removed after writing it out
674 static int
675 mustoverwrite(FILE *preciousfile, FILE *temp)
677 unsigned nread;
679 while ((nread = fread(edbuf, 1, sizeof(edbuf), temp)) != 0) {
680 if (mustwrite(edbuf, nread, preciousfile) == 0)
681 return (0);
683 return (1);
687 * return 0 on catastrophe
689 static int
690 mustwrite(const char *base, unsigned n, FILE *preciousfile)
692 unsigned nwrote;
694 if (n <= 0)
695 return (1);
696 nwrote = fwrite(base, 1, n, preciousfile);
697 if (nwrote == n)
698 return (1);
699 perror(processname);
700 switch (inquire(terse
701 ? "Botch overwriting: retry? "
702 : "Botch overwriting the source file: retry? ")) {
703 case Q_YES:
704 case Q_yes:
705 mustwrite(base + nwrote, n - nwrote, preciousfile);
706 return (1);
707 case Q_NO:
708 case Q_no:
709 switch (inquire("Are you sure? ")) {
710 case Q_error:
711 case Q_YES:
712 case Q_yes:
713 return (0);
714 case Q_NO:
715 case Q_no:
716 mustwrite(base + nwrote, n - nwrote, preciousfile);
717 return (1);
719 case Q_error:
720 default:
721 return (0);
725 void
726 onintr(int sig)
728 switch (inquire(terse
729 ? "\nContinue? "
730 : "\nInterrupt: Do you want to continue? ")) {
731 case Q_YES:
732 case Q_yes:
733 signal(sig, onintr);
734 return;
735 case Q_error:
736 default:
737 if (tempfileopen) {
739 * Don't overwrite the original file!
741 writetouched(0);
743 (void)raise_default_signal(sig);
744 _exit(127);
746 /*NOTREACHED*/
749 static void
750 errorprint(FILE *place, Eptr errorp, boolean print_all)
752 int offset = print_all ? 0 : 2;
754 if (errorp->error_e_class == C_IGNORE)
755 return;
756 fprintf(place, "[%s] ", lang_table[errorp->error_language].lang_name);
757 wordvprint(place,errorp->error_lgtext-offset,errorp->error_text+offset);
758 putc('\n', place);
762 inquire(const char *fmt, ...)
764 va_list ap;
765 char buffer[128];
767 if (queryfile == NULL)
768 return (Q_error);
769 for (;;) {
770 fflush(stdout);
771 va_start(ap, fmt);
772 vfprintf(stderr, fmt, ap);
773 va_end(ap);
774 fflush(stderr);
775 if (fgets(buffer, 127, queryfile) == NULL)
776 return (Q_error);
777 switch (buffer[0]) {
778 case 'Y': return (Q_YES);
779 case 'y': return (Q_yes);
780 case 'N': return (Q_NO);
781 case 'n': return (Q_no);
782 default: fprintf(stderr, "Yes or No only!\n");
787 static int
788 probethisfile(const char *name)
790 struct stat statbuf;
792 if (stat(name, &statbuf) < 0)
793 return (F_NOTEXIST);
794 if ((statbuf.st_mode & S_IREAD) == 0)
795 return (F_NOTREAD);
796 if ((statbuf.st_mode & S_IWRITE) == 0)
797 return (F_NOTWRITE);
798 return (F_TOUCHIT);