xref: /plan9/sys/src/cmd/idiff.c (revision 9a747e4fd48b9f4522c70c07e8f882a15030f964)
1 /*
2  * interactive diff, inspired/stolen from
3  * kernighan and pike, _unix programming environment_.
4  */
5 
6 #include <u.h>
7 #include <libc.h>
8 #include <bio.h>
9 
10 int diffbflag;
11 int diffwflag;
12 
13 void copy(Biobuf*, char*, Biobuf*, char*);
14 void idiff(Biobuf*, char*, Biobuf*, char*, Biobuf*, char*, Biobuf*, char*);
15 int opentemp(char*, int, long);
16 void rundiff(char*, char*, int);
17 
18 void
usage(void)19 usage(void)
20 {
21 	fprint(2, "usage: idiff [-bw] file1 file2\n");
22 	exits("usage");
23 }
24 
25 void
main(int argc,char ** argv)26 main(int argc, char **argv)
27 {
28 	int fd, ofd;
29 	char diffout[40], idiffout[40];
30 	Biobuf *b1, *b2, bdiff, bout, bstdout;
31 	Dir *d;
32 
33 	ARGBEGIN{
34 	default:
35 		usage();
36 	case 'b':
37 		diffbflag++;
38 		break;
39 	case 'w':
40 		diffwflag++;
41 		break;
42 	}ARGEND
43 
44 	if(argc != 2)
45 		usage();
46 
47 	if((d = dirstat(argv[0])) == nil)
48 		sysfatal("stat %s: %r", argv[0]);
49 	if(d->mode&DMDIR)
50 		sysfatal("%s is a directory", argv[0]);
51 	free(d);
52 	if((d = dirstat(argv[1])) == nil)
53 		sysfatal("stat %s: %r", argv[1]);
54 	if(d->mode&DMDIR)
55 		sysfatal("%s is a directory", argv[1]);
56 	free(d);
57 
58 	if((b1 = Bopen(argv[0], OREAD)) == nil)
59 		sysfatal("open %s: %r", argv[0]);
60 	if((b2 = Bopen(argv[1], OREAD)) == nil)
61 		sysfatal("open %s: %r", argv[1]);
62 
63 	strcpy(diffout, "/tmp/idiff.XXXXXX");
64 	fd = opentemp(diffout, ORDWR|ORCLOSE, 0);
65 	strcpy(idiffout, "/tmp/idiff.XXXXXX");
66 	ofd = opentemp(idiffout, ORDWR|ORCLOSE, 0);
67 	rundiff(argv[0], argv[1], fd);
68 	seek(fd, 0, 0);
69 	Binit(&bdiff, fd, OREAD);
70 	Binit(&bout, ofd, OWRITE);
71 	idiff(b1, argv[0], b2, argv[1], &bdiff, diffout, &bout, idiffout);
72 	Bterm(&bdiff);
73 	Bflush(&bout);
74 	seek(ofd, 0, 0);
75 	Binit(&bout, ofd, OREAD);
76 	Binit(&bstdout, 1, OWRITE);
77 	copy(&bout, idiffout, &bstdout, "<stdout>");
78 	exits(nil);
79 }
80 
81 int
opentemp(char * template,int mode,long perm)82 opentemp(char *template, int mode, long perm)
83 {
84 	int fd, i;
85 	char *p;
86 
87 	p = strdup(template);
88 	if(p == nil)
89 		sysfatal("strdup out of memory");
90 	fd = -1;
91 	for(i=0; i<10; i++){
92 		mktemp(p);
93 		if(access(p, 0) < 0 && (fd=create(p, mode, perm)) >= 0)
94 			break;
95 		strcpy(p, template);
96 	}
97 	if(fd < 0)
98 		sysfatal("could not create temporary file");
99 	strcpy(template, p);
100 	free(p);
101 
102 	return fd;
103 }
104 
105 void
rundiff(char * arg1,char * arg2,int outfd)106 rundiff(char *arg1, char *arg2, int outfd)
107 {
108 	char *arg[10], *p;
109 	int narg, pid;
110 	Waitmsg *w;
111 
112 	narg = 0;
113 	arg[narg++] = "/bin/diff";
114 	arg[narg++] = "-n";
115 	if(diffbflag)
116 		arg[narg++] = "-b";
117 	if(diffwflag)
118 		arg[narg++] = "-w";
119 	arg[narg++] = arg1;
120 	arg[narg++] = arg2;
121 	arg[narg] = nil;
122 
123 	switch(pid = fork()){
124 	case -1:
125 		sysfatal("fork: %r");
126 
127 	case 0:
128 		dup(outfd, 1);
129 		close(0);
130 		exec("/bin/diff", arg);
131 		sysfatal("exec: %r");
132 
133 	default:
134 		w = wait();
135 		if(w==nil)
136 			sysfatal("wait: %r");
137 		if(w->pid != pid)
138 			sysfatal("wait got unexpected pid %d", w->pid);
139 		if((p = strchr(w->msg, ':')) && strcmp(p, ": some") != 0)
140 			sysfatal("%s", w->msg);
141 		free(w);
142 	}
143 }
144 
145 void
runcmd(char * cmd)146 runcmd(char *cmd)
147 {
148 	char *arg[10];
149 	int narg, pid, wpid;
150 
151 	narg = 0;
152 	arg[narg++] = "/bin/rc";
153 	arg[narg++] = "-c";
154 	arg[narg++] = cmd;
155 	arg[narg] = nil;
156 
157 	switch(pid = fork()){
158 	case -1:
159 		sysfatal("fork: %r");
160 
161 	case 0:
162 		exec("/bin/rc", arg);
163 		sysfatal("exec: %r");
164 
165 	default:
166 		wpid = waitpid();
167 		if(wpid < 0)
168 			sysfatal("wait: %r");
169 		if(wpid != pid)
170 			sysfatal("wait got unexpected pid %d", wpid);
171 	}
172 }
173 
174 void
parse(char * s,int * pfrom1,int * pto1,int * pcmd,int * pfrom2,int * pto2)175 parse(char *s, int *pfrom1, int *pto1, int *pcmd, int *pfrom2, int *pto2)
176 {
177 	*pfrom1 = *pto1 = *pfrom2 = *pto2 = 0;
178 
179 	s = strchr(s, ':');
180 	if(s == nil)
181 		sysfatal("bad diff output0");
182 	s++;
183 	*pfrom1 = strtol(s, &s, 10);
184 	if(*s == ','){
185 		s++;
186 		*pto1 = strtol(s, &s, 10);
187 	}else
188 		*pto1 = *pfrom1;
189 	if(*s++ != ' ')
190 		sysfatal("bad diff output1");
191 	*pcmd = *s++;
192 	if(*s++ != ' ')
193 		sysfatal("bad diff output2");
194 	s = strchr(s, ':');
195 	if(s == nil)
196 		sysfatal("bad diff output3");
197 	s++;
198 	*pfrom2 = strtol(s, &s, 10);
199 	if(*s == ','){
200 		s++;
201 		*pto2 = strtol(s, &s, 10);
202 	}else
203 		*pto2 = *pfrom2;
204 }
205 
206 void
skiplines(Biobuf * b,char * name,int n)207 skiplines(Biobuf *b, char *name, int n)
208 {
209 	int i;
210 
211 	for(i=0; i<n; i++){
212 		while(Brdline(b, '\n')==nil){
213 			if(Blinelen(b) <= 0)
214 				sysfatal("early end of file on %s", name);
215 			Bseek(b, Blinelen(b), 1);
216 		}
217 	}
218 }
219 
220 void
copylines(Biobuf * bin,char * nin,Biobuf * bout,char * nout,int n)221 copylines(Biobuf *bin, char *nin, Biobuf *bout, char *nout, int n)
222 {
223 	char buf[4096], *p;
224 	int i, m;
225 
226 	for(i=0; i<n; i++){
227 		while((p=Brdline(bin, '\n'))==nil){
228 			if(Blinelen(bin) <= 0)
229 				sysfatal("early end of file on %s", nin);
230 			m = Blinelen(bin);
231 			if(m > sizeof buf)
232 				m = sizeof buf;
233 			m = Bread(bin, buf, m);
234 			if(Bwrite(bout, buf, m) != m)
235 				sysfatal("error writing %s: %r", nout);
236 		}
237 		if(Bwrite(bout, p, Blinelen(bin)) != Blinelen(bin))
238 			sysfatal("error writing %s: %r", nout);
239 	}
240 }
241 
242 void
copy(Biobuf * bin,char * nin,Biobuf * bout,char * nout)243 copy(Biobuf *bin, char *nin, Biobuf *bout, char *nout)
244 {
245 	char buf[4096];
246 	int m;
247 
248 	USED(nin);
249 	while((m = Bread(bin, buf, sizeof buf)) > 0)
250 		if(Bwrite(bout, buf, m) != m)
251 			sysfatal("error writing %s: %r", nout);
252 }
253 
254 void
idiff(Biobuf * b1,char * name1,Biobuf * b2,char * name2,Biobuf * bdiff,char * namediff,Biobuf * bout,char * nameout)255 idiff(Biobuf *b1, char *name1, Biobuf *b2, char *name2, Biobuf *bdiff, char *namediff, Biobuf *bout, char *nameout)
256 {
257 	char buf[256], *p;
258 	int interactive, defaultanswer, cmd, diffoffset;
259 	int n, from1, to1, from2, to2, nf1, nf2;
260 	Biobuf berr;
261 
262 	nf1 = 1;
263 	nf2 = 1;
264 	interactive = 1;
265 	defaultanswer = 0;
266 	Binit(&berr, 2, OWRITE);
267 	while(diffoffset = Boffset(bdiff), p = Brdline(bdiff, '\n')){
268 		p[Blinelen(bdiff)-1] = '\0';
269 		parse(p, &from1, &to1, &cmd, &from2, &to2);
270 		p[Blinelen(bdiff)-1] = '\n';
271 		n = to1-from1 + to2-from2 + 1;	/* #lines from diff */
272 		if(cmd == 'c')
273 			n += 2;
274 		else if(cmd == 'a')
275 			from1++;
276 		else if(cmd == 'd')
277 			from2++;
278 		to1++;	/* make half-open intervals */
279 		to2++;
280 		if(interactive){
281 			p[Blinelen(bdiff)-1] = '\0';
282 			fprint(2, "%s\n", p);
283 			p[Blinelen(bdiff)-1] = '\n';
284 			copylines(bdiff, namediff, &berr, "<stderr>", n);
285 			Bflush(&berr);
286 		}else
287 			skiplines(bdiff, namediff, n);
288 		do{
289 			if(interactive){
290 				fprint(2, "? ");
291 				memset(buf, 0, sizeof buf);
292 				if(read(0, buf, sizeof buf - 1) < 0)
293 					sysfatal("read console: %r");
294 			}else
295 				buf[0] = defaultanswer;
296 
297 			switch(buf[0]){
298 			case '>':
299 				copylines(b1, name1, bout, nameout, from1-nf1);
300 				skiplines(b1, name1, to1-from1);
301 				skiplines(b2, name2, from2-nf2);
302 				copylines(b2, name2, bout, nameout, to2-from2);
303 				break;
304 			case '<':
305 				copylines(b1, name1, bout, nameout, to1-nf1);
306 				skiplines(b2, name2, to2-nf2);
307 				break;
308 			case '=':
309 				copylines(b1, name1, bout, nameout, from1-nf1);
310 				skiplines(b1, name1, to1-from1);
311 				skiplines(b2, name2, to2-nf2);
312 				if(Bseek(bdiff, diffoffset, 0) != diffoffset)
313 					sysfatal("seek in diff output: %r");
314 				copylines(bdiff, namediff, bout, nameout, n+1);
315 				break;
316 			case '!':
317 				runcmd(buf+1);
318 				break;
319 			case 'q':
320 				if(buf[1]=='<' || buf[1]=='>' || buf[1]=='='){
321 					interactive = 0;
322 					defaultanswer = buf[1];
323 				}else
324 					fprint(2, "must be q<, q>, or q=\n");
325 				break;
326 			default:
327 				fprint(2, "expect: <, >, =, q<, q>, q=, !cmd\n");
328 				break;
329 			}
330 		}while(buf[0] != '<' && buf[0] != '>' && buf[0] != '=');
331 		nf1 = to1;
332 		nf2 = to2;
333 	}
334 	copy(b1, name1, bout, nameout);
335 }
336