xref: /openbsd-src/usr.sbin/iscsid/iscsid.c (revision 7b8339380a5a5ef7c0e48ec10c485217e37b358f)
1*7b833938Sclaudio /*	$OpenBSD: iscsid.c,v 1.25 2025/01/22 16:06:36 claudio Exp $ */
2bde1ae23Sclaudio 
3bde1ae23Sclaudio /*
4bde1ae23Sclaudio  * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
5bde1ae23Sclaudio  *
6bde1ae23Sclaudio  * Permission to use, copy, modify, and distribute this software for any
7bde1ae23Sclaudio  * purpose with or without fee is hereby granted, provided that the above
8bde1ae23Sclaudio  * copyright notice and this permission notice appear in all copies.
9bde1ae23Sclaudio  *
10bde1ae23Sclaudio  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11bde1ae23Sclaudio  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12bde1ae23Sclaudio  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13bde1ae23Sclaudio  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14bde1ae23Sclaudio  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15bde1ae23Sclaudio  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16bde1ae23Sclaudio  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17bde1ae23Sclaudio  */
18bde1ae23Sclaudio 
19bde1ae23Sclaudio #include <sys/queue.h>
20bde1ae23Sclaudio #include <sys/socket.h>
2183d39657Sclaudio #include <sys/sysctl.h>
22bde1ae23Sclaudio #include <sys/time.h>
23bde1ae23Sclaudio #include <sys/uio.h>
24bde1ae23Sclaudio 
25bde1ae23Sclaudio #include <err.h>
26bde1ae23Sclaudio #include <event.h>
27bde1ae23Sclaudio #include <pwd.h>
28bde1ae23Sclaudio #include <signal.h>
29bde1ae23Sclaudio #include <stdio.h>
30bde1ae23Sclaudio #include <stdlib.h>
31bde1ae23Sclaudio #include <string.h>
32bde1ae23Sclaudio #include <unistd.h>
33bde1ae23Sclaudio 
34bde1ae23Sclaudio #include "iscsid.h"
35bde1ae23Sclaudio #include "log.h"
36bde1ae23Sclaudio 
37bde1ae23Sclaudio void		main_sig_handler(int, short, void *);
38bde1ae23Sclaudio __dead void	usage(void);
3912bd4d7fSclaudio void		shutdown_cb(int, short, void *);
40bde1ae23Sclaudio 
4112bd4d7fSclaudio struct event exit_ev;
4212bd4d7fSclaudio int exit_rounds;
4312bd4d7fSclaudio #define ISCSI_EXIT_WAIT 5
44bde1ae23Sclaudio 
454125a3c4Sclaudio const struct session_params	iscsi_sess_defaults = {
464125a3c4Sclaudio 	.MaxBurstLength = 262144,
474125a3c4Sclaudio 	.FirstBurstLength = 65536,
484125a3c4Sclaudio 	.DefaultTime2Wait = 2,
494125a3c4Sclaudio 	.DefaultTime2Retain = 20,
504125a3c4Sclaudio 	.MaxOutstandingR2T = 1,
514125a3c4Sclaudio 	.MaxConnections = 1,
524125a3c4Sclaudio 	.InitialR2T = 1,
534125a3c4Sclaudio 	.ImmediateData = 1,
544125a3c4Sclaudio 	.DataPDUInOrder = 1,
554125a3c4Sclaudio 	.DataSequenceInOrder = 1,
56aeec9c16Sclaudio 	.ErrorRecoveryLevel = 0,
574125a3c4Sclaudio };
584125a3c4Sclaudio 
594125a3c4Sclaudio const struct connection_params	iscsi_conn_defaults = {
60aeec9c16Sclaudio 	.MaxRecvDataSegmentLength = 8192,
61aeec9c16Sclaudio 	.HeaderDigest = DIGEST_NONE,
62aeec9c16Sclaudio 	.DataDigest = DIGEST_NONE,
634125a3c4Sclaudio };
644125a3c4Sclaudio 
65bde1ae23Sclaudio int
66bde1ae23Sclaudio main(int argc, char *argv[])
67bde1ae23Sclaudio {
68bde1ae23Sclaudio 	struct event ev_sigint, ev_sigterm, ev_sighup;
69bde1ae23Sclaudio 	struct passwd *pw;
70bde1ae23Sclaudio 	char *ctrlsock = ISCSID_CONTROL;
71bde1ae23Sclaudio 	char *vscsidev = ISCSID_DEVICE;
7283d39657Sclaudio 	int name[] = { CTL_KERN, KERN_PROC_NOBROADCASTKILL, 0 };
7383d39657Sclaudio 	int ch, debug = 0, verbose = 0, nobkill = 1;
74bde1ae23Sclaudio 
7519200396Sbenno 	log_procname = getprogname();
7619200396Sbenno 
77bde1ae23Sclaudio 	log_init(1);    /* log to stderr until daemonized */
78f91d3f97Ssthen 	log_verbose(1);
79bde1ae23Sclaudio 
80f91d3f97Ssthen 	while ((ch = getopt(argc, argv, "dn:s:v")) != -1) {
81bde1ae23Sclaudio 		switch (ch) {
82bde1ae23Sclaudio 		case 'd':
83bde1ae23Sclaudio 			debug = 1;
84bde1ae23Sclaudio 			break;
85bde1ae23Sclaudio 		case 'n':
86bde1ae23Sclaudio 			vscsidev = optarg;
87bde1ae23Sclaudio 			break;
88bde1ae23Sclaudio 		case 's':
89bde1ae23Sclaudio 			ctrlsock = optarg;
90bde1ae23Sclaudio 			break;
91f91d3f97Ssthen 		case 'v':
92f91d3f97Ssthen 			verbose = 1;
93f91d3f97Ssthen 			break;
94bde1ae23Sclaudio 		default:
95bde1ae23Sclaudio 			usage();
96bde1ae23Sclaudio 			/* NOTREACHED */
97bde1ae23Sclaudio 		}
98bde1ae23Sclaudio 	}
99bde1ae23Sclaudio 
100bde1ae23Sclaudio 	argc -= optind;
101bde1ae23Sclaudio 	argv += optind;
102bde1ae23Sclaudio 
103bde1ae23Sclaudio 	if (argc > 0)
104bde1ae23Sclaudio 		usage();
105bde1ae23Sclaudio 
106bde1ae23Sclaudio 	/* check for root privileges  */
107bde1ae23Sclaudio 	if (geteuid())
108bde1ae23Sclaudio 		errx(1, "need root privileges");
109bde1ae23Sclaudio 
110bde1ae23Sclaudio 	log_init(debug);
111f91d3f97Ssthen 	log_verbose(verbose);
112f91d3f97Ssthen 
113442b4c84Sclaudio 	if (control_init(ctrlsock) == -1)
114442b4c84Sclaudio 		fatalx("control socket setup failed");
115442b4c84Sclaudio 
116bde1ae23Sclaudio 	if (!debug)
117bde1ae23Sclaudio 		daemon(1, 0);
118bde1ae23Sclaudio 	log_info("startup");
119bde1ae23Sclaudio 
12083d39657Sclaudio 	name[2] = getpid();
12183d39657Sclaudio 	if (sysctl(name, 3, NULL, 0, &nobkill, sizeof(nobkill)) != 0)
12283d39657Sclaudio 		fatal("sysctl");
12383d39657Sclaudio 
124bde1ae23Sclaudio 	event_init();
125bde1ae23Sclaudio 	vscsi_open(vscsidev);
126bde1ae23Sclaudio 
127bde1ae23Sclaudio 	/* chroot and drop to iscsid user */
128bde1ae23Sclaudio 	if ((pw = getpwnam(ISCSID_USER)) == NULL)
129bde1ae23Sclaudio 		errx(1, "unknown user %s", ISCSID_USER);
130bde1ae23Sclaudio 
131bde1ae23Sclaudio 	if (chroot(pw->pw_dir) == -1)
132bde1ae23Sclaudio 		fatal("chroot");
133bde1ae23Sclaudio 	if (chdir("/") == -1)
134bde1ae23Sclaudio 		fatal("chdir(\"/\")");
135bde1ae23Sclaudio 	if (setgroups(1, &pw->pw_gid) ||
136bde1ae23Sclaudio 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
137bde1ae23Sclaudio 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
138bde1ae23Sclaudio 		fatal("can't drop privileges");
139bde1ae23Sclaudio 
140bde1ae23Sclaudio 	/* setup signal handler */
141bde1ae23Sclaudio 	signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
142bde1ae23Sclaudio 	signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
143bde1ae23Sclaudio 	signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
144bde1ae23Sclaudio 	signal_add(&ev_sigint, NULL);
145bde1ae23Sclaudio 	signal_add(&ev_sigterm, NULL);
146bde1ae23Sclaudio 	signal_add(&ev_sighup, NULL);
147bde1ae23Sclaudio 	signal(SIGPIPE, SIG_IGN);
148bde1ae23Sclaudio 
14977822a85Sclaudio 	control_event_init();
150*7b833938Sclaudio 	initiator_init();
151bde1ae23Sclaudio 
152bde1ae23Sclaudio 	event_dispatch();
153bde1ae23Sclaudio 
1542e849a08Sclaudio 	/* do some cleanup on the way out */
155bde1ae23Sclaudio 	control_cleanup(ctrlsock);
156*7b833938Sclaudio 	initiator_cleanup();
157bde1ae23Sclaudio 	log_info("exiting.");
158bde1ae23Sclaudio 	return 0;
159bde1ae23Sclaudio }
160bde1ae23Sclaudio 
1612e849a08Sclaudio void
1622e849a08Sclaudio shutdown_cb(int fd, short event, void *arg)
1632e849a08Sclaudio {
1642e849a08Sclaudio 	struct timeval tv;
1652e849a08Sclaudio 
166*7b833938Sclaudio 	if (exit_rounds++ >= ISCSI_EXIT_WAIT || initiator_isdown())
1672e849a08Sclaudio 		event_loopexit(NULL);
1682e849a08Sclaudio 
1692e849a08Sclaudio 	timerclear(&tv);
1702e849a08Sclaudio 	tv.tv_sec = 1;
1712e849a08Sclaudio 
1722e849a08Sclaudio 	if (evtimer_add(&exit_ev, &tv) == -1)
1732e849a08Sclaudio 		fatal("shutdown_cb");
1742e849a08Sclaudio }
1752e849a08Sclaudio 
176bde1ae23Sclaudio void
177bde1ae23Sclaudio main_sig_handler(int sig, short event, void *arg)
178bde1ae23Sclaudio {
17912bd4d7fSclaudio 	struct timeval tv;
18012bd4d7fSclaudio 
181bde1ae23Sclaudio 	/* signal handler rules don't apply, libevent decouples for us */
182bde1ae23Sclaudio 	switch (sig) {
183bde1ae23Sclaudio 	case SIGTERM:
184bde1ae23Sclaudio 	case SIGINT:
185bde1ae23Sclaudio 	case SIGHUP:
186*7b833938Sclaudio 		initiator_shutdown();
18712bd4d7fSclaudio 		evtimer_set(&exit_ev, shutdown_cb, NULL);
18812bd4d7fSclaudio 		timerclear(&tv);
18912bd4d7fSclaudio 		if (evtimer_add(&exit_ev, &tv) == -1)
19012bd4d7fSclaudio 			fatal("main_sig_handler");
191bde1ae23Sclaudio 		break;
192bde1ae23Sclaudio 	default:
193bde1ae23Sclaudio 		fatalx("unexpected signal");
194bde1ae23Sclaudio 		/* NOTREACHED */
195bde1ae23Sclaudio 	}
196bde1ae23Sclaudio }
197bde1ae23Sclaudio 
198bde1ae23Sclaudio __dead void
199bde1ae23Sclaudio usage(void)
200bde1ae23Sclaudio {
201bde1ae23Sclaudio 	extern char *__progname;
202bde1ae23Sclaudio 
203f91d3f97Ssthen 	fprintf(stderr, "usage: %s [-dv] [-n device] [-s socket]\n",
204bde1ae23Sclaudio 	    __progname);
205bde1ae23Sclaudio 	exit(1);
206bde1ae23Sclaudio }
207bde1ae23Sclaudio 
208bde1ae23Sclaudio void
209bde1ae23Sclaudio iscsid_ctrl_dispatch(void *ch, struct pdu *pdu)
210bde1ae23Sclaudio {
211bde1ae23Sclaudio 	struct ctrlmsghdr *cmh;
212bde1ae23Sclaudio 	struct initiator_config *ic;
213*7b833938Sclaudio 	struct session_head *sh;
214bde1ae23Sclaudio 	struct session_config *sc;
215bde1ae23Sclaudio 	struct session *s;
216ea99aae6Sclaudio 	struct session_poll p = { 0 };
217ac43be81Sclaudio 	int *valp;
218bde1ae23Sclaudio 
219bde1ae23Sclaudio 	cmh = pdu_getbuf(pdu, NULL, 0);
220bde1ae23Sclaudio 	if (cmh == NULL)
221bde1ae23Sclaudio 		goto done;
222bde1ae23Sclaudio 
223bde1ae23Sclaudio 	switch (cmh->type) {
224bde1ae23Sclaudio 	case CTRL_INITIATOR_CONFIG:
225bde1ae23Sclaudio 		if (cmh->len[0] != sizeof(*ic)) {
226bde1ae23Sclaudio 			log_warnx("CTRL_INITIATOR_CONFIG bad size");
227bde1ae23Sclaudio 			control_compose(ch, CTRL_FAILURE, NULL, 0);
228bde1ae23Sclaudio 			break;
229bde1ae23Sclaudio 		}
230bde1ae23Sclaudio 		ic = pdu_getbuf(pdu, NULL, 1);
231*7b833938Sclaudio 		initiator_set_config(ic);
232bde1ae23Sclaudio 		control_compose(ch, CTRL_SUCCESS, NULL, 0);
233bde1ae23Sclaudio 		break;
234bde1ae23Sclaudio 	case CTRL_SESSION_CONFIG:
235bde1ae23Sclaudio 		if (cmh->len[0] != sizeof(*sc)) {
236ac43be81Sclaudio 			log_warnx("CTRL_SESSION_CONFIG bad size");
237bde1ae23Sclaudio 			control_compose(ch, CTRL_FAILURE, NULL, 0);
238bde1ae23Sclaudio 			break;
239bde1ae23Sclaudio 		}
240bde1ae23Sclaudio 		sc = pdu_getbuf(pdu, NULL, 1);
241bde1ae23Sclaudio 		if (cmh->len[1])
242bde1ae23Sclaudio 			sc->TargetName = pdu_getbuf(pdu, NULL, 2);
243bde1ae23Sclaudio 		else if (sc->SessionType != SESSION_TYPE_DISCOVERY) {
244bde1ae23Sclaudio 			control_compose(ch, CTRL_FAILURE, NULL, 0);
245bde1ae23Sclaudio 			goto done;
246bde1ae23Sclaudio 		} else
247bde1ae23Sclaudio 			sc->TargetName = NULL;
248bde1ae23Sclaudio 		if (cmh->len[2])
249bde1ae23Sclaudio 			sc->InitiatorName = pdu_getbuf(pdu, NULL, 3);
250bde1ae23Sclaudio 		else
251bde1ae23Sclaudio 			sc->InitiatorName = NULL;
252bde1ae23Sclaudio 
253*7b833938Sclaudio 		s = initiator_find_session(sc->SessionName);
254bde1ae23Sclaudio 		if (s == NULL) {
255*7b833938Sclaudio 			s = initiator_new_session(sc->SessionType);
256bde1ae23Sclaudio 			if (s == NULL) {
257bde1ae23Sclaudio 				control_compose(ch, CTRL_FAILURE, NULL, 0);
258bde1ae23Sclaudio 				goto done;
259bde1ae23Sclaudio 			}
260bde1ae23Sclaudio 		}
261bde1ae23Sclaudio 
262bde1ae23Sclaudio 		session_config(s, sc);
263cb408c6cSclaudio 		if (s->state == SESS_INIT)
2640908bd10Sclaudio 			session_fsm(&s->sev, SESS_EV_START, 0);
265bde1ae23Sclaudio 
266bde1ae23Sclaudio 		control_compose(ch, CTRL_SUCCESS, NULL, 0);
267bde1ae23Sclaudio 		break;
268ac43be81Sclaudio 	case CTRL_LOG_VERBOSE:
269ac43be81Sclaudio 		if (cmh->len[0] != sizeof(int)) {
270ac43be81Sclaudio 			log_warnx("CTRL_LOG_VERBOSE bad size");
271ac43be81Sclaudio 			control_compose(ch, CTRL_FAILURE, NULL, 0);
272ac43be81Sclaudio 			break;
273ac43be81Sclaudio 		}
274ac43be81Sclaudio 		valp = pdu_getbuf(pdu, NULL, 1);
275ac43be81Sclaudio 		log_verbose(*valp);
276ac43be81Sclaudio 		control_compose(ch, CTRL_SUCCESS, NULL, 0);
277ac43be81Sclaudio 		break;
278234d8810Sclaudio 	case CTRL_VSCSI_STATS:
279234d8810Sclaudio 		control_compose(ch, CTRL_VSCSI_STATS, vscsi_stats(),
280234d8810Sclaudio 		    sizeof(struct vscsi_stats));
281234d8810Sclaudio 		break;
2829683d5fdSclaudio 	case CTRL_SHOW_SUM:
283*7b833938Sclaudio 		ic = initiator_get_config();
284*7b833938Sclaudio 		control_compose(ch, CTRL_INITIATOR_CONFIG, ic, sizeof(*ic));
2859683d5fdSclaudio 
286*7b833938Sclaudio 		sh = initiator_get_sessions();
287*7b833938Sclaudio 		TAILQ_FOREACH(s, sh, entry) {
2885bee45bcSclaudio 			struct ctrldata cdv[3];
2895bee45bcSclaudio 			bzero(cdv, sizeof(cdv));
2905bee45bcSclaudio 
2915bee45bcSclaudio 			cdv[0].buf = &s->config;
2925bee45bcSclaudio 			cdv[0].len = sizeof(s->config);
2935bee45bcSclaudio 
2945bee45bcSclaudio 			if (s->config.TargetName) {
2955bee45bcSclaudio 				cdv[1].buf = s->config.TargetName;
2965bee45bcSclaudio 				cdv[1].len =
2975bee45bcSclaudio 				    strlen(s->config.TargetName) + 1;
2985bee45bcSclaudio 			}
2995bee45bcSclaudio 			if (s->config.InitiatorName) {
3005bee45bcSclaudio 				cdv[2].buf = s->config.InitiatorName;
3015bee45bcSclaudio 				cdv[2].len =
3025bee45bcSclaudio 				    strlen(s->config.InitiatorName) + 1;
3035bee45bcSclaudio 			}
3045bee45bcSclaudio 
3055bee45bcSclaudio 			control_build(ch, CTRL_SESSION_CONFIG,
3065bee45bcSclaudio 			    nitems(cdv), cdv);
3075bee45bcSclaudio 		}
3089683d5fdSclaudio 
3099683d5fdSclaudio 		control_compose(ch, CTRL_SUCCESS, NULL, 0);
3109683d5fdSclaudio 		break;
311ea99aae6Sclaudio 	case CTRL_SESS_POLL:
312*7b833938Sclaudio 		sh = initiator_get_sessions();
313*7b833938Sclaudio 		TAILQ_FOREACH(s, sh, entry)
314ea99aae6Sclaudio 			poll_session(&p, s);
315ea99aae6Sclaudio 		poll_finalize(&p);
316ea99aae6Sclaudio 		control_compose(ch, CTRL_SESS_POLL, &p, sizeof(p));
317ea99aae6Sclaudio 		break;
318bde1ae23Sclaudio 	default:
319bde1ae23Sclaudio 		log_warnx("unknown control message type %d", cmh->type);
320bde1ae23Sclaudio 		control_compose(ch, CTRL_FAILURE, NULL, 0);
321bde1ae23Sclaudio 		break;
322bde1ae23Sclaudio 	}
323bde1ae23Sclaudio 
324bde1ae23Sclaudio done:
325bde1ae23Sclaudio 	pdu_free(pdu);
326bde1ae23Sclaudio }
32712bd4d7fSclaudio 
3284125a3c4Sclaudio #define MERGE_MIN(r, a, b, v)				\
3295640efa3Stedu 	r->v = (a->v < b->v ? a->v : b->v)
3304125a3c4Sclaudio #define MERGE_MAX(r, a, b, v)				\
3315640efa3Stedu 	r->v = (a->v > b->v ? a->v : b->v)
3324125a3c4Sclaudio #define MERGE_OR(r, a, b, v)				\
3335640efa3Stedu 	r->v = (a->v || b->v)
3344125a3c4Sclaudio #define MERGE_AND(r, a, b, v)				\
3355640efa3Stedu 	r->v = (a->v && b->v)
3364125a3c4Sclaudio 
3374125a3c4Sclaudio void
3384125a3c4Sclaudio iscsi_merge_sess_params(struct session_params *res,
3394125a3c4Sclaudio     struct session_params *mine, struct session_params *his)
3404125a3c4Sclaudio {
341aeec9c16Sclaudio 	memset(res, 0, sizeof(*res));
342aeec9c16Sclaudio 
3434125a3c4Sclaudio 	MERGE_MIN(res, mine, his, MaxBurstLength);
3444125a3c4Sclaudio 	MERGE_MIN(res, mine, his, FirstBurstLength);
3454125a3c4Sclaudio 	MERGE_MAX(res, mine, his, DefaultTime2Wait);
3464125a3c4Sclaudio 	MERGE_MIN(res, mine, his, DefaultTime2Retain);
3474125a3c4Sclaudio 	MERGE_MIN(res, mine, his, MaxOutstandingR2T);
3484125a3c4Sclaudio 	res->TargetPortalGroupTag = his->TargetPortalGroupTag;
3494125a3c4Sclaudio 	MERGE_MIN(res, mine, his, MaxConnections);
3504125a3c4Sclaudio 	MERGE_OR(res, mine, his, InitialR2T);
3514125a3c4Sclaudio 	MERGE_AND(res, mine, his, ImmediateData);
3524125a3c4Sclaudio 	MERGE_OR(res, mine, his, DataPDUInOrder);
3534125a3c4Sclaudio 	MERGE_OR(res, mine, his, DataSequenceInOrder);
3544125a3c4Sclaudio 	MERGE_MIN(res, mine, his, ErrorRecoveryLevel);
3554125a3c4Sclaudio 
3564125a3c4Sclaudio }
3574125a3c4Sclaudio 
3584125a3c4Sclaudio void
3594125a3c4Sclaudio iscsi_merge_conn_params(struct connection_params *res,
3604125a3c4Sclaudio     struct connection_params *mine, struct connection_params *his)
3614125a3c4Sclaudio {
362aeec9c16Sclaudio 	int mask;
363aeec9c16Sclaudio 
364aeec9c16Sclaudio 	memset(res, 0, sizeof(*res));
365aeec9c16Sclaudio 
3664125a3c4Sclaudio 	res->MaxRecvDataSegmentLength = his->MaxRecvDataSegmentLength;
367aeec9c16Sclaudio 
368aeec9c16Sclaudio 	/* for digest select first bit that is set in both his and mine */
369aeec9c16Sclaudio 	mask = mine->HeaderDigest & his->HeaderDigest;
370aeec9c16Sclaudio 	mask = ffs(mask) - 1;
371aeec9c16Sclaudio 	if (mask == -1)
372aeec9c16Sclaudio 		res->HeaderDigest = 0;
373aeec9c16Sclaudio 	else
374aeec9c16Sclaudio 		res->HeaderDigest = 1 << mask;
375aeec9c16Sclaudio 
376aeec9c16Sclaudio 	mask = mine->DataDigest & his->DataDigest;
377aeec9c16Sclaudio 	mask = ffs(mask) - 1;
378aeec9c16Sclaudio 	if (mask == -1)
379aeec9c16Sclaudio 		res->DataDigest = 0;
380aeec9c16Sclaudio 	else
381aeec9c16Sclaudio 		res->DataDigest = 1 << mask;
3824125a3c4Sclaudio }
3834125a3c4Sclaudio 
3844125a3c4Sclaudio #undef MERGE_MIN
3854125a3c4Sclaudio #undef MERGE_MAX
3864125a3c4Sclaudio #undef MERGE_OR
3874125a3c4Sclaudio #undef MERGE_AND
388