1 /* $NetBSD: touch.c,v 1.21 2009/08/13 05:53:58 dholland Exp $ */
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
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
32 #include <sys/cdefs.h>
35 static char sccsid
[] = "@(#)touch.c 8.1 (Berkeley) 6/6/93";
37 __RCSID("$NetBSD: touch.c,v 1.21 2009/08/13 05:53:58 dholland Exp $");
40 #include <sys/param.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
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 *);
91 findfiles(int my_nerrors
, Eptr
*my_errors
, int *r_nfiles
, Eptr
***r_files
)
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
)))
117 * Now, and partition off all error messages
120 my_files
[1] = &my_errors
[ei
];
121 touchedfiles
[0] = touchedfiles
[1] = false;
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
];
135 my_files
[fi
] = &my_errors
[my_nerrors
];
136 *r_nfiles
= my_nfiles
;
141 countfiles(Eptr
*errors
)
150 ECITERATE(ei
, errorp
, 0, errors
, nerrors
) {
151 if (SORTABLE(errorp
->error_e_class
)) {
152 if (strcmp(errorp
->error_text
[0],name
) != 0) {
154 name
= errorp
->error_text
[0];
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};
176 filenames(int my_nfiles
, Eptr
**my_files
)
179 const char *sep
= " ";
183 * first, simply dump out errors that
184 * don't pertain to any file
186 someerrors
= nopertain(my_files
);
191 fprintf(stdout
, "%d file%s", my_nfiles
, plural(my_nfiles
));
193 fprintf(stdout
, "%d file%s contain%s errors",
194 my_nfiles
, plural(my_nfiles
), verbform(my_nfiles
));
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
]));
203 fprintf(stdout
, "\n");
206 fprintf(stdout
, "No errors.\n");
210 * Dump out errors that don't pertain to any file
213 nopertain(Eptr
**my_files
)
220 if (my_files
[1] - my_files
[0] <= 0)
222 for (type
= C_UNKNOWN
; NOTSORTABLE(type
); type
++) {
223 if (class_count
[type
] <= 0)
228 fprintf(stdout
, "\t%d %s errors NOT PRINTED\n",
229 class_count
[type
], class_table
[type
]);
231 fprintf(stdout
, "\n\t%d %s errors follow\n",
232 class_count
[type
], class_table
[type
]);
233 EITERATE(erpp
, my_files
, 0) {
235 if (errorp
->error_e_class
== type
) {
236 errorprint(stdout
, errorp
, true);
245 touchfiles(int my_nfiles
, Eptr
**my_files
, int *r_edargc
, char ***r_edargv
)
253 int n_pissed_on
; /* # of file touched*/
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.
269 EITERATE(erpp
, my_files
, fi
) {
271 if (errorp
->error_e_class
== C_TRUE
)
274 fprintf(stdout
, terse
276 : "\t%d of these errors can be inserted into the file.\n",
279 hackfile(name
, my_files
, fi
, ntrueerrors
);
283 FILEITERATE(fi
, 1, my_nfiles
) {
284 scribbled
|= touchedfiles
[fi
];
289 * Construct an execv argument
291 execvarg(n_pissed_on
, r_edargc
, r_edargv
);
295 fprintf(stdout
, "You didn't touch any files.\n");
301 hackfile(const char *name
, Eptr
**my_files
, int ix
, int my_nerrors
)
304 int errordest
; /* where errors go */
306 if (!oktotouch(name
)) {
308 errordest
= TOSTDOUT
;
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
)
320 diverterrors(name
, errordest
, my_files
, ix
, previewed
, my_nerrors
);
322 if (errordest
== TOTHEFILE
) {
324 * overwrite the original file
331 preview(const char *name
, int my_nerrors
, Eptr
**my_files
, int ix
)
340 switch (inquire(terse
342 : "Do you want to preview the errors first? ")) {
346 EITERATE(erpp
, my_files
, ix
) {
347 errorprint(stdout
, *erpp
, true);
350 fprintf(stdout
, "\n");
360 settotouch(const char *name
)
365 switch (inquire(terse
367 : "Do you want to touch file \"%s\"? ",
380 switch (probethisfile(name
)) {
383 fprintf(stdout
, terse
384 ? "\"%s\" unreadable\n"
385 : "File \"%s\" is unreadable\n",
390 fprintf(stdout
, terse
391 ? "\"%s\" unwritable\n"
392 : "File \"%s\" is unwritable\n",
397 fprintf(stdout
, terse
398 ? "\"%s\" not found\n"
399 : "Can't find file \"%s\" to insert error messages into.\n",
403 dest
= edit(name
) ? TOSTDOUT
: TOTHEFILE
;
410 diverterrors(const char *name
, int dest
, Eptr
**my_files
, int ix
,
411 boolean previewed
, int nterrors
)
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",
426 EITERATE(erpp
, my_files
, ix
) {
428 if (errorp
->error_e_class
!= C_TRUE
) {
429 if (previewed
|| touchstatus
== Q_NO
)
431 errorprint(stdout
, errorp
, true);
436 if (previewed
|| touchstatus
== Q_NO
)
438 errorprint(stdout
,errorp
, true);
441 insert(errorp
->error_line
);
449 oktotouch(const char *filename
)
460 while (*pat
++ != '.')
462 --pat
; /* point to the period */
464 for (src
= &filename
[strlen(filename
)], --src
;
465 src
> filename
&& *src
!= '.'; --src
)
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... */
478 if (*src
== 0 && (*pat
== 0 || *pat
== '.' || *pat
== '*'))
480 if (*src
!= 0 && *pat
== '*')
482 while (*pat
&& *pat
!= '.')
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.
501 execvarg(int n_pissed_on
, int *r_argc
, char ***r_argv
)
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 */
513 fprintf(stdout
, "You touched file(s):");
516 FILEITERATE(fi
, 1, nfiles
) {
517 if (!touchedfiles
[fi
])
521 fprintf(stdout
,"%s\"%s\"", sep
, p
->error_text
[0]);
524 (*r_argv
)[n_pissed_on
++] = p
->error_text
[0];
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
];
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
544 edit(const char *name
)
550 if ((o_touchedfile
= fopen(name
, "r")) == NULL
) {
551 fprintf(stderr
, "%s: Can't open file \"%s\" to touch (read).\n",
555 if ((tmpdir
= getenv("TMPDIR")) == NULL
)
557 (void)snprintf(n_name
, sizeof (n_name
), "%s/%s", tmpdir
, TMPFILE
);
559 if ((fd
= mkstemp(n_name
)) == -1 ||
560 (n_touchedfile
= fdopen(fd
, "w")) == NULL
) {
563 fprintf(stderr
,"%s: Can't open file \"%s\" to touch (write).\n",
574 * Position to the line (before, after) the line given by place
576 static char edbuf
[BUFSIZ
];
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
)
585 fputs(edbuf
, n_touchedfile
);
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] ",
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
);
604 * write the touched file to its temporary copy,
605 * then bring the temporary in over the local file
608 writetouched(int overwrite
)
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?
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
) {
640 if ((localfile
= fopen(o_name
, "w")) == NULL
) {
642 "%s: Can't open file \"%s\" to overwrite.\n",
643 processname
, o_name
);
646 if ((temp
= fopen(n_name
, "r")) == NULL
) {
647 fprintf(stderr
, "%s: Can't open file \"%s\" to read.\n",
648 processname
, n_name
);
652 oktorm
= mustoverwrite(localfile
, temp
);
653 if (localfile
!= NULL
)
659 fprintf(stderr
, "%s: Catastrophe: A copy of \"%s\": was saved in \"%s\"\n",
660 processname
, o_name
, n_name
);
664 * Kiss the temp file good bye
667 tempfileopen
= false;
672 * return 1 if the tmpfile can be removed after writing it out
675 mustoverwrite(FILE *preciousfile
, FILE *temp
)
679 while ((nread
= fread(edbuf
, 1, sizeof(edbuf
), temp
)) != 0) {
680 if (mustwrite(edbuf
, nread
, preciousfile
) == 0)
687 * return 0 on catastrophe
690 mustwrite(const char *base
, unsigned n
, FILE *preciousfile
)
696 nwrote
= fwrite(base
, 1, n
, preciousfile
);
700 switch (inquire(terse
701 ? "Botch overwriting: retry? "
702 : "Botch overwriting the source file: retry? ")) {
705 mustwrite(base
+ nwrote
, n
- nwrote
, preciousfile
);
709 switch (inquire("Are you sure? ")) {
716 mustwrite(base
+ nwrote
, n
- nwrote
, preciousfile
);
728 switch (inquire(terse
730 : "\nInterrupt: Do you want to continue? ")) {
739 * Don't overwrite the original file!
743 (void)raise_default_signal(sig
);
750 errorprint(FILE *place
, Eptr errorp
, boolean print_all
)
752 int offset
= print_all
? 0 : 2;
754 if (errorp
->error_e_class
== C_IGNORE
)
756 fprintf(place
, "[%s] ", lang_table
[errorp
->error_language
].lang_name
);
757 wordvprint(place
,errorp
->error_lgtext
-offset
,errorp
->error_text
+offset
);
762 inquire(const char *fmt
, ...)
767 if (queryfile
== NULL
)
772 vfprintf(stderr
, fmt
, ap
);
775 if (fgets(buffer
, 127, queryfile
) == NULL
)
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");
788 probethisfile(const char *name
)
792 if (stat(name
, &statbuf
) < 0)
794 if ((statbuf
.st_mode
& S_IREAD
) == 0)
796 if ((statbuf
.st_mode
& S_IWRITE
) == 0)