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 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25 /*
26 * Copyright 2006-2020 J. Schilling
27 *
28 * @(#)sccs.c 1.136 20/09/10 J. Schilling
29 */
30 #if defined(sun)
31 #pragma ident "@(#)sccs.c 1.136 20/09/10 J. Schilling"
32 #endif
33 /*
34 * @(#)sccs.c 1.85 06/12/12
35 */
36 #define SCCS_MAIN /* define global vars */
37 #include <defines.h>
38 #ifndef lint
39 static UConst char sccsid[] = "@(#)sccs.c 1.2 2/27/90";
40 #endif
41 #include <version.h>
42 #include <i18n.h>
43 #include <schily/dirent.h>
44 #include <schily/errno.h>
45 #include <schily/signal.h>
46 #include <schily/sigset.h>
47 #include <schily/getopt.h>
48 #include <schily/sysexits.h>
49 #ifndef EX_OK
50 #define EX_OK 0
51 #define EX_USAGE 64
52 #define EX_NOINPUT 66
53 #define EX_UNAVAILABLE 69
54 #define EX_SOFTWARE 70
55 #define EX_OSERR 71
56 #endif
57 #ifndef __STDC__
58 extern struct passwd *getpwnam();
59 extern char *getlogin();
60 extern char *getenv();
61 #endif
62 #define VMS_VFORK_OK
63 #include <schily/vfork.h>
64 #include <schily/varargs.h>
65 #include <schily/wait.h>
66 #define comgetline __no_comgetl__
67 #include <schily/schily.h>
68 #undef comgetline
69 #include <schily/pwd.h>
70
71 static char **diffs_np, **diffs_ap;
72
73 static int didvfork;
74
75 /*
76 ** SCCS.C -- human-oriented front end to the SCCS system.
77 **
78 ** Without trying to add any functionality to speak of, this
79 ** program tries to make SCCS a little more accessible to human
80 ** types. The main thing it does is automatically put the
81 ** string "SCCS/s." on the front of names. Also, it has a
82 ** couple of things that are designed to shorten frequent
83 ** combinations, e.g., "delget" which expands to a "delta"
84 ** and a "get".
85 **
86 ** This program can also function as a setuid front end.
87 ** To do this, you should copy the source, renaming it to
88 ** whatever you want, e.g., "syssccs". Change any defaults
89 ** in the program (e.g., syssccs might default -d to
90 ** "/usr/src/sys"). Then recompile and put the result
91 ** as setuid to whomever you want. In this mode, sccs
92 ** knows to not run setuid for certain programs in order
93 ** to preserve security, and so forth.
94 **
95 ** Usage:
96 ** sccs [flags] command [args]
97 **
98 ** Flags:
99 ** -d<dir> <dir> represents a directory to search
100 ** out of. It should be a full pathname
101 ** for general usage. E.g., if <dir> is
102 ** "/usr/src/sys", then a reference to the
103 ** file "dev/bio.c" becomes a reference to
104 ** "/usr/src/sys/dev/bio.c".
105 ** -p<path> prepends <path> to the final component
106 ** of the pathname. By default, this is
107 ** "SCCS". For example, in the -d example
108 ** above, the path then gets modified to
109 ** "/usr/src/sys/dev/SCCS/s.bio.c". In
110 ** more common usage (without the -d flag),
111 ** "prog.c" would get modified to
112 ** "SCCS/s.prog.c". In both cases, the
113 ** "s." gets automatically prepended.
114 ** -r run as the real user.
115 **
116 ** Commands:
117 ** admin,
118 ** get,
119 ** delta,
120 ** rmdel,
121 ** cdc,
122 ** etc. Straight out of SCCS; only difference
123 ** is that pathnames get modified as
124 ** described above.
125 ** enter Front end doing "sccs admin -i<name> <name>"
126 ** create Macro for "enter" followed by "get".
127 ** edit Macro for "get -e".
128 ** editor Edit a file whether or not it is controlled
129 ** by SCCS. Retrieves a version for editing
130 ** before, if needed.
131 ** unedit Removes a file being edited, knowing
132 ** about p-files, etc.
133 ** delget Macro for "delta" followed by "get".
134 ** deledit Macro for "delta" followed by "get -e".
135 ** branch Macro for "get -b -e", followed by "delta
136 ** -s -n", followd by "get -e -t -g".
137 ** diffs "diff" the specified version of files
138 ** and the checked-out version.
139 ** print Macro for "prs -e" followed by "get -p -m".
140 ** tell List what files are being edited.
141 ** info Print information about files being edited.
142 ** clean Remove all files that can be
143 ** regenerated from SCCS files.
144 ** check Like info, but return exit status, for
145 ** use in makefiles.
146 ** fix Remove a top delta & reedit, but save
147 ** the previous changes in that delta.
148 ** istext Check whether the argument files need to be
149 ** encoded
150 **
151 ** Compilation Flags:
152 ** UIDUSER -- determine who the user is by looking at the
153 ** uid rather than the login name -- for machines
154 ** where SCCS gets the user in this way.
155 ** SCCSDIR -- if defined, forces the -d flag to take on
156 ** this value. This is so that the setuid
157 ** aspects of this program cannot be abused.
158 ** This flag also disables the -p flag.
159 ** SCCSPATH -- the default for the -p flag.
160 ** MYNAME -- the title this program should print when it
161 ** gives error messages.
162 **
163 ** Compilation Instructions:
164 ** cc -O -n -s sccs.c
165 ** The flags listed above can be -D defined to simplify
166 ** recompilation for variant versions.
167 **
168 ** Author:
169 ** Eric Allman, UCB/INGRES
170 ** Copyright 1980 Regents of the University of California
171 */
172
173 /******************* Configuration Information ********************/
174
175 #ifndef SCCSPATH
176 #define SCCSPATH "SCCS" /* pathname in which to find s-files */
177 #endif /* NOT SCCSPATH */
178
179 #ifndef NSCCS
180 #define NSCCS "-N SCCS" /* conversion option for SCCS cmds */
181 #endif /* NOT NSCCS */
182 #ifndef NSCCSsd
183 #define NSCCSsd "-NSCCS/s." /* conversion option for libfind */
184 #endif /* NOT NSCCSsd */
185
186 #ifndef MYNAME
187 #define MYNAME "sccs" /* name used for printing errors */
188 #endif /* NOT MYNAME */
189
190 #ifdef DESTDIR
191 #ifndef V6
192 #if defined(__STDC__) || defined(PROTOTYPES)
193 #define PROGPATH(name) #name
194 #else
195 #define PROGPATH(name) "name"
196 #endif
197 #else
198 #if defined(__STDC__) || defined(PROTOTYPES)
199 #define PROGPATH(name) "/usr/ccs/bin/" #name /* place to find binaries */
200 #else
201 #define PROGPATH(name) "/usr/ccs/bin/name" /* place to find binaries */
202 #endif
203 #endif /* V6 */
204 #else
205 #ifdef XPG4
206 #if defined(__STDC__) || defined(PROTOTYPES)
207 #define PROGPATH(name) #name
208 #else
209 #define PROGPATH(name) "name"
210 #endif
211 #else
212 #if defined(__STDC__) || defined(PROTOTYPES)
213 #define PROGPATH(name) "/usr/ccs/bin/" #name /* place to find binaries */
214 #else
215 #define PROGPATH(name) "/usr/ccs/bin/name" /* place to find binaries */
216 #endif
217 #endif /* XPG4 */
218 #endif /* DESTDIR */
219
220 #if defined(INS_BASE) && !defined(XPG4)
221 #undef PROGPATH
222
223 #if defined(__STDC__) || defined(PROTOTYPES)
224 #define PROGPATH(name) INS_BASE "/" SCCS_BIN_PRE "bin/" #name /* place to find binaries */
225 #else
226 /*
227 * XXX With a K&R compiler, you need to edit the following string in case
228 * XXX you like to change the install path.
229 */
230 #define PROGPATH(name) "/usr/ccs/bin/name" /* place to find binaries */
231 #endif
232 #endif /* defined(INS_BASE) && !defined(XPG4) */
233
234
235 /**************** End of Configuration Information ****************/
236
237 typedef int bool;
238 #define TRUE 1
239 #define FALSE 0
240
241 #define FORCE_FORK 2 /* alternative "TRUE" forkflag */
242
243 #define bitset(bit, word) ((bool) ((bit) & (word)))
244
245 struct sccsprog
246 {
247 char *sccsname; /* name of SCCS routine */
248 short sccsoper; /* opcode, see below */
249 short sccsflags; /* flags, see below */
250 char *sccspath; /* pathname of binary implementing */
251 };
252
253 struct list_files
254 {
255 struct list_files *next;
256 char *filename;
257 char *s_filename;
258 };
259
260 /*
261 * Macro to skip the following names: "", ".", "..".
262 */
263 #define dot_dotdot(n) ((n)[(n)[0] != '.' ? 0 : (n)[1] != '.' ? 1 : 2] == '\0')
264
265
266 int main __PR((int argc, char **argv));
267 static char *getNsid __PR((char *file, char *user));
268 int command __PR((char **argv, bool forkflag, char *arg0));
269 static void get_sccscomment __PR((void));
270 static void get_list_files __PR((struct list_files **listftailpp, char *filename, bool no_sdot));
271 static struct sccsprog *lookup __PR((char *name));
272 static int callprog __PR((char *progpath, int flags, char **argv, bool forkflag));
273 static char *makefile __PR((char *name, const char *in_SccsDir));
274 static bool isdir __PR((char *name));
275 static bool isfile __PR((char *name));
276 static bool safepath __PR((register char *p));
277 static char *xstrdup __PR((char *file));
278 static int fix __PR((int nfiles, char **argv));
279 static int clean __PR((int mode, char **argv));
280 static void nothingedited __PR((bool nobranch, const char *usernm));
281 static bool isbranch __PR((char *sid));
282 static int unedit __PR((int nfiles, char **argv));
283 static void do_unedit __PR((char *fn));
284 static char *tail __PR((register char *fn));
285 static struct p_file *getpfent __PR((FILE *pfp));
286 static int checkpfent __PR((struct p_file *pf));
287 static char *nextfield __PR((register char *p));
288 static void putpfent __PR((register struct p_file *pf, register FILE *f));
289 static void syserr __PR((const char *f, ...));
290 static char *gstrcat __PR((char *to, char *from, unsigned int xlength));
291 static char *gstrncat __PR((char *to, char *from, int n, unsigned int xlength));
292 static char *gstrcpy __PR((char *to, const char *from, unsigned int xlength));
293 static void gstrbotch __PR((const char *str1, const char *str2));
294 static int diffs __PR((int nfiles, char **argv));
295 static void do_diffs __PR((char *file));
296 static int enter __PR((int nfiles, char **argv));
297 static int editor __PR((int nfiles, char **argv));
298 static int histfile __PR((int nfiles, int argc, char **argv));
299 static int istext __PR((int nfiles, int argc, char **argv));
300 static char *makegfile __PR((char *name));
301 #ifdef USE_RECURSIVE
302 static int dorecurse __PR((char **argv, char **np, char *dir, struct sccsprog *cmd));
303 #endif
304 static int fgetchk __PR((char *file, int dov6, int silent));
305
306 /* values for sccsoper */
307 #define PROG 0 /* call a program */
308 #define CMACRO 1 /* command substitution macro */
309 #define FIX 2 /* fix a delta */
310 #define CLEAN 3 /* clean out recreatable files */
311 #define UNEDIT 4 /* unedit a file */
312 #ifdef V6
313 #define SHELL 5 /* call a shell file (like PROG) */
314 #endif
315 #define DIFFS 6 /* diff between sccs & file out */
316 #define ENTER 7 /* enter new files */
317 #define EDITOR 8 /* get -e + call $EDITOR */
318 #define ISTEXT 9 /* check whether file needs encoding */
319 #define ADD 10 /* add specified files on next commit */
320 #define COMMIT 11 /* commit changes to project repository */
321 #define INIT 12 /* initialize empty project repository */
322 #define REMOVE 13 /* remove specified files on next commit */
323 #define RENAME 14 /* rename specified files on next commit */
324 #define ROOT 15 /* show project root directory */
325 #define STATUS 16 /* show changed files in the project */
326 #define HISTFILE 17 /* give history file path for s-file */
327
328 /* bits for sccsflags */
329 #define NO_SDOT 0001 /* no s. in front of args */
330 #define REALUSER 0002 /* protected (e.g., admin) */
331 #define RF_OK 0004 /* -R allowed with this command */
332 #define PDOT 0010 /* process based on on p. files */
333 #define COLLECT 0020 /* collect file names and call only once */
334 #define NO_N 0040 /* -N option not supported by program */
335
336 /* modes for the "clean", "info", "check" ops */
337 #define CLEANC 0 /* clean command */
338 #define INFOC 1 /* info command */
339 #define CHECKC 2 /* check command */
340 #define TELLC 3 /* give list of files being edited */
341
342 /*
343 ** Description of commands known to this program.
344 ** First argument puts the command into a class. Second arg is
345 ** info regarding treatment of this command. Third arg is a
346 ** list of flags this command accepts from macros, etc. Fourth
347 ** arg is the pathname of the implementing program, or the
348 ** macro definition, or the arg to a sub-algorithm.
349 */
350
351 static struct sccsprog SccsProg[] =
352 {
353 { "admin", PROG, REALUSER, PROGPATH(admin) },
354 { "cdc", PROG, 0, PROGPATH(rmdel) },
355 { "comb", PROG, 0, PROGPATH(comb) },
356 { "cvt", PROG, RF_OK, PROGPATH(sccscvt) },
357 { "delta", PROG, RF_OK|PDOT, PROGPATH(delta) },
358 { "get", PROG, RF_OK, PROGPATH(get) },
359 { "help", PROG, NO_SDOT|NO_N, PROGPATH(help) },
360 { "log", PROG, RF_OK|COLLECT, PROGPATH(sccslog) },
361 { "prs", PROG, RF_OK, PROGPATH(prs) },
362 { "prt", PROG, RF_OK, PROGPATH(prt) },
363 { "rmdel", PROG, REALUSER, PROGPATH(rmdel) },
364 { "sact", PROG, RF_OK, PROGPATH(sact) },
365 { "val", PROG, RF_OK, PROGPATH(val) },
366 { "what", PROG, NO_SDOT|NO_N, PROGPATH(what) },
367 #ifndef V6
368 { "sccsdiff", PROG, REALUSER, PROGPATH(sccsdiff) },
369 { "sccsdiffs", PROG, REALUSER, PROGPATH(sccsdiff) },
370 { "rcs2sccs", PROG, REALUSER|NO_N, PROGPATH(rcs2sccs) },
371 #else
372 { "sccsdiff", SHELL, REALUSER, PROGPATH(sccsdiff) },
373 { "sccsdiffs", SHELL, REALUSER, PROGPATH(sccsdiff) },
374 { "rcs2sccs", SHELL, REALUSER|NO_N, PROGPATH(rcs2sccs) },
375 #endif /* V6 */
376 { "edit", CMACRO, RF_OK|NO_SDOT, "get -e" },
377 { "editor", EDITOR, NO_SDOT, NULL },
378 { "histfile", HISTFILE, NO_SDOT, NULL },
379 { "delget", CMACRO, RF_OK|NO_SDOT|PDOT,
380 "delta:mysropdfq/get:ixbeskclo -t" },
381 { "deledit", CMACRO, RF_OK|NO_SDOT|PDOT,
382 "delta:mysropdfq/get:ixbskclo -e -t -d" },
383 { "fix", FIX, NO_SDOT, NULL },
384 { "clean", CLEAN, RF_OK|REALUSER|NO_SDOT, (char *) CLEANC },
385 { "info", CLEAN, RF_OK|REALUSER|NO_SDOT, (char *) INFOC },
386 { "check", CLEAN, RF_OK|REALUSER|NO_SDOT, (char *) CHECKC },
387 { "tell", CLEAN, RF_OK|REALUSER|NO_SDOT, (char *) TELLC },
388 { "istext", ISTEXT, REALUSER|NO_SDOT, NULL },
389 { "unedit", UNEDIT, RF_OK|NO_SDOT|PDOT, NULL },
390 { "unget", PROG, RF_OK|PDOT, PROGPATH(unget) },
391 { "diffs", DIFFS, RF_OK|NO_SDOT|PDOT|REALUSER, NULL },
392 { "ldiffs", DIFFS, RF_OK|NO_SDOT|PDOT|REALUSER, NULL },
393 { "-diff", PROG, NO_SDOT|REALUSER|NO_N, PROGPATH(diff) },
394 { "-ldiff", PROG, NO_SDOT|REALUSER|NO_N, "diff" },
395 { "print", CMACRO, RF_OK|NO_SDOT, "prs:ar -e/get:Anr -p -m -s" },
396 { "branch", CMACRO, RF_OK|NO_SDOT,
397 "get:ixrc -e -b/delta: -s -n -ybranch-place-holder/get:pl -e -t -g" },
398 { "enter", ENTER, NO_SDOT, NULL },
399 { "create", CMACRO, NO_SDOT,
400 "enter:abdfmortyzV/get:ixbeskclo -t" },
401 { "add", ADD, REALUSER|NO_SDOT, NULL },
402 { "commit", COMMIT, REALUSER|NO_SDOT, NULL },
403 { "init", INIT, REALUSER|NO_SDOT, NULL },
404 { "remove", REMOVE, REALUSER|NO_SDOT, NULL },
405 { "rename", RENAME, REALUSER|NO_SDOT, NULL },
406 { "root", ROOT, REALUSER|NO_SDOT, NULL },
407 { "status", STATUS, REALUSER|NO_SDOT, NULL },
408 { NULL, -1, 0, NULL }
409 };
410
411 /* one line from a p-file */
412 struct p_file
413 {
414 char *p_osid; /* old SID */
415 char *p_nsid; /* new SID */
416 char *p_user; /* user who did edit */
417 char *p_date; /* date of get */
418 char *p_time; /* time of get */
419 char *p_aux; /* extra info at end */
420 };
421
422 static char *SccsPath = SCCSPATH; /* pathname of SCCS files ("SCCS") */
423 static char _NewOpt[] = NSCCS; /* path opt. of SCCS files ("-NSCCS") */
424 static char *NewOpt = _NewOpt; /* make it modifyable */
425 static char *NewOptsd = NSCCSsd; /* path opt. of files ("-NSCCS/s.") */
426 #ifdef SCCSDIR
427 static char *SccsDir = SCCSDIR; /* directory to begin search from */
428 #else
429 static char *SccsDir = "";
430 #endif
431 static char MyName[] = MYNAME; /* name used in messages */
432 static int OutFile = -1; /* override output file for commands */
433 static bool RealUser; /* if set, running as real user */
434 static bool NewMode; /* if set, we use -NSCCS */
435 #ifdef DEBUG
436 static bool Debug; /* turn on tracing */
437 #endif
438 #ifndef V6
439 #ifndef PROTOTYPES
440 extern char *getenv();
441 #endif
442 #endif /* V6 */
443
444 static Nparms N; /* Keep -NSCCS parameters */
445 static Nparms Nsd; /* Keep -NSCCS/s. parameters */
446
447 #ifdef XPG4
448 /*static char path[] = NOGETTEXT("PATH=/usr/xpg4/bin:/usr/ccs/bin:/usr/bin");*/
449 #ifdef PROTOTYPES
450 static char path[] = NOGETTEXT("PATH=" INS_BASE "/xpg4/bin:" INS_BASE "/ccs/bin:/usr/bin");
451 #else
452 /*
453 * XXX With a K&R compiler, you need to edit the following string in case
454 * XXX you like to change the install path.
455 */
456 static char path[] = NOGETTEXT("PATH=/usr/xpg4/bin:/usr/ccs/bin:/usr/bin");
457 #endif
458 #endif
459
460 static struct sccsprog *maincmd = NULL;
461 static struct sccsprog *curcmd = NULL;
462
463 #ifdef HAVE_STRSIGNAL
464 #else
465 #ifdef HAVE_SYS_SIGLIST
466 #ifndef HAVE_SYS_SIGLIST_DEF
467 extern char *sys_siglist[];
468 #endif
469 #endif
470 #endif
471
472 extern int Fcnt;
473
474 static int create_macro = 0; /* 1 if "sccs create ..." command is running. */
475 static int del_macro = 0; /* 1 if "sccs deledit ..." or "sccs delget ..." commands are running. */
476
477 static int Rflag = 0; /* -R recursive operation selected */
478 static char *Cwd; /* -C parameter for delta/get */
479 #ifdef USE_RECURSIVE
480 static int Cwdlen; /* Allocation length for Cwd */
481 #endif
482
483 static bool Tgotedit;
484 static bool Tnobranch;
485 static char *Tusernm;
486
487 #define FBUFSIZ BUFSIZ
488 #define PFILELG 120
489
490 int
main(argc,argv)491 main(argc, argv)
492 int argc;
493 char **argv;
494 {
495 register char *p;
496 register int i;
497 int current_optind, c;
498 register char *argp;
499 bool use_old = FALSE;
500 bool use_new = FALSE;
501
502 #ifndef V6
503 #ifndef SCCSDIR
504 register struct passwd *pw;
505 char buf[FBUFSIZ], cwdpath[FBUFSIZ];
506 #endif
507
508 /*
509 * Set locale for all categories.
510 */
511 setlocale(LC_ALL, NOGETTEXT(""));
512
513 sccs_setinsbase(INS_BASE);
514
515 /*
516 * Set directory to search for general l10n SCCS messages.
517 */
518 #ifdef PROTOTYPES
519 (void) bindtextdomain(NOGETTEXT("SUNW_SPRO_SCCS"),
520 NOGETTEXT(INS_BASE "/" SCCS_BIN_PRE "lib/locale/"));
521 #else
522 (void) bindtextdomain(NOGETTEXT("SUNW_SPRO_SCCS"),
523 NOGETTEXT("/usr/ccs/lib/locale/"));
524 #endif
525
526 (void) textdomain(NOGETTEXT("SUNW_SPRO_SCCS"));
527
528 tzset(); /* Set up timezome related vars */
529
530 #ifdef SCHILY_BUILD
531 save_args(argc, argv);
532 #endif
533
534 Fflags = FTLEXIT | FTLMSG | FTLCLN;
535 #ifdef SCCS_FATALHELP
536 Fflags |= FTLFUNC;
537 Ffunc = sccsfatalhelp;
538 #endif
539
540 #ifndef SCCSDIR
541 /* Pull "SccsDir" out of the environment variable */
542 /* */
543 /* PROJECTDIR */
544 /* If contains an absolute path name (beginning with a */
545 /* slash), sccs searches for SCCS history files in the */
546 /* directory given by that variable. */
547 /* */
548 /* If PROJECTDIR does not begin with a slash, it is taken */
549 /* as the name of a user, and sccs searches the src or */
550 /* source subdirectory of that user's home directory for */
551 /* history files. If such a directory is found, it is */
552 /* used. Otherwise, the value is used as a relative path */
553 /* name. */
554
555 p = getenv(NOGETTEXT("PROJECTDIR"));
556 if (p != NULL && p[0] != '\0') {
557 if (p[0] == '/') {
558 SccsDir = p;
559 } else {
560 SccsDir = NULL;
561 pw = getpwnam(p);
562 if (pw != NULL) {
563 SccsDir = buf;
564 gstrcpy(buf, pw->pw_dir, sizeof (buf));
565 gstrcat(buf, NOGETTEXT("/src/."), sizeof (buf));
566 if (access(buf, 0) < 0) {
567 gstrcpy(buf, pw->pw_dir, sizeof (buf));
568 gstrcat(buf, NOGETTEXT("/source/."),
569 sizeof (buf));
570 if (access(buf, 0) < 0) {
571 gstrcpy(buf, pw->pw_dir,
572 sizeof (buf));
573 gstrcat(buf, p, sizeof (buf));
574 if (access(buf, 0) < 0) {
575 SccsDir = NULL;
576 }
577 }
578 }
579 }
580 if (SccsDir == NULL) {
581 if (getcwd(cwdpath, FBUFSIZ - 1) == NULL) {
582 usrerr(
583 gettext("cannot determine current directory!"));
584 exit(EX_USAGE);
585 } else { /* Try local directory */
586 sprintf(buf, NOGETTEXT("%s/%s/."), cwdpath, p);
587 if (access(buf, 0) < 0) {
588 usrerr(
589 gettext("project %s has no source!"), p);
590 usrerr(
591 gettext("directory %s doesn't exist in the current directory!"),
592 p);
593 exit(EX_USAGE);
594 }
595 }
596 SccsDir = buf;
597 }
598 /*
599 * Remove trailing "/."
600 */
601 SccsDir[strlen(SccsDir) - 2] = '\0';
602 }
603 }
604 #endif /* SCCSDIR */
605 #endif /* V6 */
606
607 /*
608 ** Detect and decode flags intended for this program.
609 */
610
611 #ifdef V6
612 argv[argc] = NULL;
613 #endif
614
615 #ifdef XPG4
616 if (putenv(path) != 0) {
617 perror(gettext("Sccs: no mem"));
618 exit(EX_OSERR);
619 }
620 #endif
621 if (argv[0] != NULL && lookup(argv[0]) == NULL)
622 {
623
624 current_optind = 1;
625 optind = 1;
626 opterr = 0;
627 i = 1;
628 /*CONSTCOND*/
629 while (1) {
630 if (current_optind < optind) {
631 current_optind = optind;
632 argv[i] = 0;
633 if (optind > i+1) {
634 argv[i+1] = NULL;
635 }
636 i = current_optind;
637 }
638 c = getopt(argc, argv, "()-rp:d:NORTV(version)");
639 if (c == EOF) {
640 break;
641 }
642 argp = optarg;
643 switch (c) {
644 case 'r': /* run as real user */
645 if (setuid(getuid()) < 0)
646 syserr(gettext("Cannot set uid"));
647 RealUser++;
648 break;
649
650 #ifndef SCCSDIR
651 case 'p': /* path of sccs files */
652 SccsPath = argp;
653 NewOpt = NULL;
654 break;
655
656 case 'd': /* directory to search from */
657 SccsDir = argp;
658 break;
659 #endif
660
661 #ifdef DEBUG
662 case 'T': /* trace */
663 sccs_setdebug(++Debug);
664 break;
665 #endif
666
667 case 'N':
668 use_old = FALSE;
669 use_new = TRUE;
670 break;
671 case 'O':
672 use_old = TRUE;
673 use_new = FALSE;
674 break;
675
676 #ifdef USE_RECURSIVE
677 case 'R': /* recursion */
678 Rflag++;
679 break;
680 #endif
681
682 case 'V': /* version */
683 printf(gettext(
684 "sccs %s-SCCS version %s %s (%s-%s-%s)\n"),
685 PROVIDER,
686 VERSION,
687 VDATE,
688 HOST_CPU, HOST_VENDOR, HOST_OS);
689 exit(EX_OK);
690
691 default:
692 usrerr("%s %s", gettext("unknown option"), argv[i]);
693 #ifdef USE_RECURSIVE
694 fprintf(stderr, gettext("Usage: sccs [-N][-O][-R][ -r ][ -drootprefix ][ -p subdir ]\n\t subcommand [ option ...] [ filename ...]\n"));
695 #else
696 fprintf(stderr, gettext("Usage: sccs [ -r ][ -drootprefix ][ -p subdir ]\n\t subcommand [ option ...] [ filename ...]\n"));
697 #endif
698 exit(EX_USAGE);
699 }
700 }
701 argv += current_optind;
702 if (SccsPath[0] == '\0')
703 SccsPath = ".";
704 }
705 if (SccsDir == NULL) /* Paranoia */
706 SccsDir = "";
707
708 if (*argv == NULL)
709 {
710 #ifdef USE_RECURSIVE
711 fprintf(stderr, gettext("Usage: sccs [-N][-O][-R][ -r ][ -drootprefix ][ -p subdir ]\n\t subcommand [ option ...] [ filename ...]\n"));
712 #else
713 fprintf(stderr, gettext("Usage: sccs [ -r ][ -drootprefix ][ -p subdir ]\n\t subcommand [ option ...] [ filename ...]\n"));
714 #endif
715 sccshelp(stderr, "basic sub-commands");
716 exit(EX_USAGE);
717 /*NOTREACHED*/
718 }
719
720 if (xsethome(NULL) > 0)
721 NewMode = TRUE;
722
723 if (use_old) {
724 NewMode = FALSE;
725 } else if (use_new) {
726 NewMode = TRUE;
727 } else if ((p = getenv("SCCS_NMODE")) != NULL) {
728 /*
729 * XXX Should we also disable any other SCCS v6 extensions
730 * XXX instead of just disabling the use of -NSCCS?
731 */
732 if (strcmp(p, "FALSE") == 0)
733 NewMode = FALSE;
734 if (strcmp(p, "TRUE") == 0)
735 NewMode = TRUE;
736 /*
737 * In all other cases, keep the value from xsethome(NULL).
738 */
739 } else {
740 #ifndef __not_yet__
741 /*
742 * If environmenment "SCCS_NMODE" is missing,
743 * NewMode is currently always set to FALSE.
744 */
745 NewMode = FALSE;
746 #endif
747 }
748 if (NewMode && NewOpt == NULL) {
749 perror(gettext("Sccs: -p cannot be used in New Mode"));
750 exit(EX_OSERR);
751 }
752 if (NewMode) {
753 setnewmode();
754 if (NewOpt == NULL) {
755 NewOpt = malloc(strlen(SccsPath)+6);
756 if (NewOpt == NULL) {
757 perror(gettext("Sccs: no mem"));
758 exit(EX_OSERR);
759 }
760 strcpy(NewOpt, "-N "); /* Three flag placeholders */
761 strcat(NewOpt, SccsPath);
762
763 NewOptsd = malloc(strlen(SccsPath)+6);
764 if (NewOptsd == NULL) {
765 perror(gettext("Sccs: no mem"));
766 exit(EX_OSERR);
767 }
768 strcpy(NewOptsd, "-N");
769 strcat(NewOptsd, SccsPath);
770 strcat(NewOptsd, "/s.");
771 }
772 initN(&N);
773 initN(&Nsd);
774 N.n_parm = &NewOpt[2];
775 Nsd.n_parm = &NewOptsd[2];
776 parseN(&N);
777 parseN(&Nsd);
778 } else {
779 unsetnewmode();
780 }
781
782 i = command(argv, FALSE, "");
783 return (i);
784 }
785
786
787 static int NelemArrSids;
788 static int size_ap_for_get;
789 static int cur_num_file;
790 static char Nsid[50];
791 static char * user_name;
792 static char * r_option_value;
793 static char ** ArrSids;
794 static char ** ap_for_get;
795 static char ** macro_files;
796
797 /* getNsid() returns the latest checked out version of file. */
798 /* This function is used in 'deledit' macro (see 1083894 bug) */
799
800 static char *
getNsid(file,user)801 getNsid(file, user)
802 char * file;
803 char * user;
804 {
805 int cnt = -1;
806 char line[BUFSIZ];
807 char * cp;
808 struct packet gpkt;
809 struct pfile pf;
810 struct pfile goodpf;
811 FILE * in;
812
813 if (NewMode) {
814 file = bulkprepare(&N, file);
815 if (file == NULL) {
816 /*
817 * The error is typically
818 * "directory specified as s-file (cm14)"
819 */
820 fatal(gettext(bulkerror(&N)));
821 }
822 }
823 sinit(&gpkt, file, SI_INIT);
824 cp = auxf(gpkt.p_file, 'p');
825 if (!exists(cp))
826 return (NULL);
827 strcpy(Nsid, "");
828 zero((char *)&goodpf, sizeof (goodpf));
829 in = xfopen(cp, O_RDONLY|O_BINARY);
830 while (fgets(line, sizeof (line), in) != NULL) {
831 pf_ab(line, &pf, 1);
832 if (equal(pf.pf_user, user) || getuid() == 0) {
833 if (++cnt) {
834 fclose(in);
835 return (r_option_value);
836 }
837 goodpf = pf;
838 continue;
839 }
840 }
841 fclose(in);
842 if (NewMode) {
843 bulkchdir(&N); /* chdir() back to our previous "." */
844 }
845 if (!goodpf.pf_user[0])
846 return (NULL);
847 if (!goodpf.pf_nsid.s_br) {
848 sprintf(Nsid, NOGETTEXT("-r%d.%d"),
849 goodpf.pf_nsid.s_rel, goodpf.pf_nsid.s_lev);
850 } else {
851 sprintf(Nsid, NOGETTEXT("-r%d.%d.%d.%d"),
852 goodpf.pf_nsid.s_rel, goodpf.pf_nsid.s_lev,
853 goodpf.pf_nsid.s_br, goodpf.pf_nsid.s_seq);
854 }
855 return (xstrdup(Nsid));
856 }
857
858 /*
859 ** COMMAND -- look up and perform a command
860 **
861 ** This routine is the guts of this program. Given an
862 ** argument vector, it looks up the "command" (argv[0])
863 ** in the configuration table and does the necessary stuff.
864 **
865 ** Parameters:
866 ** argv -- an argument vector to process.
867 ** forkflag -- if set, fork before executing the command.
868 ** editflag -- if set, only include flags listed in the
869 ** sccsklets field of the command descriptor.
870 ** arg0 -- a space-separated list of arguments to insert
871 ** before argv.
872 **
873 ** Returns:
874 ** zero -- command executed ok.
875 ** else -- error status.
876 **
877 ** Side Effects:
878 ** none.
879 */
880
881 int
command(argv,forkflag,arg0)882 command(argv, forkflag, arg0)
883 char **argv;
884 bool forkflag;
885 char *arg0;
886 {
887 register struct sccsprog *cmd;
888 register char *p;
889 char buf[FILESIZE];
890 char **nav;
891 char macro_opstr[64];
892 char **np, *nextp;
893 register char **ap;
894 register char *q;
895 int ac = 0;
896 int rval = 0;
897 int hady = 0;
898 int len;
899 int nav_size, cnt_files_from_stdin;
900 int nfiles;
901 char *editchs, *macro_opstr_p;
902 bool no_sdot;
903 struct list_files head_files;
904 struct list_files *listftailp;
905 struct list_files *listfilesp;
906
907 #ifdef DEBUG
908 if (Debug)
909 {
910 printf(gettext("command:\n\t\"%s\"\n"), arg0);
911 for (np = argv; *np != NULL; np++)
912 printf("\t\"%s\"\n", *np);
913 }
914 #endif
915 fflush(stdout);
916
917 /*
918 * Select the second half of the macro string for NewMode if avaliable.
919 * Macro string is: "old mode command %new mode command".
920 * If the "new mode command" command starts with a space, up to 3
921 * flag characters for the -N option follow.
922 */
923 if (NewMode && (p = strchr(arg0, '%')) != NULL)
924 arg0 = ++p;
925
926 /* reserve the space for nav[] */
927 nav_size = 0;
928 for (p = arg0, q = buf; *p != '\0' && *p != '/' && *p != '%'; ) {
929 nav_size++;
930 while (*p == ' ')
931 p++;
932 while (*p != ' ' && *p != '\0' &&
933 *p != '/' && *p != '%' && *p != ':')
934 p++;
935 if (*p == ':') {
936 while (*++p != '\0' &&
937 *p != '/' && *p != '%' && *p != ' ')
938 ;
939 }
940 }
941 /* added seven elements for: */
942 /* - command; */
943 /* - additional DIFFS param; */
944 /* - -ycoment; */
945 /* - -Cworkdir; */
946 /* - -NSCCS; */
947 /* - -R spare dir element; */
948 /* - last element (NULL). */
949 for (nav_size += 7, ap = argv; *ap != NULL; ap++) {
950 nav_size++;
951 }
952 if ((nav = malloc(nav_size * (sizeof (char *)))) == NULL) {
953 perror(gettext("Sccs: no mem"));
954 exit(EX_OSERR);
955 }
956
957 /*
958 ** Copy arguments.
959 ** Copy from arg0 & if necessary at most one arg
960 ** from argv[0].
961 */
962
963 np = ap = &nav[1];
964 head_files.next = NULL;
965 listftailp = &head_files;
966 editchs = NULL; /* arg0 -> cmd:editchs/next... */
967 macro_opstr_p = NULL;
968 buf[0] = '\0';
969 if (NewMode) {
970 NewOpt[2] = ' '; /* Reset flags to placeholders */
971 NewOpt[3] = ' ';
972 NewOpt[4] = ' ';
973 }
974 p = arg0;
975 if (NewMode && *p == ' ') {
976 if (*++p == '+')
977 NewOpt[2] = *p++;
978 if (*p == '-')
979 NewOpt[3] = *p++;
980 if (*p == ',')
981 NewOpt[4] = *p++;
982 }
983 for (q = buf; *p != '\0' && *p != '/' && *p != '%'; ) {
984 *np++ = q;
985 while (*p == ' ')
986 p++;
987 while (*p != ' ' && *p != '\0' &&
988 *p != '/' && *p != '%' && *p != ':')
989 *q++ = *p++;
990 *q++ = '\0';
991 if (*p == ':') {
992 editchs = q;
993 while (*++p != '\0' &&
994 *p != '/' && *p != '%' && *p != ' ')
995 *q++ = *p;
996 *q++ = '\0';
997 }
998 }
999 *np = NULL;
1000 if (*ap == NULL)
1001 *np++ = *argv++;
1002
1003 /*
1004 ** Look up command.
1005 ** At this point, *ap is the command name.
1006 */
1007
1008 curcmd = cmd = lookup(*ap);
1009 if (cmd == NULL) {
1010 usrerr("%s \"%s\"", gettext("Unknown command"), *ap);
1011 return (EX_USAGE);
1012 }
1013 #ifdef USE_RECURSIVE
1014 if (Rflag > 0 && !bitset(RF_OK, cmd->sccsflags)) {
1015 usrerr("%s \"%s\"", gettext("Recursion not supported for"), *ap);
1016 return (EX_USAGE);
1017 }
1018 #endif
1019 if (maincmd == NULL)
1020 maincmd = cmd;
1021 no_sdot = bitset(NO_SDOT, cmd->sccsflags);
1022 if (cmd->sccsoper == CMACRO) {
1023
1024 char *cp, *cp_opstr;
1025
1026 /*
1027 * Fill the sum of all permitted option chars into "macro_opstr".
1028 */
1029 cp_opstr = NULL;
1030 cp = cmd->sccspath;
1031 while (*cp != '\0') {
1032 while (*cp == ' ')
1033 cp++;
1034 while (*cp != ' ' && *cp != '\0' &&
1035 *cp != '/' && *p != '%' && *cp != ':')
1036 cp++;
1037 if (*cp == '\0')
1038 continue;
1039 if (*cp == ':') {
1040 if (cp_opstr == NULL) {
1041 macro_opstr_p = cp_opstr = ¯o_opstr[0];
1042 }
1043 cp++;
1044 while (*cp != '\0' && *cp != '/' && *p != '%' && *cp != ' ')
1045 *cp_opstr++ = *cp++;
1046 } else {
1047 cp++;
1048 }
1049 }
1050 if (cp_opstr != NULL) {
1051 *cp_opstr = '\0';
1052 }
1053 }
1054
1055 /*
1056 ** Copy remaining arguments doing editing as appropriate.
1057 */
1058
1059 cnt_files_from_stdin = 0;
1060 for (; *argv != NULL; argv++) {
1061 p = *argv;
1062 if (*p == '-') {
1063 if (p[1] == '\0') {
1064 struct stat _Statbuf;
1065 char *ibuf, *str;
1066 DIR *dirf;
1067 struct dirent *dir;
1068 extern char *Ffile;
1069
1070 ibuf = malloc(BUFSIZ);
1071 if (ibuf == NULL) {
1072 perror(gettext("Sccs: no mem"));
1073 exit(EX_OSERR);
1074 }
1075 while (fgets(ibuf, BUFSIZ, stdin) != NULL) {
1076 char *cp;
1077 int nline;
1078
1079 nline = 0;
1080 for (cp = ibuf; *cp != '\0'; cp++) {
1081 if (*cp == '\n') {
1082 *cp = '\0';
1083 nline = 1;
1084 break;
1085 }
1086 }
1087 if (!nline) {
1088 usrerr(gettext("bad file name \"%s\""), ibuf);
1089 exit(1);
1090 }
1091 if (_exists(ibuf)&&(_Statbuf.st_mode&S_IFMT) == S_IFDIR) {
1092 Ffile = ibuf;
1093 if ((dirf = opendir(ibuf)) == NULL)
1094 break;
1095 while ((dir = readdir(dirf)) != NULL) {
1096 if (dot_dotdot(dir->d_name))
1097 continue;
1098 #ifdef HAVE_DIRENT_D_INO
1099 if (dir->d_ino == 0)
1100 continue;
1101 #endif
1102 str = malloc(BUFSIZ);
1103 if (str == NULL) {
1104 perror(gettext("Sccs: no mem"));
1105 exit(EX_OSERR);
1106 }
1107 sprintf(str, "%s/%s", ibuf, dir->d_name);
1108 get_list_files(&listftailp, str, no_sdot);
1109 cnt_files_from_stdin++;
1110 }
1111 closedir(dirf);
1112 } else {
1113 get_list_files(&listftailp, ibuf, no_sdot);
1114 cnt_files_from_stdin++;
1115 ibuf = malloc(BUFSIZ);
1116 if (ibuf == NULL) {
1117 perror(gettext("Sccs: no mem"));
1118 exit(EX_OSERR);
1119 }
1120 }
1121 }
1122 } else {
1123 char **pp = NULL;
1124
1125 if (macro_opstr_p != 0) {
1126 if (strchr(macro_opstr_p, p[1]) == NULL) {
1127 usrerr("%s %s", gettext("unknown option"), p);
1128 exit(EX_USAGE);
1129 }
1130 }
1131 if (editchs == NULL || strchr(editchs, p[1]) != NULL) {
1132 pp = np;
1133 *np++ = p;
1134 }
1135 nextp = *(argv+1);
1136 if (p[2] == '\0' && nextp != 0 && *nextp != '-') {
1137 if ((strcmp(maincmd->sccsname,"print") != 0) &&
1138 (strcmp(maincmd->sccsname,"prt") != 0) &&
1139 (strcmp(maincmd->sccsname,"branch") != 0) &&
1140 (strcmp(maincmd->sccsname,"vc") != 0)) {
1141 switch(p[1]) {
1142 case 'x':
1143 case 'c':
1144 if (strcmp(cmd->sccsname,"sccsdiff") != 0) {
1145 if (editchs != NULL
1146 && strchr(editchs, p[1]) == NULL) {
1147 argv++;
1148 } else {
1149 *np++ = *++argv;
1150 }
1151 }
1152 break;
1153 case 'u':
1154 if ((strcmp(cmd->sccsname,"sccsdiff") != 0) &&
1155 (strcmp(cmd->sccsname,"diffs") != 0) &&
1156 (strcmp(cmd->sccsname,"-diff") != 0)) {
1157 if (editchs != NULL
1158 && strchr(editchs, p[1]) == NULL) {
1159 argv++;
1160 } else {
1161 *np++ = *++argv;
1162 }
1163 }
1164 break;
1165 case 'a':
1166 if ((strcmp(cmd->sccsname,"prs") != 0) &&
1167 (strcmp(cmd->sccsname,"log") != 0)) {
1168 if (editchs != NULL
1169 && strchr(editchs, p[1]) == NULL) {
1170 argv++;
1171 } else {
1172 *np++ = *++argv;
1173 }
1174 }
1175 break;
1176 case 'e':
1177 if ((strcmp(cmd->sccsname,"get") != 0) &&
1178 (strcmp(maincmd->sccsname,"sccsdiff") != 0) &&
1179 (strcmp(maincmd->sccsname,"diffs") != 0) &&
1180 (strcmp(maincmd->sccsname,"deledit") != 0) &&
1181 (strcmp(maincmd->sccsname,"delget") != 0) &&
1182 (strcmp(maincmd->sccsname,"create") != 0) &&
1183 (strcmp(maincmd->sccsname,"enter") != 0) &&
1184 (strcmp(cmd->sccsname,"prs") != 0)) {
1185 if (editchs != NULL
1186 && strchr(editchs, p[1]) == NULL) {
1187 argv++;
1188 } else {
1189 *np++ = *++argv;
1190 }
1191 }
1192 break;
1193 case 'f':
1194 if ((strcmp(maincmd->sccsname,"create") == 0) ||
1195 ((strcmp(maincmd->sccsname,"diffs") != 0) &&
1196 (strcmp(maincmd->sccsname,"sccsdiff") != 0) &&
1197 (strcmp(cmd->sccsname,"delta") != 0) &&
1198 (strcmp(cmd->sccsname,"get") != 0))) {
1199 if (editchs != NULL
1200 && strchr(editchs, p[1]) == NULL) {
1201 argv++;
1202 } else {
1203 *np++ = *++argv;
1204 }
1205 }
1206 break;
1207 case 'i':
1208 if ((strcmp(cmd->sccsname,"admin") != 0) &&
1209 (strcmp(maincmd->sccsname,"sccsdiff") != 0)) {
1210 if (editchs != NULL
1211 && strchr(editchs, p[1]) == NULL) {
1212 argv++;
1213 } else {
1214 *np++ = *++argv;
1215 }
1216 }
1217 break;
1218 case 'g':
1219 if ((strcmp(cmd->sccsname, "get") != 0)) {
1220 if (editchs != NULL
1221 && strchr(editchs, p[1]) == NULL) {
1222 argv++;
1223 } else {
1224 *np++ = *++argv;
1225 }
1226 }
1227 break;
1228 case 'r':
1229 if (strcmp(cmd->sccsname, "prs") != 0) {
1230 if (strcmp(cmd->sccsname, "get") == 0) {
1231 if (*(omit_sid(nextp)) != '\0') {
1232 break;
1233 }
1234 }
1235 if (editchs != NULL
1236 && strchr(editchs, p[1]) == NULL) {
1237 argv++;
1238 } else {
1239 *np++ = *++argv;
1240 }
1241 }
1242 break;
1243 case 'm':
1244 if ((strcmp(maincmd->sccsname,"deledit") == 0) ||
1245 (strcmp(maincmd->sccsname,"delget") == 0) ||
1246 (strcmp(maincmd->sccsname,"create") == 0) ||
1247 (strcmp(cmd->sccsname, "get") != 0)) {
1248 if (editchs != NULL
1249 && strchr(editchs, p[1]) == NULL) {
1250 argv++;
1251 } else {
1252 *np++ = *++argv;
1253 }
1254 }
1255 break;
1256 case 'd':
1257 if ((strcmp(cmd->sccsname,"prs") == 0) ||
1258 (strcmp(cmd->sccsname,"admin") == 0) ||
1259 (strcmp(maincmd->sccsname,"create") == 0) ||
1260 (strcmp(cmd->sccsname, "enter") == 0)) {
1261 if (editchs != NULL
1262 && strchr(editchs, p[1]) == NULL) {
1263 argv++;
1264 } else {
1265 *np++ = *++argv;
1266 }
1267 }
1268 break;
1269 case 'p':
1270 if (strcmp(cmd->sccsname, "comb") == 0) {
1271 if (editchs != NULL
1272 && strchr(editchs, p[1]) == NULL) {
1273 argv++;
1274 } else {
1275 *np++ = *++argv;
1276 }
1277 }
1278 break;
1279 case 'y':
1280 if (strcmp(cmd->sccsname, "val") == 0) {
1281 if (editchs != NULL
1282 && strchr(editchs, p[1]) == NULL) {
1283 argv++;
1284 } else {
1285 *np++ = *++argv;
1286 }
1287 }
1288 hady = 1;
1289 break;
1290 case 'G':
1291 case 'w':
1292 if (strcmp(cmd->sccsname, "get") == 0) {
1293 if (editchs != NULL
1294 && strchr(editchs, p[1]) == NULL) {
1295 argv++;
1296 } else {
1297 *np++ = *++argv;
1298 }
1299 }
1300 break;
1301 case 'C':
1302 case 'D':
1303 if ((strcmp(cmd->sccsname,"sccsdiff") == 0) ||
1304 (strcmp(cmd->sccsname,"get") == 0) ||
1305 (strcmp(cmd->sccsname,"diffs") == 0)) {
1306 if (editchs != NULL
1307 && strchr(editchs, p[1]) == NULL) {
1308 argv++;
1309 } else {
1310 *np++ = *++argv;
1311 }
1312 }
1313 break;
1314 case 'U':
1315 if ((strcmp(cmd->sccsname,"sccsdiff") == 0) ||
1316 (strcmp(cmd->sccsname,"get") == 0) ||
1317 (strcmp(cmd->sccsname,"diffs") == 0) ||
1318 (strcmp(cmd->sccsname,"-diff") == 0)) {
1319 if (editchs != NULL
1320 && strchr(editchs, p[1]) == NULL) {
1321 argv++;
1322 } else {
1323 *np++ = *++argv;
1324 }
1325 }
1326 break;
1327 default:
1328 break;
1329 }
1330 }
1331 }
1332 if (!hady && strncmp(p, "-y", 2) == 0) {
1333 if (strcmp(cmd->sccsname, "deledit") == 0 ||
1334 strcmp(cmd->sccsname, "delget") == 0)
1335 hady = 1;
1336 }
1337 if (strcmp(p,"-C") == 0) {
1338 if (strcmp(cmd->sccsname, "-diff") == 0) {
1339 if (pp != NULL)
1340 *pp = "-c";
1341 }
1342 }
1343 if (strcmp(p,"-I") == 0) {
1344 if (strcmp(cmd->sccsname, "-diff") == 0) {
1345 if (pp != NULL)
1346 *pp = "-i";
1347 }
1348 }
1349 pp = NULL;
1350 }
1351 } else {
1352 get_list_files(&listftailp, p, no_sdot);
1353 }
1354 }
1355 if (cnt_files_from_stdin) {
1356 char ** new_nav;
1357
1358 nav_size += cnt_files_from_stdin;
1359 if ((new_nav = realloc(nav, nav_size * (sizeof (char *)))) == NULL) {
1360 perror(gettext("Sccs: no mem"));
1361 exit(EX_OSERR);
1362 }
1363 np = new_nav + (np - nav);
1364 nav = new_nav;
1365 ap = &new_nav[1];
1366 }
1367 #ifdef USE_RECURSIVE
1368 if (Rflag > 0) {
1369 np[0] = NULL;
1370 if (!hady &&
1371 (strcmp(cmd->sccsname, "delta") == 0 ||
1372 strcmp(cmd->sccsname, "deledit") == 0 ||
1373 strcmp(cmd->sccsname, "delget") == 0)) {
1374 if (Comments == NULL)
1375 get_sccscomment();
1376 *np++ = Comments;
1377 hady = 1;
1378 }
1379 np[1] = NULL;
1380
1381 listfilesp = head_files.next;
1382 if (listfilesp == NULL)
1383 rval |= dorecurse(ap, np, ".", cmd);
1384 else while (listfilesp != NULL) {
1385 if (cmd->sccsoper == CLEAN && listfilesp->next) {
1386 usrerr(gettext("too many args"));
1387 return (EX_USAGE);
1388 }
1389 rval |= dorecurse(ap, np, listfilesp->filename, cmd);
1390 listfilesp = listfilesp->next;
1391 }
1392 if (cmd->sccsoper == CLEAN && cmd->sccspath == (char *)INFOC &&
1393 !Tgotedit) {
1394 nothingedited(Tnobranch, Tusernm);
1395 }
1396 return (rval);
1397 }
1398 if (Rflag && bitset(COLLECT, cmd->sccsflags) &&
1399 strcmp(cmd->sccsname, "log") == 0) {
1400 *np++ = "-p";
1401 *np++ = SccsPath;
1402 } else if (Cwd && Cwd[2] && cmd->sccsoper == PROG &&
1403 (strcmp(cmd->sccsname, "get") == 0 ||
1404 strcmp(cmd->sccsname, "delta") == 0)) {
1405 *np++ = Cwd;
1406 }
1407 #endif
1408 #ifdef SHELL
1409 if (NewMode && (cmd->sccsoper == PROG || cmd->sccsoper == SHELL) &&
1410 #else
1411 if (NewMode && cmd->sccsoper == PROG &&
1412 #endif
1413 (cmd->sccsflags & NO_N) == 0)
1414 *np++ = NewOpt;
1415 nfiles = 0;
1416 listfilesp = head_files.next;
1417 while (listfilesp != 0) {
1418 if (!no_sdot) {
1419 *np++ = listfilesp->s_filename;
1420 } else {
1421 *np++ = listfilesp->filename;
1422 }
1423 listfilesp = listfilesp->next;
1424 nfiles++;
1425 }
1426 *np = NULL;
1427 ac = np - ap;
1428
1429 /*
1430 ** Interpret operation associated with this command.
1431 */
1432
1433 switch (cmd->sccsoper)
1434 {
1435 #ifdef V6
1436 case SHELL: /* call a shell file */
1437 *ap = cmd->sccspath;
1438 *--ap = NOGETTEXT("sh");
1439 rval = callprog(NOGETTEXT("/bin/sh"), cmd->sccsflags, ap, forkflag);
1440 break;
1441 #endif
1442
1443 case PROG: /* call an sccs prog */
1444 if (create_macro == 1 && strcmp(cmd->sccsname, "get") == 0) {
1445 /*
1446 * The "sccs create ..." macro is running.
1447 */
1448 char Gname[FILESIZE];
1449 char * gfp;
1450 int ind, ind1 = 0;
1451
1452 for (ind = 0; ap[ind] != NULL; ind++) {
1453 ind1 = ind;
1454 }
1455 ind = ind1;
1456 size_ap_for_get = ind + 3;
1457 if (ap_for_get == NULL) {
1458 if ((ap_for_get = malloc(size_ap_for_get * (sizeof (char *)))) == NULL) {
1459 perror(gettext("Sccs: no mem"));
1460 exit(EX_OSERR);
1461 }
1462 for (ind1 = 0; ind1 < ind; ind1++) {
1463 ap_for_get[ind1] = ap[ind1];
1464 }
1465 }
1466 /*
1467 * Get length of possible prefix before SCCS/s.file
1468 * to construct the full path name for the -G option.
1469 */
1470 if (NewMode) {
1471 strcpy(Gname, "-G"); /* Start -G option */
1472 strcat(Gname, ap[ind]); /* Copy g-file name */
1473 } else {
1474 gfp = auxf(ap[ind], 'g');
1475 strcpy(Gname, SccsPath);
1476 strcat(Gname, "/s.");
1477 strcat(Gname, gfp);
1478 len = strlen(ap[ind]) - strlen(Gname);
1479 strcpy(Gname, "-G"); /* Start -G option */
1480 strncat(Gname, ap[ind], /* Copy path base */
1481 len);
1482 strcat(Gname, gfp); /* Append g-file name */
1483 }
1484 ap_for_get[size_ap_for_get - 3] = Gname;
1485 ap_for_get[size_ap_for_get - 2] = ap[ind];
1486 ap_for_get[size_ap_for_get - 1] = NULL;
1487 rval = callprog(cmd->sccspath, cmd->sccsflags, ap_for_get, TRUE);
1488 } else {
1489 if (del_macro == 1) {
1490 /*
1491 * The "sccs deledit ..." or "sccs delget ..."
1492 * macro is running.
1493 */
1494 int ind, ind1;
1495 char ** Arr = NULL;
1496
1497 for (ind = ind1 = 0; ap[ind] != NULL; ind++) {
1498 ind1 = ind;
1499 }
1500 ind = ind1;
1501 if (!isdir(ap[ind])) {
1502 Arr = ArrSids;
1503 if (macro_files != NULL) {
1504 Arr += cur_num_file;
1505 }
1506 }
1507 if (strcmp(cmd->sccsname, "delta") == 0) {
1508 /* first part of del_macro (deledit or delget) */
1509 if (Arr != NULL) {
1510 *Arr = getNsid(ap[ind], user_name);
1511 }
1512 rval = callprog(cmd->sccspath, cmd->sccsflags, ap, TRUE);
1513 } else {
1514 /* second part of del_macro (deledit or delget) */
1515 if (ap_for_get == NULL) {
1516 size_ap_for_get = ind + 3;
1517 if ((ap_for_get = malloc(size_ap_for_get * (sizeof (char *)))) == NULL) {
1518 perror(gettext("Sccs: no mem"));
1519 exit(EX_OSERR);
1520 }
1521 for (ind1 = 0; ind1 < ind; ind1++) {
1522 ap_for_get[ind1] = ap[ind1];
1523 }
1524 ap_for_get[size_ap_for_get - 1] = NULL;
1525 }
1526 if (isdir(ap[ind])) {
1527 ap_for_get[size_ap_for_get - 3] = ap[ind];
1528 ap_for_get[size_ap_for_get - 2] = NULL;
1529 } else {
1530 /* 'delta' command closed delta of file. */
1531 /* its necessary to run get command */
1532 ap_for_get[size_ap_for_get - 3] = *Arr;
1533
1534 /*
1535 * If we call "delget -f -q", we
1536 * have no p. file and thus *Arr
1537 * is NULL. Do not add a sid
1538 * argument in this case, but
1539 * hope that get -t will do.
1540 * Check out with -k to keep the
1541 * file writable.
1542 */
1543 if (*Arr == NULL)
1544 ap_for_get[size_ap_for_get - 3] = "-k";
1545 ap_for_get[size_ap_for_get - 2] = ap[ind];
1546 }
1547 rval = callprog(cmd->sccspath, cmd->sccsflags, ap_for_get, TRUE);
1548 }
1549 } else {
1550 /*
1551 * All normal program calls.
1552 */
1553 rval = callprog(cmd->sccspath, cmd->sccsflags, ap, forkflag);
1554 }
1555 }
1556 break;
1557
1558 case CMACRO: /* command macro */
1559 {
1560 int cnt, ind, xsize, first_part_macro = 0, macro_rval = 0;
1561 char **ap1 = NULL, **next_file;
1562 char **cp, **file_arg = NULL;
1563
1564 /* step through & execute each part of the macro */
1565 ap_for_get = NULL;
1566 if (strcmp(cmd->sccsname, "create") == 0) {
1567 create_macro = 1;
1568 } else {
1569 if (strcmp(cmd->sccsname, "deledit") == 0 ||
1570 strcmp(cmd->sccsname, "delget") == 0) {
1571
1572 del_macro = 1;
1573 if ((user_name = logname()) == NULL)
1574 fatal(gettext("User ID not in password file (cm9)"));
1575 for (ind = 0; ap[ind] != NULL; ind++) {
1576 if (!r_option_value) {
1577 /* in search of '-r' option */
1578 if (strstr(ap[ind], "-r") != NULL) {
1579 if (strcmp(ap[ind], "-r")) {
1580 r_option_value = xstrdup(ap[ind]);
1581 } else {
1582 if (ap[ind+1] != NULL)
1583 r_option_value = xstrdup(ap[ind+1]);
1584 }
1585 }
1586 }
1587 }
1588 if (nfiles > 0) {
1589 NelemArrSids = nfiles;
1590 if ((ArrSids = calloc(NelemArrSids, sizeof (char *))) == NULL) {
1591 perror(gettext("Sccs: no mem"));
1592 exit(EX_OSERR);
1593 }
1594 }
1595 } else {
1596 create_macro = 0;
1597 del_macro = 0;
1598 }
1599 }
1600 if (nfiles > 1) {
1601 first_part_macro = 1;
1602 }
1603
1604 /*
1605 * Select the macro string for NewMode if avaliable.
1606 */
1607 p = cmd->sccspath;
1608 if (NewMode && (q = strchr(p, '%')) != NULL)
1609 p = ++q;
1610 for (; *p != '\0'; p++)
1611 {
1612 if (!forkflag) /* Keep FORCE_FORK value */
1613 forkflag = TRUE;
1614 q = p;
1615 while (*p != '\0' && *p != '/' && *p != '%')
1616 p++;
1617 if (*p == '\0' || *p == '%') {
1618 if (nfiles == 1 && !Rflag && forkflag == TRUE) {
1619 forkflag = FALSE;
1620 }
1621 /*
1622 * In case command() returns, we need to check
1623 * the string end before for(;;) increments p.
1624 */
1625 }
1626 if (nfiles > 1) {
1627 if (first_part_macro) {
1628 macro_rval = first_part_macro = 0;
1629 for (cnt = 0; ap[cnt] != NULL; cnt++)
1630 ;
1631 xsize = cnt - nfiles + 2;
1632 if (!hady) {
1633 if (strcmp(cmd->sccsname, "deledit") == 0 ||
1634 strcmp(cmd->sccsname, "delget") == 0) {
1635 /* additional element for '-ycomments' parameter */
1636 xsize++;
1637 }
1638 }
1639 if ((macro_files = calloc(nfiles, (sizeof (char *)))) == NULL ||
1640 (ap1 = calloc(xsize, (sizeof (char *)))) == NULL) {
1641 perror(gettext("Sccs: no mem"));
1642 exit(EX_OSERR);
1643 }
1644 for (ind = 0; ind < (cnt - nfiles); ind++) {
1645 ap1[ind] = ap[ind];
1646 }
1647 if (!hady) {
1648 if (strcmp(cmd->sccsname, "deledit") == 0 ||
1649 strcmp(cmd->sccsname, "delget") == 0) {
1650 if (Comments == NULL)
1651 get_sccscomment();
1652 ap1[xsize - 3] = Comments;
1653 }
1654 }
1655 next_file = ap + cnt - nfiles;
1656 file_arg = ap1 + xsize - 2;
1657 } else {
1658 next_file = macro_files;
1659 }
1660 cp = macro_files;
1661 for (ind = 0; ind < nfiles; ind++) {
1662 if (*next_file != NULL) {
1663 cur_num_file = ind;
1664 *file_arg = *next_file;
1665 if ((rval = command(&ap1[1], forkflag, q)) != 0) {
1666 macro_rval = rval;
1667 *cp = NULL;
1668 } else {
1669 *cp = *next_file;
1670 }
1671 }
1672 cp++;
1673 next_file++;
1674 }
1675 } else {
1676 if ((rval = command(&ap[1], forkflag, q)) != 0)
1677 break;
1678 }
1679 /*
1680 * Stop at the end of the string.
1681 * If we are not in NewMode, stop at the end of the
1682 * old macro.
1683 */
1684 if (*p == '\0' || *p == '%')
1685 break;
1686 }
1687 if (nfiles > 1) {
1688 rval = macro_rval;
1689 free(ap1);
1690 free(macro_files);
1691 }
1692 if (ap_for_get != NULL) {
1693 free(ap_for_get);
1694 }
1695 if (Comments != NULL && !Rflag) {
1696 free(Comments);
1697 Comments = NULL;
1698 }
1699 break;
1700 }
1701
1702 case FIX: /* fix a delta */
1703 rval = fix(nfiles, ap);
1704 break;
1705
1706 case CLEAN: /* clean out recreatable files */
1707 rval = clean((int) (Intptr_t)cmd->sccspath, ap);
1708 break;
1709
1710 case UNEDIT:
1711 rval = unedit(nfiles, ap);
1712 break;
1713
1714 case DIFFS: /* diff between s-file & edit file */
1715 rval = diffs(nfiles, ap);
1716 break;
1717
1718 case ENTER: /* enter new sccs files */
1719 rval = enter(nfiles, ap);
1720 break;
1721
1722 case EDITOR: /* get -e + call $EDITOR */
1723 rval = editor(nfiles, ap);
1724 break;
1725
1726 case HISTFILE: /* give history file path for s-file */
1727 rval = histfile(nfiles, ac, ap);
1728 break;
1729
1730 case ISTEXT: /* check whether file needs encoding */
1731 rval = istext(nfiles, ac, ap);
1732 break;
1733
1734 case ADD: /* add specified files on next commit */
1735 rval = addcmd(nfiles, ac, ap);
1736 break;
1737
1738 case COMMIT: /* commit changes to project repository */
1739 rval = commitcmd(nfiles, ac, ap);
1740 break;
1741
1742 case INIT: /* initialize empty project repository */
1743 rval = initcmd(nfiles, ac, ap);
1744 break;
1745
1746 case REMOVE: /* remove specified files on next commit */
1747 rval = removecmd(nfiles, ac, ap);
1748 break;
1749
1750 case RENAME: /* rename specified files on next commit */
1751 rval = renamecmd(nfiles, ac, ap);
1752 break;
1753
1754 case ROOT: /* show project root directory */
1755 rval = rootcmd(nfiles, ac, ap);
1756 break;
1757
1758 case STATUS: /* show changed files in the project */
1759 rval = statuscmd(nfiles, ac, ap);
1760 break;
1761
1762 default:
1763 syserr("oper %d (sc2)", cmd->sccsoper);
1764 exit(EX_SOFTWARE);
1765 }
1766 #ifdef DEBUG
1767 if (Debug)
1768 printf(gettext("command: rval=%d\n"), rval);
1769 #endif
1770 free(nav);
1771 fflush(stdout);
1772 return (rval);
1773 }
1774
1775 static void
get_sccscomment()1776 get_sccscomment()
1777 {
1778 if (Comments == NULL) {
1779 char * ccp;
1780
1781 if (isatty(0) == 1)
1782 printf(gettext("comments? "));
1783 ccp = get_Sccs_Comments();
1784 if ((Comments = malloc(strlen(ccp) + 3)) == NULL) {
1785 perror(gettext("Sccs: no mem"));
1786 exit(EX_OSERR);
1787 }
1788 strcpy(Comments, "-y");
1789 strcat(Comments, ccp);
1790 free(ccp);
1791 }
1792 }
1793
1794 static void
get_list_files(listftailpp,filename,no_sdot)1795 get_list_files(listftailpp, filename, no_sdot)
1796 struct list_files **listftailpp;
1797 char *filename;
1798 bool no_sdot;
1799 {
1800 struct list_files *listfilesp = *listftailpp;
1801
1802 listfilesp->next = malloc(sizeof (struct list_files));
1803 if (listfilesp->next == NULL) {
1804 perror(gettext("Sccs: no mem"));
1805 exit(EX_OSERR);
1806 }
1807 listfilesp = listfilesp->next;
1808 listfilesp->next = NULL;
1809 listfilesp->filename = filename;
1810 if (!no_sdot) {
1811 filename = makefile(filename, SccsDir);
1812 }
1813 listfilesp->s_filename = filename;
1814 *listftailpp = listfilesp;
1815 }
1816
1817 /*
1818 ** LOOKUP -- look up an SCCS command name.
1819 **
1820 ** Parameters:
1821 ** name -- the name of the command to look up.
1822 **
1823 ** Returns:
1824 ** ptr to command descriptor for this command.
1825 ** NULL if no such entry.
1826 **
1827 ** Side Effects:
1828 ** none.
1829 */
1830
1831 static struct sccsprog *
lookup(name)1832 lookup(name)
1833 char *name;
1834 {
1835 register struct sccsprog *cmd;
1836
1837 for (cmd = SccsProg; cmd->sccsname != NULL; cmd++)
1838 {
1839 if (strcmp(cmd->sccsname, name) == 0)
1840 return (cmd);
1841 }
1842 return (NULL);
1843 }
1844
1845 /*
1846 ** CALLPROG -- call a program
1847 **
1848 ** Used to call the SCCS programs.
1849 **
1850 ** Parameters:
1851 ** progpath -- pathname of the program to call.
1852 ** flags -- status flags from the command descriptors.
1853 ** argv -- an argument vector to pass to the program.
1854 ** forkflag -- if true, fork before calling, else just
1855 ** exec.
1856 **
1857 ** Returns:
1858 ** The exit status of the program.
1859 ** Nothing if forkflag == FALSE.
1860 **
1861 ** Side Effects:
1862 ** Can exit if forkflag == FALSE.
1863 */
1864
1865 static int
callprog(progpath,flags,argv,forkflag)1866 callprog(progpath, flags, argv, forkflag)
1867 char *progpath;
1868 short flags;
1869 char **argv;
1870 bool forkflag;
1871 {
1872 register int i;
1873 register int wpid;
1874 auto int st;
1875 register int sigcode;
1876 register int coredumped;
1877 register const char *sigmsg;
1878 #ifndef HAVE_STRSIGNAL
1879 #ifdef HAVE_SYS_SIGLIST
1880 auto char sigmsgbuf[10+1]; /* "Signal 127" + terminating '\0' */
1881 #endif
1882 #endif
1883
1884 #ifdef DEBUG
1885 if (Debug)
1886 {
1887 printf("callprog:\n");
1888 for (i = 0; argv[i] != NULL; i++)
1889 printf("\t\"%s\"\n", argv[i]);
1890 /*
1891 * Avoid flush() problems caused by fork()/vfork()
1892 */
1893 if (forkflag)
1894 fflush(stdout);
1895 }
1896 #endif
1897
1898 if (*argv == NULL)
1899 return (-1);
1900
1901 /*
1902 ** Fork if appropriate.
1903 */
1904
1905 if (forkflag)
1906 {
1907 #ifdef DEBUG
1908 if (Debug)
1909 printf("Forking\n");
1910 #endif
1911 i = vfork();
1912 if (i < 0) {
1913 syserr(gettext("cannot fork"));
1914 exit(EX_OSERR);
1915 } else if (i > 0) {
1916 while ((wpid = wait(&st)) != -1 && wpid != i)
1917 ;
1918 if (WIFEXITED(st))
1919 st = WEXITSTATUS(st);
1920 else
1921 {
1922 coredumped = WCOREDUMP(st);
1923 sigcode = WTERMSIG(st);
1924 #ifdef SIGPIPE
1925 if (sigcode != SIGINT && sigcode != SIGPIPE)
1926 #else
1927 if (sigcode != SIGINT)
1928 #endif
1929 {
1930 #ifdef HAVE_STRSIGNAL
1931 sigmsg = strsignal(sigcode);
1932 #else
1933 #ifdef HAVE_SYS_SIGLIST
1934 if (sigcode < NSIG)
1935 sigmsg = sys_siglist[sigcode];
1936 else
1937 {
1938 sprintf(sigmsgbuf, "%s %d",
1939 gettext("Signal"),
1940 sigcode);
1941 sigmsg = sigmsgbuf;
1942 }
1943 #else
1944 /* bandaid */
1945 sigmsg = gettext("fork() error");
1946 #endif
1947 #endif /* HAVE_STRSIGNAL */
1948 fprintf(stderr, "sccs: %s: %s%s\n", argv[0],
1949 sigmsg,
1950 coredumped ? gettext(" - core dumped") : "");
1951 }
1952 st = EX_SOFTWARE;
1953 }
1954 if (OutFile >= 0)
1955 {
1956 close(OutFile);
1957 OutFile = -1;
1958 }
1959 return (st);
1960 }
1961 #ifdef HAVE_VFORK
1962 didvfork = 1;
1963 #endif
1964 } else if (OutFile >= 0) { /* !forkflag && ... */
1965
1966 syserr(gettext("callprog: setting stdout w/o forking"));
1967 if (didvfork)
1968 _exit(EX_SOFTWARE);
1969 exit(EX_SOFTWARE);
1970 }
1971
1972 /* set protection as appropriate */
1973 if (bitset(REALUSER, flags))
1974 if (setuid(getuid()) < 0)
1975 syserr(gettext("Cannot set uid"));
1976
1977 /* change standard input & output if needed */
1978 if (OutFile >= 0)
1979 {
1980 #ifdef set_child_standard_fds
1981 set_child_standard_fds(STDIN_FILENO,
1982 OutFile,
1983 STDERR_FILENO);
1984 #else
1985 close(1);
1986 (void) dup(OutFile);
1987 close(OutFile);
1988 #endif
1989 }
1990
1991 /* call real SCCS program */
1992 #ifdef DEBUG
1993 if (Debug) {
1994 printf("exec: %s\n", argv[0]?argv[0]:"(NULL)");
1995 printf("progpath: %s\n", progpath);
1996 for (i = 0; argv[i] != NULL; i++)
1997 printf("\t\"%s\"\n", argv[i]);
1998 fflush(stdout);
1999 }
2000 #endif
2001 if (getenv("SCCS_NOEXEC"))
2002 _exit(0);
2003 #ifndef V6
2004 execvp(progpath, argv);
2005 #else
2006 execv(progpath, argv);
2007 #endif /* V6 */
2008 syserr(gettext("cannot execute %s"), progpath);
2009 if (didvfork)
2010 _exit(EX_UNAVAILABLE);
2011 exit(EX_UNAVAILABLE);
2012 /*NOTREACHED*/
2013 }
2014
2015 /*
2016 ** MAKEFILE -- make filename of SCCS file
2017 **
2018 ** If the name passed is already the name of an SCCS file,
2019 ** just return it. Otherwise, munge the name into the name
2020 ** of the actual SCCS file.
2021 **
2022 ** There are cases when it is not clear what you want to
2023 ** do. For example, if SccsPath is an absolute pathname
2024 ** and the name given is also an absolute pathname, we go
2025 ** for SccsPath (& only use the last component of the name
2026 ** passed) -- this is important for security reasons (if
2027 ** sccs is being used as a setuid front end), but not
2028 ** particularly intuitive.
2029 **
2030 ** Parameters:
2031 ** name -- the file name to be munged.
2032 **
2033 ** Returns:
2034 ** The pathname of the sccs file.
2035 ** NULL on error.
2036 **
2037 ** Side Effects:
2038 ** none.
2039 */
2040
2041 static char *
makefile(name,in_SccsDir)2042 makefile(name, in_SccsDir)
2043 char *name;
2044 const char *in_SccsDir;
2045 {
2046 register char *p;
2047 char buf[3*FBUFSIZ];
2048 register char *q;
2049 int Spath = FALSE;
2050 char *Sp, *np;
2051 struct stat _Statbuf;
2052
2053 np = p = strrchr(name, '/');
2054 if (p == NULL) {
2055 p = name;
2056 } else {
2057 p++;
2058 }
2059 if (strcmp(p, SccsPath) == 0) {
2060 Spath = TRUE;
2061 } else {
2062 if (p != name) {
2063 /*
2064 * If we do not check for /s., we get funny results
2065 * for SCCS/a.file. If we do, we get SCCS/SCCS/s.a.file
2066 * but if we like to use the new option -NSCCS, we
2067 * should include the test.
2068 */
2069 if (np[1] == 's' && np[2] == '.' &&
2070 (Sp = strstr(name, SccsPath)) != 0) {
2071 if ((Sp+strlen(SccsPath)) == np) {
2072 Spath = TRUE;
2073 }
2074 }
2075 }
2076 }
2077
2078 /*
2079 ** Check to see that the path is "safe", i.e., that we
2080 ** are not letting some nasty person use the setuid part
2081 ** of this program to look at or munge some presumably
2082 ** hidden files.
2083 */
2084
2085 if (in_SccsDir[0] == '/' && !safepath(name))
2086 return (NULL);
2087
2088 /*
2089 ** Create the base pathname.
2090 */
2091
2092 /*
2093 * first the directory part
2094 */
2095 if ((in_SccsDir[0] != '\0') &&
2096 (name[0] != '/') &&
2097 (strncmp(name, "./", 2) != 0)) {
2098 gstrcpy(buf, in_SccsDir, sizeof (buf));
2099 gstrcat(buf, "/", sizeof (buf));
2100 } else {
2101 gstrcpy(buf, "", sizeof (buf));
2102 }
2103
2104 /*
2105 * then the head of the pathname
2106 */
2107 gstrncat(buf, name, p - name, sizeof (buf));
2108 q = &buf[strlen(buf)];
2109
2110 /*
2111 * now copy the final part of the name, in case useful
2112 */
2113 gstrcpy(q, p, sizeof (buf));
2114
2115 /*
2116 * so is it useful?
2117 */
2118 if (Spath == FALSE && !NewMode) {
2119 if (strncmp(p, "s.", 2) != 0) {
2120 /*
2121 * Definitely not a s.file name.
2122 */
2123 if ((strcmp(curcmd->sccsname, "create") == 0) ||
2124 (strcmp(curcmd->sccsname, "enter") == 0) ||
2125 (strcmp(curcmd->sccsname, "editor") == 0) ||
2126 (strcmp(curcmd->sccsname, "admin") == 0) ||
2127 (isdir(buf) == 0)) {
2128 gstrcpy(q, SccsPath, sizeof (buf));
2129 gstrcat(buf, "/s.", sizeof (buf));
2130 gstrcat(buf, p, sizeof (buf));
2131 } else if (strcmp(curcmd->sccsname, "histfile") == 0)
2132 gstrcpy(q, SccsPath, sizeof (buf));
2133 } else {
2134 /*
2135 * May be a s.file name, but a g-file may also start
2136 * with "s.".
2137 */
2138 if ((strcmp(curcmd->sccsname, "create") == 0) ||
2139 (strcmp(curcmd->sccsname, "enter") == 0) ||
2140 (strcmp(curcmd->sccsname, "editor") == 0) ||
2141 (strcmp(curcmd->sccsname, "admin") == 0)) {
2142 /*
2143 * If it is related to new files, assume a
2144 * g-file and add SCCS/s. before the final name.
2145 */
2146 gstrcpy(q, SccsPath, sizeof (buf));
2147 gstrcat(buf, "/s.", sizeof (buf));
2148 gstrcat(buf, p, sizeof (buf));
2149 } else if (isdir(buf) == 0) {
2150 /*
2151 * In other cases first assume a g-file, but
2152 * check for the existence of the assumed
2153 * s.file.
2154 */
2155 gstrcpy(q, SccsPath, sizeof (buf));
2156 gstrcat(buf, "/s.", sizeof (buf));
2157 gstrcat(buf, p, sizeof (buf));
2158 if (!_exists(buf)) {
2159 /*
2160 * The assumed related s.file is
2161 * missing, try the given file name
2162 * as s.file name.
2163 */
2164 gstrcpy(q, p, sizeof (buf));
2165 }
2166 }
2167 }
2168 }
2169
2170 /*
2171 * if i haven't changed it, why did I do all this?
2172 */
2173 if (strcmp(buf, name) == 0) {
2174 p = name;
2175 } else {
2176 /*
2177 * but if I have, squirrel it away
2178 */
2179 p = xstrdup(buf);
2180 }
2181 return (p);
2182 }
2183
2184 /*
2185 ** ISDIR -- return true if the argument is a directory.
2186 **
2187 ** Parameters:
2188 ** name -- the pathname of the file to check.
2189 **
2190 ** Returns:
2191 ** TRUE if 'name' is a directory, FALSE otherwise.
2192 **
2193 ** Side Effects:
2194 ** none.
2195 */
2196
2197 static bool
isdir(name)2198 isdir(name)
2199 char *name;
2200 {
2201 struct stat stbuf;
2202
2203 return (stat(name, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) == S_IFDIR);
2204 }
2205
2206 /*
2207 ** ISFILE -- return true if the argument is a normal file.
2208 **
2209 ** Parameters:
2210 ** name -- the pathname of the file to check.
2211 **
2212 ** Returns:
2213 ** TRUE if 'name' is a normal file, FALSE otherwise.
2214 **
2215 ** Side Effects:
2216 ** none.
2217 */
2218
2219 static bool
isfile(name)2220 isfile(name)
2221 char *name;
2222 {
2223 struct stat stbuf;
2224
2225 return (stat(name, &stbuf) == 0 && (stbuf.st_mode & S_IFMT) == S_IFREG);
2226 }
2227
2228 /*
2229 ** SAFEPATH -- determine whether a pathname is "safe"
2230 **
2231 ** "Safe" pathnames only allow you to get deeper into the
2232 ** directory structure, i.e., full pathnames and ".." are
2233 ** not allowed.
2234 **
2235 ** Parameters:
2236 ** p -- the name to check.
2237 **
2238 ** Returns:
2239 ** TRUE -- if the path is safe.
2240 ** FALSE -- if the path is not safe.
2241 **
2242 ** Side Effects:
2243 ** Prints a message if the path is not safe.
2244 */
2245
2246 static bool
safepath(p)2247 safepath(p)
2248 register char *p;
2249 {
2250 if (*p != '/') {
2251 while (strncmp(p, "../", 3) != 0 && strcmp(p, "..") != 0) {
2252 p = strchr(p, '/');
2253 if (p == NULL)
2254 return (TRUE);
2255 p++;
2256 }
2257 }
2258 usrerr(gettext("You may not use full pathname or \"..\"\n"));
2259 exit(EX_USAGE);
2260
2261 /* NOTREACHED */
2262 return (FALSE);
2263 }
2264
2265 static char *
xstrdup(file)2266 xstrdup(file)
2267 char *file;
2268 {
2269 char *dfile;
2270
2271 dfile = strdup(file);
2272 if (dfile == NULL) {
2273 perror(gettext("Sccs: no mem"));
2274 exit(EX_OSERR);
2275 }
2276 return (dfile);
2277 }
2278
2279 static int
fix(nfiles,argv)2280 fix(nfiles, argv)
2281 int nfiles;
2282 char **argv;
2283 {
2284 register char *p;
2285 int rezult;
2286 int rval = 0;
2287 char **np;
2288 char **ap = argv;
2289 int n = 0;
2290 int rflag = 0;
2291 char *sidp = NULL;
2292 struct sid sid;
2293
2294 /* find the end of the flag arguments */
2295 for (np = &ap[1]; *np != NULL && **np == '-'; np++) {
2296 if (**np == '-') {
2297 if (np[0][1] == 'r') {
2298 rflag = 1;
2299 if (np[0][2] == '\0') {
2300 np++;
2301 sidp = *np;
2302 } else {
2303 sidp = *np + 2;
2304 }
2305 }
2306 }
2307 }
2308 if (*np == NULL) {
2309 usrerr(gettext(" missing file arg (cm3)"));
2310 rval = EX_USAGE;
2311 exit(EX_USAGE);
2312 }
2313 if (rflag == 0) {
2314 usrerr(gettext("-r flag needed for fix command"));
2315 rval = EX_USAGE;
2316 exit(EX_USAGE);
2317 }
2318 sid_ab(sidp, &sid);
2319 if (sid.s_lev > 0 || sid.s_seq > 0) {
2320 for (n = length(sidp); n > 0; n--) {
2321 if (sidp[n] == '.') {
2322 break;
2323 }
2324 }
2325 }
2326 argv = np;
2327 /* for each file, do the fix */
2328 p = argv[1];
2329 rezult = 0;
2330 while (*np != NULL) {
2331 *argv = *np++;
2332 argv[1] = NULL;
2333 rval |= rezult;
2334 if (nfiles > 1) {
2335 printf("\n%s:\n", *argv);
2336 fflush(stdout);
2337 }
2338 /* mersy, but we need a null terminated argv */
2339 /* get the version with all changes */
2340 rezult = command(&ap[1], TRUE, NOGETTEXT("get: -k"));
2341 if (rezult != 0) {
2342 argv[1] = p;
2343 continue;
2344 }
2345 /* now remove that version from the s-file */
2346 rezult = command(&ap[1], TRUE, NOGETTEXT("rmdel:rd"));
2347 if (rezult != 0) {
2348 unlink(*argv);
2349 argv[1] = p;
2350 continue;
2351 }
2352 /* and edit the old version (but don't clobber new vers) */
2353 if (n > 0) {
2354 sidp[n] = '\0';
2355 }
2356 rezult = command(&ap[1], TRUE, NOGETTEXT("get:r -e -g"));
2357 sidp[n] = '.';
2358 argv[1] = p;
2359 }
2360 rval |= rezult;
2361 return (rval);
2362 }
2363
2364 /*
2365 ** CLEAN -- clean out recreatable files
2366 **
2367 ** Any file for which an "s." file exists but no "p." file
2368 ** exists in the current directory is purged.
2369 **
2370 ** Parameters:
2371 ** mode -- tells whether this came from a "clean", "info",
2372 ** "tell" or "check" command.
2373 ** argv -- the rest of the argument vector.
2374 **
2375 ** Returns:
2376 ** none.
2377 **
2378 ** Side Effects:
2379 ** Removes files in the current directory.
2380 ** Prints information regarding files being edited.
2381 ** Exits if a "check" command.
2382 */
2383
2384 static int
clean(mode,argv)2385 clean(mode, argv)
2386 int mode;
2387 char **argv;
2388 {
2389 struct dirent *dir;
2390 char buf[MAXPATHLEN];
2391 char namefile[MAXPATHLEN];
2392 char basebuf[MAXPATHLEN];
2393 char *bufend;
2394 char *baseend;
2395 register DIR *dir_fd;
2396 register char *basefile;
2397 bool gotedit;
2398 bool edited;
2399 bool gotpfent;
2400 FILE *pfp;
2401 bool nobranch = FALSE;
2402 register struct p_file *pf;
2403 register char **ap;
2404 char *usernm = NULL;
2405 char *subdir = NULL;
2406 struct stat _Statbuf;
2407 int ex_status;
2408
2409 /*
2410 ** Process the argv
2411 */
2412
2413 ex_status = EX_OK;
2414 for (ap = argv; *++ap != NULL; )
2415 {
2416 if (**ap == '-') {
2417 /* we have a flag */
2418 switch ((*ap)[1]) {
2419 case 'b':
2420 nobranch = TRUE;
2421 break;
2422
2423 case 'u':
2424 if ((*ap)[2] != '\0')
2425 usernm = sccs_user(&(*ap)[2]);
2426 else if (Rflag && (ap[1] == NULL || ap[2] == NULL))
2427 usernm = logname();
2428 else if (ap[1] != NULL && ap[1][0] != '-')
2429 usernm = sccs_user(*++ap);
2430 else
2431 usernm = logname();
2432 break;
2433 case 'U':
2434 usernm = logname();
2435 break;
2436 }
2437 } else {
2438 if (subdir != NULL)
2439 usrerr(gettext("too many args"));
2440 else
2441 subdir = *ap;
2442 }
2443 }
2444
2445 /*
2446 ** Find and open the SCCS directory.
2447 */
2448 if (NewMode) {
2449 if (subdir == NULL)
2450 subdir = ".";
2451 N.n_pflags = NP_DIR;
2452 subdir = bulkprepare(&N, subdir);
2453 if (subdir == NULL) {
2454 /*
2455 * The error is typically
2456 * "non directory specified as argument (cm20)"
2457 */
2458 fatal(gettext(bulkerror(&N)));
2459 }
2460 N.n_pflags = 0;
2461 strlcpy(buf, subdir, sizeof (buf));
2462 } else {
2463 gstrcpy(buf, SccsDir, sizeof (buf));
2464 if (buf[0] != '\0')
2465 gstrcat(buf, "/", sizeof (buf));
2466 if (subdir != NULL)
2467 gstrcat(buf, subdir, sizeof (buf));
2468 bufend = &buf[strlen(buf)-1];
2469 while (bufend > buf && *bufend == '/')
2470 *bufend-- = '\0';
2471 if ((bufend = strstr(buf, SccsPath)) == NULL ||
2472 bufend[strlen(SccsPath)] != '\0') {
2473 if (subdir != NULL)
2474 gstrcat(buf, "/", sizeof (buf));
2475 gstrcat(buf, SccsPath, sizeof (buf));
2476 }
2477 }
2478 bufend = &buf[strlen(buf)];
2479
2480 basebuf[0] = '\0';
2481 baseend = basebuf;
2482 if (Rflag) {
2483 gstrncat(basebuf, buf, bufend - buf - strlen(SccsPath),
2484 sizeof (basebuf));
2485 baseend = &basebuf[strlen(basebuf)];
2486 }
2487
2488 dir_fd = opendir(buf);
2489 if (dir_fd == NULL)
2490 {
2491 usrerr(gettext("cannot open %s"), buf);
2492 return (EX_NOINPUT);
2493 }
2494
2495 if (!check_permission_SccsDir(buf)) {
2496 return (EX_NOINPUT);
2497 }
2498 /*
2499 ** Scan the SCCS directory looking for s. files.
2500 ** gotedit tells whether we have tried to clean any
2501 ** files that are being edited.
2502 */
2503
2504 gotedit = FALSE;
2505 while ((dir = readdir(dir_fd)) != NULL) {
2506 if (strncmp(dir->d_name, "s.", 2) != 0) {
2507 continue;
2508 } else {
2509 *bufend = '\0';
2510 gstrcpy(namefile, buf, sizeof (namefile));
2511 gstrcat(namefile, "/", sizeof (namefile));
2512 gstrcat(namefile, dir->d_name, sizeof (namefile));
2513 }
2514
2515 /* got an s. file -- see if the p. file exists */
2516 gstrcpy(bufend, NOGETTEXT("/p."), sizeof (buf) - (bufend - buf));
2517 basefile = bufend + 3;
2518 gstrcpy(basefile, &dir->d_name[2], sizeof (buf) - (basefile - buf));
2519 gstrcpy(baseend, &dir->d_name[2], sizeof (basebuf) - (baseend - basebuf));
2520
2521 /*
2522 ** open and scan the p-file.
2523 ** 'gotpfent' tells if we have found a valid p-file
2524 ** entry.
2525 */
2526
2527 pfp = fopen(buf, "rb");
2528 gotpfent = FALSE;
2529 edited = FALSE;
2530 if (pfp != NULL)
2531 {
2532 /* the file exists -- report it's contents */
2533 while ((pf = getpfent(pfp)) != NULL)
2534 {
2535 edited = TRUE;
2536 if (nobranch && isbranch(pf->p_nsid))
2537 continue;
2538 if (usernm != NULL && strcmp(usernm, pf->p_user) != 0 && mode != CLEANC)
2539 continue;
2540 gotedit = TRUE;
2541 gotpfent = TRUE;
2542 if (mode == TELLC)
2543 {
2544 printf("%s\n", Rflag ? basebuf : basefile);
2545 break;
2546 }
2547 if (checkpfent(pf)) {
2548 printf(gettext("%12s: being edited: "), Rflag ? basebuf : basefile);
2549 putpfent(pf, stdout);
2550 } else {
2551 fatal(gettext("bad p-file format (co17)"));
2552 }
2553 }
2554 fclose(pfp);
2555 }
2556
2557 /* the s. file exists and no p. file exists -- unlink the g-file */
2558 if (mode == CLEANC && !gotpfent) {
2559 if (_exists(basebuf) != 0) {
2560 if (((_Statbuf.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) == 0)||
2561 (edited != 0)) {
2562 unlink(basebuf);
2563 } else {
2564 ex_status = 1;
2565 fprintf(stderr,
2566 gettext("ERROR [%s]: the file `%s' is writable\n"),
2567 namefile, Rflag ? basebuf : basefile);
2568 }
2569 }
2570 }
2571 }
2572
2573 /* cleanup & report results */
2574 closedir(dir_fd);
2575 if (!Rflag && !gotedit && mode == INFOC)
2576 nothingedited(nobranch, usernm);
2577 Tgotedit |= gotedit;
2578 Tnobranch |= nobranch;
2579 Tusernm = usernm;
2580
2581 if (mode == CHECKC)
2582 return (gotedit);
2583 else
2584 return (ex_status);
2585 }
2586
2587 static void
nothingedited(nobranch,usernm)2588 nothingedited(nobranch, usernm)
2589 bool nobranch;
2590 const char *usernm;
2591 {
2592 printf(gettext("Nothing being edited"));
2593 if (nobranch)
2594 /*
2595 TRANSLATION_NOTE
2596 The following message is a possible continuation of the text
2597 "Nothing being edited" ...
2598 */
2599 printf(gettext(" (on trunk)"));
2600 if (usernm == NULL)
2601 printf("\n");
2602 else
2603 /*
2604 TRANSLATION_NOTE
2605 The following message is a possible continuation of the text
2606 "Nothing being edited" ...
2607 */
2608 printf(gettext(" by %s\n"), usernm);
2609 }
2610
2611 /*
2612 ** ISBRANCH -- is the SID a branch?
2613 **
2614 ** Parameters:
2615 ** sid -- the sid to check.
2616 **
2617 ** Returns:
2618 ** TRUE if the sid represents a branch.
2619 ** FALSE otherwise.
2620 **
2621 ** Side Effects:
2622 ** none.
2623 */
2624
2625 static bool
isbranch(sid)2626 isbranch(sid)
2627 char *sid;
2628 {
2629 register char *p;
2630 int dots;
2631
2632 dots = 0;
2633 for (p = sid; *p != '\0'; p++)
2634 {
2635 if (*p == '.')
2636 dots++;
2637 if (dots > 1)
2638 return (TRUE);
2639 }
2640 return (FALSE);
2641 }
2642
2643
2644 static int
unedit(nfiles,argv)2645 unedit(nfiles, argv)
2646 int nfiles;
2647 char **argv;
2648 {
2649 int err;
2650 int rval = 0;
2651 char **np;
2652 char **ap = argv;
2653
2654 if (!nfiles) {
2655 usrerr(gettext(" missing file arg (cm3)"));
2656 rval = EX_USAGE;
2657 exit(EX_USAGE);
2658 }
2659 err = 0;
2660 for (argv = np = &ap[1]; *argv != NULL; argv++) {
2661 char *cp;
2662
2663 if (strcmp(*np, "-o") == 0) { /* Keep get -o option */
2664 np++;
2665 continue;
2666 }
2667 cp = makefile(*argv, SccsDir);
2668 if (cp == NULL) {
2669 err = 1;
2670 continue;
2671 }
2672 do_file(cp, do_unedit, 1, 1, NULL);
2673 if (!Fcnt)
2674 *np++ = *argv;
2675 else
2676 err = 1;
2677 }
2678 *np = NULL;
2679
2680 /* get all the files that we unedited successfully */
2681 if (np > &ap[1])
2682 rval = command(&ap[1], TRUE, NOGETTEXT("get"));
2683
2684 if (rval == 0)
2685 rval = err;
2686 return (rval);
2687 }
2688
2689 /*
2690 ** UNEDIT -- unedit a file
2691 **
2692 ** Checks to see that the current user is actually editting
2693 ** the file and arranges that s/he is not editting it.
2694 **
2695 ** Parameters:
2696 ** fn -- the name of the file to be unedited.
2697 **
2698 ** Returns:
2699 ** TRUE -- if the file was successfully unedited.
2700 ** FALSE -- if the file was not unedited for some
2701 ** reason.
2702 **
2703 ** Side Effects:
2704 ** fn is removed
2705 ** entries are removed from p_file.
2706 */
2707
2708 static void
do_unedit(fn)2709 do_unedit(fn)
2710 char *fn;
2711 {
2712 register FILE *pfp;
2713 char *gfile, *gfn, *pfn;
2714 char *Gfile = NULL;
2715 #ifdef PROTOTYPES
2716 char template[] = NOGETTEXT("/tmp/sccsXXXXXX");
2717 #else
2718 char *template = NOGETTEXT("/tmp/sccsXXXXXX");
2719 #endif
2720 static char tfn[20];
2721 FILE *tfp;
2722 register char *q;
2723 bool delete = FALSE;
2724 bool others = FALSE;
2725 char *myname;
2726 struct p_file *pent;
2727 char buf[PFILELG];
2728
2729 Fcnt = 1;
2730 /* make "s." filename & find the trailing component */
2731 /* assumed that fn is a "s." filename already */
2732 if (fn == NULL)
2733 return;
2734
2735 if (NewMode) {
2736 /*
2737 * In NewMode, "fn" is always the f-file name.
2738 */
2739 gfn = fn;
2740 fn = bulkprepare(&N, fn);
2741 if (fn == NULL) {
2742 /*
2743 * The error is typically
2744 * "directory specified as s-file (cm14)"
2745 */
2746 fatal(gettext(bulkerror(&N)));
2747 }
2748 pfn = xstrdup(fn);
2749 gfile = gfn;
2750 } else {
2751 pfn = xstrdup(fn);
2752 if (!sccsfile(pfn)) {
2753 usrerr(gettext("bad file name \"%s\""), fn);
2754 free(pfn);
2755 return;
2756 }
2757 gfile = auxf(pfn, 'g');
2758 }
2759 if (Cwd && Cwd[2]) {
2760 Gfile = malloc(strlen(&Cwd[2]) + strlen(gfile) + 1);
2761 if (Gfile == NULL) {
2762 perror(gettext("Sccs: no mem"));
2763 exit(EX_OSERR);
2764 }
2765 cat(Gfile, &Cwd[2], gfile, (char *)0);
2766 gfile = Gfile;
2767 }
2768 q = strrchr(pfn, '/');
2769 if (q == NULL)
2770 q = &pfn[-1];
2771
2772 /* turn "s." into "p." & try to open it */
2773 *++q = 'p';
2774
2775 pfp = fopen(pfn, "rb");
2776 if (pfp == NULL)
2777 {
2778 printf(gettext("%12s: not being edited\n"),
2779 gfile);
2780 free(pfn);
2781 if (Gfile)
2782 free(Gfile);
2783 return;
2784 }
2785
2786 /* create temp file for editing p-file */
2787 strcpy(tfn, template);
2788 #ifdef HAVE_MKSTEMP
2789 tfp = fdopen(mkstemp(tfn), "wb");
2790 #else
2791 mktemp(tfn);
2792 tfp = fopen(tfn, "wb");
2793 #endif
2794 if (tfp == NULL)
2795 {
2796 usrerr(gettext("cannot create \"%s\""), tfn);
2797 exit(EX_OSERR);
2798 }
2799 setmode(fileno(tfp), O_BINARY);
2800
2801 /* figure out who I am */
2802 myname = logname();
2803
2804 /*
2805 ** Copy p-file to temp file, doing deletions as needed.
2806 */
2807
2808 while ((pent = getpfent(pfp)) != NULL)
2809 {
2810 if (strcmp(pent->p_user, myname) == 0)
2811 {
2812 /* a match */
2813 delete++;
2814 if (delete > 1) {
2815 printf(gettext(
2816 "%s: more than one delta of a file is checked out. Use 'sccs unget -r<SID>' instead.\n"),
2817 gfile);
2818 fclose(tfp);
2819 fclose(pfp);
2820 unlink(tfn);
2821 free(pfn);
2822 if (Gfile)
2823 free(Gfile);
2824 return;
2825 }
2826 }
2827 else
2828 {
2829 if (checkpfent(pent)) {
2830 /* output it again */
2831 putpfent(pent, tfp);
2832 others++;
2833 } else {
2834 fatal(gettext("bad p-file format (co17)"));
2835 }
2836 }
2837 }
2838
2839 /*
2840 * Before changing anything, make sure we can remove
2841 * the file in question (assuming it exists).
2842 */
2843 if (delete) {
2844 errno = 0;
2845 if (access(gfile, 0) < 0 && errno != ENOENT)
2846 goto bad;
2847 if (errno == 0)
2848 /*
2849 * This is wrong, but the rest of the program
2850 * has built in assumptions about "." as well,
2851 * so why make unedit a special case?
2852 */
2853 if (access(".", 2) < 0) {
2854 bad:
2855 printf(gettext("%12s: can't remove\n"), gfile);
2856 fclose(tfp);
2857 fclose(pfp);
2858 unlink(tfn);
2859 free(pfn);
2860 if (Gfile)
2861 free(Gfile);
2862 return;
2863 }
2864 }
2865 /* do final cleanup */
2866 if (others)
2867 {
2868 /* copy it back (perhaps it should be linked?) */
2869 if (freopen(tfn, "rb", tfp) == NULL)
2870 {
2871 syserr(gettext("cannot reopen \"%s\""), tfn);
2872 exit(EX_OSERR);
2873 }
2874 if (freopen(pfn, "wb", pfp) == NULL)
2875 {
2876 usrerr(gettext("cannot create \"%s\""), pfn);
2877 free(pfn);
2878 if (Gfile)
2879 free(Gfile);
2880 return;
2881 }
2882 while (fgets(buf, sizeof (buf), tfp) != NULL) {
2883 if (fputs(buf, pfp) == EOF) {
2884 xmsg(pfn, NOGETTEXT("unedit"));
2885 }
2886 }
2887 }
2888 else
2889 {
2890 /* it's empty -- remove it */
2891 if (unlink(pfn) == -1)
2892 {
2893 syserr(gettext("cannot remove \"%s\""), pfn);
2894 exit(EX_OSERR);
2895 }
2896 }
2897 fclose(tfp);
2898 fclose(pfp);
2899 unlink(tfn);
2900
2901 /* actually remove the g-file */
2902 if (delete)
2903 {
2904 /*
2905 * Since we've checked above, we can
2906 * use the return from unlink to
2907 * determine if the file existed or not.
2908 */
2909 if (unlink(gfile) >= 0)
2910 printf(gettext("%12s: removed\n"), gfile);
2911 Fcnt = 0;
2912 }
2913 else
2914 {
2915 printf(gettext("%12s: not being edited by you\n"), gfile);
2916 }
2917 free(pfn);
2918 if (Gfile)
2919 free(Gfile);
2920 }
2921
2922 /*
2923 ** TAIL -- return tail of filename.
2924 **
2925 ** Parameters:
2926 ** fn -- the filename.
2927 **
2928 ** Returns:
2929 ** a pointer to the tail of the filename; e.g., given
2930 ** "cmd/ls.c", "ls.c" is returned.
2931 **
2932 ** Side Effects:
2933 ** none.
2934 */
2935
2936 static char *
tail(fn)2937 tail(fn)
2938 register char *fn;
2939 {
2940 register char *p;
2941
2942 for (p = fn; *p != 0; p++)
2943 if (*p == '/' && p[1] != '\0' && p[1] != '/')
2944 fn = &p[1];
2945 return (fn);
2946 }
2947
2948 /*
2949 ** GETPFENT -- get an entry from the p-file
2950 **
2951 ** Parameters:
2952 ** pfp -- p-file file pointer
2953 **
2954 ** Returns:
2955 ** pointer to p-file struct for next entry
2956 ** NULL on EOF or error
2957 **
2958 ** Side Effects:
2959 ** Each call wipes out results of previous call.
2960 */
2961
2962 static struct p_file *
getpfent(pfp)2963 getpfent(pfp)
2964 FILE *pfp;
2965 {
2966 static struct p_file ent;
2967 static char buf[PFILELG];
2968 register char *p;
2969
2970 if (fgets(buf, sizeof (buf), pfp) == NULL)
2971 return (NULL);
2972
2973 ent.p_osid = p = buf;
2974 ent.p_nsid = p = nextfield(p);
2975 ent.p_user = p = nextfield(p);
2976 ent.p_date = p = nextfield(p);
2977 ent.p_time = p = nextfield(p);
2978 ent.p_aux = p = nextfield(p);
2979
2980 return (&ent);
2981 }
2982
2983 static int
checkpfent(pf)2984 checkpfent(pf)
2985 struct p_file *pf;
2986 {
2987 if (pf->p_osid == NULL ||
2988 pf->p_nsid == NULL ||
2989 pf->p_user == NULL ||
2990 pf->p_date == NULL ||
2991 pf->p_time == NULL) {
2992 return (0);
2993 } else {
2994 return (1);
2995 }
2996 }
2997
2998 static char *
nextfield(p)2999 nextfield(p)
3000 register char *p;
3001 {
3002 if (p == NULL || *p == '\0')
3003 return (NULL);
3004 while (*p != ' ' && *p != '\n' && *p != '\0')
3005 p++;
3006 if (*p == '\n' || *p == '\0')
3007 {
3008 *p = '\0';
3009 return (NULL);
3010 }
3011 *p++ = '\0';
3012 return (p);
3013 }
3014
3015 /*
3016 ** PUTPFENT -- output a p-file entry to a file
3017 **
3018 ** Parameters:
3019 ** pf -- the p-file entry
3020 ** f -- the file to put it on.
3021 **
3022 ** Returns:
3023 ** none.
3024 **
3025 ** Side Effects:
3026 ** pf is written onto file f.
3027 */
3028
3029 static void
putpfent(pf,f)3030 putpfent(pf, f)
3031 register struct p_file *pf;
3032 register FILE *f;
3033 {
3034 fprintf(f, "%s %s %s %s %s", pf->p_osid, pf->p_nsid,
3035 pf->p_user, pf->p_date, pf->p_time);
3036 if (pf->p_aux != NULL)
3037 fprintf(f, " %s", pf->p_aux);
3038 else
3039 fprintf(f, "\n");
3040 }
3041
3042 /*
3043 ** SYSERR -- print system-generated error.
3044 **
3045 ** Parameters:
3046 ** f -- format string to a printf.
3047 ** p1, p2, p3 -- parameters to f.
3048 **
3049 ** Returns:
3050 ** never.
3051 **
3052 ** Side Effects:
3053 ** none.
3054 */
3055
3056 #ifdef PROTOTYPES
3057 static void
syserr(const char * f,...)3058 syserr(const char *f, ...)
3059 #else
3060 static void
3061 syserr(f, va_alist)
3062 const char *f;
3063 va_dcl
3064 #endif
3065 {
3066 va_list ap;
3067
3068 #ifdef PROTOTYPES
3069 va_start(ap, f);
3070 #else
3071 va_start(ap);
3072 #endif
3073 fprintf(stderr, gettext("\n%s SYSERR: "), MyName);
3074 vfprintf(stderr, f, ap);
3075 fprintf(stderr, "\n");
3076 va_end(ap);
3077
3078 #ifdef SCCS_FATALHELP
3079 if (errno == 0 && strchr(f, '(')) {
3080 sccsfatalhelp((char *)f);
3081 errno = 0;
3082 }
3083 #endif
3084 if (errno == 0) {
3085 if (didvfork)
3086 _exit(EX_SOFTWARE);
3087 exit(EX_SOFTWARE);
3088 }
3089 else
3090 {
3091 perror(NULL);
3092 if (didvfork)
3093 _exit(EX_OSERR);
3094 exit(EX_OSERR);
3095 }
3096 }
3097
3098 /*
3099 ** Guarded string manipulation routines; the last argument
3100 ** is the length of the buffer into which the strcpy or strcat
3101 ** is to be done.
3102 */
3103
gstrcat(to,from,xlength)3104 static char *gstrcat(to, from, xlength)
3105 char *to, *from;
3106 unsigned xlength;
3107 {
3108 if (strlen(from) + strlen(to) >= xlength) {
3109 gstrbotch(to, from);
3110 }
3111 return (strcat(to, from));
3112 }
3113
gstrncat(to,from,n,xlength)3114 static char *gstrncat(to, from, n, xlength)
3115 char *to, *from;
3116 int n;
3117 unsigned xlength;
3118 {
3119 if (n + strlen(to) >= xlength) {
3120 gstrbotch(to, from);
3121 }
3122 return (strncat(to, from, n));
3123 }
3124
gstrcpy(to,from,xlength)3125 static char *gstrcpy(to, from, xlength)
3126 char *to;
3127 const char *from;
3128 unsigned xlength;
3129 {
3130 if (strlen(from) >= xlength) {
3131 gstrbotch(from, (char *)0);
3132 }
3133 return (strcpy(to, from));
3134 }
3135
3136 static void
gstrbotch(str1,str2)3137 gstrbotch(str1, str2)
3138 const char *str1, *str2;
3139 {
3140 usrerr(gettext("Filename(s) too long: %s %s"),
3141 str1,
3142 str2);
3143 }
3144
3145 static int
diffs(nfiles,argv)3146 diffs(nfiles, argv)
3147 int nfiles;
3148 char **argv;
3149 {
3150 register int i;
3151 int err;
3152 int rval = 0;
3153 char **np;
3154 char **ap = argv;
3155 int nargs;
3156 char **args;
3157 char **cur_arg;
3158
3159 /*
3160 * find the end of the flag arguments
3161 */
3162 for (np = &ap[1]; *np != NULL && **np == '-'; np++) {
3163 if (**np == '-') {
3164 if (np[0][2] == '\0') {
3165 switch (np[0][1]) {
3166 case 'r':
3167 case 'i':
3168 case 'x':
3169 case 'c':
3170 case 'D':
3171 case 'U':
3172 np++;
3173 break;
3174 }
3175 }
3176 }
3177 }
3178 if (*np == NULL) {
3179 usrerr(gettext(" missing file arg (cm3)"));
3180 rval = EX_USAGE;
3181 exit(EX_USAGE);
3182 }
3183 nargs = 0;
3184 for (argv = np; *argv != NULL; argv++) {
3185 nargs++;
3186 }
3187 args = cur_arg = malloc(sizeof (char **) * nargs);
3188 argv = np;
3189 for (i = 0; i < nargs; i++) {
3190 *cur_arg++ = *argv++;
3191 }
3192 /* for each file, do the diff */
3193 cur_arg = args;
3194 np[2] = NULL;
3195 err = 0;
3196 diffs_ap = ap;
3197 diffs_np = np;
3198 for (i = 0; i < nargs; i++) {
3199 do_file(*cur_arg, do_diffs, 1, 0, NULL);
3200 if (Fcnt) {
3201 err = 1;
3202 }
3203 cur_arg++;
3204 }
3205 diffs_ap = NULL;
3206 diffs_np = NULL;
3207 rval = err;
3208 free(args);
3209
3210 return (rval);
3211 }
3212
3213 static void
do_diffs(file)3214 do_diffs(file)
3215 char *file;
3216 {
3217 #ifdef PROTOTYPES
3218 char template[] = NOGETTEXT("/tmp/sccs.XXXXXX");
3219 #else
3220 char *template = NOGETTEXT("/tmp/sccs.XXXXXX");
3221 #endif
3222 char buf1[20];
3223 char buf2[20];
3224 char *tmp_file, *gfile, *p, *pfile, *getcmd;
3225 char *newSccsDir = NOGETTEXT("");
3226 bool sfile_exists = FALSE;
3227
3228 if ((diffs_ap == NULL) && (diffs_np == NULL))
3229 return;
3230 if (NewMode) {
3231 /*
3232 * We may not do a chdir() in bulkprepare() since this would
3233 * affect subcommands like get and diff.
3234 */
3235 N.n_pflags = NP_NOCHDIR;
3236 Nsd.n_pflags = NP_NOCHDIR;
3237
3238 pfile = bulkprepare(&N, file);
3239 if (pfile == NULL) {
3240 /*
3241 * The error is typically
3242 * "directory specified as s-file (cm14)"
3243 */
3244 fatal(gettext(bulkerror(&N)));
3245 }
3246 N.n_pflags = 0;
3247 Nsd.n_pflags = 0;
3248
3249 pfile = xstrdup(pfile);
3250 } else if ((pfile = makefile(file, newSccsDir)) == NULL)
3251 return;
3252 if (NewMode)
3253 gfile = file;
3254 else if ((gfile = makegfile(pfile)) == NULL)
3255 return;
3256 sfile_exists = isfile(pfile);
3257
3258 /* make "p." filename */
3259 if (pfile == file) {
3260 pfile = xstrdup(file);
3261 }
3262 p = strrchr(pfile, '/');
3263 if (p == NULL) {
3264 p = pfile;
3265 } else {
3266 p++;
3267 }
3268 *p = 'p';
3269
3270 Fcnt = 0;
3271 printf("\n------- %s -------\n", Rflag ? gfile : tail(gfile));
3272 fflush(stdout);
3273 if (isfile(pfile)) {
3274 getcmd = NOGETTEXT("get:Grcixt -o -s -k");
3275 } else {
3276 getcmd = NOGETTEXT("get:Grcixt -o -s");
3277 }
3278 strcpy(buf1, template);
3279 #ifdef HAVE_MKSTEMP
3280 close(mkstemp(buf1)); /* Still a bit safer than mktemp() */
3281 chmod(buf1, S_IRUSR); /* get will not overwrite if writable */
3282 #else
3283 mktemp(buf1);
3284 #endif
3285 tmp_file = buf1;
3286 strcpy(buf2, NOGETTEXT("-G"));
3287 strcat(buf2, buf1);
3288 diffs_np[0] = buf2;
3289 diffs_np[1] = gfile;
3290 if (!command(&diffs_ap[1], TRUE, getcmd))
3291 {
3292 diffs_np[0] = tmp_file;
3293 diffs_np[1] = gfile;
3294
3295 /*
3296 * 1. We dont want to exec diff command in case when s-file exists and clear file doesnt.
3297 * 2. We want to exec diff command in case when p-file exists and clear file doesnt.
3298 */
3299 if (!(sfile_exists && !isfile(gfile)) ||
3300 (isfile(pfile) && !isfile(gfile))) {
3301 char *diffcmd = NOGETTEXT("-diff:elsfnhqabBNpwtCIDUu");
3302
3303 if (strcmp(maincmd->sccsname, "ldiffs") == 0)
3304 diffcmd = NOGETTEXT("-ldiff:elsfnhqabBNpwtCIDUu");
3305 if (command(&diffs_ap[1], TRUE, diffcmd) > 1) {
3306 Fcnt = 1;
3307 }
3308 }
3309 } else {
3310 Fcnt = 1;
3311 }
3312 free(pfile);
3313 if (gfile != file)
3314 free(gfile);
3315 unlink(tmp_file);
3316 }
3317
3318 static int
enter(nfiles,argv)3319 enter(nfiles, argv)
3320 int nfiles;
3321 char **argv;
3322 {
3323 register char *p;
3324 int rval = 0;
3325 int len;
3326 char **np;
3327 char **ap = argv;
3328 char buf[FILESIZE];
3329 struct stat statb;
3330
3331 /* skip over flag arguments */
3332 for (np = &ap[1]; *np != NULL && **np == '-'; np++) {
3333 if (**np == '-') {
3334 if (np[0][2] == '\0') {
3335 switch (np[0][1]) {
3336 case 'a':
3337 case 'd':
3338 case 'f':
3339 case 'r':
3340 case 'm':
3341 np++;
3342 break;
3343 }
3344 }
3345 }
3346 }
3347 argv = np;
3348 /* do an admin for each file */
3349 p = argv[1];
3350 while (*np != NULL) {
3351 /*
3352 * Make sure the directory to hold the s. files exists.
3353 * If not, create it. If it exists but is not a directory,
3354 * complain.
3355 */
3356 char *filep, *cp;
3357
3358 filep = makefile(*np, SccsDir);
3359 if (!NewMode) {
3360 gstrcpy(buf, filep, sizeof (buf));
3361 cp = strrchr(buf, '/');
3362 if (cp != 0) {
3363 *cp = '\0';
3364 }
3365 if (stat(buf, &statb) == -1) {
3366 if (mkdir(buf, 0777) == -1) {
3367 syserr(gettext("Cannot mkdir %s"), buf);
3368 exit(EX_SOFTWARE);
3369 }
3370 } else {
3371 if (!(statb.st_mode & S_IFDIR)) {
3372 usrerr(
3373 "File `%s' exists, but is not directory",
3374 buf);
3375 exit(EX_SOFTWARE);
3376 }
3377 }
3378 }
3379 printf("\n%s:\n", *np);
3380 strcpy(buf, NOGETTEXT("-i"));
3381 gstrcat(buf, *np, sizeof (buf));
3382 ap[0] = buf;
3383 argv[0] = *np;
3384 argv[1] = NULL;
3385 rval = command(ap, TRUE, "admin");
3386 argv[1] = p;
3387 if (rval == 0) {
3388 buf[0] = 0;
3389 if (strstr(*np, tail(*np)) != NULL) {
3390 len = strlen(*np) - strlen(tail(*np));
3391 strncpy(buf, *np, len);
3392 buf[len] = 0;
3393 }
3394 strcat(buf, ",");
3395 gstrcat(buf, tail(*np), sizeof (buf));
3396 (void) rename(*np, buf);
3397 }
3398 np++;
3399 }
3400 return (rval);
3401 }
3402
3403 static int
editor(nfiles,argv)3404 editor(nfiles, argv)
3405 int nfiles;
3406 char **argv;
3407 {
3408 register struct sccsprog *cmd;
3409 register char *q;
3410 register int i;
3411 int rval = 0;
3412 char **np;
3413 char **ap = argv;
3414 struct stat statb;
3415
3416 /* get -e + call $EDITOR */
3417 struct fs {
3418 char *name; /* file name to edit */
3419 struct stat statb; /* stat() after "sccs edit" */
3420 struct timespec mtime; /* mtime before "sccs edit" */
3421 int nogfile; /* miss. before "sccs edit" */
3422 };
3423 struct fs _fs[16];
3424 struct fs *fs = _fs;
3425 struct fs *rs = NULL;
3426 int fslen = sizeof (_fs) / sizeof (struct fs);
3427 int fsidx = 0;
3428 char *xp[2];
3429 sigset_t oldmask;
3430
3431 /*
3432 * prepare args, skip over flag arguments
3433 */
3434 for (np = &ap[1]; *np != NULL; np++) {
3435 char *filep;
3436
3437 if (**np == '-')
3438 continue;
3439 filep = makefile(*np, SccsDir);
3440 if (!exists(filep)) /* No s.file, */
3441 continue; /* not under SCCS */
3442
3443 /*
3444 * Run a lightweight "sact s.file"
3445 */
3446 if (exists(auxf(filep, 'p'))) /* Already edited */
3447 continue; /* so ignore */
3448
3449 if (filep != *np)
3450 free(filep);
3451
3452 if (fsidx >= fslen) {
3453 /*
3454 * Expand rule name space.
3455 */
3456 fslen += 16;
3457 fs = realloc(rs, fslen * sizeof (struct fs));
3458 if (fs == NULL) {
3459 perror(gettext("Sccs: no mem"));
3460 exit(EX_OSERR);
3461 }
3462 if (rs == NULL)
3463 memmove(fs, _fs, sizeof (_fs));
3464 rs = fs;
3465 }
3466 fs[fsidx].nogfile = 0;
3467 fs[fsidx].mtime.tv_sec = (time_t)0;
3468 fs[fsidx].mtime.tv_nsec = 0;
3469 if (!exists(*np)) {
3470 fs[fsidx].nogfile = 1;
3471 } else {
3472 fs[fsidx].mtime.tv_sec = Statbuf.st_mtime;
3473 fs[fsidx].mtime.tv_nsec = stat_mnsecs(&Statbuf);
3474 }
3475 xp[0] = *np;
3476 xp[1] = NULL;
3477 rval = command(xp, FORCE_FORK, "edit");
3478 if (rval != 0) /* Checkout problem */
3479 continue; /* so ignore */
3480
3481 /*
3482 * Save unedited state from after sccs edit *np
3483 */
3484 fs[fsidx].name = *np;
3485 if (stat(*np, &fs[fsidx].statb) == -1)
3486 continue;
3487 fsidx++;
3488 }
3489 q = getenv("SCCS_EDITOR");
3490 if (q == NULL)
3491 q = getenv("EDITOR");
3492 if (q == NULL)
3493 q = "vi";
3494 cmd = lookup(q);
3495 if (cmd != NULL)
3496 fatal(gettext("illegal editor in environment (sc1)"));
3497 cmd = lookup(argv[0]);
3498 ap[0] = q;
3499 block_sigs(oldmask);
3500 rval = callprog(q, cmd->sccsflags, ap, TRUE);
3501 restore_sigs(oldmask);
3502
3503 for (i = 0; i < fsidx; i++) {
3504 if (stat(fs[i].name, &statb) != -1) {
3505 if (fs[i].statb.st_mtime != statb.st_mtime)
3506 continue;
3507 if (stat_mnsecs(&fs[i].statb) !=
3508 stat_mnsecs(&statb))
3509 continue;
3510 }
3511 xp[0] = fs[i].name;
3512 xp[1] = NULL;
3513 if (fs[i].nogfile) {
3514 rval = command(xp, TRUE, "unget -s");
3515 } else {
3516 struct timespec ts[2];
3517
3518 rval = command(xp, TRUE, "unedit");
3519
3520 ts[0].tv_sec = statb.st_atime;
3521 ts[0].tv_nsec = stat_ansecs(&statb);
3522 ts[1].tv_sec = fs[i].mtime.tv_sec;
3523 ts[1].tv_nsec = fs[i].mtime.tv_nsec;
3524
3525 utimensat(AT_FDCWD, fs[i].name, ts, 0);
3526 }
3527 }
3528 if (rs)
3529 free(rs);
3530 return (rval);
3531 }
3532
3533 static int
histfile(nfiles,argc,argv)3534 histfile(nfiles, argc, argv)
3535 int nfiles;
3536 int argc;
3537 char **argv;
3538 {
3539 int rval;
3540 char **np;
3541 char *g_name;
3542 char *s_name;
3543 bool is_dir;
3544 bool get_g = FALSE;
3545
3546 optind = 1;
3547 opt_sp = 1;
3548 while ((rval = getopt(argc, argv, ":g")) != -1) {
3549 switch (rval) {
3550
3551 case 'g':
3552 /*
3553 * The option -g is experimental and undocumented
3554 * until it works correctly.
3555 */
3556 get_g = TRUE;
3557 break;
3558
3559 case ':':
3560 usrerr("%s %s",
3561 gettext("option requires an argument"),
3562 argv[optind-1]);
3563 rval = EX_USAGE;
3564 exit(EX_USAGE);
3565 /*NOTREACHED*/
3566 default:
3567 usrerr("%s %s",
3568 gettext("unknown option"),
3569 argv[optind-1]);
3570 rval = EX_USAGE;
3571 exit(EX_USAGE);
3572 /*NOTREACHED*/
3573 }
3574 }
3575 argc -= optind;
3576
3577 if (argc <= 0)
3578 fatal(gettext("missing file arg (cm3)"));
3579 if (argc > 1)
3580 fatal(gettext("too many file args (cm18)"));
3581
3582 rval = 0;
3583 for (np = &argv[optind]; *np != NULL; np++) {
3584 g_name = *np;
3585 is_dir = isdir(g_name);
3586 if (NewMode) {
3587 N.n_pflags = NP_NOCHDIR;
3588 Nsd.n_pflags = NP_NOCHDIR;
3589 if (is_dir) {
3590 N.n_pflags |= NP_DIR;
3591 Nsd.n_pflags |= NP_DIR;
3592 }
3593 s_name = bulkprepare(get_g?&Nsd:&N, g_name);
3594 if (s_name == NULL)
3595 fatal(gettext(bulkerror(&N)));
3596 N.n_pflags = 0;
3597 Nsd.n_pflags = 0;
3598 } else {
3599 if (get_g)
3600 s_name = makegfile(g_name);
3601 else
3602 s_name = makefile(g_name, SccsDir);
3603 }
3604 printf("%s\n", s_name);
3605 }
3606
3607 return (rval);
3608 }
3609
3610 static int
istext(nfiles,argc,argv)3611 istext(nfiles, argc, argv)
3612 int nfiles;
3613 int argc;
3614 char **argv;
3615 {
3616 int rval;
3617 char **np;
3618 int showdefault = 0;
3619 int silent = 0;
3620 int dov6 = NewMode?1:0;
3621 int files = 0;
3622
3623 optind = 1;
3624 opt_sp = 1;
3625 while ((rval = getopt(argc, argv, ":DsV:")) != -1) {
3626 switch (rval) {
3627
3628 case 'D':
3629 showdefault = TRUE;
3630 break;
3631 case 's':
3632 silent = TRUE;
3633 break;
3634 case 'V':
3635 switch (*optarg) {
3636 case '4':
3637 dov6 = FALSE;
3638 break;
3639 case '6':
3640 dov6 = TRUE;
3641 break;
3642 default:
3643 usrerr("%s -V%s",
3644 gettext("unknown option"),
3645 optarg);
3646 rval = EX_USAGE;
3647 exit(EX_USAGE);
3648 break;
3649 }
3650 break;
3651 case ':':
3652 usrerr("%s %s",
3653 gettext("option requires an argument"),
3654 argv[optind-1]);
3655 rval = EX_USAGE;
3656 exit(EX_USAGE);
3657 /*NOTREACHED*/
3658 default:
3659 usrerr("%s %s",
3660 gettext("unknown option"),
3661 argv[optind-1]);
3662 rval = EX_USAGE;
3663 exit(EX_USAGE);
3664 /*NOTREACHED*/
3665 }
3666 }
3667
3668 rval = 0;
3669 if (showdefault) {
3670 if (argv[optind]) {
3671 usrerr(gettext("too many args"));
3672 return (EX_USAGE);
3673 }
3674 printf(gettext("Current default is SCCSv%d.\n"), dov6?6:4);
3675 return (rval);
3676 }
3677
3678 for (np = &argv[optind]; *np != NULL; np++) {
3679 files |= 1;
3680 rval |= fgetchk(*np, dov6, silent);
3681 }
3682 if (files == 0) {
3683 usrerr(gettext(" missing file arg (cm3)"));
3684 rval = EX_USAGE;
3685 exit(EX_USAGE);
3686 }
3687 return (rval);
3688 }
3689
3690 /*
3691 ** MAKEGFILE -- make filename of clear file
3692 **
3693 ** Parameters:
3694 ** name -- the file name to be munged.
3695 **
3696 ** Returns:
3697 ** The pathname of the clear file.
3698 ** NULL on error.
3699 **
3700 */
3701
3702 static char *
makegfile(name)3703 makegfile(name)
3704 char *name;
3705 {
3706 register char *gname, *p, *g, *s;
3707
3708 if (name == NULL || *name == '\0') {
3709 return (NULL);
3710 }
3711 if (sccsfile(name)) {
3712 gname = name;
3713 } else {
3714 gname = makefile(name, SccsDir);
3715 if (gname == NULL) {
3716 return (NULL);
3717 }
3718 }
3719 if (gname == name) {
3720 gname = xstrdup(name);
3721 }
3722 if (!sccsfile(gname)) {
3723 free(gname);
3724 return (NULL);
3725 }
3726 g = auxf(gname, 'g');
3727 s = malloc(strlen(SccsPath) + strlen(g) + 4); /* "%s/s.%s" */
3728 if (s == NULL) {
3729 perror(gettext("Sccs: no mem"));
3730 exit(EX_OSERR);
3731 }
3732 sprintf(s, "%s/s.%s", SccsPath, g);
3733 p = gname + strlen(gname) - strlen(s);
3734 if (strcmp(p, s) != 0) {
3735 free(gname);
3736 free(s);
3737 return (NULL);
3738 }
3739 strcpy(p, g);
3740 free(s);
3741 return (gname);
3742 }
3743
3744 #ifdef USE_RECURSIVE
3745
3746 /*
3747 * Code to implement support for "sccs -R ..."
3748 */
3749 #include <schily/walk.h>
3750 #include <schily/find.h>
3751 #include <schily/getcwd.h>
3752
3753 #undef roundup
3754 #define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
3755
3756 /*
3757 * Structure used to transfer date from dorecurse() into walkfun().
3758 */
3759 struct wargs {
3760 int rval; /* Return value for dorecurse() */
3761 int sccslen; /* strlen(SccsPath) */
3762 short sccsflags; /* sccsflags for current cmd */
3763 char **argv; /* Current arg vector */
3764 int argind; /* Where to insert path name */
3765 int argsize; /* Size of allocated argv */
3766 };
3767
3768 LOCAL int walkfunc __PR((char *_nm, struct stat *_fs,
3769 int _type, struct WALK *_state));
3770
3771 LOCAL int
walkfunc(nm,fs,type,state)3772 walkfunc(nm, fs, type, state)
3773 char *nm;
3774 struct stat *fs;
3775 int type;
3776 struct WALK *state;
3777 {
3778 if (type == WALK_NS) {
3779 if (curcmd->sccsoper == CLEAN && curcmd->sccspath == CLEANC) {
3780 /*
3781 * With "sccs clean", we remove plenty of plain files
3782 * and it would be a bad idea to complain about this.
3783 * So return here early in case that the current path
3784 * is not a path that is a possible argument for the
3785 * clean command.
3786 */
3787 if (strcmp(nm + state->base, SccsPath) != 0)
3788 return (0);
3789 }
3790 errmsg("Cannot stat '%s'.\n", nm);
3791 state->err = 1;
3792 return (0);
3793 } else if (type == WALK_SLN && (state->walkflags & WALK_PHYS) == 0) {
3794 errmsg("Cannot follow symlink '%s'.\n", nm);
3795 state->err = 1;
3796 return (0);
3797 } else if (type == WALK_DNR) {
3798 if (state->flags & WALK_WF_NOCHDIR)
3799 errmsg("Cannot chdir to '%s'.\n", nm);
3800 else
3801 errmsg("Cannot read '%s'.\n", nm);
3802 state->err = 1;
3803 return (0);
3804 }
3805
3806 if (state->tree == NULL ||
3807 find_expr(nm, nm + state->base, fs, state, state->tree)) {
3808 if (S_ISDIR(fs->st_mode)) {
3809 if (strcmp(nm + state->base, SccsPath) != 0) {
3810 char nb[MAXPATHLEN];
3811
3812 nb[0] = '\0';
3813 if ((strlen(nm+state->base) + 11) < sizeof (nb))
3814 cat(nb, nm + state->base, "/",
3815 ".sccsignore", (char *)0);
3816
3817 /*
3818 * Check whether this directory contains a file
3819 * ".sccsignore" and thus this sub-tree should
3820 * be ignored.
3821 */
3822 if (nb[0] && access(nb, F_OK) >= 0)
3823 state->flags |= WALK_WF_PRUNE;
3824 /*
3825 * This is not a "SCCS" directory, so do
3826 * nothing and just return.
3827 */
3828 return (0);
3829 }
3830 }
3831 } else {
3832 /*
3833 * The find expression did not match, so return.
3834 */
3835 return (0);
3836 }
3837
3838 /*
3839 * At this point we either found a SCCS directory or a matching file.
3840 */
3841 {
3842 struct wargs *wp = state->auxp;
3843 int cwdlen;
3844 /*
3845 * The chdir code is only needed as long as we do not have
3846 * a portable treewalk() that does not require itself
3847 * an internal chdir() to work correctly.
3848 *
3849 * First fetch the current directory in case we collect names
3850 * and call the command only once.
3851 */
3852 #ifdef HAVE_FCHDIR
3853 int f = -1;
3854
3855 if (!bitset(COLLECT, wp->sccsflags)) {
3856 f = open(".", O_SEARCH);
3857 if (f < 0) {
3858 errmsg("Cannot get working directory.\n");
3859 state->flags |= WALK_WF_QUIT;
3860 return (0);
3861 }
3862 }
3863 #else
3864 char cwd[MAXPATHLEN+1];
3865
3866 if (!bitset(COLLECT, wp->sccsflags) &&
3867 getcwd(cwd, MAXPATHLEN) == NULL) {
3868 errmsg("Cannot get working directory.\n");
3869 state->flags |= WALK_WF_QUIT;
3870 return (0);
3871 }
3872 #endif
3873
3874 if (bitset(PDOT, wp->sccsflags)) { /* SCCS/p.* */
3875 nm[state->base] = 's';
3876 cwdlen = state->base - wp->sccslen - 1;
3877 } else { /* /SCCS/ */
3878 cwdlen = state->base;
3879 state->flags |= WALK_WF_PRUNE; /* Don't go into SCCS */
3880 }
3881 if (bitset(COLLECT, wp->sccsflags)) {
3882 if ((wp->argind+2) > wp->argsize) {
3883 wp->argsize += 2;
3884 wp->argsize = roundup(wp->argsize, 64);
3885 wp->argv = realloc(wp->argv,
3886 wp->argsize * sizeof (char *));
3887 if (wp->argv == NULL) {
3888 perror(gettext("Sccs: no mem"));
3889 exit(EX_OSERR);
3890 }
3891 }
3892 wp->argv[wp->argind++] = xstrdup(nm);
3893 wp->argv[wp->argind] = NULL;
3894 return (0);
3895 }
3896
3897 if ((cwdlen + 3) > Cwdlen) {
3898 Cwdlen = roundup(cwdlen+3, 64);
3899 Cwd = realloc(Cwd, Cwdlen);
3900 if (Cwd == NULL) {
3901 perror(gettext("Sccs: no mem"));
3902 exit(EX_OSERR);
3903 }
3904 }
3905 strlcpy(&Cwd[2], nm, cwdlen+1);
3906
3907 if (walkhome(state) < 0) {
3908 state->flags |= WALK_WF_QUIT;
3909 return (0);
3910 }
3911
3912 wp->argv[wp->argind] = nm;
3913 wp->rval |= command(wp->argv, TRUE, "");
3914
3915 #ifdef HAVE_FCHDIR
3916 if (fchdir(f) < 0) {
3917 errmsg("Cannot chdir back.\n");
3918 state->flags |= WALK_WF_QUIT;
3919 return (0);
3920 }
3921 close(f);
3922 #else
3923 if (chdir(cwd) < 0) {
3924 errmsg("Cannot chdir back.\n");
3925 state->flags |= WALK_WF_QUIT;
3926 return (0);
3927 }
3928 #endif
3929 }
3930 return (0);
3931 }
3932
3933 LOCAL int
dorecurse(argv,np,dir,cmd)3934 dorecurse(argv, np, dir, cmd)
3935 char **argv;
3936 char **np;
3937 char *dir;
3938 struct sccsprog *cmd;
3939 {
3940 int ac;
3941 char *av[10];
3942 char *dpath = NULL;
3943 char *ppath = NULL;
3944 char *oSccsDir = SccsDir;
3945 finda_t fa;
3946 findn_t *find_node;
3947 struct WALK walkstate;
3948 struct wargs wa;
3949 struct stat sb;
3950
3951 if (Cwd == NULL) {
3952 Cwdlen = 64;
3953 Cwd = malloc(Cwdlen);
3954 if (Cwd == NULL) {
3955 perror(gettext("Sccs: no mem"));
3956 exit(EX_OSERR);
3957 }
3958 strcpy(Cwd, "-C");
3959 }
3960 Cwd[2] = '\0';
3961
3962 if (SccsDir && SccsDir[0]) {
3963 dpath = malloc(strlen(SccsDir) + strlen(dir) + 2);
3964 if (dpath == NULL) {
3965 perror(gettext("Sccs: no mem"));
3966 exit(EX_OSERR);
3967 }
3968 cat(dpath, SccsDir, "/", dir, (char *)0);
3969 SccsDir = "";
3970 }
3971
3972 /*
3973 * If recursion should find all files, "dir" equals "." and we will not
3974 * use the following block that only applies to commands with explicit
3975 * path names to non-directories.
3976 */
3977 if (stat(dir, &sb) < 0 || !S_ISDIR(sb.st_mode)) {
3978 char *p = strrchr(dir, '/');
3979 int cwdlen;
3980
3981 cwdlen = p - dir;
3982 if (p == NULL) {
3983 strcpy(&Cwd[2], "./");
3984 cwdlen = 1;
3985 } else if (p == dir) {
3986 strcpy(&Cwd[2], "/");
3987 } else {
3988 if ((cwdlen + 4) > Cwdlen) {
3989 Cwdlen = roundup(cwdlen+4, 64);
3990 Cwd = realloc(Cwd, Cwdlen);
3991 if (Cwd == NULL) {
3992 perror(gettext("Sccs: no mem"));
3993 exit(EX_OSERR);
3994 }
3995 }
3996 strlcpy(&Cwd[2], dir, cwdlen + 2);
3997 }
3998
3999 *np = dpath ? dpath : dir;
4000 Rflag = -1; /* avoid further recursion */
4001 ac = command(argv, TRUE, "");
4002 Rflag = 1;
4003 SccsDir = oSccsDir;
4004 return (ac);
4005 }
4006
4007 ac = 0;
4008 if (bitset(PDOT, cmd->sccsflags)) {
4009 int l = strlen(SccsPath) + 6; /* "* /" SccsPath "/p.*" */
4010
4011 ppath = malloc(l);
4012 if (ppath == NULL) {
4013 perror(gettext("Sccs: no mem"));
4014 exit(EX_OSERR);
4015 }
4016 cat(ppath, "*/", SccsPath, "/p.*", (char *)NULL);
4017 av[ac++] = "-type";
4018 av[ac++] = "f";
4019 av[ac++] = "-path";
4020 av[ac++] = ppath;
4021 } else {
4022 av[ac++] = "-type";
4023 av[ac++] = "d";
4024 }
4025 av[ac] = NULL;
4026
4027 find_argsinit(&fa);
4028 fa.walkflags = WALK_CHDIR | WALK_PHYS | WALK_NOEXIT;
4029 fa.Argc = ac;
4030 fa.Argv = (char **)av;
4031
4032 find_node = find_parse(&fa);
4033 if (fa.primtype == FIND_ERRARG || fa.primtype != FIND_ENDARGS) {
4034 wa.rval = 1;
4035 goto out;
4036 }
4037
4038 walkinitstate(&walkstate);
4039 find_timeinit(time(0));
4040 walkstate.walkflags = fa.walkflags;
4041 walkstate.tree = find_node;
4042 wa.rval = 0;
4043 wa.sccslen = strlen(SccsPath);
4044 wa.sccsflags = cmd->sccsflags;
4045 wa.argv = argv;
4046 wa.argind = np - argv;
4047 wa.argsize = 0;
4048 walkstate.auxp = &wa;
4049 if (bitset(COLLECT, cmd->sccsflags)) {
4050 int i;
4051
4052 wa.argind++;
4053 wa.argsize = roundup(wa.argind, 64);
4054 wa.argv = malloc(wa.argsize * sizeof (char *));
4055 if (wa.argv == NULL) {
4056 perror(gettext("Sccs: no mem"));
4057 exit(EX_OSERR);
4058 }
4059 wa.argind--;
4060 for (i = 0; i <= wa.argind; i++)
4061 wa.argv[i] = argv[i];
4062 }
4063 if (fa.patlen > 0) {
4064 walkstate.patstate = malloc(sizeof (int) * fa.patlen);
4065 if (walkstate.patstate == NULL) {
4066 perror(gettext("Sccs: no mem"));
4067 exit(EX_OSERR);
4068 }
4069 }
4070
4071 Rflag = -1; /* avoid further recursion */
4072 treewalk(dpath ? dpath : dir, walkfunc, &walkstate);
4073 Rflag = 1;
4074
4075 if (bitset(COLLECT, cmd->sccsflags)) {
4076 Cwd[2] = '\0';
4077 Rflag = -1; /* avoid further recursion */
4078 wa.rval |= command(wa.argv, TRUE, "");
4079 Rflag = 1;
4080 }
4081
4082 if (walkstate.patstate == NULL)
4083 free(walkstate.patstate);
4084 out:
4085 if (wa.argv != argv)
4086 free(wa.argv);
4087 find_free(find_node, &fa);
4088 if (dpath)
4089 free(dpath);
4090 if (ppath)
4091 free(ppath);
4092
4093 SccsDir = oSccsDir;
4094 return (wa.rval);
4095 }
4096
4097 #endif /* USE_RECURSIVE */
4098
4099
4100
4101 LOCAL int
fgetchk(file,dov6,silent)4102 fgetchk(file, dov6, silent)
4103 char *file;
4104 int dov6;
4105 int silent;
4106 {
4107 FILE *inptr;
4108 char *p = NULL; /* Intialize to make gcc quiet */
4109 char *pn = NULL;
4110 char line[VBUF_SIZE];
4111 int idx = 0;
4112 off_t nline;
4113 off_t soh = 0;
4114 int err = 0;
4115 char lastchar;
4116
4117 inptr = fopen(file, "rb");
4118 if (inptr == (FILE *)NULL) {
4119 if (!silent)
4120 fprintf(stderr,
4121 gettext(
4122 "%s: Cannot open.\n"), file);
4123 return (0);
4124 }
4125 setvbuf(inptr, NULL, _IOFBF, VBUF_SIZE);
4126
4127 /*
4128 * This gives the illusion that a zero-length file ends
4129 * in a newline so that it won't be mistaken for a
4130 * binary file.
4131 */
4132 lastchar = '\n';
4133 (void) memset(line, '\377', sizeof (line));
4134 nline = 0;
4135 /*
4136 * In most cases (non record oriented I/O), we can optimize the way we
4137 * scan files for '\0' bytes, line-ends '\n' and ^A '\1'. The optimized
4138 * algorithm allows to avoid to do a reverse scan for '\0' from the end
4139 * of the buffer.
4140 */
4141 while ((idx = fread(line, 1, sizeof (line), inptr)) > 0) {
4142 if (lastchar == '\n' && line[0] == CTLCHAR) {
4143 if (soh == 0 && !dov6) {
4144 soh = nline + 1;
4145 goto issoh;
4146 }
4147 }
4148 lastchar = line[idx-1];
4149 p = findbytes(line, idx, '\0');
4150 if (p != NULL)
4151 pn = p;
4152 for (p = line;
4153 (p = findbytes(p, idx - (p-line), '\n')) != NULL; p++) {
4154 if (pn && p > pn) {
4155 errout:
4156 if (inptr)
4157 fclose(inptr);
4158 if (silent)
4159 return (1);
4160 fprintf(stderr,
4161 gettext(
4162 "%s: line %jd contains '\\000' (de14)\n"),
4163 file, (Intmax_t)++nline);
4164 return (1);
4165 }
4166 nline++;
4167 if ((p - line) >= (idx-1))
4168 break;
4169
4170 if (p[1] == CTLCHAR) {
4171 if (soh == 0 && !dov6) {
4172 soh = nline + 1;
4173 goto issoh;
4174 }
4175 }
4176 }
4177 }
4178 issoh:
4179 fclose(inptr);
4180 inptr = NULL;
4181 if (soh) {
4182 if (!silent)
4183 fprintf(stderr,
4184 gettext(
4185 "%s: line %jd begins with '\\001' (de20)\n"),
4186 file, (Intmax_t)soh);
4187 err = 1;
4188 }
4189 if (lastchar != '\n') {
4190 if (pn && nline == 0) /* Found null byte but no newline */
4191 goto errout;
4192 if (dov6)
4193 return (err);
4194 if (!silent)
4195 fprintf(stderr,
4196 gettext("%s: no newline at end of file (de18)\n"),
4197 file);
4198 err = 1;
4199 }
4200 return (err);
4201 }
4202