]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/iscsid/iscsid.c
Add network QoS support for PCP to iscsi target.
[FreeBSD/FreeBSD.git] / usr.sbin / iscsid / iscsid.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2012 The FreeBSD Foundation
5  * All rights reserved.
6  *
7  * This software was developed by Edward Tomasz Napierala under sponsorship
8  * from the FreeBSD Foundation.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  *
31  */
32
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35
36 #include <sys/types.h>
37 #include <sys/time.h>
38 #include <sys/ioctl.h>
39 #include <sys/param.h>
40 #include <sys/linker.h>
41 #include <sys/socket.h>
42 #include <sys/capsicum.h>
43 #include <sys/wait.h>
44 #include <netinet/in.h>
45 #include <assert.h>
46 #include <capsicum_helpers.h>
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <libutil.h>
50 #include <netdb.h>
51 #include <signal.h>
52 #include <stdbool.h>
53 #include <stdint.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <unistd.h>
58
59 #include "iscsid.h"
60
61 static volatile bool sigalrm_received = false;
62
63 static int nchildren = 0;
64
65 static void
66 usage(void)
67 {
68
69         fprintf(stderr, "usage: iscsid [-P pidfile][-d][-m maxproc][-t timeout]\n");
70         exit(1);
71 }
72
73 char *
74 checked_strdup(const char *s)
75 {
76         char *c;
77
78         c = strdup(s);
79         if (c == NULL)
80                 log_err(1, "strdup");
81         return (c);
82 }
83
84 static void
85 resolve_addr(const struct connection *conn, const char *address,
86     struct addrinfo **ai, bool initiator_side)
87 {
88         struct addrinfo hints;
89         char *arg, *addr, *ch;
90         const char *port;
91         int error, colons = 0;
92
93         arg = checked_strdup(address);
94
95         if (arg[0] == '\0') {
96                 fail(conn, "empty address");
97                 log_errx(1, "empty address");
98         }
99         if (arg[0] == '[') {
100                 /*
101                  * IPv6 address in square brackets, perhaps with port.
102                  */
103                 arg++;
104                 addr = strsep(&arg, "]");
105                 if (arg == NULL) {
106                         fail(conn, "malformed address");
107                         log_errx(1, "malformed address %s", address);
108                 }
109                 if (arg[0] == '\0') {
110                         port = NULL;
111                 } else if (arg[0] == ':') {
112                         port = arg + 1;
113                 } else {
114                         fail(conn, "malformed address");
115                         log_errx(1, "malformed address %s", address);
116                 }
117         } else {
118                 /*
119                  * Either IPv6 address without brackets - and without
120                  * a port - or IPv4 address.  Just count the colons.
121                  */
122                 for (ch = arg; *ch != '\0'; ch++) {
123                         if (*ch == ':')
124                                 colons++;
125                 }
126                 if (colons > 1) {
127                         addr = arg;
128                         port = NULL;
129                 } else {
130                         addr = strsep(&arg, ":");
131                         if (arg == NULL)
132                                 port = NULL;
133                         else
134                                 port = arg;
135                 }
136         }
137
138         if (port == NULL && !initiator_side)
139                 port = "3260";
140
141         memset(&hints, 0, sizeof(hints));
142         hints.ai_family = PF_UNSPEC;
143         hints.ai_socktype = SOCK_STREAM;
144         hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
145         if (initiator_side)
146                 hints.ai_flags |= AI_PASSIVE;
147
148         error = getaddrinfo(addr, port, &hints, ai);
149         if (error != 0) {
150                 fail(conn, gai_strerror(error));
151                 log_errx(1, "getaddrinfo for %s failed: %s",
152                     address, gai_strerror(error));
153         }
154 }
155
156 static struct connection *
157 connection_new(int iscsi_fd, const struct iscsi_daemon_request *request)
158 {
159         struct connection *conn;
160         struct iscsi_session_limits *isl;
161         struct addrinfo *from_ai, *to_ai;
162         const char *from_addr, *to_addr;
163 #ifdef ICL_KERNEL_PROXY
164         struct iscsi_daemon_connect idc;
165 #endif
166         int error, sockbuf;
167
168         conn = calloc(1, sizeof(*conn));
169         if (conn == NULL)
170                 log_err(1, "calloc");
171
172         /*
173          * Default values, from RFC 3720, section 12.
174          */
175         conn->conn_protocol_level = 0;
176         conn->conn_header_digest = CONN_DIGEST_NONE;
177         conn->conn_data_digest = CONN_DIGEST_NONE;
178         conn->conn_initial_r2t = true;
179         conn->conn_immediate_data = true;
180         conn->conn_max_recv_data_segment_length = 8192;
181         conn->conn_max_send_data_segment_length = 8192;
182         conn->conn_max_burst_length = 262144;
183         conn->conn_first_burst_length = 65536;
184         conn->conn_iscsi_fd = iscsi_fd;
185
186         conn->conn_session_id = request->idr_session_id;
187         memcpy(&conn->conn_conf, &request->idr_conf, sizeof(conn->conn_conf));
188         memcpy(&conn->conn_isid, &request->idr_isid, sizeof(conn->conn_isid));
189         conn->conn_tsih = request->idr_tsih;
190
191         /*
192          * Read the driver limits and provide reasonable defaults for the ones
193          * the driver doesn't care about.  If a max_snd_dsl is not explicitly
194          * provided by the driver then we'll make sure both conn->max_snd_dsl
195          * and isl->max_snd_dsl are set to the rcv_dsl.  This preserves historic
196          * behavior.
197          */
198         isl = &conn->conn_limits;
199         memcpy(isl, &request->idr_limits, sizeof(*isl));
200         if (isl->isl_max_recv_data_segment_length == 0)
201                 isl->isl_max_recv_data_segment_length = (1 << 24) - 1;
202         if (isl->isl_max_send_data_segment_length == 0)
203                 isl->isl_max_send_data_segment_length =
204                     isl->isl_max_recv_data_segment_length;
205         if (isl->isl_max_burst_length == 0)
206                 isl->isl_max_burst_length = (1 << 24) - 1;
207         if (isl->isl_first_burst_length == 0)
208                 isl->isl_first_burst_length = (1 << 24) - 1;
209         if (isl->isl_first_burst_length > isl->isl_max_burst_length)
210                 isl->isl_first_burst_length = isl->isl_max_burst_length;
211
212         /*
213          * Limit default send length in case it won't be negotiated.
214          * We can't do it for other limits, since they may affect both
215          * sender and receiver operation, and we must obey defaults.
216          */
217         if (conn->conn_max_send_data_segment_length >
218             isl->isl_max_send_data_segment_length) {
219                 conn->conn_max_send_data_segment_length =
220                     isl->isl_max_send_data_segment_length;
221         }
222
223         from_addr = conn->conn_conf.isc_initiator_addr;
224         to_addr = conn->conn_conf.isc_target_addr;
225
226         if (from_addr[0] != '\0')
227                 resolve_addr(conn, from_addr, &from_ai, true);
228         else
229                 from_ai = NULL;
230
231         resolve_addr(conn, to_addr, &to_ai, false);
232
233 #ifdef ICL_KERNEL_PROXY
234         if (conn->conn_conf.isc_iser) {
235                 memset(&idc, 0, sizeof(idc));
236                 idc.idc_session_id = conn->conn_session_id;
237                 if (conn->conn_conf.isc_iser)
238                         idc.idc_iser = 1;
239                 idc.idc_domain = to_ai->ai_family;
240                 idc.idc_socktype = to_ai->ai_socktype;
241                 idc.idc_protocol = to_ai->ai_protocol;
242                 if (from_ai != NULL) {
243                         idc.idc_from_addr = from_ai->ai_addr;
244                         idc.idc_from_addrlen = from_ai->ai_addrlen;
245                 }
246                 idc.idc_to_addr = to_ai->ai_addr;
247                 idc.idc_to_addrlen = to_ai->ai_addrlen;
248
249                 log_debugx("connecting to %s using ICL kernel proxy", to_addr);
250                 error = ioctl(iscsi_fd, ISCSIDCONNECT, &idc);
251                 if (error != 0) {
252                         fail(conn, strerror(errno));
253                         log_err(1, "failed to connect to %s "
254                             "using ICL kernel proxy: ISCSIDCONNECT", to_addr);
255                 }
256
257                 return (conn);
258         }
259 #endif /* ICL_KERNEL_PROXY */
260
261         if (conn->conn_conf.isc_iser) {
262                 fail(conn, "iSER not supported");
263                 log_errx(1, "iscsid(8) compiled without ICL_KERNEL_PROXY "
264                     "does not support iSER");
265         }
266
267         conn->conn_socket = socket(to_ai->ai_family, to_ai->ai_socktype,
268             to_ai->ai_protocol);
269         if (conn->conn_socket < 0) {
270                 fail(conn, strerror(errno));
271                 log_err(1, "failed to create socket for %s", from_addr);
272         }
273         sockbuf = SOCKBUF_SIZE;
274         if (setsockopt(conn->conn_socket, SOL_SOCKET, SO_RCVBUF,
275             &sockbuf, sizeof(sockbuf)) == -1)
276                 log_warn("setsockopt(SO_RCVBUF) failed");
277         sockbuf = SOCKBUF_SIZE;
278         if (setsockopt(conn->conn_socket, SOL_SOCKET, SO_SNDBUF,
279             &sockbuf, sizeof(sockbuf)) == -1)
280                 log_warn("setsockopt(SO_SNDBUF) failed");
281         if (conn->conn_conf.isc_dscp != -1) {
282                 int tos = conn->conn_conf.isc_dscp << 2;
283                 if (to_ai->ai_family == AF_INET) {
284                         if (setsockopt(conn->conn_socket,
285                             IPPROTO_IP, IP_TOS,
286                             &tos, sizeof(tos)) == -1)
287                                 log_warn("setsockopt(IP_TOS) "
288                                     "failed for %s",
289                                     from_addr);
290                 } else
291                 if (to_ai->ai_family == AF_INET6) {
292                         if (setsockopt(conn->conn_socket,
293                             IPPROTO_IPV6, IPV6_TCLASS,
294                             &tos, sizeof(tos)) == -1)
295                                 log_warn("setsockopt(IPV6_TCLASS) "
296                                     "failed for %s",
297                                     from_addr);
298                 }
299         }
300         if (conn->conn_conf.isc_pcp != -1) {
301                 int pcp = conn->conn_conf.isc_pcp;
302                 if (to_ai->ai_family == AF_INET) {
303                         if (setsockopt(conn->conn_socket,
304                             IPPROTO_IP, IP_VLAN_PCP,
305                             &pcp, sizeof(pcp)) == -1)
306                                 log_warn("setsockopt(IP_VLAN_PCP) "
307                                     "failed for %s",
308                                     from_addr);
309                 } else
310                 if (to_ai->ai_family == AF_INET6) {
311                         if (setsockopt(conn->conn_socket,
312                             IPPROTO_IPV6, IPV6_VLAN_PCP,
313                             &pcp, sizeof(pcp)) == -1)
314                                 log_warn("setsockopt(IPV6_VLAN_PCP) "
315                                     "failed for %s",
316                                     from_addr);
317                 }
318         }
319         if (from_ai != NULL) {
320                 error = bind(conn->conn_socket, from_ai->ai_addr,
321                     from_ai->ai_addrlen);
322                 if (error != 0) {
323                         fail(conn, strerror(errno));
324                         log_err(1, "failed to bind to %s", from_addr);
325                 }
326         }
327         log_debugx("connecting to %s", to_addr);
328         error = connect(conn->conn_socket, to_ai->ai_addr, to_ai->ai_addrlen);
329         if (error != 0) {
330                 fail(conn, strerror(errno));
331                 log_err(1, "failed to connect to %s", to_addr);
332         }
333
334         return (conn);
335 }
336
337 static void
338 handoff(struct connection *conn)
339 {
340         struct iscsi_daemon_handoff idh;
341         int error;
342
343         log_debugx("handing off connection to the kernel");
344
345         memset(&idh, 0, sizeof(idh));
346         idh.idh_session_id = conn->conn_session_id;
347         idh.idh_socket = conn->conn_socket;
348         strlcpy(idh.idh_target_alias, conn->conn_target_alias,
349             sizeof(idh.idh_target_alias));
350         idh.idh_tsih = conn->conn_tsih;
351         idh.idh_statsn = conn->conn_statsn;
352         idh.idh_protocol_level = conn->conn_protocol_level;
353         idh.idh_header_digest = conn->conn_header_digest;
354         idh.idh_data_digest = conn->conn_data_digest;
355         idh.idh_initial_r2t = conn->conn_initial_r2t;
356         idh.idh_immediate_data = conn->conn_immediate_data;
357         idh.idh_max_recv_data_segment_length =
358             conn->conn_max_recv_data_segment_length;
359         idh.idh_max_send_data_segment_length =
360             conn->conn_max_send_data_segment_length;
361         idh.idh_max_burst_length = conn->conn_max_burst_length;
362         idh.idh_first_burst_length = conn->conn_first_burst_length;
363
364         error = ioctl(conn->conn_iscsi_fd, ISCSIDHANDOFF, &idh);
365         if (error != 0)
366                 log_err(1, "ISCSIDHANDOFF");
367 }
368
369 void
370 fail(const struct connection *conn, const char *reason)
371 {
372         struct iscsi_daemon_fail idf;
373         int error, saved_errno;
374
375         saved_errno = errno;
376
377         memset(&idf, 0, sizeof(idf));
378         idf.idf_session_id = conn->conn_session_id;
379         strlcpy(idf.idf_reason, reason, sizeof(idf.idf_reason));
380
381         error = ioctl(conn->conn_iscsi_fd, ISCSIDFAIL, &idf);
382         if (error != 0)
383                 log_err(1, "ISCSIDFAIL");
384
385         errno = saved_errno;
386 }
387
388 /*
389  * XXX: I CANT INTO LATIN
390  */
391 static void
392 capsicate(struct connection *conn)
393 {
394         cap_rights_t rights;
395 #ifdef ICL_KERNEL_PROXY
396         const unsigned long cmds[] = { ISCSIDCONNECT, ISCSIDSEND, ISCSIDRECEIVE,
397             ISCSIDHANDOFF, ISCSIDFAIL, ISCSISADD, ISCSISREMOVE, ISCSISMODIFY };
398 #else
399         const unsigned long cmds[] = { ISCSIDHANDOFF, ISCSIDFAIL, ISCSISADD,
400             ISCSISREMOVE, ISCSISMODIFY };
401 #endif
402
403         cap_rights_init(&rights, CAP_IOCTL);
404         if (caph_rights_limit(conn->conn_iscsi_fd, &rights) < 0)
405                 log_err(1, "cap_rights_limit");
406
407         if (caph_ioctls_limit(conn->conn_iscsi_fd, cmds, nitems(cmds)) < 0)
408                 log_err(1, "cap_ioctls_limit");
409
410         if (caph_enter() != 0)
411                 log_err(1, "cap_enter");
412
413         if (cap_sandboxed())
414                 log_debugx("Capsicum capability mode enabled");
415         else
416                 log_warnx("Capsicum capability mode not supported");
417 }
418
419 bool
420 timed_out(void)
421 {
422
423         return (sigalrm_received);
424 }
425
426 static void
427 sigalrm_handler(int dummy __unused)
428 {
429         /*
430          * It would be easiest to just log an error and exit.  We can't
431          * do this, though, because log_errx() is not signal safe, since
432          * it calls syslog(3).  Instead, set a flag checked by pdu_send()
433          * and pdu_receive(), to call log_errx() there.  Should they fail
434          * to notice, we'll exit here one second later.
435          */
436         if (sigalrm_received) {
437                 /*
438                  * Oh well.  Just give up and quit.
439                  */
440                 _exit(2);
441         }
442
443         sigalrm_received = true;
444 }
445
446 static void
447 set_timeout(int timeout)
448 {
449         struct sigaction sa;
450         struct itimerval itv;
451         int error;
452
453         if (timeout <= 0) {
454                 log_debugx("session timeout disabled");
455                 return;
456         }
457
458         bzero(&sa, sizeof(sa));
459         sa.sa_handler = sigalrm_handler;
460         sigfillset(&sa.sa_mask);
461         error = sigaction(SIGALRM, &sa, NULL);
462         if (error != 0)
463                 log_err(1, "sigaction");
464
465         /*
466          * First SIGALRM will arive after conf_timeout seconds.
467          * If we do nothing, another one will arrive a second later.
468          */
469         bzero(&itv, sizeof(itv));
470         itv.it_interval.tv_sec = 1;
471         itv.it_value.tv_sec = timeout;
472
473         log_debugx("setting session timeout to %d seconds",
474             timeout);
475         error = setitimer(ITIMER_REAL, &itv, NULL);
476         if (error != 0)
477                 log_err(1, "setitimer");
478 }
479
480 static void
481 sigchld_handler(int dummy __unused)
482 {
483
484         /*
485          * The only purpose of this handler is to make SIGCHLD
486          * interrupt the ISCSIDWAIT ioctl(2), so we can call
487          * wait_for_children().
488          */
489 }
490
491 static void
492 register_sigchld(void)
493 {
494         struct sigaction sa;
495         int error;
496
497         bzero(&sa, sizeof(sa));
498         sa.sa_handler = sigchld_handler;
499         sigfillset(&sa.sa_mask);
500         error = sigaction(SIGCHLD, &sa, NULL);
501         if (error != 0)
502                 log_err(1, "sigaction");
503
504 }
505
506 static void
507 handle_request(int iscsi_fd, const struct iscsi_daemon_request *request, int timeout)
508 {
509         struct connection *conn;
510
511         log_set_peer_addr(request->idr_conf.isc_target_addr);
512         if (request->idr_conf.isc_target[0] != '\0') {
513                 log_set_peer_name(request->idr_conf.isc_target);
514                 setproctitle("%s (%s)", request->idr_conf.isc_target_addr, request->idr_conf.isc_target);
515         } else {
516                 setproctitle("%s", request->idr_conf.isc_target_addr);
517         }
518
519         conn = connection_new(iscsi_fd, request);
520         set_timeout(timeout);
521         capsicate(conn);
522         login(conn);
523         if (conn->conn_conf.isc_discovery != 0)
524                 discovery(conn);
525         else
526                 handoff(conn);
527
528         log_debugx("nothing more to do; exiting");
529         exit (0);
530 }
531
532 static int
533 wait_for_children(bool block)
534 {
535         pid_t pid;
536         int status;
537         int num = 0;
538
539         for (;;) {
540                 /*
541                  * If "block" is true, wait for at least one process.
542                  */
543                 if (block && num == 0)
544                         pid = wait4(-1, &status, 0, NULL);
545                 else
546                         pid = wait4(-1, &status, WNOHANG, NULL);
547                 if (pid <= 0)
548                         break;
549                 if (WIFSIGNALED(status)) {
550                         log_warnx("child process %d terminated with signal %d",
551                             pid, WTERMSIG(status));
552                 } else if (WEXITSTATUS(status) != 0) {
553                         log_warnx("child process %d terminated with exit status %d",
554                             pid, WEXITSTATUS(status));
555                 } else {
556                         log_debugx("child process %d terminated gracefully", pid);
557                 }
558                 num++;
559         }
560
561         return (num);
562 }
563
564 int
565 main(int argc, char **argv)
566 {
567         int ch, debug = 0, error, iscsi_fd, maxproc = 30, retval, saved_errno,
568             timeout = 60;
569         bool dont_daemonize = false;
570         struct pidfh *pidfh;
571         pid_t pid, otherpid;
572         const char *pidfile_path = DEFAULT_PIDFILE;
573         struct iscsi_daemon_request request;
574
575         while ((ch = getopt(argc, argv, "P:dl:m:t:")) != -1) {
576                 switch (ch) {
577                 case 'P':
578                         pidfile_path = optarg;
579                         break;
580                 case 'd':
581                         dont_daemonize = true;
582                         debug++;
583                         break;
584                 case 'l':
585                         debug = atoi(optarg);
586                         break;
587                 case 'm':
588                         maxproc = atoi(optarg);
589                         break;
590                 case 't':
591                         timeout = atoi(optarg);
592                         break;
593                 case '?':
594                 default:
595                         usage();
596                 }
597         }
598         argc -= optind;
599         if (argc != 0)
600                 usage();
601
602         log_init(debug);
603
604         pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
605         if (pidfh == NULL) {
606                 if (errno == EEXIST)
607                         log_errx(1, "daemon already running, pid: %jd.",
608                             (intmax_t)otherpid);
609                 log_err(1, "cannot open or create pidfile \"%s\"",
610                     pidfile_path);
611         }
612
613         iscsi_fd = open(ISCSI_PATH, O_RDWR);
614         if (iscsi_fd < 0 && errno == ENOENT) {
615                 saved_errno = errno;
616                 retval = kldload("iscsi");
617                 if (retval != -1)
618                         iscsi_fd = open(ISCSI_PATH, O_RDWR);
619                 else
620                         errno = saved_errno;
621         }
622         if (iscsi_fd < 0)
623                 log_err(1, "failed to open %s", ISCSI_PATH);
624
625         if (dont_daemonize == false) {
626                 if (daemon(0, 0) == -1) {
627                         log_warn("cannot daemonize");
628                         pidfile_remove(pidfh);
629                         exit(1);
630                 }
631         }
632
633         pidfile_write(pidfh);
634
635         register_sigchld();
636
637         for (;;) {
638                 log_debugx("waiting for request from the kernel");
639
640                 memset(&request, 0, sizeof(request));
641                 error = ioctl(iscsi_fd, ISCSIDWAIT, &request);
642                 if (error != 0) {
643                         if (errno == EINTR) {
644                                 nchildren -= wait_for_children(false);
645                                 assert(nchildren >= 0);
646                                 continue;
647                         }
648
649                         log_err(1, "ISCSIDWAIT");
650                 }
651
652                 if (dont_daemonize) {
653                         log_debugx("not forking due to -d flag; "
654                             "will exit after servicing a single request");
655                 } else {
656                         nchildren -= wait_for_children(false);
657                         assert(nchildren >= 0);
658
659                         while (maxproc > 0 && nchildren >= maxproc) {
660                                 log_debugx("maxproc limit of %d child processes hit; "
661                                     "waiting for child process to exit", maxproc);
662                                 nchildren -= wait_for_children(true);
663                                 assert(nchildren >= 0);
664                         }
665                         log_debugx("incoming connection; forking child process #%d",
666                             nchildren);
667                         nchildren++;
668
669                         pid = fork();
670                         if (pid < 0)
671                                 log_err(1, "fork");
672                         if (pid > 0)
673                                 continue;
674                 }
675
676                 pidfile_close(pidfh);
677                 handle_request(iscsi_fd, &request, timeout);
678         }
679
680         return (0);
681 }