1/*
2 * ----------------------------------------------------------------------------
3 * "THE BEER-WARE LICENSE" (Revision 42):
4 * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
5 * can do whatever you want with this stuff. If we meet some day, and you think
6 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
7 * ----------------------------------------------------------------------------
8 *
9 * $FreeBSD$
10 *
11 * This is the client program of 'CTM'.  It will apply a CTM-patch to a
12 * collection of files.
13 *
14 * Options we'd like to see:
15 *
16 * -a 			Attempt best effort.
17 * -d <int>		Debug TBD.
18 * -m <mail-addr>	Email me instead.
19 * -r <name>		Reconstruct file.
20 * -R <file>		Read list of files to reconstruct.
21 *
22 * Options we have:
23 * -b <dir>		Base-dir
24 * -B <file>		Backup to tar-file.
25 * -t 			Tar command (default as in TARCMD).
26 * -c			Check it out, don't do anything.
27 * -F      		Force
28 * -q 			Tell us less.
29 * -T <tmpdir>.		Temporary files.
30 * -u			Set all file modification times to the timestamp
31 * -v 			Tell us more.
32 * -V <level>		Tell us more level = number of -v
33 * -k			Keep files and directories that would have been removed.
34 * -l			List actions.
35 *
36 * Options we don't actually use:
37 * -p			Less paranoid.
38 * -P			Paranoid.
39 */
40
41#define EXTERN /* */
42#include <paths.h>
43#include "ctm.h"
44
45#define CTM_STATUS ".ctm_status"
46
47extern int Proc(char *, unsigned applied);
48
49int
50main(int argc, char **argv)
51{
52    int stat=0, err=0;
53    int c;
54    unsigned applied = 0;
55    FILE *statfile;
56    struct CTM_Filter *nfilter = NULL;	/* new filter */
57    u_char * basedir;
58
59    basedir = NULL;
60    Verbose = 1;
61    Paranoid = 1;
62    SetTime = 0;
63    KeepIt = 0;
64    ListIt = 0;
65    BackupFile = NULL;
66    TarCmd = TARCMD;
67    LastFilter = FilterList = NULL;
68    TmpDir = getenv("TMPDIR");
69    if (TmpDir == NULL)
70	TmpDir = strdup(_PATH_TMP);
71    setbuf(stderr,0);
72    setbuf(stdout,0);
73
74    while((c=getopt(argc,argv,"ab:B:cd:e:Fklm:pPqr:R:t:T:uV:vx:")) != -1) {
75	switch (c) {
76	    case 'b': basedir = optarg;	break; /* Base Directory */
77	    case 'B': BackupFile = optarg;	break;
78	    case 'c': CheckIt++;	break; /* Only check it */
79	    case 'F': Force = 1;	break;
80	    case 'k': KeepIt++;		break; /* Don't do removes */
81	    case 'l': ListIt++;		break; /* Only list actions and files */
82	    case 'p': Paranoid--;	break; /* Less Paranoid */
83	    case 'P': Paranoid++;	break; /* More Paranoid */
84	    case 'q': Verbose--;	break; /* Quiet */
85	    case 't': TarCmd = optarg;	break; /* archiver command */
86	    case 'T': TmpDir = optarg;	break; /* set temporary directory */
87	    case 'u': SetTime++;	break; /* Set timestamp on files */
88	    case 'v': Verbose++;	break; /* Verbose */
89	    case 'V': sscanf(optarg,"%d", &c); /* Verbose */
90		      Verbose += c;
91		      break;
92	    case 'e':				/* filter expressions */
93	    case 'x':
94		if (NULL == (nfilter =  Malloc(sizeof(struct CTM_Filter)))) {
95			warnx("out of memory for expressions: \"%s\"", optarg);
96			stat++;
97			break;
98		}
99
100		(void) memset(nfilter, 0, sizeof(struct CTM_Filter));
101
102		if (0 != (err =
103			regcomp(&nfilter->CompiledRegex, optarg, REG_NOSUB))) {
104
105			char errmsg[128];
106
107			regerror(err, &nfilter->CompiledRegex, errmsg,
108				sizeof(errmsg));
109			warnx("regular expression: \"%s\"", errmsg);
110			stat++;
111			break;
112		}
113
114		/* note whether the filter enables or disables on match */
115		nfilter->Action =
116			(('e' == c) ? CTM_FILTER_ENABLE : CTM_FILTER_DISABLE);
117
118		/* link in the expression into the list */
119	        nfilter->Next = NULL;
120		if (NULL == FilterList) {
121		  LastFilter = FilterList = nfilter; /* init head and tail */
122		} else {    /* place at tail */
123		  LastFilter->Next = nfilter;
124		  LastFilter = nfilter;
125		}
126		break;
127	    case ':':
128		warnx("option '%c' requires an argument",optopt);
129		stat++;
130		break;
131	    case '?':
132		warnx("option '%c' not supported",optopt);
133		stat++;
134		break;
135	    default:
136		warnx("option '%c' not yet implemented",optopt);
137		break;
138	}
139    }
140
141    if(stat) {
142	warnx("%d errors during option processing",stat);
143	return Exit_Pilot;
144    }
145    stat = Exit_Done;
146    argc -= optind;
147    argv += optind;
148
149    if (basedir == NULL) {
150	Buffer = (u_char *)Malloc(BUFSIZ + strlen(SUBSUFF) +1);
151	CatPtr = Buffer;
152	*Buffer  = '\0';
153    } else {
154	Buffer = (u_char *)Malloc(strlen(basedir)+ BUFSIZ + strlen(SUBSUFF) +1);
155	strcpy(Buffer, basedir);
156	CatPtr = Buffer + strlen(basedir);
157	if (CatPtr[-1] != '/') {
158		strcat(Buffer, "/");
159		CatPtr++;
160	}
161    }
162    strcat(Buffer, CTM_STATUS);
163
164    if(ListIt)
165	applied = 0;
166    else
167	if((statfile = fopen(Buffer, "r")) == NULL) {
168	    if (Verbose > 0)
169	    	warnx("warning: %s not found", Buffer);
170	} else {
171	    fscanf(statfile, "%*s %u", &applied);
172	    fclose(statfile);
173	}
174
175    if(!argc)
176	stat |= Proc("-", applied);
177
178    while(argc-- && stat == Exit_Done) {
179	stat |= Proc(*argv++, applied);
180	stat &= ~(Exit_Version | Exit_NoMatch);
181    }
182
183    if(stat == Exit_Done)
184	stat = Exit_OK;
185
186    if(Verbose > 0)
187	warnx("exit(%d)",stat);
188
189    if (FilterList)
190	for (nfilter = FilterList; nfilter; ) {
191	    struct CTM_Filter *tmp = nfilter->Next;
192	    Free(nfilter);
193	    nfilter = tmp;
194	}
195    return stat;
196}
197
198int
199Proc(char *filename, unsigned applied)
200{
201    FILE *f;
202    int i;
203    char *p = strrchr(filename,'.');
204
205    if(!strcmp(filename,"-")) {
206	p = 0;
207	f = stdin;
208    } else if(p && (!strcmp(p,".gz") || !strcmp(p,".Z"))) {
209	p = alloca(20 + strlen(filename));
210	strcpy(p,"gunzip < ");
211	strcat(p,filename);
212	f = popen(p,"r");
213	if(!f) { warn("%s", p); return Exit_Garbage; }
214    } else {
215	p = 0;
216	f = fopen(filename,"r");
217    }
218    if(!f) {
219	warn("%s", filename);
220	return Exit_Garbage;
221    }
222
223    if(Verbose > 1)
224	fprintf(stderr,"Working on <%s>\n",filename);
225
226    Delete(FileName);
227    FileName = String(filename);
228
229    /* If we cannot seek, we're doomed, so copy to a tmp-file in that case */
230    if(!p &&  -1 == fseek(f,0,SEEK_END)) {
231	char *fn;
232	FILE *f2;
233	int fd;
234
235	if (asprintf(&fn, "%s/CTMclient.XXXXXXXXXX", TmpDir) == -1) {
236	    fprintf(stderr, "Cannot allocate memory\n");
237	    fclose(f);
238	    return Exit_Broke;
239	}
240	if ((fd = mkstemp(fn)) == -1 || (f2 = fdopen(fd, "w+")) == NULL) {
241 	    warn("%s", fn);
242	    free(fn);
243	    if (fd != -1)
244		close(fd);
245 	    fclose(f);
246 	    return Exit_Broke;
247 	}
248	unlink(fn);
249	if (Verbose > 0)
250	    fprintf(stderr,"Writing tmp-file \"%s\"\n",fn);
251	free(fn);
252	while(EOF != (i=getc(f)))
253	    if(EOF == putc(i,f2)) {
254		fclose(f2);
255		return Exit_Broke;
256	    }
257	fclose(f);
258	f = f2;
259    }
260
261    if(!p)
262	rewind(f);
263
264    if((i=Pass1(f, applied)))
265	goto exit_and_close;
266
267    if(ListIt) {
268	i = Exit_Done;
269	goto exit_and_close;
270    }
271
272    if(!p) {
273        rewind(f);
274    } else {
275	pclose(f);
276	f = popen(p,"r");
277	if(!f) { warn("%s", p); return Exit_Broke; }
278    }
279
280    i=Pass2(f);
281
282    if(!p) {
283        rewind(f);
284    } else {
285	pclose(f);
286	f = popen(p,"r");
287	if(!f) { warn("%s", p); return Exit_Broke; }
288    }
289
290    if(i) {
291	if((!Force) || (i & ~Exit_Forcible))
292	    goto exit_and_close;
293    }
294
295    if(CheckIt) {
296	if (Verbose > 0)
297	    fprintf(stderr,"All checks out ok.\n");
298	i = Exit_Done;
299	goto exit_and_close;
300    }
301
302    /* backup files if requested */
303    if(BackupFile) {
304
305	i = PassB(f);
306
307	if(!p) {
308	    rewind(f);
309	} else {
310	    pclose(f);
311	    f = popen(p,"r");
312	    if(!f) { warn("%s", p); return Exit_Broke; }
313	}
314    }
315
316    i=Pass3(f);
317
318exit_and_close:
319    if(!p)
320        fclose(f);
321    else
322	pclose(f);
323
324    if(i)
325	return i;
326
327    if (Verbose > 0)
328	fprintf(stderr,"All done ok\n");
329
330    return Exit_Done;
331}
332