xref: /illumos-gate/usr/src/cmd/svr4pkg/pkgrm/check.c (revision eb9a1df2aeb866bf1de4494433b6d7e5fa07b3ae)
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 
28 #include <stdio.h>
29 #include <limits.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <sys/stat.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <utmpx.h>
36 #include <dirent.h>
37 #include <sys/types.h>
38 #include <locale.h>
39 #include <libintl.h>
40 #include <pkgstrct.h>
41 #include <pkglocs.h>
42 #include <assert.h>
43 #include <pkglib.h>
44 #include <install.h>
45 #include <libinst.h>
46 #include <libadm.h>
47 #include <messages.h>
48 #include <instzones_api.h>
49 
50 extern int	npkgs;	/* the number of packages yet to be installed */
51 
52 /*
53  * ckquit is a global that controls 'ckyorn' (defined in libadm)
54  * If ckquit is non-zero, then "quit" is allowed as an answer when
55  * ckyorn is called. If is it zero, then "quit" is not an allowed answer.
56  */
57 extern int	ckquit;
58 
59 extern struct admin adm;
60 
61 /*
62  * each one of these represents a single kind of dependency check
63  */
64 
65 static depckError_t er_depsonme = {0, (depckErrorRecord_t *)NULL};
66 static depckError_t er_prenci = {0, (depckErrorRecord_t *)NULL};
67 static depckError_t er_prereq = {0, (depckErrorRecord_t *)NULL};
68 static depckError_t er_rckdepend = {0, (depckErrorRecord_t *)NULL};
69 static depckError_t er_rckpriv = {0, (depckErrorRecord_t *)NULL};
70 static depckError_t er_rckrunlevel = {0, (depckErrorRecord_t *)NULL};
71 static depckError_t er_runlevel = {0, (depckErrorRecord_t *)NULL};
72 
73 /*
74  * each one of these represents a localized message for a single kind
75  * of dependency check
76  */
77 
78 static char *IMSG_PKGRMCHK_CKRUNLVL = (char *)NULL;
79 static char *IMSG_PKGRMCHK_DEPEND = (char *)NULL;
80 static char *IMSG_PKGRMCHK_DEPSONME = (char *)NULL;
81 static char *IMSG_PKGRMCHK_PRENCI = (char *)NULL;
82 static char *IMSG_PKGRMCHK_PREREQ = (char *)NULL;
83 static char *IMSG_PKGRMCHK_PRIV = (char *)NULL;
84 static char *IMSG_PKGRMCHK_RUNLEVEL = (char *)NULL;
85 
86 /*
87  * each one of these represents a function to handle a single kind of
88  * dependency check
89  */
90 
91 static int rckdepend(char *a_msg, char *a_pkg);
92 static int rckdepsonme(char *a_msg, char *a_pkg);
93 static int rckprenci(char *a_msg, char *a_pkg);
94 static int rckprereq(char *a_msg, char *a_pkg);
95 static int rckpriv(char *a_msg, char *a_pkg);
96 static int rckrunlevel(char *a_msg, char *a_pkg);
97 
98 static depckl_t DEPCKL[] = {
99 	/*
100 	 * Message hierarchy:
101 	 * -- runlevel=%s
102 	 * --- rckrunlevel=%d
103 	 * --- rckpriv=%d  ****
104 	 * -- incompat=%s
105 	 * -- prerequisite-incomplete=%s
106 	 * -- dependonme=%s
107 	 * -- dependsonme=%s:%s
108 	 * -- prerequisite-installed=%s
109 	 * ---rckdepend=%d ****
110 	 */
111 
112 	/* name,	ignore_values,	err_msg,	depcklFunc,	recrd */
113 	/*
114 	 * package and zone information is collected in the "record" object for
115 	 * each occurance - then a message is constructed for each zone that
116 	 * reported the condition - the message includes that portion of the
117 	 * check past the "=" - then the specified "depcklFunc" is called to
118 	 * process each message.
119 	 * Message format:
120 	 * 	%s %s <%s> %s <%s>
121 	 * Message arguments:
122 	 *	value, "package", package-name, "zone/zones", zone-name
123 	 */
124 
125 	{ "dependsonme=",		NULL, 	&IMSG_PKGRMCHK_DEPSONME,
126 					&rckdepsonme,	&er_depsonme
127 	},
128 	{ "dependonme=",		NULL, 	&IMSG_PKGRMCHK_DEPSONME,
129 					&rckdepsonme,	&er_depsonme
130 	},
131 	{ "prerequisite-incomplete=",	NULL,	&IMSG_PKGRMCHK_PRENCI,
132 					&rckprenci,	&er_prenci
133 	},
134 	{ "prerequisite-installed=",	NULL,	&IMSG_PKGRMCHK_PREREQ,
135 					&rckprereq,	&er_prereq
136 	},
137 	{ "runlevel=",			NULL,	&IMSG_PKGRMCHK_RUNLEVEL,
138 					NULL,		&er_runlevel
139 	},
140 
141 	/*
142 	 * these checks are ignored if they return one of the listed values
143 	 * if they do NOT return one of the listed values, then the package
144 	 * and zone information is collected in the "record" object for each
145 	 * occurance - then a single unified message is constructed for all
146 	 * zones that report the same condition; then the specified "depcklFunc"
147 	 * is called to process the resulting combined message.
148 	 * Message format:
149 	 * 	%s <%s> %s <%s>
150 	 * Message arguments:
151 	 *	"package", package-name, "zone/zones", zone-name(s)
152 	 */
153 
154 	{ "rckdepend=",			"0",	&IMSG_PKGRMCHK_DEPEND,
155 					&rckdepend,	&er_rckdepend
156 	},
157 	{ "rckpriv=",			"0",	&IMSG_PKGRMCHK_PRIV,
158 					&rckpriv,	&er_rckpriv
159 	},
160 	{ "rckrunlevel=",		"0",	&IMSG_PKGRMCHK_CKRUNLVL,
161 					&rckrunlevel,	&er_rckrunlevel
162 	},
163 
164 	/*
165 	 * same as above BUT no check to ignore is done; message always reported
166 	 */
167 
168 	{ NULL,				NULL,	NULL,
169 						NULL,		NULL
170 	}
171 };
172 
173 /*
174  * Name:	preremove_verify
175  * Description:	verify results of preremoval dependency checking
176  * Arguments:	a_pkglist - pointer to array of strings representing the names
177  *			of all the packages that have been checked
178  *		a_zlst - list of zones that dependencies were checked on
179  *		a_zoneTempDir - pointer to string representing the path where
180  *			the files containing the preremoval dependency
181  *			check data are located
182  * Returns:	int
183  *		== 0 - continue processing
184  *		!= 0 - do not continue processing
185  */
186 
187 int
188 preremove_verify(char **a_pkglist, zoneList_t a_zlst, char *a_zoneTempDir)
189 {
190 	char		*pkginst;
191 	int		i;
192 	int		savenpkgs = npkgs;
193 
194 	/*
195 	 * entry assertions
196 	 */
197 
198 	assert(a_pkglist != (char **)NULL);
199 	assert(a_zlst != (zoneList_t)NULL);
200 	assert(a_zoneTempDir != (char *)NULL);
201 
202 	/*
203 	 * entry debugging info
204 	 */
205 
206 	echoDebug(DBG_PRERVFY_ENTRY);
207 
208 	/*
209 	 * localize messages
210 	 */
211 
212 	IMSG_PKGRMCHK_DEPSONME = MSG_PKGRMCHK_DEPSONME;
213 	IMSG_PKGRMCHK_PRENCI = MSG_PKGRMCHK_PRENCI;
214 	IMSG_PKGRMCHK_PREREQ = MSG_PKGRMCHK_PREREQ;
215 	IMSG_PKGRMCHK_RUNLEVEL = MSG_PKGRMCHK_RUNLEVEL;
216 	IMSG_PKGRMCHK_DEPEND = MSG_PKGRMCHK_DEPEND;
217 	IMSG_PKGRMCHK_PRIV = MSG_PKGRMCHK_PRIV;
218 	IMSG_PKGRMCHK_CKRUNLVL = MSG_PKGRMCHK_CKRUNLVL;
219 
220 	/*
221 	 * outer loop - process each package first
222 	 */
223 
224 	for (i = 0; (pkginst = a_pkglist[i]) != NULL; i++) {
225 
226 		char	*zoneName;
227 		int	zoneIndex;
228 
229 		/*
230 		 * inner loop - for each package process each zone second
231 		 */
232 
233 		if (pkgIsPkgInGzOnly(get_inst_root(), pkginst) == B_TRUE) {
234 			continue;
235 		}
236 
237 		for (zoneIndex = 0;
238 			(zoneName = z_zlist_get_zonename(a_zlst, zoneIndex)) !=
239 				(char *)NULL; zoneIndex++) {
240 
241 			FILE	*fp;
242 			char	line[PATH_MAX+1];
243 			char	preremovecheckPath[PATH_MAX+1];
244 			int	len;
245 
246 			/* skip the zone if it is NOT bootable */
247 
248 			if (z_zlist_is_zone_runnable(a_zlst,
249 			    zoneIndex) == B_FALSE) {
250 				continue;
251 			}
252 
253 			/* create path to this packages preremove check data */
254 
255 			len = snprintf(preremovecheckPath,
256 			    sizeof (preremovecheckPath),
257 			    "%s/%s.%s.preremovecheck.txt",
258 			    a_zoneTempDir, pkginst,
259 			    z_zlist_get_scratch(a_zlst, zoneIndex));
260 
261 			if (len > sizeof (preremovecheckPath)) {
262 				progerr(ERR_CREATE_PATH_3, a_zoneTempDir,
263 					pkginst, zoneName);
264 				continue;
265 			}
266 
267 			/* error if preremove check data path is not a file */
268 
269 			if (isfile((char *)NULL, preremovecheckPath) != 0) {
270 				echoDebug(DBG_PRERVFY_NOFILE, pkginst, zoneName,
271 					preremovecheckPath, strerror(errno));
272 				progerr(ERR_PRERVFY_NOFILE, pkginst, zoneName);
273 				continue;
274 			}
275 
276 			/* open the preremove check data file */
277 
278 			fp = fopen(preremovecheckPath, "r");
279 			if (fp == (FILE *)NULL) {
280 				progerr(ERR_PRERVFY_OPEN_FILE,
281 					preremovecheckPath, pkginst, zoneName,
282 					strerror(errno));
283 				continue;
284 			}
285 
286 			/* read and process each preremove check data line */
287 
288 			while (fgets(line, sizeof (line), fp) != (char *)NULL) {
289 				int	len;
290 				int	j;
291 
292 				/* remove all new-lines from end of line */
293 
294 				len = strlen(line);
295 				while ((len > 0) && (line[len-1] == '\n')) {
296 					line[--len] = '\0';
297 				}
298 
299 				/* ignore comment lines */
300 
301 				if (line[0] == '#') {
302 					continue;
303 				}
304 
305 				/* ignore empty lines */
306 
307 				if (line[0] == '\0') {
308 					continue;
309 				}
310 
311 				/* scan dependency list for this item */
312 
313 				for (j = 0;
314 					DEPCKL[j].name != (char *)NULL; j++) {
315 					len = strlen(DEPCKL[j].name);
316 
317 					if (strncmp(line, DEPCKL[j].name,
318 								len) == 0) {
319 						break;
320 					}
321 				}
322 
323 				echoDebug(DBG_PRERVFY_SCAN, line, pkginst,
324 						zoneName);
325 
326 				/* ignore line if not found */
327 
328 				if (DEPCKL[j].name == (char *)NULL) {
329 					progerr(ERR_PRERVFY_UNKNOWN_LINE, line,
330 							pkginst, zoneName);
331 					continue;
332 				}
333 
334 				if ((DEPCKL[j].ignore_values != (char *)NULL) &&
335 					(*(DEPCKL[j].ignore_values) != '\0') &&
336 					(strchr(DEPCKL[j].ignore_values,
337 						line[len]) != (char *)NULL)) {
338 						continue;
339 				}
340 				/* found match - record this dependency issue */
341 
342 				depchkRecordError(DEPCKL[j].record, pkginst,
343 					zoneName, &line[len]);
344 			}
345 
346 			/* close preremove check data file */
347 
348 			(void) fclose(fp);
349 		}
350 	}
351 
352 	/*
353 	 * all dependency issues have been recorded; report results
354 	 */
355 
356 	i = depchkReportErrors(DEPCKL);
357 
358 	/* restore "npkgs" */
359 
360 	npkgs = savenpkgs;
361 
362 	/* return continue/dont dontinue results */
363 
364 	return (i);
365 }
366 
367 /*
368  * Name:	getyorn
369  * Description:	Deliver dependency check reason; ask question; return response
370  * Arguments:	a_msg - pointer to string representing the message to output
371  *			such as 'The package <..> contains <...>'
372  *		a_pkg - pointer to string representing the package for which
373  *			the question is being asked
374  *		a_nocheck - should the message be output?
375  *			== 0 - do not output the message
376  *			!= 0 - output the message
377  *		a_quit - should the question NOT be asked?
378  *			== 0 - ask the question
379  *			!= 0 - do not ask the question - return "no"
380  *		a_helpMsg - pointer to string representing help message to be
381  *			made available if the question is asked
382  *			== NULL - no help message is available
383  *		a_adminMsg - pointer to string representing the dependency check
384  *			failure 'reason' - such as "Privilege checking failed."
385  *			== NULL - no failure reason is available
386  * Returns:	int - results of question/response actions
387  *			0 - success
388  *			1 - end of file
389  *			2 - undefined error
390  *			3 - answer was not "y"/was "q"
391  *			4 - quit action taken
392  *			5 - interactive mode required
393  */
394 
395 static int
396 getyorn(char *a_msg, char *a_pkg, int a_nocheck, int a_quit,
397 	char *a_helpMsg, char *a_adminMsg)
398 {
399 	char	ans[MAX_INPUT];
400 	char	ask_cont[MSG_MAX];
401 	int	n;
402 	int	saveCkquit;
403 
404 	/*
405 	 * entry assertions
406 	 */
407 
408 	assert(a_pkg != (char *)NULL);
409 	assert(*a_pkg != '\0');
410 
411 	/*
412 	 * entry debugging info
413 	 */
414 
415 	echoDebug(DBG_PRERVFY_GETYORN_ARGS, a_pkg, a_nocheck, a_quit, a_msg,
416 			a_adminMsg ? a_adminMsg : "");
417 
418 	/* return success (0) if "nocheck" is non-zero */
419 
420 	if (a_nocheck != 0) {
421 		echoDebug(DBG_PRERVFY_GETYORN_NOCHECK, a_pkg);
422 		return (0);
423 	}
424 
425 	/* output reason for this particular failure */
426 
427 	if ((a_msg != (char *)NULL) && (*a_msg != '\0')) {
428 		ptext(stderr, "%s", a_msg);
429 	}
430 
431 	/* return "4 (administration)" if "quit" is non-zero */
432 
433 	if (a_quit != 0) {
434 		/* output failure "admin reason" if available */
435 		if ((a_adminMsg != (char *)NULL) && (*a_adminMsg != '\0')) {
436 			ptext(stderr, a_adminMsg);
437 		}
438 		echoDebug(DBG_PRERVFY_GETYORN_QUIT, a_pkg);
439 		return (4);
440 	}
441 
442 	/* return "5 (administration interaction required)" if -n */
443 
444 	if (echoGetFlag() == B_FALSE) {
445 		ptext(stderr, MSG_PRERVFY_GETYORN_SUSP, a_pkg);
446 		echoDebug(DBG_PRERVFY_GETYORN_QUIT_USER, a_pkg);
447 		return (5);
448 	}
449 
450 	/* prepare question to ask "continue with removal of pkg <xxx>?" */
451 
452 	(void) snprintf(ask_cont, sizeof (ask_cont), gettext(ASK_PKGRMCHK_CONT),
453 		a_pkg);
454 
455 	/* ask question */
456 
457 	saveCkquit = ckquit;
458 	ckquit = 0;
459 
460 	n = ckyorn(ans, NULL, NULL, a_helpMsg, ask_cont);
461 
462 	ckquit = saveCkquit;
463 
464 	if (n != 0) {
465 		ptext(stderr, MSG_PRERVFY_GETYORN_TERM, a_pkg);
466 		echoDebug(DBG_PRERVFY_GETYORN_CKYORN, a_pkg, n);
467 		return (n);
468 	}
469 
470 	/* return "3 (interruption) if not "y" or "Y" */
471 
472 	if (strchr("yY", *ans) == NULL) {
473 		ptext(stderr, MSG_PRERVFY_GETYORN_TERM_USER, a_pkg);
474 		echoDebug(DBG_PRERVFY_GETYORN_NOT_Y, a_pkg, ans);
475 		return (3);
476 	}
477 
478 	/* return "0 - success" */
479 
480 	echoDebug(DBG_PRERVFY_GETYORN_SUCCESS, a_pkg);
481 
482 	return (0);
483 }
484 
485 /*
486  * Trigger:	dependsonme=<<package>>
487  * Sequence:	- one or more: dependsonme=<<package>>
488  *		- one: rckdepend=<<n>>
489  * Actions:	Output message if "rdepend!=nocheck"
490  *		Return 0
491  *		Terminate when 'rckdepend' processed
492  */
493 
494 static int
495 rckdepsonme(char *a_msg, char *a_pkg)
496 {
497 	echoDebug(DBG_PRERVFY_RCKDEPSONME, a_pkg, a_msg);
498 
499 	if (!(ADM(rdepend, "nocheck"))) {
500 		ptext(stderr, "%s", a_msg);
501 	}
502 
503 	return (0);
504 }
505 
506 /*
507  * Trigger:	prerequisite-incomplete=<<package>>
508  * Sequence:	- one or more: prerequisite-incomplete=<<package>>
509  *		- one: rckdepend=<<n>>
510  * Actions:	Output message if "rdepend!=nocheck"
511  *		Return 0
512  *		Terminate when 'rckdepend' processed
513  */
514 
515 static int
516 rckprenci(char *a_msg, char *a_pkg)
517 {
518 	echoDebug(DBG_PRERVFY_RCKPRENCI, a_pkg, a_msg);
519 
520 	if (!(ADM(rdepend, "nocheck"))) {
521 		ptext(stderr, "%s", a_msg);
522 	}
523 
524 	return (0);
525 }
526 
527 /*
528  * Trigger:	prerequisite-installed=<<package>>
529  * Sequence:	- one or more: prerequisite-installed=<<package>>
530  *		- one: rckdepend=<<n>>
531  * Actions:	Output message if "rdepend!=nocheck"
532  *		Return 0
533  *		Terminate when 'rckdepend' processed
534  */
535 
536 static int
537 rckprereq(char *a_msg, char *a_pkg)
538 {
539 	echoDebug(DBG_PRERVFY_RCKPREREQ, a_pkg, a_msg);
540 
541 	if (!(ADM(rdepend, "nocheck"))) {
542 		ptext(stderr, "%s", a_msg);
543 	}
544 
545 	return (0);
546 }
547 
548 /*
549  * Return value:	int
550  *			0 - success
551  *			1 - end of file
552  *			2 - undefined error
553  *			3 - answer was not "y"/was "q"
554  *			4 - quit action taken
555  *			5 - interactive mode required
556  *			99 - fatal error
557  */
558 
559 static int
560 rckrunlevel(char *a_msg, char *a_pkg)
561 {
562 	echoDebug(DBG_PRERVFY_RCKRUNLEVEL, a_pkg, a_msg);
563 	/*
564 	 * For now, we are ignoring runlevel removal issues within
565 	 * non-global zones.  This is questionable, but the RSTATES
566 	 * feature is rarely used and known uses within Solaris are
567 	 * effectively no-ops as of this time
568 	 */
569 	return (0);
570 }
571 
572 /*
573  * Trigger:	rckdepend=<<n>>
574  * Sequence:	- one or more of:
575  *		-- incompat=<<package>>
576  *		-- prerequisite-incomplete=<<package>>
577  *		-- prerequisite-installed=<<package>>
578  *		-- dependson=<<package>>
579  *		-- dependsonme=<<package>>
580  *		- one: ckpdepend=<<n>>
581  * Actions:	process according to settings
582  * Return value:	int
583  *			0 - success
584  *			1 - end of file
585  *			2 - undefined error
586  *			3 - answer was not "y"/was "q"
587  *			4 - quit action taken
588  *			5 - interactive mode required
589  */
590 
591 static int
592 rckdepend(char *a_msg, char *a_pkg)
593 {
594 	echoDebug(DBG_PRERVFY_RCKDEPEND, a_pkg, a_msg);
595 
596 	return (getyorn(a_msg, a_pkg, ADM(rdepend, "nocheck"),
597 		ADM(rdepend, "quit"), HLP_PKGRMCHK_DEPEND,
598 		ERR_PKGRMCHK_DEPFAILED));
599 }
600 
601 /*
602  * Trigger:	rckpriv=<<n>>
603  * Sequence:	- one: rckpriv=<<n>>
604  * Actions:	process according to settings
605  * Return value:	int
606  *			0 - success
607  *			1 - end of file
608  *			2 - undefined error
609  *			3 - answer was not "y"/was "q"
610  *			4 - quit action taken
611  *			5 - interactive mode required
612  */
613 
614 static int
615 rckpriv(char *a_msg, char *a_pkg)
616 {
617 	echoDebug(DBG_PRERVFY_RCKPRIV, a_pkg, a_msg);
618 
619 	return (getyorn(a_msg, a_pkg, ADM(action, "nocheck"),
620 		ADM(action, "quit"), HLP_PKGRMCHK_PRIV,
621 		ERR_PKGRMCHK_PRIVFAILED));
622 }
623