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 (c) 2017 Peter Tribble.
24 */
25
26/*
27 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
28 * Use is subject to license terms.
29 */
30
31#include <pkglib.h>
32
33#include <alloca.h>
34#include <assert.h>
35#include <door.h>
36#include <errno.h>
37#include <fcntl.h>
38#include <pthread.h>
39#include <spawn.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <strings.h>
43#include <sys/mman.h>
44#include <sys/param.h>
45#include <sys/stat.h>
46#include <sys/wait.h>
47#include <unistd.h>
48#include <libintl.h>
49#include <sys/mnttab.h>
50#include <sys/mkdev.h>
51
52#define	PKGADD_MAX	(512 * 1024)
53
54#define	SADM_DIR	"/var/sadm/install"
55
56#define	PKGSERV_PATH	"/usr/sadm/install/bin/pkgserv"
57
58#define	ERR_PATH_TOO_BIG	"alternate root path is too long"
59#define	ERR_OPEN_DOOR		"cannot open pkgserv door"
60#define	ERR_START_SERVER	"cannot start pkgserv daemon: %s"
61#define	ERR_START_FILTER	"cannot enumerate database entries"
62#define	ERR_FIND_SADM		"cannot find sadm directory"
63
64struct pkg_server {
65	FILE		*fp;
66	char		*curbuf;
67	int		buflen;
68	int		door;
69	boolean_t	onetime;
70};
71
72static PKGserver current_server;
73
74static start_mode_t defmode = INVALID;
75static boolean_t registered = B_FALSE;
76static pid_t master_pid = -1;
77
78static void
79pkgfilename(char path[PATH_MAX], const char *root, const char *sadmdir,
80    const char *file)
81{
82	if (snprintf(path, PATH_MAX, "%s%s/%s", root == NULL ? "" : root,
83	    sadmdir == NULL ? SADM_DIR : sadmdir, file) >= PATH_MAX) {
84		progerr(gettext(ERR_PATH_TOO_BIG));
85		exit(99);
86	}
87}
88
89static void
90free_xmnt(struct extmnttab *xmnt)
91{
92	free(xmnt->mnt_special);
93	free(xmnt->mnt_mountp);
94	free(xmnt->mnt_fstype);
95}
96
97static void
98copy_xmnt(const struct extmnttab *xmnt, struct extmnttab *saved)
99{
100
101	free_xmnt(saved);
102
103	/*
104	 * Copy everything and then strdup the strings we later use and NULL
105	 * the ones we don't.
106	 */
107	*saved = *xmnt;
108
109	if (saved->mnt_special != NULL)
110		saved->mnt_special = strdup(saved->mnt_special);
111	if (saved->mnt_mountp != NULL)
112		saved->mnt_mountp = strdup(saved->mnt_mountp);
113	if (saved->mnt_fstype != NULL)
114		saved->mnt_fstype = strdup(saved->mnt_fstype);
115
116	saved->mnt_mntopts = NULL;
117	saved->mnt_time = NULL;
118}
119
120static int
121testdoor(char *path)
122{
123	int dir;
124	int fd;
125	struct door_info di;
126	int res;
127
128	dir = open(path, O_RDONLY);
129
130	if (dir == -1)
131		return (-1);
132
133	fd = openat(dir, PKGDOOR, O_RDWR);
134	(void) close(dir);
135	if (fd == -1)
136		return (-1);
137
138	res = door_info(fd, &di);
139	(void) close(fd);
140	return (res);
141}
142
143/*
144 * We need to make sure that we can locate the pkgserv and the door;
145 * lofs mounts makes this more difficult: "nosub" mounts don't propagate
146 * the door and doors created in lofs mounts are not propagated back to
147 * the original filesystem.
148 * Here we peel off the lofs mount points until we're
149 *	at /var/sadm/install or
150 *	we find a working door or
151 *	there's nothing more to peel off.
152 * The fullpath parameter is used to return the result (stored in *sadmdir),
153 * root is used but returned in the computed sadmdir and so the caller should
154 * not use "root" any longer or set it to NULL.
155 */
156static void
157pkgfindrealsadmdir(char fullpath[PATH_MAX], const char *root,
158    const char **sadmdir)
159{
160	struct stat buf;
161	struct extmnttab xmnt;
162	FILE *mnttab = NULL;
163	char temp[PATH_MAX];
164	struct extmnttab saved = {NULL, NULL, NULL, NULL, NULL, 0, 0};
165
166	if (snprintf(temp, PATH_MAX, "%s%s",
167	    root == NULL ? "" : root,
168	    *sadmdir == NULL ? SADM_DIR : *sadmdir) >= PATH_MAX) {
169		progerr(gettext(ERR_PATH_TOO_BIG));
170		exit(99);
171	}
172
173	if (stat(temp, &buf) != 0) {
174		progerr(gettext(ERR_FIND_SADM));
175		exit(99);
176	}
177
178	/*
179	 * To find the underlying mount point, you will need to
180	 * search the mnttab and find our mountpoint and the underlying
181	 * filesystem.
182	 * To find the mount point: use the longest prefix but limit
183	 * us to the filesystems with the same major/minor numbers.
184	 * To find the underlying mount point: find a non-lofs file
185	 * system or a <mnt> <mnt> entry (fake mountpoint for zones).
186	 */
187	for (;;) {
188		size_t max = 0;
189
190		if (realpath(temp, fullpath) == NULL) {
191			progerr(gettext(ERR_FIND_SADM));
192			exit(99);
193		}
194
195		if (strcmp(fullpath, SADM_DIR) == 0)
196			break;
197
198		if (testdoor(fullpath) == 0)
199			break;
200
201		if (mnttab == NULL)
202			mnttab = fopen(MNTTAB, "r");
203		else
204			resetmnttab(mnttab);
205
206		while (getextmntent(mnttab, &xmnt, 0) == 0) {
207			size_t len;
208
209			if (major(buf.st_dev) != xmnt.mnt_major ||
210			    minor(buf.st_dev) != xmnt.mnt_minor)
211				continue;
212
213			len = strlen(xmnt.mnt_mountp);
214			if (len < max)
215				continue;
216
217			if (strncmp(xmnt.mnt_mountp, fullpath, len) == 0 &&
218			    (len == 1 || fullpath[len] == '/' ||
219			    fullpath[len] == '\0')) {
220				max = len;
221				copy_xmnt(&xmnt, &saved);
222			}
223		}
224		if (strcmp(saved.mnt_fstype, "lofs") != 0 ||
225		    strcmp(saved.mnt_mountp, saved.mnt_special) == 0) {
226			break;
227		}
228		/* Create a new path in the underlying filesystem. */
229		if (snprintf(temp, PATH_MAX, "%s%s", saved.mnt_special,
230		    &fullpath[max]) >= PATH_MAX) {
231			progerr(gettext(ERR_PATH_TOO_BIG));
232			exit(99);
233		}
234	}
235
236	if (mnttab != NULL) {
237		free_xmnt(&saved);
238		(void) fclose(mnttab);
239	}
240	*sadmdir = fullpath;
241}
242
243static void
244pkgexit_close(void)
245{
246	if (current_server != NULL)
247		pkgcloseserver(current_server);
248}
249
250static PKGserver
251pkgopenserver_i(const char *root, const char *sadmdir, boolean_t readonly,
252	start_mode_t mode)
253{
254	PKGserver server;
255	struct door_info di;
256	pid_t pid;
257	int stat;
258	int first = B_TRUE;
259	char *cmd[16];
260	int args;
261	char pkgdoor[PATH_MAX];
262	char realsadmdir[PATH_MAX];
263	extern char **environ;
264	char *prog;
265	char pidbuf[12];
266
267	if (current_server != NULL)
268		return (current_server);
269
270	if (!registered) {
271		registered = B_TRUE;
272		(void) atexit(pkgexit_close);
273	}
274	if (readonly) {
275		int fd;
276
277		(void) strcpy(pkgdoor, "/tmp/pkgdoor.XXXXXX");
278		if ((fd = mkstemp(pkgdoor)) < 0) {
279			progerr(gettext(ERR_OPEN_DOOR));
280			return (NULL);
281		}
282		(void) close(fd);
283	} else {
284		pkgfindrealsadmdir(realsadmdir, root, &sadmdir);
285		root = NULL;
286		pkgfilename(pkgdoor, root, sadmdir, PKGDOOR);
287	}
288
289	server = malloc(sizeof (*server));
290
291	if (server == NULL)
292		goto return_null;
293
294	server->fp = NULL;
295	server->onetime = readonly;
296
297openserver:
298	server->door = open(pkgdoor, O_RDWR);
299
300	if (server->door >= 0) {
301		if (door_info(server->door, &di) == 0 && di.di_target >= 0) {
302			pkgcmd_t n;
303			n.cmd = PKG_NOP;
304			server->buflen = 1024;
305			server->curbuf = malloc(1024);
306			if (server->curbuf == NULL ||
307			    pkgcmd(server, &n, sizeof (n), NULL, NULL, NULL)) {
308				pkgcloseserver(server);
309				return (NULL);
310			}
311			return (current_server = server);
312		}
313
314		(void) close(server->door);
315	}
316
317	if (!first || mode == NEVER)
318		goto return_null;
319
320	first = B_FALSE;
321
322	args = 0;
323	cmd[args++] = strrchr(PKGSERV_PATH, '/') + 1;
324	if (root != NULL && strcmp(root, "/") != 0) {
325		cmd[args++] = "-R";
326		cmd[args++] = (char *)root;
327	}
328	if (sadmdir != NULL && strcmp(sadmdir, SADM_DIR) != 0) {
329		cmd[args++] = "-d";
330		cmd[args++] = (char *)sadmdir;
331	}
332	if (readonly) {
333		cmd[args++] = "-r";
334		cmd[args++] = pkgdoor;
335	}
336	prog = get_prog_name();
337	if (prog != NULL) {
338		cmd[args++] = "-N";
339		cmd[args++] = prog;
340	}
341
342	switch (mode) {
343	case FLUSH_LOG:
344		cmd[args++] = "-e";
345		break;
346	case RUN_ONCE:
347		cmd[args++] = "-o";
348		break;
349	case PERMANENT:
350		cmd[args++] = "-p";
351		break;
352	default:
353		break;
354	}
355
356	if (master_pid != -1) {
357		cmd[args++] = "-P";
358		(void) snprintf(pidbuf, sizeof (pidbuf), "%d", master_pid);
359		cmd[args++] = pidbuf;
360	}
361	cmd[args++] = NULL;
362	assert(args <= sizeof (cmd)/sizeof (char *));
363
364	if (posix_spawn(&pid, PKGSERV_PATH, NULL, NULL, cmd, environ) == 0) {
365		server->onetime |= (mode == RUN_ONCE);
366		while (wait4(pid, &stat, 0, NULL) != -1) {
367			if (WIFEXITED(stat)) {
368				int s = WEXITSTATUS(stat);
369				if (s == 0 || s == 1)
370					if (mode == FLUSH_LOG)
371						goto return_null;
372					else
373						goto openserver;
374				if (s == 2)
375					goto return_null;
376				break;
377			} else if (WIFSIGNALED(stat)) {
378				break;
379			}
380		}
381	}
382
383	progerr(gettext(ERR_START_SERVER), strerror(errno));
384
385return_null:
386	if (readonly)
387		(void) unlink(pkgdoor);
388	free(server);
389	return (NULL);
390}
391
392PKGserver
393pkgopenserver(const char *root, const char *sadmdir, boolean_t ro)
394{
395	return (pkgopenserver_i(root, sadmdir, ro, pkgservergetmode()));
396}
397
398start_mode_t
399pkgparsemode(const char *mode)
400{
401	if (strcasecmp(mode, MODE_PERMANENT) == 0) {
402		return (PERMANENT);
403	} else if (strncasecmp(mode, MODE_TIMEOUT,
404	    sizeof (MODE_TIMEOUT) - 1) == 0) {
405		const char *pidstr = mode + sizeof (MODE_TIMEOUT) - 1;
406		if (pidstr[0] != '\0') {
407			master_pid = atoi(pidstr);
408			if (master_pid <= 1 || kill(master_pid, 0) != 0)
409				master_pid = -1;
410		}
411
412		return (TIMEOUT);
413	} else if (strcasecmp(mode, MODE_RUN_ONCE) == 0) {
414		return (RUN_ONCE);
415	} else {
416		progerr(gettext("invalid pkgserver mode: %s"), mode);
417		exit(99);
418		/*NOTREACHED*/
419	}
420}
421
422char *
423pkgmodeargument(start_mode_t mode)
424{
425	static char timebuf[sizeof (PKGSERV_MODE) + sizeof (MODE_TIMEOUT) + 10];
426
427	switch (mode) {
428	case PERMANENT:
429		return (PKGSERV_MODE MODE_PERMANENT);
430	case TIMEOUT:
431		(void) snprintf(timebuf, sizeof (timebuf),
432		    PKGSERV_MODE MODE_TIMEOUT "%d",
433		    (master_pid > 1 && kill(master_pid, 0) == 0) ? master_pid :
434		    getpid());
435		return (timebuf);
436	case RUN_ONCE:
437		return (PKGSERV_MODE MODE_RUN_ONCE);
438	}
439	progerr(gettext("Bad pkgserv mode: %d"), (int)mode);
440	exit(99);
441	/*NOTREACHED*/
442}
443
444void
445pkgserversetmode(start_mode_t mode)
446{
447	if (mode == DEFAULTMODE || mode == INVALID) {
448		char *var = getenv(SUNW_PKG_SERVERMODE);
449
450		if (var != NULL)
451			defmode = pkgparsemode(var);
452		else
453			defmode = DEFAULTMODE;
454	} else {
455		defmode = mode;
456	}
457}
458
459start_mode_t
460pkgservergetmode(void)
461{
462	if (defmode == INVALID)
463		pkgserversetmode(DEFAULTMODE);
464	return (defmode);
465}
466
467void
468pkgcloseserver(PKGserver server)
469{
470
471	if (server->fp != NULL)
472		(void) fclose(server->fp);
473	free(server->curbuf);
474	if (server->onetime) {
475		pkgcmd_t cmd;
476		cmd.cmd = PKG_EXIT;
477		(void) pkgcmd(server, &cmd, sizeof (cmd), NULL, NULL, NULL);
478	}
479	(void) close(server->door);
480	if (server == current_server)
481		current_server = NULL;
482	free(server);
483}
484
485int
486pkgcmd(PKGserver srv, void *cmd, size_t len, char **result, size_t *rlen,
487    int *fd)
488{
489	door_arg_t da;
490
491	da.data_ptr = cmd;
492	da.data_size = len;
493	da.desc_ptr = NULL;
494	da.desc_num = 0;
495	da.rbuf = result == NULL ? NULL : *result;
496	da.rsize = rlen == NULL ? 0 : *rlen;
497
498	if (door_call(srv->door, &da) != 0) {
499		if (((pkgcmd_t *)cmd)->cmd == PKG_EXIT && errno == EINTR)
500			return (0);
501		return (-1);
502	}
503
504	if (da.desc_ptr != NULL) {
505		int i = 0;
506		if (fd != NULL)
507			*fd = da.desc_ptr[i++].d_data.d_desc.d_descriptor;
508		for (; i < da.desc_num; i++)
509			(void) close(da.desc_ptr[i].d_data.d_desc.d_descriptor);
510	}
511	/* Error return */
512	if (da.data_size == sizeof (int)) {
513		/* LINTED */
514		int x = *(int *)da.data_ptr;
515		if (x != 0) {
516			if (result == NULL || da.rbuf != *result)
517				(void) munmap(da.rbuf, da.rsize);
518			return (x);
519		}
520	}
521
522	/* Other result */
523	if (result != NULL) {
524		/* Make sure that the result is at the start of the buffer. */
525		if (da.data_ptr != NULL && da.rbuf != da.data_ptr)
526			(void) memmove(da.rbuf, da.data_ptr, da.data_size);
527		*result = da.rbuf;
528		*rlen = da.data_size;
529	} else if (da.rbuf != NULL) {
530		(void) munmap(da.rbuf, da.rsize);
531	}
532	return (0);
533}
534
535/*
536 * Pkgsync:
537 *	If the server is running, make sure that the contents
538 *	file is written.
539 *	If the server is not running, check for the log file;
540 *	if there's a non-empty log file, we need to start the server
541 *	as it will incorporate the log file into the contents file.
542 *	And then check if the door is present.  If it doesn't, we don't
543 *	need to call it.
544 */
545
546boolean_t
547pkgsync_needed(const char *root, const char *sadmdir, boolean_t want_quit)
548{
549	struct stat pbuf;
550	char pkgfile[PATH_MAX];
551	boolean_t sync_needed, running;
552	int fd;
553	struct door_info di;
554
555	pkgfilename(pkgfile, root, sadmdir, PKGLOG);
556
557	sync_needed = stat(pkgfile, &pbuf) == 0 && pbuf.st_size > 0;
558
559	if (!sync_needed && !want_quit)
560		return (B_FALSE);
561
562	pkgfilename(pkgfile, root, sadmdir, PKGDOOR);
563
564	/* sync_needed == B_TRUE || want_quit == B_TRUE */
565	running = B_FALSE;
566
567	fd = open(pkgfile, O_RDWR);
568
569	if (fd >= 0) {
570		if (door_info(fd, &di) == 0) {
571			/* It's mounted, so the server is likely there */
572			running = B_TRUE;
573		}
574		(void) close(fd);
575	}
576	return (running || sync_needed);
577}
578
579int
580pkgsync(const char *root, const char *sadmdir, boolean_t force_quit)
581{
582	void *server;
583	pkgcmd_t cmd;
584
585	/* No need to write contents file; don't start if not running */
586	if (!pkgsync_needed(root, sadmdir, force_quit))
587		return (0);
588
589	server = pkgopenserver_i(root, sadmdir, B_FALSE, FLUSH_LOG);
590	/*
591	 * We're assuming that it started the server and exited immediately.
592	 * If that didn't work, there's nothing we can do.
593	 */
594	if (server == NULL)
595		return (0);
596
597	cmd.cmd = force_quit ? PKG_EXIT : PKG_DUMP;
598
599	(void) pkgcmd(server, &cmd, sizeof (cmd), NULL, NULL, NULL);
600	(void) pkgcloseserver(server);
601	return (0);
602}
603
604int
605pkgservercommitfile(VFP_T *a_vfp, PKGserver server)
606{
607	size_t len = vfpGetModifiedLen(a_vfp);
608	ssize_t rem = len;
609	size_t off;
610	pkgfilter_t *pcmd;
611	char *map = a_vfp->_vfpStart;
612
613	if (len < PKGADD_MAX)
614		pcmd = alloca(sizeof (*pcmd) + len);
615	else
616		pcmd = alloca(sizeof (*pcmd) + PKGADD_MAX);
617
618
619	off = 0;
620	pcmd->cmd = PKG_ADDLINES;
621	while (rem > 0) {
622		char *p = map + off;
623		len = rem;
624
625		if (len >= PKGADD_MAX) {
626			len = PKGADD_MAX - 1;
627			while (p[len] != '\n' && len > 0)
628				len--;
629			if (p[len] != '\n')
630				return (-1);
631			len++;
632		}
633		(void) memcpy(&pcmd->buf[0], p, len);
634		pcmd->len = len;
635
636		if (pkgcmd(server, pcmd, sizeof (*pcmd) + len - 1,
637		    NULL, NULL, NULL) != 0) {
638			return (-1);
639		}
640		rem -= len;
641		off += len;
642	}
643	pcmd->len = 0;
644	pcmd->cmd = PKG_PKGSYNC;
645	if (pkgcmd(server, pcmd, sizeof (*pcmd), NULL, NULL, NULL) != 0)
646		return (-1);
647
648	/* Mark it unmodified. */
649	vfpTruncate(a_vfp);
650	(void) vfpClearModified(a_vfp);
651
652	return (0);
653}
654
655int
656pkgopenfilter(PKGserver server, const char *filt)
657{
658	int fd;
659	pkgfilter_t *pfcmd;
660	int clen = filt == NULL ? 0 : strlen(filt);
661	int len = sizeof (*pfcmd) + clen;
662
663	pfcmd = alloca(len);
664
665	if (server->fp != NULL) {
666		(void) fclose(server->fp);
667		server->fp = NULL;
668	}
669
670	pfcmd->cmd = PKG_FILTER;
671	pfcmd->len = clen;
672	if (filt != NULL)
673		(void) strcpy(pfcmd->buf, filt);
674
675	fd = -1;
676
677	if (pkgcmd(server, pfcmd, len, NULL, NULL, &fd) != 0 || fd == -1) {
678		progerr(gettext(ERR_START_FILTER));
679		return (-1);
680	}
681	(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
682
683	server->fp = fdopen(fd, "r");
684	if (server->fp == NULL) {
685		(void) close(fd);
686		progerr(gettext(ERR_START_FILTER));
687		return (-1);
688	}
689	return (0);
690}
691
692void
693pkgclosefilter(PKGserver server)
694{
695	if (server->fp != NULL) {
696		(void) fclose(server->fp);
697		server->fp = NULL;
698	}
699}
700
701/*
702 * Report the next entry from the contents file.
703 */
704char *
705pkggetentry(PKGserver server, int *len, int *pathlen)
706{
707	int num[2];
708
709	if (server->fp == NULL)
710		return (NULL);
711
712	if (feof(server->fp) || ferror(server->fp))
713		return (NULL);
714
715	if (fread(num, sizeof (int), 2, server->fp) != 2)
716		return (NULL);
717
718	if (num[0] > server->buflen) {
719		free(server->curbuf);
720		server->buflen = num[0];
721		server->curbuf = malloc(server->buflen);
722		if (server->curbuf == NULL)
723			return (NULL);
724	}
725	if (fread(server->curbuf, 1, num[0], server->fp) != num[0])
726		return (NULL);
727
728	*len = num[0];
729	*pathlen = num[1];
730
731	return (server->curbuf);
732}
733
734char *
735pkggetentry_named(PKGserver server, const char *path, int *len, int *pathlen)
736{
737	int plen = strlen(path);
738	pkgfilter_t *pcmd = alloca(sizeof (*pcmd) + plen);
739	char *result;
740	unsigned int rlen;
741
742	pcmd->cmd = PKG_FINDFILE;
743	*pathlen = pcmd->len = plen;
744	(void) memcpy(pcmd->buf, path, pcmd->len + 1);
745
746	result = server->curbuf;
747	rlen = server->buflen;
748
749	if (pkgcmd(server, pcmd, sizeof (*pcmd) + pcmd->len,
750	    &result, &rlen, NULL) != 0) {
751		return (NULL);
752	}
753	if (rlen == 0)
754		return (NULL);
755
756	/* Result too big */
757	if (result != server->curbuf) {
758		free(server->curbuf);
759		server->buflen = rlen;
760		server->curbuf = malloc(server->buflen);
761		if (server->curbuf == NULL)
762			return (NULL);
763		(void) memcpy(server->curbuf, result, rlen);
764		(void) munmap(result, rlen);
765	}
766	*len = rlen;
767
768	return (server->curbuf);
769}
770