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 /*
23  * Copyright 2015-2020 J. Schilling
24  *
25  * @(#)add.c	1.1 20/05/09 J. Schilling
26  */
27 #if defined(sun)
28 #pragma ident "@(#)add.c 1.1 20/05/09 J. Schilling"
29 #endif
30 #include <defines.h>
31 #include <version.h>
32 #include <i18n.h>
33 #include <schily/stat.h>
34 #include <schily/getopt.h>
35 #include <schily/sysexits.h>
36 #include <schily/schily.h>
37 #include "sccs.h"
38 
39 /*
40  * Code to support project enhancements for SCCS.
41  * This is mainly the project set home directory, the directory ".sccs" in that
42  * directory and the changeset file.
43  */
44 static char	**anames;
45 static int	alen;
46 static int	anum;
47 static int	addfile		__PR((char *file));
48 static void	free_anames	__PR((void));
49 static int	addcmp		__PR((const void *file1, const void *file2));
50 static void	cvtpath		__PR((char *nm, char *nbuf, size_t nbsize,
51 					int cwd, int phome));
52 
53 EXPORT char **
sccs_getanames()54 sccs_getanames()
55 {
56 	return (anames);
57 }
58 
59 EXPORT int
sccs_getanum()60 sccs_getanum()
61 {
62 	return (anum);
63 }
64 
65 /*
66  * Add a single file name to the array of files that describes the project.
67  */
68 static int
addfile(file)69 addfile(file)
70 	char	*file;
71 {
72 	if (alen <= anum) {
73 		alen += 128;
74 		anames = realloc(anames, alen * sizeof (char *));
75 		if (anames == NULL)
76 			efatal("out of space (ut9)");
77 	}
78 	if ((anames[anum++] = strdup(file)) == NULL)
79 		efatal("out of space (ut9)");
80 	return (0);
81 }
82 
83 /*
84  * Free old strings from anames[].
85  */
86 static void
free_anames()87 free_anames()
88 {
89 	int	i;
90 
91 	for (i = 0; i < anum; i++) {
92 		free(anames[i]);
93 	}
94 	free(anames);
95 	anames = NULL;
96 	alen = 0;
97 }
98 
99 /*
100  * Read the current name cache file and keep it in an in core array.
101  */
102 EXPORT int
sccs_readncache()103 sccs_readncache()
104 {
105 	char	nbuf[FILESIZE];
106 	char	lbuf[MAXLINE];
107 	FILE	*nfp;
108 
109 	strlcpy(nbuf, setrhome, sizeof (nbuf));
110 	strlcat(nbuf, "/.sccs/ncache", sizeof (nbuf));
111 	nfp = fopen(nbuf, "rb");
112 	if (nfp == NULL) {
113 		return (-1);
114 	}
115 
116 	/*
117 	 * Reset old entries in anames[].
118 	 * Then read in the file.
119 	 */
120 	free_anames();
121 	while (fgets(lbuf, sizeof (lbuf), nfp) != NULL) {
122 		size_t	llen = strlen(lbuf);
123 
124 		if (llen > 0 && lbuf[llen-1] == '\n')
125 			lbuf[llen-1] = '\0';
126 		addfile(lbuf);
127 	}
128 	fclose(nfp);
129 	return (0);
130 }
131 
132 /*
133  * Add compare function for qsort().
134  * Note that we do not compare the time_t field and thus use an offset of 15,
135  * see below for the format used for this string.
136  */
137 static int
addcmp(file1,file2)138 addcmp(file1, file2)
139 	const void	*file1;
140 	const void	*file2;
141 {
142 	int	ret = strcmp(&(*(char **)file1)[15], &(*(char **)file2)[15]);
143 
144 	if (ret == 0) {
145 		Ffile = &(*(char **)file1)[15];
146 		fatal(gettext("already tracked (sc4)"));
147 	}
148 	return (ret);
149 }
150 
151 /*
152  * Convert a path name that is relative to the current working directory into
153  * a path name that is relative to the change set home directory and normalize
154  * the resulting path name.
155  */
156 static void
cvtpath(nm,nbuf,nbsize,cwd,phome)157 cvtpath(nm, nbuf, nbsize, cwd, phome)
158 	char	*nm;		/* The file name argument */
159 	char	*nbuf;		/* The file name output buffer */
160 	size_t	nbsize;		/* The size of the file name output buffer */
161 	int	cwd;		/* File descriptor to working dir */
162 	int	phome;		/* File descriptor to change set home dir */
163 {
164 	char	npbuf[FILESIZE];
165 	size_t	npboff;
166 	int	nlen;
167 	char	*name;
168 
169 	name = nm;
170 	npboff = 0;
171 	if (cwdprefix[0] && name[0] != '/') {
172 		/*
173 		 * We are not in the change set home directory and
174 		 * "name" is not an absolute path name.
175 		 * Copy the cwd prefix before the path to get a path
176 		 * name that is relative to the the change set home.
177 		 */
178 		strlcpy(&npbuf[npboff], cwdprefix, sizeof (npbuf) - npboff);
179 		npboff += cwdprefixlen;
180 		strlcpy(&npbuf[npboff], "/", sizeof (npbuf) - npboff);
181 		npboff += 1;
182 		strlcpy(&npbuf[npboff], name, sizeof (npbuf) - npboff);
183 		name = npbuf;
184 	}
185 	/*
186 	 * Since all our path names are relative to the change set
187 	 * home directory, we need to fchdir() to that directory before
188 	 * we normalize the path name.
189 	 */
190 	if (*nm != '/' && fchdir(phome) < 0) {
191 		Ffile = setahome ? setahome : setrhome;
192 		efatal("cannot change directory (cm16)");
193 	}
194 	nlen = resolvepath(name, nbuf, nbsize);	/* Must exist */
195 	if (nlen < 0) {
196 		efatal("path conversion error (cm12)");
197 	} else if (nlen >= nbsize) {
198 		fatal("resolved path too long (cm13)");
199 	} else {
200 		/*
201 		 * While the libschily implementation null terminates
202 		 * the names, this is not the case for the Solaris
203 		 * syscall resolvepath().
204 		 */
205 		nbuf[nlen] = '\0';
206 
207 		/*
208 		 * If the resulting name is an absolute path that starts
209 		 * with the absolute change set home directory string,
210 		 * try to make it relative by removing the absolute home
211 		 * path string.
212 		 *
213 		 * If the remaining path is not inside the change set
214 		 * home tree, abort.
215 		 */
216 		if (nbuf[0] == '/' && setahome)
217 			make_relative(nbuf);
218 		if (!in_tree(nbuf)) {
219 			Ffile = nbuf;
220 			fatal("not in tree (cm17)");
221 		}
222 	}
223 	/*
224 	 * Chdir() back to our previous working directory since all
225 	 * file arguments are relative to that directory.
226 	 */
227 	if (*nm != '/' && fchdir(cwd) < 0) {
228 		Ffile = ".";
229 		efatal("cannot change directory (cm16)");
230 	}
231 }
232 
233 /*
234  * Add file to the current file set for the current change set.
235  * This command always needs to have file type argument(s).
236  */
237 EXPORT int
addcmd(nfiles,argc,argv)238 addcmd(nfiles, argc, argv)
239 	int	nfiles;
240 	int	argc;
241 	char	**argv;
242 {
243 	char	nbuf[FILESIZE];
244 	size_t	nboff;
245 	int	rval;
246 	char	**np;
247 	int	files = 0;
248 	int	cwd = -1;	/* File descriptor to cwd */
249 	int	phome = -1;	/* File descriptor to project home */
250 	struct stat statb;
251 
252 	optind = 1;
253 	opt_sp = 1;
254 	while ((rval = getopt(argc, argv, "")) != -1) {
255 		switch (rval) {
256 
257 		default:
258 			usrerr("%s %s",
259 				gettext("unknown option"),
260 				argv[optind-1]);
261 			rval = EX_USAGE;
262 			exit(EX_USAGE);
263 			/*NOTREACHED*/
264 		}
265 	}
266 
267 	rval = 0;
268 	for (np = &argv[optind]; *np != NULL; np++) {
269 		if (files == 0) {
270 			/*
271 			 * In order to keep the first implementation simple,
272 			 * we assume that the current working directory is
273 			 * always inside the change set tree and that all
274 			 * file arguments are from the same change set.
275 			 *
276 			 * XXX If we like to enhance this, we first need to
277 			 * XXX find what we like to support.
278 			 */
279 			checkhome(NULL);	/* No project set home: abort */
280 			sccs_readncache();	/* Read already known files */
281 
282 			cwd = opencwd();
283 			phome = openphome();
284 		}
285 		files |= 1;
286 		/*
287 		 * 12 digits work from year -1199 up to year 33658 but we may
288 		 * need to check for strange time stamps if time_t has more
289 		 * that 32 bits since such a strange time stamp could cause
290 		 * an overflow in the string below.
291 		 */
292 		strlcpy(nbuf, "A 000000000000 ", sizeof (nbuf));
293 		if (stat(*np, &statb) < 0)
294 			xmsg(*np, NOGETTEXT("add"));
295 #if SIZEOF_TIME_T > 4
296 		if (statb.st_mtime > 999999999999L ||
297 		    statb.st_mtime < -99999999999L)
298 			efatal("file not in supported time range (cm15)");
299 #endif
300 		sprintf(&nbuf[2], "%12ld ", (long)statb.st_mtime);
301 		nboff = 15;
302 		cvtpath(*np, &nbuf[nboff], sizeof (nbuf) - nboff, cwd, phome);
303 		addfile(nbuf);
304 	}
305 	closedirfd(cwd);
306 	closedirfd(phome);
307 
308 	if (files == 0) {
309 		usrerr(gettext(" missing file arg (cm3)"));
310 	} else {
311 		FILE	*nfp;
312 		int	i;
313 
314 		qsort((void *)anames, anum, sizeof (char *), addcmp);
315 
316 		strlcpy(nbuf, setrhome, sizeof (nbuf));
317 		strlcat(nbuf, "/.sccs/ncache", sizeof (nbuf));
318 		nfp = xfcreat(nbuf, 0666);
319 		for (i = 0; i < anum; i++) {
320 			fputs(anames[i], nfp);
321 			fputs("\n", nfp);
322 		}
323 		fclose(nfp);
324 	}
325 	return (rval);
326 }
327