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