reindent-20030715
[openafs.git] / src / vol / fssync.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11         System:         VICE-TWO
12         Module:         fssync.c
13         Institution:    The Information Technology Center, Carnegie-Mellon University
14
15  */
16 #ifdef notdef
17
18 /* All this is going away in early 1989 */
19 int newVLDB;                    /* Compatibility flag */
20
21 #endif
22 static int newVLDB = 1;
23
24 #ifndef AFS_PTHREAD_ENV
25 #define USUAL_PRIORITY (LWP_MAX_PRIORITY - 2)
26
27 /*
28  * stack size increased from 8K because the HP machine seemed to have trouble
29  * with the smaller stack
30  */
31 #define USUAL_STACK_SIZE        (24 * 1024)
32 #endif /* !AFS_PTHREAD_ENV */
33
34 /*
35    fsync.c
36    File server synchronization with external volume utilities.
37  */
38 #include <afsconfig.h>
39 #include <afs/param.h>
40
41 RCSID
42     ("$Header$");
43
44 #include <sys/types.h>
45 #include <stdio.h>
46 #ifdef AFS_NT40_ENV
47 #include <winsock2.h>
48 #include <time.h>
49 #else
50 #include <sys/param.h>
51 #include <sys/socket.h>
52 #include <netinet/in.h>
53 #include <netdb.h>
54 #include <sys/time.h>
55 #endif
56 #include <errno.h>
57 #ifdef AFS_PTHREAD_ENV
58 #include <assert.h>
59 #else /* AFS_PTHREAD_ENV */
60 #include <afs/assert.h>
61 #endif /* AFS_PTHREAD_ENV */
62 #include <signal.h>
63
64 #ifdef HAVE_STRING_H
65 #include <string.h>
66 #else
67 #ifdef HAVE_STRINGS_H
68 #include <strings.h>
69 #endif
70 #endif
71
72
73 #include <rx/xdr.h>
74 #include <afs/afsint.h>
75 #include "nfs.h"
76 #include <afs/errors.h>
77 #include "fssync.h"
78 #include "lwp.h"
79 #include "lock.h"
80 #include <afs/afssyscalls.h>
81 #include "ihandle.h"
82 #include "vnode.h"
83 #include "volume.h"
84 #include "partition.h"
85
86 /*@printflike@*/ extern void Log(const char *format, ...);
87
88 #ifdef osi_Assert
89 #undef osi_Assert
90 #endif
91 #define osi_Assert(e) (void)(e)
92
93 extern int LogLevel;            /* Vice loglevel */
94 int (*V_BreakVolumeCallbacks) ();
95
96 #define MAXHANDLERS     4       /* Up to 4 clients; must be at least 2, so that
97                                  * move = dump+restore can run on single server */
98 #define MAXOFFLINEVOLUMES 30    /* This needs to be as big as the maximum
99                                  * number that would be offline for 1 operation.
100                                  * Current winner is salvage, which needs all
101                                  * cloned read-only copies offline when salvaging
102                                  * a single read-write volume */
103
104 #define MAX_BIND_TRIES  5       /* Number of times to retry socket bind */
105
106
107 struct offlineInfo {
108     VolumeId volumeID;
109     char partName[16];
110 };
111
112 static struct offlineInfo OfflineVolumes[MAXHANDLERS][MAXOFFLINEVOLUMES];
113
114 static FS_sd = -1;              /* Client socket for talking to file server */
115 static AcceptSd = -1;           /* Socket used by server for accepting connections */
116
117 static int getport();
118
119 struct command {
120     bit32 command;
121     bit32 reason;
122     VolumeId volume;
123     char partName[16];          /* partition name, e.g. /vicepa */
124 };
125
126
127 /* Forward declarations */
128 static int getport();
129 static void FSYNC_sync();
130 static void FSYNC_newconnection();
131 static void FSYNC_com();
132 static void FSYNC_Drop();
133 static void AcceptOn();
134 static void AcceptOff();
135 static void InitHandler();
136 static void CallHandler(fd_set * fdsetp);
137 static int AddHandler();
138 static int FindHandler();
139 static int FindHandler_r();
140 static int RemoveHandler();
141 static void GetHandler(fd_set * fdsetp, int *maxfdp);
142
143 /*
144  * This lock controls access to the handler array. The overhead
145  * is minimal in non-preemptive environments.
146  */
147 struct Lock FSYNC_handler_lock;
148
149 int
150 FSYNC_clientInit(void)
151 {
152     struct sockaddr_in addr;
153     /* I can't believe the following is needed for localhost connections!! */
154     static time_t backoff[] =
155         { 3, 3, 3, 5, 5, 5, 7, 15, 16, 24, 32, 40, 48, 0 };
156     time_t *timeout = &backoff[0];
157
158     for (;;) {
159         FS_sd = getport(&addr);
160         if (connect(FS_sd, (struct sockaddr *)&addr, sizeof(addr)) >= 0)
161             return 1;
162 #if defined(AFS_SGI_ENV)
163         /* down with worthless error messages! */
164         if (!*timeout) {
165             perror("FSYNC_clientInit failed (after many retries)");
166             break;
167         }
168 #else
169         if (!*timeout)
170             break;
171         if (!(*timeout & 1))
172             perror("FSYNC_clientInit temporary failure (will retry)");
173 #endif
174         FSYNC_clientFinis();
175         sleep(*timeout++);
176     }
177     perror("FSYNC_clientInit failed (giving up!)");
178     return 0;
179 }
180
181 void
182 FSYNC_clientFinis(void)
183 {
184 #ifdef AFS_NT40_ENV
185     closesocket(FS_sd);
186 #else
187     close(FS_sd);
188 #endif
189     FS_sd = -1;
190     Lock_Destroy(&FSYNC_handler_lock);
191 }
192
193 int
194 FSYNC_askfs(VolumeId volume, char *partName, int com, int reason)
195 {
196     byte response;
197     struct command command;
198     int n;
199     command.volume = volume;
200     command.command = com;
201     command.reason = reason;
202     if (partName)
203         strcpy(command.partName, partName);
204     else
205         command.partName[0] = 0;
206     assert(FS_sd != -1);
207 #ifdef AFS_NT40_ENV
208     if (send(FS_sd, (char *)&command, sizeof(command), 0) != sizeof(command)) {
209         printf("FSYNC_askfs: write to file server failed\n");
210         return FSYNC_DENIED;
211     }
212     while ((n = recv(FS_sd, &response, 1, 0)) != 1) {
213         if (n == 0 || WSAEINTR != WSAGetLastError()) {
214             printf("FSYNC_askfs: No response from file server\n");
215             return FSYNC_DENIED;
216         }
217     }
218 #else
219     if (write(FS_sd, &command, sizeof(command)) != sizeof(command)) {
220         printf("FSYNC_askfs: write to file server failed\n");
221         return FSYNC_DENIED;
222     }
223     while ((n = read(FS_sd, &response, 1)) != 1) {
224         if (n == 0 || errno != EINTR) {
225             printf("FSYNC_askfs: No response from file server\n");
226             return FSYNC_DENIED;
227         }
228     }
229 #endif
230     if (response == 0) {
231         printf
232             ("FSYNC_askfs: negative response from file server; volume %u, command %d\n",
233              command.volume, (int)command.command);
234     }
235
236     return response;
237 }
238
239 void
240 FSYNC_fsInit(void)
241 {
242 #ifdef AFS_PTHREAD_ENV
243     pthread_t tid;
244     pthread_attr_t tattr;
245     assert(pthread_attr_init(&tattr) == 0);
246     assert(pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED) == 0);
247     assert(pthread_create(&tid, &tattr, FSYNC_sync, NULL) == 0);
248 #else /* AFS_PTHREAD_ENV */
249     PROCESS pid;
250     assert(LWP_CreateProcess
251            (FSYNC_sync, USUAL_STACK_SIZE, USUAL_PRIORITY, (void *)0,
252             "FSYNC_sync", &pid) == LWP_SUCCESS);
253 #endif /* AFS_PTHREAD_ENV */
254 }
255
256 static int
257 getport(struct sockaddr_in *addr)
258 {
259     int sd;
260
261     memset(addr, 0, sizeof(*addr));
262     assert((sd = socket(AF_INET, SOCK_STREAM, 0)) >= 0);
263 #ifdef STRUCT_SOCKADDR_HAS_SA_LEN
264     addr->sin_len = sizeof(struct sockaddr_in);
265 #endif
266     addr->sin_addr.s_addr = htonl(0x7f000001);
267     addr->sin_family = AF_INET; /* was localhost->h_addrtype */
268     addr->sin_port = htons(2040);       /* XXXX htons not _really_ neccessary */
269
270     return sd;
271 }
272
273 static void
274 FSYNC_sync()
275 {
276     struct sockaddr_in addr;
277     int on = 1;
278     extern VInit;
279     int code;
280     int numTries;
281 #ifdef AFS_PTHREAD_ENV
282     int tid;
283 #endif
284
285 #ifndef AFS_NT40_ENV
286     (void)signal(SIGPIPE, SIG_IGN);
287 #endif
288
289 #ifdef AFS_PTHREAD_ENV
290     /* set our 'thread-id' so that the host hold table works */
291     MUTEX_ENTER(&rx_stats_mutex);       /* protects rxi_pthread_hinum */
292     tid = ++rxi_pthread_hinum;
293     MUTEX_EXIT(&rx_stats_mutex);
294     pthread_setspecific(rx_thread_id_key, (void *)tid);
295     Log("Set thread id %d for FSYNC_sync\n", tid);
296 #endif /* AFS_PTHREAD_ENV */
297
298     while (!VInit) {
299         /* Let somebody else run until level > 0.  That doesn't mean that 
300          * all volumes have been attached. */
301 #ifdef AFS_PTHREAD_ENV
302         pthread_yield();
303 #else /* AFS_PTHREAD_ENV */
304         LWP_DispatchProcess();
305 #endif /* AFS_PTHREAD_ENV */
306     }
307     AcceptSd = getport(&addr);
308     /* Reuseaddr needed because system inexplicably leaves crud lying around */
309     code =
310         setsockopt(AcceptSd, SOL_SOCKET, SO_REUSEADDR, (char *)&on,
311                    sizeof(on));
312     if (code)
313         Log("FSYNC_sync: setsockopt failed with (%d)\n", errno);
314
315     for (numTries = 0; numTries < MAX_BIND_TRIES; numTries++) {
316         if ((code =
317              bind(AcceptSd, (struct sockaddr *)&addr, sizeof(addr))) == 0)
318             break;
319         Log("FSYNC_sync: bind failed with (%d), will sleep and retry\n",
320             errno);
321         sleep(5);
322     }
323     assert(!code);
324     listen(AcceptSd, 100);
325     InitHandler();
326     AcceptOn();
327     for (;;) {
328         fd_set readfds;
329         int maxfd;
330         GetHandler(&readfds, &maxfd);
331         /* Note: check for >= 1 below is essential since IOMGR_select
332          * doesn't have exactly same semantics as select.
333          */
334 #ifdef AFS_PTHREAD_ENV
335         if (select(maxfd + 1, &readfds, NULL, NULL, NULL) >= 1)
336 #else /* AFS_PTHREAD_ENV */
337         if (IOMGR_Select(maxfd + 1, &readfds, NULL, NULL, NULL) >= 1)
338 #endif /* AFS_PTHREAD_ENV */
339             CallHandler(&readfds);
340     }
341 }
342
343 static void
344 FSYNC_newconnection(int afd)
345 {
346     struct sockaddr_in other;
347     int junk, fd;
348     junk = sizeof(other);
349     fd = accept(afd, (struct sockaddr *)&other, &junk);
350     if (fd == -1) {
351         Log("FSYNC_newconnection:  accept failed, errno==%d\n", errno);
352         assert(1 == 2);
353     } else if (!AddHandler(fd, FSYNC_com)) {
354         AcceptOff();
355         assert(AddHandler(fd, FSYNC_com));
356     }
357 }
358
359 /*
360 #define TEST2081
361 */
362
363 afs_int32 FS_cnt = 0;
364 static void
365 FSYNC_com(int fd)
366 {
367     byte rc = FSYNC_OK;
368     int n, i;
369     Error error;
370     struct command command;
371     int leaveonline;
372     register struct offlineInfo *volumes, *v;
373     Volume *vp;
374     char tvolName[VMAXPATHLEN];
375
376     FS_cnt++;
377 #ifdef AFS_NT40_ENV
378     n = recv(fd, &command, sizeof(command), 0);
379 #else
380     n = read(fd, &command, sizeof(command));
381 #endif
382     if (n <= 0) {
383         FSYNC_Drop(fd);
384         return;
385     }
386     if (n < sizeof(command)) {
387         Log("FSYNC_com:  partial read (%d instead of %d); dropping connection (cnt=%d)\n", n, sizeof(command), FS_cnt);
388         FSYNC_Drop(fd);
389         return;
390     }
391     VATTACH_LOCK VOL_LOCK volumes = OfflineVolumes[FindHandler(fd)];
392     for (v = 0, i = 0; i < MAXOFFLINEVOLUMES; i++) {
393         if (volumes[i].volumeID == command.volume
394             && strcmp(volumes[i].partName, command.partName) == 0) {
395             v = &volumes[i];
396             break;
397         }
398     }
399     switch (command.command) {
400     case FSYNC_DONE:
401         /* don't try to put online, this call is made only after deleting
402          * a volume, in which case we want to remove the vol # from the
403          * OfflineVolumes array only */
404         if (v)
405             v->volumeID = 0;
406         break;
407     case FSYNC_ON:
408
409 /*
410 This is where a detatched volume gets reattached. However in the
411 special case where the volume is merely busy, it is already
412 attatched and it is only necessary to clear the busy flag. See
413 defect #2080 for details.
414 */
415
416         /* is the volume already attatched? */
417 #ifdef  notdef
418 /*
419  * XXX With the following enabled we had bizarre problems where the backup id would
420  * be reset to 0; that was due to the interaction between fileserver/volserver in that they
421  * both keep volumes in memory and the changes wouldn't be made to the fileserver. Some of
422  * the problems were due to refcnt changes as result of VGetVolume/VPutVolume which would call
423  * VOffline, etc. when we don't want to; someday the whole #2080 issue should be revisited to
424  * be done right XXX
425  */
426         vp = VGetVolume_r(&error, command.volume);
427         if (vp) {
428             /* yep, is the BUSY flag set? */
429             if (vp->specialStatus == VBUSY) {
430 /* test harness for defect #2081 */
431
432 #ifdef TEST2081
433                 /*
434                  * test #2081 by releasing TEST.2081,
435                  * so leave it alone here, zap it after
436                  */
437
438                 if (strcmp(vp->header->diskstuff.name, "TEST.2081") == 0)
439                     break;
440 #endif
441                 /* yep, clear BUSY flag */
442
443                 vp->specialStatus = 0;
444                 /* make sure vol is online */
445                 if (v) {
446                     v->volumeID = 0;
447                     V_inUse(vp) = 1;    /* online */
448                 }
449                 VPutVolume_r(vp);
450                 break;
451             }
452             VPutVolume_r(vp);
453         }
454 #endif
455
456         /* so, we need to attach the volume */
457
458         if (v)
459             v->volumeID = 0;
460         tvolName[0] = '/';
461         sprintf(&tvolName[1], VFORMAT, command.volume);
462
463         vp = VAttachVolumeByName_r(&error, command.partName, tvolName,
464                                    V_VOLUPD);
465         if (vp)
466             VPutVolume_r(vp);
467         break;
468     case FSYNC_OFF:
469     case FSYNC_NEEDVOLUME:{
470             leaveonline = 0;
471             /* not already offline, we need to find a slot for newly offline volume */
472             if (!v) {
473                 for (i = 0; i < MAXOFFLINEVOLUMES; i++) {
474                     if (volumes[i].volumeID == 0) {
475                         v = &volumes[i];
476                         break;
477                     }
478                 }
479             }
480             if (!v) {
481                 rc = FSYNC_DENIED;
482                 break;
483             }
484             vp = VGetVolume_r(&error, command.volume);
485             if (vp) {
486                 if (command.partName[0] != 0
487                     && strcmp(command.partName, vp->partition->name) != 0) {
488                     /* volume on desired partition is not online, so we
489                      * should treat this as an offline volume.
490                      */
491                     VPutVolume_r(vp);
492                     vp = (Volume *) 0;
493                 }
494             }
495             if (vp) {
496                 leaveonline = (command.command == FSYNC_NEEDVOLUME
497                                && (command.reason == V_READONLY
498                                    || (!VolumeWriteable(vp)
499                                        && (command.reason == V_CLONE
500                                            || command.reason == V_DUMP))
501                                )
502                     );
503                 if (!leaveonline) {
504                     if (command.command == FSYNC_NEEDVOLUME
505                         && (command.reason == V_CLONE
506                             || command.reason == V_DUMP)) {
507                         vp->specialStatus = VBUSY;
508                     }
509                     /* remember what volume we got, so we can keep track of how
510                      * many volumes the volserver or whatever is using.  Note that
511                      * vp is valid since leaveonline is only set when vp is valid.
512                      */
513                     v->volumeID = command.volume;
514                     strcpy(v->partName, vp->partition->name);
515                     if (!V_inUse(vp)) {
516                         /* in this case, VOffline just returns sans decrementing
517                          * ref count.  We could try to fix it, but it has lots of
518                          * weird callers.
519                          */
520                         VPutVolume_r(vp);
521                     } else {
522                         VOffline_r(vp, "A volume utility is running.");
523                     }
524                     vp = 0;
525                 } else {
526                     VUpdateVolume_r(&error, vp);        /* At least get volume stats right */
527                     if (LogLevel) {
528                         Log("FSYNC: Volume %u (%s) was left on line for an external %s request\n", V_id(vp), V_name(vp), command.reason == V_CLONE ? "clone" : command.reason == V_READONLY ? "readonly" : command.reason == V_DUMP ? "dump" : "UNKNOWN");
529                     }
530                 }
531                 if (vp)
532                     VPutVolume_r(vp);
533             }
534             rc = FSYNC_OK;
535             break;
536         }
537     case FSYNC_MOVEVOLUME:
538         /* Yuch:  the "reason" for the move is the site it got moved to... */
539         /* still set specialStatus so we stop sending back VBUSY.
540          * also should still break callbacks.  Note that I don't know
541          * how to tell if we should break all or not, so we just do it
542          * since it doesn't matter much if we do an extra break
543          * volume callbacks on a volume move within the same server */
544         vp = VGetVolume_r(&error, command.volume);
545         if (vp) {
546             vp->specialStatus = VMOVED;
547             VPutVolume_r(vp);
548         }
549
550         if (V_BreakVolumeCallbacks) {
551             Log("fssync: volume %u moved to %x; breaking all call backs\n",
552                 command.volume, command.reason);
553             VOL_UNLOCK VATTACH_UNLOCK(*V_BreakVolumeCallbacks) (command.
554                                                                 volume);
555         VATTACH_LOCK VOL_LOCK}
556         break;
557     case FSYNC_RESTOREVOLUME:
558         /* if the volume is being restored, break all callbacks on it */
559         if (V_BreakVolumeCallbacks) {
560             Log("fssync: volume %u restored; breaking all call backs\n",
561                 command.volume);
562             VOL_UNLOCK VATTACH_UNLOCK(*V_BreakVolumeCallbacks) (command.
563                                                                 volume);
564         VATTACH_LOCK VOL_LOCK}
565         break;
566     default:
567         rc = FSYNC_DENIED;
568         break;
569     }
570     VOL_UNLOCK VATTACH_UNLOCK
571 #ifdef AFS_NT40_ENV
572       (void) send(fd, &rc, 1, 0);
573 #else
574       (void) write(fd, &rc, 1);
575 #endif
576 }
577
578 static void
579 FSYNC_Drop(int fd)
580 {
581     struct offlineInfo *p;
582     register i;
583     Error error;
584     char tvolName[VMAXPATHLEN];
585
586     VATTACH_LOCK VOL_LOCK p = OfflineVolumes[FindHandler(fd)];
587     for (i = 0; i < MAXOFFLINEVOLUMES; i++) {
588         if (p[i].volumeID) {
589             Volume *vp;
590
591             tvolName[0] = '/';
592             sprintf(&tvolName[1], VFORMAT, p[i].volumeID);
593             vp = VAttachVolumeByName_r(&error, p[i].partName, tvolName,
594                                        V_VOLUPD);
595             if (vp)
596                 VPutVolume_r(vp);
597             p[i].volumeID = 0;
598         }
599     }
600     VOL_UNLOCK VATTACH_UNLOCK RemoveHandler(fd);
601 #ifdef AFS_NT40_ENV
602     closesocket(fd);
603 #else
604     close(fd);
605 #endif
606     AcceptOn();
607 }
608
609 static int AcceptHandler = -1;  /* handler id for accept, if turned on */
610
611 static void
612 AcceptOn()
613 {
614     if (AcceptHandler == -1) {
615         assert(AddHandler(AcceptSd, FSYNC_newconnection));
616         AcceptHandler = FindHandler(AcceptSd);
617     }
618 }
619
620 static void
621 AcceptOff()
622 {
623     if (AcceptHandler != -1) {
624         assert(RemoveHandler(AcceptSd));
625         AcceptHandler = -1;
626     }
627 }
628
629 /* The multiple FD handling code. */
630
631 static int HandlerFD[MAXHANDLERS];
632 static int (*HandlerProc[MAXHANDLERS]) ();
633
634 static void
635 InitHandler()
636 {
637     register int i;
638     ObtainWriteLock(&FSYNC_handler_lock);
639     for (i = 0; i < MAXHANDLERS; i++) {
640         HandlerFD[i] = -1;
641         HandlerProc[i] = 0;
642     }
643     ReleaseWriteLock(&FSYNC_handler_lock);
644 }
645
646 static void
647 CallHandler(fd_set * fdsetp)
648 {
649     register int i;
650     ObtainReadLock(&FSYNC_handler_lock);
651     for (i = 0; i < MAXHANDLERS; i++) {
652         if (HandlerFD[i] >= 0 && FD_ISSET(HandlerFD[i], fdsetp)) {
653             ReleaseReadLock(&FSYNC_handler_lock);
654             (*HandlerProc[i]) (HandlerFD[i]);
655             ObtainReadLock(&FSYNC_handler_lock);
656         }
657     }
658     ReleaseReadLock(&FSYNC_handler_lock);
659 }
660
661 static int
662 AddHandler(int afd, int (*aproc) ())
663 {
664     register int i;
665     ObtainWriteLock(&FSYNC_handler_lock);
666     for (i = 0; i < MAXHANDLERS; i++)
667         if (HandlerFD[i] == -1)
668             break;
669     if (i >= MAXHANDLERS) {
670         ReleaseWriteLock(&FSYNC_handler_lock);
671         return 0;
672     }
673     HandlerFD[i] = afd;
674     HandlerProc[i] = aproc;
675     ReleaseWriteLock(&FSYNC_handler_lock);
676     return 1;
677 }
678
679 static int
680 FindHandler(register int afd)
681 {
682     register int i;
683     ObtainReadLock(&FSYNC_handler_lock);
684     for (i = 0; i < MAXHANDLERS; i++)
685         if (HandlerFD[i] == afd) {
686             ReleaseReadLock(&FSYNC_handler_lock);
687             return i;
688         }
689     ReleaseReadLock(&FSYNC_handler_lock);       /* just in case */
690     assert(1 == 2);
691     return -1;                  /* satisfy compiler */
692 }
693
694 static int
695 FindHandler_r(register int afd)
696 {
697     register int i;
698     for (i = 0; i < MAXHANDLERS; i++)
699         if (HandlerFD[i] == afd) {
700             return i;
701         }
702     assert(1 == 2);
703     return -1;                  /* satisfy compiler */
704 }
705
706 static int
707 RemoveHandler(register int afd)
708 {
709     ObtainWriteLock(&FSYNC_handler_lock);
710     HandlerFD[FindHandler_r(afd)] = -1;
711     ReleaseWriteLock(&FSYNC_handler_lock);
712     return 1;
713 }
714
715 static void
716 GetHandler(fd_set * fdsetp, int *maxfdp)
717 {
718     register int i;
719     register int maxfd = -1;
720     FD_ZERO(fdsetp);
721     ObtainReadLock(&FSYNC_handler_lock);        /* just in case */
722     for (i = 0; i < MAXHANDLERS; i++)
723         if (HandlerFD[i] != -1) {
724             FD_SET(HandlerFD[i], fdsetp);
725             if (maxfd < HandlerFD[i])
726                 maxfd = HandlerFD[i];
727         }
728     *maxfdp = maxfd;
729     ReleaseReadLock(&FSYNC_handler_lock);       /* just in case */
730 }