1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License ("CDDL"), version 1.0.
6  * You may use this file only in accordance with the terms of version
7  * 1.0 of the CDDL.
8  *
9  * A full copy of the text of the CDDL should have accompanied this
10  * source.  A copy of the CDDL is also available via the Internet at
11  * http://www.opensource.org/licenses/cddl1.txt
12  * See the License for the specific language governing permissions
13  * and limitations under the License.
14  *
15  * When distributing Covered Code, include this CDDL HEADER in each
16  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17  * If applicable, add the following below this CDDL HEADER, with the
18  * fields enclosed by brackets "[]" replaced with your own identifying
19  * information: Portions Copyright [yyyy] [name of copyright owner]
20  *
21  * CDDL HEADER END
22  */
23 /* Copyright (c) 1988 AT&T */
24 /* All Rights Reserved */
25 /*
26  * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
27  * Use is subject to license terms.
28  */
29 /*
30  * Copyright 2006-2020 J. Schilling
31  *
32  * @(#)unget.c	1.46 20/08/23 J. Schilling
33  */
34 #if defined(sun)
35 #pragma ident "@(#)unget.c 1.46 20/08/23 J. Schilling"
36 #endif
37 /*
38  * @(#)unget.c 1.24 06/12/12
39  */
40 
41 #if defined(sun)
42 #pragma ident	"@(#)unget.c"
43 #pragma ident	"@(#)sccs:cmd/unget.c"
44 #endif
45 #define		SCCS_MAIN			/* define global vars */
46 #include	<defines.h>
47 #include	<version.h>
48 #include	<had.h>
49 #include	<i18n.h>
50 #include	<schily/utsname.h>
51 #include	<schily/wait.h>
52 #include	<schily/ctype.h>
53 #include	<ccstypes.h>
54 #include	<schily/sysexits.h>
55 
56 #ifdef	HAVE_SETRESUID
57 /*
58  * HP-UX does not have seteuid(). use setresuid() instead.
59  */
60 #define	seteuid(u)	setresuid((uid_t)-1, (uid_t)(u), (uid_t)-1)
61 #endif
62 
63 /*
64  *		Program can be invoked as either "unget" or
65  *		"sact".  Sact simply displays the p-file on the
66  *		standard output.  Unget removes a specified entry
67  *		from the p-file.
68  */
69 
70 extern	char had_dir, had_standinp;
71 
72 static int	num_files;
73 static int	cmd;
74 static off_t	Szqfile;
75 static char	Pfilename[FILESIZE];
76 static char	Zhold[MAXPATHLEN];	/* temporary z-file name */
77 static struct packet	gpkt;
78 static struct sid	sid;
79 static struct utsname 	un;
80 static char *uuname;
81 static Nparms	N;			/* Keep -N parameters		*/
82 static Xparms	X;			/* Keep -X parameters		*/
83 
84 	int	main	__PR((int argc, char **argv));
85 static void	unget	__PR((char *file));
86 static struct	pfile *edpfile __PR((struct packet *pkt, struct sid *sp));
87 static void	clean_up __PR((void));
88 static void	catpfile __PR((struct packet *pkt));
89 
90 int
main(argc,argv)91 main(argc, argv)
92 int argc;
93 char *argv[];
94 {
95 	int	c, i, testmore;
96 	char	*p;
97 	extern	int Fcnt;
98 	int current_optind;
99 	int no_arg;
100 
101 	/*
102 	 * get the user name unfront.
103 	 */
104 	logname();
105 	/*
106 	 * Set locale for all categories.
107 	 */
108 	setlocale(LC_ALL, NOGETTEXT(""));
109 
110 	sccs_setinsbase(INS_BASE);
111 
112 	/*
113 	 * Set directory to search for general l10n SCCS messages.
114 	 */
115 #ifdef	PROTOTYPES
116 	(void) bindtextdomain(NOGETTEXT("SUNW_SPRO_SCCS"),
117 	    NOGETTEXT(INS_BASE "/" SCCS_BIN_PRE "lib/locale/"));
118 #else
119 	(void) bindtextdomain(NOGETTEXT("SUNW_SPRO_SCCS"),
120 	    NOGETTEXT("/usr/ccs/lib/locale/"));
121 #endif
122 
123 	(void) textdomain(NOGETTEXT("SUNW_SPRO_SCCS"));
124 
125 	tzset();	/* Set up timezome related vars */
126 
127 #ifdef	SCHILY_BUILD
128 	save_args(argc, argv);
129 #endif
130 	set_clean_up(clean_up);
131 	Fflags = FTLEXIT | FTLMSG | FTLCLN;
132 #ifdef	SCCS_FATALHELP
133 	Fflags |= FTLFUNC;
134 	Ffunc = sccsfatalhelp;
135 #endif
136 
137 	current_optind = 1;
138 	optind = 1;
139 	opterr = 0;
140 	no_arg = 0;
141 	i = 1;
142 	/*CONSTCOND*/
143 	while (1) {
144 			if (current_optind < optind) {
145 			    current_optind = optind;
146 			    argv[i] = 0;
147 			    if (optind > i+1) {
148 				if ((argv[i+1][0] != '-') && (no_arg == 0)) {
149 					argv[i+1] = NULL;
150 				} else {
151 					optind = i+1;
152 					current_optind = optind;
153 				}
154 			    }
155 			}
156 			no_arg = 0;
157 			i = current_optind;
158 			c = getopt(argc, argv, "()-r:snqN:X:V(version)");
159 
160 				/*
161 				 * This takes care of options given after
162 				 * file names.
163 				 */
164 			if (c == EOF) {
165 			    if (optind < argc) {
166 				/* if it's due to -- then break; */
167 				if (argv[i][0] == '-' &&
168 				    argv[i][1] == '-') {
169 					argv[i] = 0;
170 					break;
171 				}
172 				optind++;
173 				current_optind = optind;
174 				continue;
175 			    } else {
176 				break;
177 			    }
178 			}
179 			p = optarg;
180 			testmore = 0;
181 			switch (c) {
182 
183 			case 'r':
184 				if ((p[0] == 0) ||
185 				    (isdigit(((unsigned char *)p)[0]) == 0)) {
186 					no_arg = 1;
187 					continue;
188 				}
189 				chksid(sid_ab(p, &sid), &sid);
190 				break;
191 			case 'n':
192 			case 's':
193 				testmore++;
194 				break;
195 			case 'q': /* enable NSE mode */
196 				if (p) {
197 					if (*p) {
198 						nsedelim = p;
199 					}
200 				} else {
201 					nsedelim = (char *) 0;
202 				}
203 				break;
204 
205 			case 'N':	/* Bulk names */
206 				initN(&N);
207 				if (optarg == argv[i+1]) {
208 				   no_arg = 1;
209 				   break;
210 				}
211 				N.n_parm = p;
212 				break;
213 
214 			case 'X':	/* -Xtended options */
215 				X.x_parm = optarg;
216 				X.x_flags = XO_NULLPATH;
217 				if (!parseX(&X))
218 					goto err;
219 				had[NLOWER+c-'A'] = 0;	/* Allow mult -X */
220 				break;
221 
222 			case 'V':		/* version */
223 				p = sname(argv[0]);
224 				printf(gettext(
225 				    "%s %s-SCCS version %s %s (%s-%s-%s)\n"),
226 					p,
227 					PROVIDER,
228 					VERSION,
229 					VDATE,
230 					HOST_CPU, HOST_VENDOR, HOST_OS);
231 				exit(EX_OK);
232 
233 			default:
234 			err:
235 				p = sname(argv[0]);
236 				if (equal(p,"sact"))
237 					fatal(gettext("Usage: sact [-s][-N[bulk-spec]] s.filename ..."));
238 				else
239 					fatal(gettext("Usage: unget [-ns][-r SID][-N[bulk-spec]][ -Xxopts ] s.filename ..."));
240 			}
241 
242 			if (testmore) {
243 				testmore = 0;
244 				if (p) {
245 				    if (*p) {
246 					sprintf(SccsError,
247 						gettext("value after %c arg (cm7)"),
248 						c);
249 					fatal(SccsError);
250 				    }
251 				}
252 			}
253 
254 			/*
255 			 * Make sure that we only collect option letters from
256 			 * the range 'a'..'z' and 'A'..'Z'.
257 			 */
258 			if (ALPHA(c) &&
259 			    (had[LOWER(c)? c-'a' : NLOWER+c-'A']++)) {
260 				if (c != 'X')
261 					fatal(gettext("key letter twice (cm2)"));
262 			}
263 	}
264 
265 	for (i = 1; i < argc; i++) {
266 		if (argv[i]) {
267 			num_files++;
268 		}
269 	}
270 
271 	if (num_files == 0)
272 		fatal(gettext("missing file arg (cm3)"));
273 
274 	setsig();
275 	xsethome(NULL);
276 	if (HADUCN) {					/* Parse -N args  */
277 		parseN(&N);
278 
279 		if (N.n_sdot && (sethomestat & SETHOME_OFFTREE))
280 			fatal(gettext("-Ns. not supported in off-tree project mode"));
281 	}
282 
283 	/*
284 	 *	If envoked as "sact", set flag
285 	 *	otherwise executed as "unget".
286 	 */
287 	if (equal(sname(argv[0]), NOGETTEXT("sact"))) {
288 		cmd = 1;
289 	}
290 
291 	/*
292 	 * Get the name of our machine to be used for the lockfile.
293 	 */
294 	uname(&un);
295 	uuname = un.nodename;
296 
297 	/*
298 	 * Set up a project global lock on the changeset file.
299 	 * Since we set FTLJMP, we do not need to unlockchset() from clean_up().
300 	 */
301 	if (cmd == 0 && SETHOME_CHSET())
302 		lockchset(getppid(), getpid(), uuname);
303 	timerchsetlock();
304 
305 	Fflags &= ~FTLEXIT;
306 	Fflags |= FTLJMP;
307 	for (i = 1; i < argc; i++)
308 		if ((p = argv[i]) != NULL)
309 			do_file(p, unget, 1, N.n_sdot, &X);
310 
311 	/*
312 	 * Only remove the global lock it it was created by us and not by
313 	 * our parent.
314 	 */
315 	if (cmd == 0 && SETHOME_CHSET()) {
316 		if (HADUCN)
317 			bulkchdir(&N);
318 		unlockchset(getpid(), uuname);
319 	}
320 
321 	return (Fcnt ? 1 : 0);
322 }
323 
324 static void
unget(file)325 unget(file)
326 char *file;
327 {
328 	char	gfilename[FILESIZE];
329 	char	str[max(BUFSIZ, SID_STRSIZE)];
330 	struct	pfile *pp;
331 	uid_t	holduid;
332 
333 	if (setjmp(Fjmp))
334 		return;
335 
336 	/*
337 	 * In order to make the global lock with a potentially long duration
338 	 * not look as if it was expired, we refresh it for every file in our
339 	 * task list. This is needed since another SCCS instance on a different
340 	 * NFS machine cannot use kill() to check for a still active process.
341 	 */
342 	if (SETHOME_CHSET()) {
343 		if (HADUCN)
344 			bulkchdir(&N);	/* Done by bulkprepare() anyway */
345 		refreshchsetlock();
346 	}
347 
348 	if (HADUCN) {
349 #ifdef	__needed__
350 		char	*ofile = file;
351 #endif
352 
353 		file = bulkprepare(&N, file);
354 		if (file == NULL) {
355 #ifdef	__needed__
356 			if (N.n_ifile)
357 				ofile = N.n_ifile;
358 #endif
359 			/*
360 			 * The error is typically
361 			 * "directory specified as s-file (cm14)"
362 			 */
363 			fatal(gettext(bulkerror(&N)));
364 		}
365 		if (sid.s_rel == 0 && N.n_sid.s_rel != 0) {
366 			sid.s_rel = N.n_sid.s_rel;
367 			sid.s_lev = N.n_sid.s_lev;
368 			sid.s_br  = N.n_sid.s_br;
369 			sid.s_seq = N.n_sid.s_seq;
370 		}
371 	}
372 
373 	/*
374 	 * Initialize packet, but do not open SCCS file.
375 	 * As we do not open the file, we may obtain the lock later.
376 	 */
377 	sinit(&gpkt, file, SI_INIT);
378 	gpkt.p_stdout = stdout;
379 	gpkt.p_verbose = (HADS) ? 0 : 1;
380 
381 	if (HADUCN && N.n_ifile) {
382 		copy(N.n_ifile, gfilename);
383 	} else {
384 		copy(auxf(gpkt.p_file, 'g'), gfilename);
385 	}
386 	if (cmd == 0) {
387 	    if (gpkt.p_verbose && (num_files > 1 || had_dir || had_standinp))
388 		fprintf(gpkt.p_stdout, "\n%s:\n", gpkt.p_file);
389 	} else {
390 	    /*  envoked as "sact", call catpfile() and return. */
391 	    catpfile(&gpkt);
392 	    return;
393 	}
394 
395 	/*
396 	 * Lock out any other user who may be trying to process
397 	 * the same file.
398 	 */
399 	if (!islockchset(copy(auxf(gpkt.p_file, 'z'), Zhold)) &&
400 	    lockit(Zhold, SCCS_LOCK_ATTEMPTS, getpid(), uuname)) {
401 		lockfatal(Zhold, getpid(), uuname);
402 	} else {
403 		timersetlockfile(Zhold);
404 	}
405 
406 	pp = edpfile(&gpkt, &sid);
407 	if (gpkt.p_verbose) {
408 		sid_ba(&pp->pf_nsid, str);
409 		fprintf(gpkt.p_stdout, "%s\n", str);
410 	}
411 
412 	/*
413 	 *	If the size of the q-file is greater than zero,
414 	 *	rename the q-file the p-file and remove the
415 	 *	old p-file; else remove both the q-file and
416 	 *	the p-file.
417 	 */
418 	if (Szqfile)
419 		rename(auxf(gpkt.p_file, 'q'), Pfilename);
420 	else {
421 		xunlink(Pfilename);
422 		xunlink(auxf(gpkt.p_file, 'q'));
423 	}
424 	ffreeall();
425 
426 	timersetlockfile(NULL);
427 	if (!islockchset(Zhold))
428 		unlockit(Zhold, getpid(), uuname);
429 
430 	if (!HADN) {
431 		fflush(gpkt.p_stdout);
432 
433 		holduid = geteuid();
434 		seteuid(getuid());
435 		unlink(gfilename);
436 		seteuid(holduid);
437 	}
438 }
439 
440 
441 static struct pfile *
edpfile(pkt,sp)442 edpfile(pkt, sp)
443 struct packet *pkt;
444 struct sid *sp;
445 {
446 	static	struct pfile goodpf;
447 	char	*user, *cp;
448 	char	line[BUFSIZ];
449 	struct	pfile pf;
450 	int	cnt, name;
451 	FILE	*in, *out;
452 
453 	cnt = -1;
454 	name = 0;
455 	user = logname();
456 	zero((char *)&goodpf, sizeof (goodpf));
457 	in = xfopen(auxf(pkt->p_file, 'p'), O_RDONLY|O_BINARY);
458 	cp  = auxf(pkt->p_file, 'q');
459 	out = xfcreat(cp, (mode_t)0644);
460 	while (fgets(line, sizeof (line), in) != NULL) {
461 		pf_ab(line, &pf, 1);
462 		if (equal(pf.pf_user, user)) {
463 			name++;
464 			if (sp->s_rel == 0) {
465 				if (++cnt) {
466 					fclose(out);
467 					fclose(in);
468 					fatal(gettext("SID must be specified (un1)"));
469 				}
470 				goodpf = pf;
471 				continue;
472 			} else if (sp->s_rel == pf.pf_nsid.s_rel &&
473 				sp->s_lev == pf.pf_nsid.s_lev &&
474 				sp->s_br == pf.pf_nsid.s_br &&
475 				sp->s_seq == pf.pf_nsid.s_seq) {
476 					goodpf = pf;
477 					continue;
478 			}
479 		}
480 		if (fputs(line, out) == EOF) {
481 			xmsg(cp, NOGETTEXT("edpfile"));
482 		}
483 	}
484 	fflush(out);
485 	fflush(stderr);
486 	fstat((int) fileno(out), &Statbuf);
487 	Szqfile = Statbuf.st_size;
488 	copy(auxf(pkt->p_file, 'p'), Pfilename);
489 	fclose(out);
490 	fclose(in);
491 	if (!goodpf.pf_user[0]) {
492 		if (!name)
493 			fatal(gettext("login name not in p-file (un2)"));
494 		else fatal(gettext("specified SID not in p-file (un3)"));
495 	}
496 	return (&goodpf);
497 }
498 
499 
500 /*
501  * clean_up() only called from fatal().
502  */
503 
504 static void
clean_up()505 clean_up()
506 {
507 	/*
508 	 * Lockfile and q-file only removed if lockfile
509 	 * was created by this process.
510 	 */
511 	uname(&un);
512 	uuname = un.nodename;
513 	if (mylock(auxf(gpkt.p_file, 'z'), getpid(), uuname)) {
514 		unlink(auxf(gpkt.p_file, 'q'));
515 		ffreeall();
516 		timersetlockfile(NULL);
517 		if (!islockchset(Zhold))
518 			unlockit(Zhold, getpid(), uuname);
519 	}
520 }
521 
522 static void
catpfile(pkt)523 catpfile(pkt)
524 struct packet *pkt;
525 {
526 	int c;
527 	FILE *in;
528 
529 	if (!(in = fopen(auxf(pkt->p_file, 'p'), NOGETTEXT("rb")))) {
530 	    if (gpkt.p_verbose && (num_files > 1 || had_dir || had_standinp))
531 		fprintf(stderr, "\n%s:\n", gpkt.p_file);
532 	    if (cmd == 1 && HADS) {
533 		clean_up();
534 		exit(1);
535 	    } else {
536 		fatal(gettext("No outstanding deltas"));
537 	    }
538 	} else {
539 	    if (gpkt.p_verbose && (num_files > 1 || had_dir || had_standinp))
540 		fprintf(gpkt.p_stdout, "\n%s:\n", gpkt.p_file);
541 	    while ((c = getc(in)) != EOF)
542 		putc(c, pkt->p_stdout);
543 	    fclose(in);
544 	}
545 }
546