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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
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 2009 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28/* All Rights Reserved */
29
30
31#include <stdio.h>
32#include <ctype.h>
33#include <dirent.h>
34#include <limits.h>
35#include <stdlib.h>
36#include <unistd.h>
37#include <string.h>
38#include <sys/types.h>
39#include <sys/stat.h>
40#include <pkgstrct.h>
41#include <errno.h>
42#include <locale.h>
43#include <libintl.h>
44#include <pkglib.h>
45#include "libadm.h"
46#include "libinst.h"
47
48extern int	holdcinfo;
49
50#define	WRN_SCARYLINK	"WARNING: <%s>, target of symlink <%s>, does not exist."
51
52#define	ERR_PATHLONG	"path argument too long"
53#define	ERR_CLASSLONG	"classname argument too long"
54#define	ERR_CLASSCHAR	"bad character in classname"
55#define	ERR_STAT	"unable to stat <%s>"
56#define	ERR_WRITE	"write of entry failed"
57#define	ERR_POPEN	"unable to create pipe to <%s>"
58#define	ERR_PCLOSE	"unable to close pipe to <%s>"
59#define	ERR_RDLINK	"unable to read link for <%s>"
60#define	ERR_MEMORY	"memory allocation failure, errno=%d"
61
62#define	LINK	1
63
64struct link {
65	char	*path;
66	ino_t	ino;
67	dev_t	dev;
68	struct link *next;
69};
70
71static struct link *firstlink = (struct link *)0;
72static struct link *lastlink = (struct link *)0;
73static char *scan_raw_ln(char *targ_name, char *link_name);
74
75static char	*def_class = "none";
76
77static int	errflg = 0;
78static int	iflag = 0;	/* follow symlinks */
79static int	xflag = 0;	/* confirm contents of files */
80static int	nflag = 0;
81static char	construction[PATH_MAX], mylocal[PATH_MAX];
82
83static void	findlink(struct cfent *ept, char *path, char *svpath);
84static void	follow(char *path);
85static void	output(char *path, int n, char *local);
86static void	usage(void);
87
88int
89main(int argc, char *argv[])
90{
91	int c;
92	char *pt, path[PATH_MAX];
93	char	*abi_sym_ptr;
94	extern char	*optarg;
95	extern int	optind;
96
97	(void) setlocale(LC_ALL, "");
98
99#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
100#define	TEXT_DOMAIN "SYS_TEST"
101#endif
102	(void) textdomain(TEXT_DOMAIN);
103
104	(void) set_prog_name(argv[0]);
105
106	while ((c = getopt(argc, argv, "xnic:?")) != EOF) {
107		switch (c) {
108		    case 'x':	/* include content info */
109			xflag++;
110			break;
111
112		    case 'n':
113			nflag++;
114			break;
115
116		    case 'c':	/* assign class */
117			def_class = optarg;
118			/* validate that classname is acceptable */
119			if (strlen(def_class) > (size_t)CLSSIZ) {
120				progerr(gettext(ERR_CLASSLONG));
121				exit(1);
122			}
123			for (pt = def_class; *pt; pt++) {
124				if (!isalpha(*pt) && !isdigit(*pt)) {
125					progerr(gettext(ERR_CLASSCHAR));
126					exit(1);
127				}
128			}
129			break;
130
131		    case 'i':	/* follow symlinks */
132			iflag++;
133			break;
134
135		    default:
136			usage();
137		}
138	}
139
140	if (iflag) {
141		/* follow symlinks */
142		set_nonABI_symlinks();
143	} else {
144		/* bug id 4244631, not ABI compliant */
145		abi_sym_ptr = getenv("PKG_NONABI_SYMLINKS");
146		if (abi_sym_ptr && strncasecmp(abi_sym_ptr, "TRUE", 4) == 0) {
147			set_nonABI_symlinks();
148		}
149	}
150	holdcinfo = !xflag;
151	if (optind == argc) {
152		/* take path list from stdin */
153		while (fgets(path, sizeof (path), stdin) != (char *)NULL) {
154			output(path, 0, NULL);
155		}
156	} else {
157		while (optind < argc) {
158			follow(argv[optind++]);
159		}
160	}
161
162	return (errflg ? 1 : 0);
163}
164
165static void
166output(char *path, int n, char *local)
167{
168	char		mypath[PATH_MAX];
169	int		len;
170	int		s;
171	struct cfent	entry;
172
173	/*
174	 * remove any trailing newline characters from the end of path
175	 */
176
177	len = strlen(path);
178	while ((len > 0) && (path[len-1] == '\n')) {
179		path[--len] = '\0';
180	}
181
182	entry.volno = 0;
183	entry.ftype = '?';
184	entry.path = mypath;
185	(void) strlcpy(entry.pkg_class, def_class, sizeof (entry.pkg_class));
186	(void) strlcpy(entry.path, path, PATH_MAX);
187	entry.ainfo.local = NULL;
188	entry.ainfo.mode = BADMODE;
189	(void) strlcpy(entry.ainfo.owner, BADOWNER, sizeof (entry.ainfo.owner));
190	(void) strlcpy(entry.ainfo.group, BADGROUP, sizeof (entry.ainfo.group));
191	errflg = 0;
192
193	if (xflag) {
194		entry.ftype = '?';
195		if (cverify(0, &entry.ftype, path, &entry.cinfo, 1)) {
196			errflg++;
197			logerr(gettext("ERROR: %s"), path);
198			logerr(getErrbufAddr());
199			return;
200		}
201	}
202
203	/*
204	 * Use averify to figure out the attributes. This has trouble
205	 * divining the identity of a symlink which points to a
206	 * non-existant target. For that reason, if it comes back as
207	 * an existence problem, we fake in a symlink and see if averify
208	 * likes that. If it does, all we have is a risky symlink.
209	 */
210	if ((s = averify(0, &entry.ftype, path, &entry.ainfo)) == VE_EXIST &&
211	    !iflag) {
212		entry.ftype = 's';	/* try again assuming symlink */
213		/* try to read what it points to */
214		if ((s = readlink(path, mylocal, PATH_MAX)) > 0) {
215			mylocal[s] = '\000';	/* terminate it */
216			entry.ainfo.local = mylocal;
217			if (averify(0, &entry.ftype, path, &entry.ainfo)) {
218				errflg++;
219			} else
220				/* It's a link to a file not in this package. */
221				ptext(stderr, gettext(WRN_SCARYLINK),
222				    mylocal, path);
223		} else {
224			errflg++;
225		}
226	} else if (s != 0 && s != VE_CONT)
227		errflg++;
228
229	if (errflg) {
230		logerr(gettext("ERROR: %s"), path);
231		logerr(getErrbufAddr());
232		return;
233	}
234
235	if (n) {
236		/* replace first n characters with 'local' */
237		if (strchr("fev", entry.ftype)) {
238			entry.ainfo.local = mylocal;
239			(void) strlcpy(entry.ainfo.local, entry.path,
240				PATH_MAX);
241			canonize(entry.ainfo.local);
242		}
243		if (local[0]) {
244			entry.ainfo.local = mylocal;
245			(void) strlcpy(entry.path, local, PATH_MAX);
246			(void) strcat(entry.path, path+n);
247		} else
248			(void) strlcpy(entry.path,
249				(path[n] == '/') ? path+n+1 : path+n,
250				PATH_MAX);
251	}
252
253	canonize(entry.path);
254	if (entry.path[0]) {
255		findlink(&entry, path, entry.path);
256		if (strchr("dcbp", entry.ftype) ||
257		(nflag && !strchr("sl", entry.ftype)))
258			entry.ainfo.local = NULL;
259		if (ppkgmap(&entry, stdout)) {
260			progerr(gettext(ERR_WRITE));
261			exit(99);
262		}
263	}
264}
265
266static void
267follow(char *path)
268{
269	struct stat stbuf;
270	FILE	*pp;
271	char	*pt,
272		local[PATH_MAX],
273		newpath[PATH_MAX],
274		cmd[PATH_MAX+32];
275	int n;
276
277	errflg = 0;
278
279	if (pt = strchr(path, '=')) {
280		*pt++ = '\0';
281		n = ((unsigned int)pt - (unsigned int)path - 1);
282		if (n >= PATH_MAX) {
283			progerr(gettext(ERR_PATHLONG));
284			errflg++;
285			return;
286		}
287
288		n = strlen(pt);
289
290		if (n < PATH_MAX) {
291			(void) strlcpy(local, pt, sizeof (local));
292			n = strlen(path);
293		} else {
294			progerr(gettext(ERR_PATHLONG));
295			errflg++;
296			return;
297		}
298	} else {
299		n = 0;
300		local[0] = '\0';
301	}
302
303	if (stat(path, &stbuf)) {
304		progerr(gettext(ERR_STAT), path);
305		errflg++;
306		return;
307	}
308
309	if (stbuf.st_mode & S_IFDIR) {
310		(void) snprintf(cmd, sizeof (cmd), "find %s -print", path);
311		if ((pp = popen(cmd, "r")) == NULL) {
312			progerr(gettext(ERR_POPEN), cmd);
313			exit(1);
314		}
315		while (fscanf(pp, "%[^\n]\n", newpath) == 1)
316			output(newpath, n, local);
317		if (pclose(pp)) {
318			progerr(gettext(ERR_PCLOSE), cmd);
319			errflg++;
320		}
321	} else
322		output(path, n, local);
323}
324
325/*
326 * Scan a raw link for origination errors. Given
327 *	targ_name = hlink/path/file1
328 *		and
329 *	link_name = hlink/path/file2
330 * we don't want the link to be verbatim since link_name must be relative
331 * to it's source. This functions checks for identical directory paths
332 * and if it's clearly a misplaced relative path, the duplicate
333 * directories are stripped. This is necessary because pkgadd is actually
334 * in the source directory (hlink/path) when it creates the link.
335 *
336 * NOTE : The buffer we get with targ_name is going to be used later
337 * and cannot be modified. That's why we have yet another PATH_MAX
338 * size buffer in this function.
339 */
340static char *
341scan_raw_ln(char *targ_name, char *link_name)
342{
343	char *const_ptr;	/* what we return */
344	char *file_name;	/* name of the file in link_name */
345	char *this_dir;		/* current directory in targ_name */
346	char *next_dir;		/* next directory in targ_name  */
347	char *targ_ptr;		/* current character in targ_name */
348
349	const_ptr = targ_name;	/* Point to here 'til we know it's different. */
350
351	/*
352	 * If the link is absolute or it is in the current directory, no
353	 * further testing necessary.
354	 */
355	if (RELATIVE(targ_name) &&
356	    (file_name = strrchr(link_name, '/')) != NULL) {
357
358		/*
359		 * This will be walked down to the highest directory
360		 * not common to both the link and the target.
361		 */
362		targ_ptr = targ_name;
363
364		/*
365		 * At this point targ_name is a relative path through at
366		 * least one directory.
367		 */
368		this_dir = targ_ptr;	/* first directory in targ_name */
369		file_name++;		/* point to the name not the '/' */
370
371		/*
372		 * Scan across the pathname until we reach a different
373		 * directory or the final file name.
374		 */
375		do {
376			size_t str_size;
377
378			next_dir = strchr(targ_ptr, '/');
379			if (next_dir)
380				next_dir++;	/* point to name not '/' */
381			else	/* point to the end of the string */
382				next_dir = targ_ptr+strlen(targ_ptr);
383
384			/* length to compare */
385			str_size = ((ptrdiff_t)next_dir - (ptrdiff_t)this_dir);
386
387			/*
388			 * If both paths begin with the same directory, then
389			 * skip that common directory in both the link and
390			 * the target.
391			 */
392			if (strncmp(this_dir, link_name, str_size) == 0) {
393				/* point to the target so far */
394				const_ptr = this_dir = next_dir;
395				/* Skip past it in the target */
396				targ_ptr = (char *)(targ_ptr+str_size);
397				/* Skip past it in the link */
398				link_name = (char *)(link_name+str_size);
399			/*
400			 * If these directories don't match then the
401			 * directory above is the lowest common directory. We
402			 * need to construct a relative path from the lowest
403			 * child up to that directory.
404			 */
405			} else {
406				int d = 0;
407				char *dptr = link_name;
408
409				/* Count the intermediate directories. */
410				while ((dptr = strchr(dptr, '/')) != NULL) {
411					dptr++;
412					d++;
413				}
414				/*
415				 * Now targ_ptr is pointing to the fork in
416				 * the path and dptr is pointing to the lowest
417				 * child in the link. We now insert the
418				 * appropriate number of "../'s" to get to
419				 * the first common directory. We'll
420				 * construct this in the construction
421				 * buffer.
422				 */
423				if (d) {
424					char *tptr;
425
426					const_ptr = tptr = construction;
427					while (d--) {
428						(void) strlcpy(tptr,
429							"../", PATH_MAX);
430						tptr += 3;
431					}
432					(void) strlcpy(tptr, targ_ptr,
433						PATH_MAX);
434				}
435				break;		/* done */
436			}
437		} while (link_name != file_name);	/* at file name */
438	}
439
440	return (const_ptr);
441}
442
443static void
444findlink(struct cfent *ept, char *path, char *svpath)
445{
446	struct stat	statbuf;
447	struct link	*link, *new;
448	char		buf[PATH_MAX];
449	int		n;
450
451	if (lstat(path, &statbuf)) {
452		progerr(gettext(ERR_STAT), path);
453		errflg++;
454	}
455	if ((statbuf.st_mode & S_IFMT) == S_IFLNK) {
456		if (!iflag) {
457			ept->ainfo.local = mylocal;
458			ept->ftype = 's';
459			n = readlink(path, buf, PATH_MAX);
460			if (n <= 0) {
461				progerr(gettext(ERR_RDLINK), path);
462				errflg++;
463				(void) strlcpy(ept->ainfo.local,
464					"unknown", PATH_MAX);
465			} else {
466				(void) strncpy(ept->ainfo.local, buf, n);
467				ept->ainfo.local[n] = '\0';
468			}
469		}
470		return;
471	}
472
473	if (stat(path, &statbuf))
474		return;
475	if (statbuf.st_nlink <= 1)
476		return;
477
478	for (link = firstlink; link; link = link->next) {
479		if ((statbuf.st_ino == link->ino) &&
480		(statbuf.st_dev == link->dev)) {
481			ept->ftype = 'l';
482			ept->ainfo.local = mylocal;
483			(void) strlcpy(ept->ainfo.local,
484					scan_raw_ln(link->path, ept->path),
485					PATH_MAX);
486			return;
487		}
488	}
489	if ((new = (struct link *)calloc(1, sizeof (struct link))) == NULL) {
490		progerr(gettext(ERR_MEMORY), errno);
491		exit(1);
492	}
493
494	if (firstlink) {
495		lastlink->next = new;
496		lastlink = new;
497	} else
498		firstlink = lastlink = new;
499
500	new->path = strdup(svpath);
501	new->ino = statbuf.st_ino;
502	new->dev = statbuf.st_dev;
503}
504
505static void
506usage(void)
507{
508	(void) fprintf(stderr,
509	    gettext("usage: %s [-i] [-c class] [path ...]\n"), get_prog_name());
510	exit(1);
511	/*NOTREACHED*/
512}
513