1 /*
2  * The contents of this file are subject to the terms of the
3  * Common Development and Distribution License, Version 1.0 only
4  * (the "License").  You may not use this file except in compliance
5  * with the License.
6  *
7  * See the file CDDL.Schily.txt in this distribution for details.
8  *
9  * When distributing Covered Code, include this CDDL HEADER in each
10  * file and include the License file CDDL.Schily.txt from this distribution.
11  */
12 /*
13  *	Find the "home" directory for a "File Set"
14  *
15  *	Search for $SET_HOME/.sccs
16  *
17  * @(#)sethome.c	1.18 20/09/25 Copyright 2011-2020 J. Schilling
18  */
19 #if defined(sun)
20 #pragma ident "@(#)sethome.c	1.18 20/09/25 Copyright 2011-2020 J. Schilling"
21 #endif
22 
23 #if defined(sun)
24 #pragma ident	"@(#)sethome.c"
25 #pragma ident	"@(#)sccs:lib/comobj/sethome.c"
26 #endif
27 #include	<defines.h>
28 
29 LOCAL	int	shinit;	/* sethome was initialized */
30 /*
31  * If it is not possible to retrieve "setahome", it may be a NULL pointer.
32  * The variables "setrhome" and "cwdprefix" are always set from sethome().
33  *
34  * Note that if we switch to shared libraries, these variables will not work
35  * on Mac OS since Mac OS only supports a broken linker.
36  */
37 char	*setphome;	/* Best path to the project set home directory */
38 char	*setrhome;	/* Relative path to the project set home directory */
39 char	*setahome;	/* Absolute path to the project set home directory */
40 char	*cwdprefix;	/* Prefix from project set home directory to cwd */
41 char	*changesetfile;	/* The path to the changeset history file */
42 char	*changesetgfile; /* The path to the changeset file */
43 int	homedist;	/* The # of directories to the project set home dir */
44 int	setphomelen;	/* strlen(setphome) */
45 int	setrhomelen;	/* strlen(setrhome) */
46 int	setahomelen;	/* strlen(setahome) */
47 int	cwdprefixlen;	/* strlen(cwdprefix) */
48 int	sethomestat;	/* sethome() status flags */
49 
50 EXPORT	void	unsethome	__PR((void));
51 EXPORT	int	xsethome	__PR((char *path));
52 EXPORT	int	sethome		__PR((char *path));
53 LOCAL	int	searchabs	__PR((char *path));
54 LOCAL	int	dorel		__PR((char *bp, size_t len));
55 LOCAL	void	mkrelhome	__PR((char *bp, size_t len, int i));
56 LOCAL	int	mkprefix	__PR((char *bp, size_t len, int i));
57 LOCAL	int	checkdotsccs	__PR((char *path));
58 EXPORT	int	checkhome	__PR((char *path));
59 LOCAL	void	gchangeset	__PR((void));
60 
61 /*
62  * Undo the effect of a previous sethome() call.
63  * Free all data and change the state tu "uninitialized".
64  */
65 EXPORT void
unsethome()66 unsethome()
67 {
68 	if (setrhome != NULL) {
69 		free(setrhome);
70 		setrhome = NULL;
71 	}
72 	if (setahome != NULL) {
73 		free(setahome);
74 		setahome = NULL;
75 	}
76 	if (cwdprefix != NULL) {
77 		free(cwdprefix);
78 		cwdprefix = NULL;
79 	}
80 	if (changesetfile != NULL) {
81 		free(changesetfile);
82 		changesetfile = NULL;
83 	}
84 	if (changesetgfile != NULL) {
85 		free(changesetgfile);
86 		changesetgfile = NULL;
87 	}
88 	setphome = NULL;
89 	homedist = setphomelen = setrhomelen = setahomelen =
90 			cwdprefixlen = sethomestat = 0;
91 	shinit = 0;
92 }
93 
94 /*
95  * A variant of sethome() that includes error handling.
96  */
97 EXPORT int
xsethome(path)98 xsethome(path)
99 	char	*path;
100 {
101 	int	ret;
102 
103 	errno = 0;
104 	ret = sethome(path);
105 	if (ret < 0)
106 		efatal(gettext("cannot get project home directory (co32)"));
107 	errno = 0;
108 	return (ret);
109 }
110 
111 /*
112  * Try to find the project set home directory and a path from that directory
113  * to the current directory.
114  *
115  * This is the central function for sccs when it tries to support whole
116  * projects instead of just unrelated single files.
117  *
118  * Returns:
119  *
120  *	-1	Error, cannot scan directories for ".sccs"
121  *	 0	$SET_HOME/.sccs not found
122  *	 1	$SET_HOME/.sccs found
123  */
124 EXPORT int
sethome(path)125 sethome(path)
126 	char	*path;
127 {
128 	char	buf[max(8192, PATH_MAX+1)];
129 	int	len;
130 	int	blen;
131 
132 	if (shinit != 0)
133 		return (sethomestat & SETHOME_OK);
134 	shinit = 1;
135 
136 	if (path == NULL)
137 		path = ".";
138 	if (abspath(path, buf, sizeof (buf)) == NULL) {	/* Can't get abspath */
139 		/*
140 		 * Resolve symlinks and .. in path
141 		 */
142 		resolvenpath(path, buf, sizeof (buf));	/* Rationalize path */
143 		return (dorel(buf, sizeof (buf)));
144 	}
145 
146 	errno = 0;
147 	len = searchabs(buf);				/* Index after home */
148 	if (len < 0) {
149 		if (errno != 0)				/* Other than ENOENT */
150 			return (-1);
151 		return (0);				/* No $SET_HOME/.sccs */
152 	}
153 
154 	/*
155 	 * $SET_HOME/.sccs was found and len is the offset
156 	 * in buf where we may append "/.sccs".
157 	 */
158 	blen = strlen(buf);
159 	buf[len] = '\0';
160 	if (len == 0) {
161 		setahome = strdup("/");
162 		setahomelen = 1;
163 	} else {
164 		setahome = strdup(buf);
165 		setahomelen = len;
166 	}
167 	if (len >= blen) {				/* in project home */
168 		cwdprefix = strdup("");
169 		cwdprefixlen = 0;
170 	} else {					/* deeper in tree */
171 		cwdprefix = strdup(&buf[len+1]);
172 		cwdprefixlen = strlen(&buf[len+1]);
173 	}
174 	if (setahome == NULL || cwdprefix == NULL) {	/* no mem */
175 		setahomelen = 0;
176 		cwdprefixlen = 0;
177 		return (-1);				/* Return error */
178 	}
179 	resolvenpath(path, buf, sizeof (buf));		/* Rationalize path */
180 	mkrelhome(buf, sizeof (buf), homedist);
181 	if (setrhome == NULL)				/* no mem */
182 		return (-1);				/* return error */
183 	if (setahomelen > 0 && setahomelen < setrhomelen) {
184 		sethomestat |= SETHOME_ABS;
185 		setphome = setahome;
186 		setphomelen = setahomelen;
187 	} else {
188 		setphome = setrhome;
189 		setphomelen = setrhomelen;
190 	}
191 	sethomestat |= SETHOME_OK;
192 	(void) checkdotsccs(setrhome);
193 	gchangeset();
194 	return (1);					/* $SET_HOME/.sccs OK */
195 }
196 
197 /*
198  * Search for the directory $SET_HOME/.sccs using the absolute path name to the
199  * the current directory.
200  */
201 LOCAL int
searchabs(path)202 searchabs(path)
203 	char	*path;
204 {
205 	struct stat sb;
206 	char	buf[max(8192, PATH_MAX+1)];
207 	char	*p;
208 	int	len;
209 	int	err = 0;
210 	int	i = 0;
211 	int	found = 0;
212 
213 	strlcpy(buf, path, sizeof (buf));
214 	len = strlen(buf);
215 	p = &buf[len];
216 
217 	while (p >= buf) {
218 		strlcpy(p, "/.sccs", sizeof (buf) - (p - buf));
219 		found = 0;
220 		if (stat(buf, &sb) >= 0) {
221 			if (S_ISDIR(sb.st_mode))
222 				found++;
223 			break;
224 		} else if (errno != ENOENT) {
225 			err = errno;
226 		}
227 		if (p == buf)
228 			break;
229 		while (p > buf) {
230 			if (*--p == '/')
231 				break;
232 		}
233 		i++;
234 	}
235 	if (!found) {
236 		if (err)
237 			errno = err;
238 		else
239 			errno = 0;
240 		return (-1);
241 	}
242 
243 	homedist = i;
244 
245 	return (p - buf);
246 }
247 
248 /*
249  * Search for the directory $SET_HOME/.sccs using the relative path names from
250  * the current directory. This function is only called in case that it did not
251  * work to retrieve the absolute pathname.
252  */
253 LOCAL int
dorel(bp,len)254 dorel(bp, len)
255 	char	*bp;
256 	size_t	len;
257 {
258 	struct stat sb;
259 	struct stat sb2;
260 	char	*p;
261 	char	*s;
262 	int	err = 0;
263 	int	i;
264 	int	found = 0;
265 
266 	p = bp + strlen(bp);
267 	if (bp[0] == '.' && bp[1] == '\0') {
268 		p = bp;
269 		s = &bp[1];
270 		strlcpy(bp, ".sccs", len);
271 	} else {
272 		if (stat(bp, &sb) < 0) {
273 			/*
274 			 * If we cannot stat() the given initial path for
275 			 * whatever reason, we will not be able to scan the
276 			 * filesystem tree to search for ".sccs".
277 			 */
278 			return (0);
279 		}
280 		if (strlcat(bp, "/.sccs", len) >= len) {
281 			return (-1);
282 		} else {
283 			s = p + 2;
284 		}
285 	}
286 	i = 0;
287 	for (;;) {
288 		found = 0;
289 		if (stat(bp, &sb) >= 0) {
290 			if (S_ISDIR(sb.st_mode))
291 				found++;
292 			break;
293 		} else if (errno != ENOENT) {
294 			err = errno;
295 			break;
296 		}
297 		*s = '\0';
298 		sb.st_ino = (ino_t)-1;
299 		sb2.st_ino = (ino_t)-2;
300 		stat(bp, &sb);		/* "." */
301 		s[0] = '.';		/* Make it ".." */
302 		s[1] = '\0';
303 		stat(bp, &sb2);		/* ".." */
304 		if (sb.st_ino == sb2.st_ino &&
305 		    sb.st_dev == sb2.st_dev) {
306 			break;		/* We found the root directory. */
307 		}
308 		if (strlcat(bp, "/.sccs", len) >= len)
309 			return (-1);
310 		s += 3;
311 		i++;
312 	}
313 	if (!found) {
314 		if (err) {
315 			errno = err;
316 			return (-1);			/* Other than ENOENT */
317 		}
318 		return (0);				/* No $SET_HOME/.sccs */
319 	}
320 	s -= 2;
321 	if (s > bp)
322 		s[0] = '\0';
323 	if (p == bp) {
324 		setrhome = strdup(".");
325 	} else
326 		setrhome = strdup(bp);
327 	if (setrhome != NULL)
328 		setrhomelen = strlen(bp);
329 	homedist = i;
330 	if (setrhome == NULL)				/* no mem */
331 		return (-1);
332 	setphome = setrhome;
333 	setphomelen = setrhomelen;
334 	sethomestat |= SETHOME_OK;
335 	(void) checkdotsccs(setrhome);
336 	*p = '\0';					/* Cut off new text */
337 	errno = 0;
338 	if (!mkprefix(bp, len, i))			/* Not found */
339 		return (-1);
340 	if (cwdprefix == NULL)				/* no mem */
341 		return (-1);
342 	gchangeset();
343 	return (1);
344 }
345 
346 /*
347  * Create a relative path from the current directory to the
348  * "project set home" directory. The input is the number of directories
349  * betweem the current dir and the project set home directory.
350  */
351 LOCAL void
mkrelhome(bp,len,n)352 mkrelhome(bp, len, n)
353 	char	*bp;
354 	size_t	len;
355 	int	n;
356 {
357 	int	isdot = bp[0] == '.' && bp[1] == '\0';
358 
359 	if (isdot)
360 		bp[0] = '\0';
361 	if (n == 0 && isdot) {
362 		strlcat(bp, ".", len);
363 	} else {
364 		if (isdot) {
365 			bp[1] = '.';
366 			bp[2] = '\0';
367 		}
368 		if (n > 0 && bp[0] == '\0') {
369 			strlcat(bp, "..", len);
370 			n--;
371 		}
372 		while (--n >= 0) {
373 			strlcat(bp, "/..", len);
374 		}
375 	}
376 	setrhome = strdup(bp);
377 	if (setrhome != NULL)
378 		setrhomelen = strlen(bp);
379 }
380 
381 /*
382  * Create the path prefix from the "project set home" directory to the current
383  * directory. The input is the number of directories betweem the current dir
384  * and the project set home directory. We implement a partial getcwd() by
385  * scanning directories and matching inode numbers for ".".
386  */
387 LOCAL int
mkprefix(bp,len,n)388 mkprefix(bp, len, n)
389 	char	*bp;
390 	size_t	len;
391 	int	n;
392 {
393 	struct stat sb;
394 	struct stat sb2;
395 	char	buf[max(8192, PATH_MAX+1)];
396 	char	pfx[max(8192, PATH_MAX+1)];
397 	char	*p;
398 	char	*s;
399 	int	i;
400 	int	found;
401 	DIR	*dp;
402 	struct dirent *de;
403 
404 	if (bp[0]) {
405 		if (stat(bp, &sb) < 0) {
406 			/*
407 			 * If we cannot stat() the given initial path for
408 			 * whatever reason, we will not be able to scan the
409 			 * filesystem tree to search for ".sccs".
410 			 */
411 			return (0);
412 		}
413 		strlcat(bp, "/", len);
414 	}
415 	i = n;
416 	for (; --i >= 0; ) {
417 		strlcat(bp, "../", len);
418 	}
419 	strlcat(bp, ".", len);
420 
421 	s = bp + strlen(bp);
422 	i = n;
423 	pfx[0] = '\0';
424 	while (--i >= 0) {		/* Enter only if n > 0 */
425 
426 		s[-3] = '\0';
427 		stat(bp, &sb);		/* Dir to match */
428 		s[-3] = '.';
429 		dp = opendir(bp);	/* one level above that dir */
430 
431 		strlcpy(buf, bp, sizeof (buf));
432 		p = buf + strlen(buf)-1;
433 		found = 0;
434 		while ((de = readdir(dp)) != NULL) {
435 			if (strcmp(de->d_name, ".") == 0 ||
436 			    strcmp(de->d_name, "..") == 0)
437 				continue;
438 			strlcpy(p, de->d_name, sizeof (buf) - (p - buf));
439 			stat(buf, &sb2);
440 			if (sb.st_ino == sb2.st_ino &&
441 			    sb.st_dev == sb2.st_dev) {
442 				found++;
443 				break;
444 			}
445 		}
446 		if (!found)
447 			return (0);
448 		if (pfx[0])
449 			strlcat(pfx, "/", sizeof (pfx));
450 		strlcat(pfx, sname(buf), sizeof (pfx));
451 		s -= 3;
452 		*s = '\0';
453 	}
454 	cwdprefix = strdup(pfx);
455 	if (cwdprefix != NULL)
456 		cwdprefixlen = strlen(pfx);
457 	return (1);				/* cwdprefix found */
458 }
459 
460 /*
461  * Check $SET_HOME/.sccs for specific sub-directories and flag the
462  * search results.
463  */
464 LOCAL int
checkdotsccs(path)465 checkdotsccs(path)
466 	char	*path;
467 {
468 	struct stat sb;
469 	char	buf[max(8192, PATH_MAX+1)];
470 	char	*p;
471 	int	len;
472 	int	err = 0;
473 
474 	strlcpy(buf, path, sizeof (buf));
475 	len = strlen(buf);
476 	p = &buf[len];
477 
478 	strlcpy(p, "/.sccs/data", sizeof (buf) - (p - buf));
479 	if (stat(buf, &sb) >= 0) {
480 		if (S_ISDIR(sb.st_mode)) {
481 			sethomestat &= ~SETHOME_INTREE;
482 			sethomestat |=  SETHOME_OFFTREE;
483 		}
484 	} else if (errno != ENOENT) {
485 		err = errno;
486 	} else {
487 		sethomestat |=  SETHOME_INTREE;
488 		sethomestat &= ~SETHOME_OFFTREE;
489 	}
490 	p += 7;					/* Skip "/.sccs/" */
491 	strlcpy(p, "dels", sizeof (buf) - (p - buf));
492 	if (stat(buf, &sb) >= 0) {
493 		if (S_ISDIR(sb.st_mode))
494 			sethomestat |= SETHOME_DELS_OK;
495 	} else if (errno != ENOENT) {
496 		err = errno;
497 	} else {
498 		sethomestat &= ~SETHOME_DELS_OK;
499 	}
500 	/*
501 	 * At least during the period, where we are converting to the new
502 	 * interfaces, we need a way to intentionally select a behavior.
503 	 * SCCS_NMODE=i	enforces intree history
504 	 * SCCS_NMODE=o	enforces offree history
505 	 * SCCS_NMODE=	keeps current defaults
506 	 */
507 	if ((p = getenv("SCCS_NMODE")) != NULL) {
508 		if (*p == 'i') {
509 			sethomestat |=  SETHOME_INTREE;
510 			sethomestat &= ~SETHOME_OFFTREE;
511 		} else if (*p == 'o') {
512 			sethomestat &= ~SETHOME_INTREE;
513 			sethomestat |=  SETHOME_OFFTREE;
514 		}
515 	}
516 	if (err) {
517 		errno = err;
518 		return (-1);			/* Other than ENOENT */
519 	}
520 	return (0);
521 }
522 
523 /*
524  * Check whether the change set home directory could be detected and
525  * abort in case it is missing.
526  */
527 EXPORT int
checkhome(path)528 checkhome(path)
529 	char	*path;
530 {
531 	if (shinit != 0)
532 		xsethome(path);
533 	if (!SETHOME_INIT()) {
534 		efatal(gettext("no SCCS project home directory (co33)"));
535 		return (0);			/* (Fflags & FTLACT= == 0) */
536 	}
537 	return (1);
538 }
539 
540 LOCAL void
gchangeset()541 gchangeset()
542 {
543 	char		buf[max(8192, PATH_MAX+1)];
544 	struct stat	_Statbuf;
545 
546 	if (setphome == NULL)
547 		return;
548 	strlcpy(buf, setphome, sizeof (buf));
549 	strlcat(buf, "/.sccs/SCCS/s.changeset", sizeof (buf));
550 	if (_exists(buf) && S_ISREG(_Statbuf.st_mode))
551 		sethomestat |= SETHOME_CHSET_OK;
552 	changesetfile = strdup(buf);
553 
554 	strlcpy(buf, setphome, sizeof (buf));
555 	strlcat(buf, "/.sccs/changeset", sizeof (buf));
556 	changesetgfile = strdup(buf);
557 }
558 
559 EXPORT void
setnewmode()560 setnewmode()
561 {
562 	sethomestat |= SETHOME_NEWMODE;
563 }
564 
565 EXPORT void
unsetnewmode()566 unsetnewmode()
567 {
568 	sethomestat &= ~SETHOME_NEWMODE;
569 }
570