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)
m_check_dot_vs_arrow_syntaxGetValueForExpressionPathOptions213 #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 */
DontAllowBitfieldSyntaxGetValueForExpressionPathOptions242 
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 
DefaultOptionsGetValueForExpressionPathOptions253 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));
GetExecutionContextRef()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));
SetIsConstant()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));
IsConstant()280 static void nothingedited __PR((bool nobranch, const char *usernm));
281 static bool isbranch __PR((char *sid));
GetModID()282 static int unedit __PR((int nfiles, char **argv));
283 static void do_unedit __PR((char *fn));
SetUpdateID(ProcessModID new_id)284 static char *tail __PR((register char *fn));
285 static struct p_file *getpfent __PR((FILE *pfp));
SetNeedsUpdate()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, ...));
NeedsUpdating(bool accept_invalid_exe_ctx)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));
IsValid()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 
SetInvalid()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 */
IsInScope()420 };
421 
GetByteOffset()422 static char	*SccsPath = SCCSPATH;	/* pathname of SCCS files ("SCCS") */
423 static char	_NewOpt[] = NSCCS;	/* path opt. of SCCS files ("-NSCCS") */
GetBitfieldBitSize()424 static char	*NewOpt = _NewOpt;	/* make it modifyable		   */
425 static char	*NewOptsd = NSCCSsd;	/* path opt. of files ("-NSCCS/s.") */
GetBitfieldBitOffset()426 #ifdef SCCSDIR
427 static char	*SccsDir = SCCSDIR;	/* directory to begin search from */
IsBitfield()428 #else
429 static char	*SccsDir = "";
430 #endif
431 static char	MyName[] = MYNAME;	/* name used in messages */
IsArrayItemForPointer()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
GetName()467 extern char	*sys_siglist[];
468 #endif
469 #endif
GetID()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
491 main(argc, argv)
492 	int argc;
493 	char **argv;
494 {
495 	register char *p;
496 	register int i;
GetValue()497 	int current_optind, c;
498 	register char *argp;
GetValue()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 
GetLocationAsCString()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 */
GetValueIsValid()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 {
SetName(ConstString name)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);
GetStaticValue()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 			}
IsSynthetic()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
GetLiveAddress()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) {
ValueUpdated()631 				current_optind = optind;
632 				argv[i] = 0;
633 				if (optind > i+1) {
634 					argv[i+1] = NULL;
635 				}
636 				i = current_optind;
IsDynamic()637 			}
638 			c = getopt(argc, argv, "()-rp:d:NORTV(version)");
DoesProvideSyntheticValue()639 			if (c == EOF) {
640 				break;
641 			}
642 			argp = optarg;
643 			switch (c) {
644 			  case 'r':		/* run as real user */
SetSyntheticChildrenGenerated(bool b)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]);
GetIsConstant()693 #ifdef	USE_RECURSIVE
694 				fprintf(stderr, gettext("Usage: sccs [-N][-O][-R][ -r ][ -drootprefix ][ -p subdir ]\n\t subcommand [ option ...] [ filename ...]\n"));
NeedsUpdating()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 	}
SetIsConstant()701 	argv += current_optind;
702 	if (SccsPath[0] == '\0')
703 	   SccsPath = ".";
704 	}
SetFormat(lldb::Format format)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
SetPreferredDisplayLanguage(lldb::LanguageType lt)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);
GetSummaryFormat()717 		/*NOTREACHED*/
718 	}
719 
720 	if (xsethome(NULL) > 0)
721 		NewMode = TRUE;
SetSummaryFormat(lldb::TypeSummaryImplSP format)722 
723 	if (use_old) {
724 		NewMode = FALSE;
725 	} else if (use_new) {
726 		NewMode = TRUE;
SetValueFormat(lldb::TypeFormatImplSP format)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.
GetSyntheticChildren()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) {
GetParent()753 		setnewmode();
754 		if (NewOpt == NULL) {
GetParent()755 			NewOpt = malloc(strlen(SccsPath)+6);
756 			if (NewOpt == NULL) {
757 				perror(gettext("Sccs: no mem"));
758 				exit(EX_OSERR);
SetAddressTypeOfChildren(AddressType at)759 			}
760 			strcpy(NewOpt, "-N   "); /* Three flag placeholders */
761 			strcat(NewOpt, SccsPath);
762 
763 			NewOptsd = malloc(strlen(SccsPath)+6);
764 			if (NewOptsd == NULL) {
SetHasCompleteType()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 }
GetVariable()785 
786 
787 static int	NelemArrSids;
788 static int	size_ap_for_get;
GetLanguageFlags()789 static int	cur_num_file;
790 static char	Nsid[50];
SetLanguageFlags(uint64_t flags)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.  */
ChildrenManager()798 /* This function is used in 'deledit' macro (see 1083894 bug) */
799 
800 static char *
801 getNsid(file, user)
802 	char * file;
803 	char * user;
804 {
GetChildAtIndex(size_t idx)805 	int		cnt = -1;
806 	char		line[BUFSIZ];
807 	char *		cp;
808 	struct packet	gpkt;
809 	struct pfile	pf;
810 	struct pfile	goodpf;
SetChildAtIndex(size_t idx,ValueObject * valobj)811 	FILE *		in;
812 
813 	if (NewMode) {
814 		file = bulkprepare(&N, file);
815 		if (file == NULL) {
816 			/*
817 			 * The error is typically
SetChildrenCount(size_t count)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
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);
BitflagsBitflags916 
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;		     */
GetManager()946 	/* - -NSCCS;		     */
947 	/* - -R spare dir element;   */
948 	/* - last element (NULL).    */
949 	for (nav_size += 7, ap = argv; *ap != NULL; ap++) {
CanUpdateWithInvalidExecutionContext()950 		nav_size++;
951 	}
952 	if ((nav = malloc(nav_size * (sizeof (char *)))) == NULL) {
953 		perror(gettext("Sccs: no mem"));
954 		exit(EX_OSERR);
955 	}
GetDynamicValueTypeImpl()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 == ' ') {
SetValueDidChange(bool value_changed)976 		if (*++p == '+')
977 			NewOpt[2] = *p++;
978 		if (*p == '-')
979 			NewOpt[3] = *p++;
SetValueIsValid(bool valid)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 		}
IsChecksumEmpty()998 	}
999 	*np = NULL;
1000 	if (*ap == NULL)
1001 		*np++ = *argv++;
1002 
DoUpdateChildrenAddressType(ValueObject & valobj)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 = &macro_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
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
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 *
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
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 *
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
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
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
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 *
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
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
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
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
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
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
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 *
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 *
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
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 *
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
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
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 
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 
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 
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
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
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
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
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
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
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
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 *
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
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
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
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