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