]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - 6/sys/coda/coda_psdev.c
merge fix for boot-time hang on centos' xen
[FreeBSD/FreeBSD.git] / 6 / sys / coda / coda_psdev.c
1 /*-
2  *             Coda: an Experimental Distributed File System
3  *                              Release 3.1
4  * 
5  *           Copyright (c) 1987-1998 Carnegie Mellon University
6  *                          All Rights Reserved
7  * 
8  * Permission  to  use, copy, modify and distribute this software and its
9  * documentation is hereby granted,  provided  that  both  the  copyright
10  * notice  and  this  permission  notice  appear  in  all  copies  of the
11  * software, derivative works or  modified  versions,  and  any  portions
12  * thereof, and that both notices appear in supporting documentation, and
13  * that credit is given to Carnegie Mellon University  in  all  documents
14  * and publicity pertaining to direct or indirect use of this code or its
15  * derivatives.
16  * 
17  * CODA IS AN EXPERIMENTAL SOFTWARE SYSTEM AND IS  KNOWN  TO  HAVE  BUGS,
18  * SOME  OF  WHICH MAY HAVE SERIOUS CONSEQUENCES.  CARNEGIE MELLON ALLOWS
19  * FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION.   CARNEGIE  MELLON
20  * DISCLAIMS  ANY  LIABILITY  OF  ANY  KIND  FOR  ANY  DAMAGES WHATSOEVER
21  * RESULTING DIRECTLY OR INDIRECTLY FROM THE USE OF THIS SOFTWARE  OR  OF
22  * ANY DERIVATIVE WORK.
23  * 
24  * Carnegie  Mellon  encourages  users  of  this  software  to return any
25  * improvements or extensions that  they  make,  and  to  grant  Carnegie
26  * Mellon the rights to redistribute these changes without encumbrance.
27  * 
28  *      @(#) src/sys/coda/coda_psdev.c,v 1.1.1.1 1998/08/29 21:14:52 rvb Exp $
29  */
30 /*-
31  * Mach Operating System
32  * Copyright (c) 1989 Carnegie-Mellon University
33  * All rights reserved.  The CMU software License Agreement specifies
34  * the terms and conditions for use and redistribution.
35  */
36
37 /*
38  * This code was written for the Coda filesystem at Carnegie Mellon
39  * University.  Contributers include David Steere, James Kistler, and
40  * M. Satyanarayanan.  */
41
42 /* 
43  * These routines define the psuedo device for communication between
44  * Coda's Venus and Minicache in Mach 2.6. They used to be in cfs_subr.c, 
45  * but I moved them to make it easier to port the Minicache without 
46  * porting coda. -- DCS 10/12/94
47  */
48
49 /* These routines are the device entry points for Venus. */
50
51 #include <sys/cdefs.h>
52 __FBSDID("$FreeBSD$");
53
54
55 extern int coda_nc_initialized;    /* Set if cache has been initialized */
56
57 #include <sys/param.h>
58 #include <sys/systm.h>
59 #include <sys/ioccom.h>
60 #include <sys/kernel.h>
61 #include <sys/lock.h>
62 #include <sys/malloc.h>
63 #include <sys/file.h>           /* must come after sys/malloc.h */
64 #include <sys/mount.h>
65 #include <sys/mutex.h>
66 #include <sys/poll.h>
67 #include <sys/proc.h>
68
69 #include <coda/coda.h>
70 #include <coda/cnode.h>
71 #include <coda/coda_namecache.h>
72 #include <coda/coda_io.h>
73 #include <coda/coda_psdev.h>
74
75 #define CTL_C
76
77 #ifdef CTL_C
78 #include <sys/signalvar.h>
79 #endif
80
81 int coda_psdev_print_entry = 0;
82 static
83 int outstanding_upcalls = 0;
84 int coda_call_sleep = PZERO - 1;
85 #ifdef  CTL_C
86 int coda_pcatch = PCATCH;
87 #else
88 #endif
89
90 #define ENTRY if(coda_psdev_print_entry) myprintf(("Entered %s\n",__func__))
91
92 void vcodaattach(int n);
93
94 struct vmsg {
95     struct queue vm_chain;
96     caddr_t      vm_data;
97     u_short      vm_flags;
98     u_short      vm_inSize;     /* Size is at most 5000 bytes */
99     u_short      vm_outSize;
100     u_short      vm_opcode;     /* copied from data to save ptr lookup */
101     int          vm_unique;
102     caddr_t      vm_sleep;      /* Not used by Mach. */
103 };
104
105 #define VM_READ     1
106 #define VM_WRITE    2
107 #define VM_INTR     4
108
109 /* vcodaattach: do nothing */
110 void
111 vcodaattach(n)
112     int n;
113 {
114 }
115
116 int 
117 vc_nb_open(dev, flag, mode, td)    
118     struct cdev *dev;      
119     int          flag;     
120     int          mode;     
121     struct thread *td;             /* NetBSD only */
122 {
123     struct vcomm *vcp;
124     struct coda_mntinfo *mnt;
125     
126     ENTRY;
127
128     if (!coda_nc_initialized)
129         coda_nc_init();
130     
131     mnt = dev2coda_mntinfo(dev);
132     vcp = &mnt->mi_vcomm;
133     if (VC_OPEN(vcp))
134         return(EBUSY);
135     
136     bzero(&(vcp->vc_selproc), sizeof (struct selinfo));
137     INIT_QUEUE(vcp->vc_requests);
138     INIT_QUEUE(vcp->vc_replys);
139     MARK_VC_OPEN(vcp);
140     
141     mnt->mi_vfsp = NULL;
142     mnt->mi_rootvp = NULL;
143
144     return(0);
145 }
146
147 int 
148 vc_nb_close (dev, flag, mode, td)    
149     struct cdev *dev;      
150     int          flag;     
151     int          mode;     
152     struct thread *td;
153 {
154     register struct vcomm *vcp;
155     register struct vmsg *vmp, *nvmp = NULL;
156     struct coda_mntinfo *mi;
157     int                 err;
158         
159     ENTRY;
160
161     mi = dev2coda_mntinfo(dev);
162     vcp = &(mi->mi_vcomm);
163     
164     if (!VC_OPEN(vcp))
165         panic("vcclose: not open");
166     
167     /* prevent future operations on this vfs from succeeding by auto-
168      * unmounting any vfs mounted via this device. This frees user or
169      * sysadm from having to remember where all mount points are located.
170      * Put this before WAKEUPs to avoid queuing new messages between
171      * the WAKEUP and the unmount (which can happen if we're unlucky)
172      */
173     if (!mi->mi_rootvp) {
174         /* just a simple open/close w no mount */
175         MARK_VC_CLOSED(vcp);
176         return 0;
177     }
178
179     /* Let unmount know this is for real */
180     VTOC(mi->mi_rootvp)->c_flags |= C_UNMOUNTING;
181     coda_unmounting(mi->mi_vfsp);
182
183     outstanding_upcalls = 0;
184     /* Wakeup clients so they can return. */
185     for (vmp = (struct vmsg *)GETNEXT(vcp->vc_requests);
186          !EOQ(vmp, vcp->vc_requests);
187          vmp = nvmp)
188     {
189         nvmp = (struct vmsg *)GETNEXT(vmp->vm_chain);
190         /* Free signal request messages and don't wakeup cause
191            no one is waiting. */
192         if (vmp->vm_opcode == CODA_SIGNAL) {
193             CODA_FREE((caddr_t)vmp->vm_data, (u_int)VC_IN_NO_DATA);
194             CODA_FREE((caddr_t)vmp, (u_int)sizeof(struct vmsg));
195             continue;
196         }
197         outstanding_upcalls++;  
198         wakeup(&vmp->vm_sleep);
199     }
200
201     for (vmp = (struct vmsg *)GETNEXT(vcp->vc_replys);
202          !EOQ(vmp, vcp->vc_replys);
203          vmp = (struct vmsg *)GETNEXT(vmp->vm_chain))
204     {
205         outstanding_upcalls++;  
206         wakeup(&vmp->vm_sleep);
207     }
208
209     MARK_VC_CLOSED(vcp);
210
211     if (outstanding_upcalls) {
212 #ifdef  CODA_VERBOSE
213         printf("presleep: outstanding_upcalls = %d\n", outstanding_upcalls);
214         (void) tsleep(&outstanding_upcalls, coda_call_sleep, "coda_umount", 0);
215         printf("postsleep: outstanding_upcalls = %d\n", outstanding_upcalls);
216 #else
217         (void) tsleep(&outstanding_upcalls, coda_call_sleep, "coda_umount", 0);
218 #endif
219     }
220
221     err = dounmount(mi->mi_vfsp, flag, td);
222     if (err)
223         myprintf(("Error %d unmounting vfs in vcclose(%s)\n", 
224                    err, devtoname(dev)));
225     return 0;
226 }
227
228 int 
229 vc_nb_read(dev, uiop, flag)   
230     struct cdev *dev;  
231     struct uio  *uiop; 
232     int          flag;
233 {
234     register struct vcomm *     vcp;
235     register struct vmsg *vmp;
236     int error = 0;
237     
238     ENTRY;
239
240     vcp = &dev2coda_mntinfo(dev)->mi_vcomm;
241     /* Get message at head of request queue. */
242     if (EMPTY(vcp->vc_requests))
243         return(0);      /* Nothing to read */
244     
245     vmp = (struct vmsg *)GETNEXT(vcp->vc_requests);
246     
247     /* Move the input args into userspace */
248     uiop->uio_rw = UIO_READ;
249     error = uiomove(vmp->vm_data, vmp->vm_inSize, uiop);
250     if (error) {
251         myprintf(("vcread: error (%d) on uiomove\n", error));
252         error = EINVAL;
253     }
254
255 #ifdef OLD_DIAGNOSTIC    
256     if (vmp->vm_chain.forw == 0 || vmp->vm_chain.back == 0)
257         panic("vc_nb_read: bad chain");
258 #endif
259
260     REMQUE(vmp->vm_chain);
261     
262     /* If request was a signal, free up the message and don't
263        enqueue it in the reply queue. */
264     if (vmp->vm_opcode == CODA_SIGNAL) {
265         if (codadebug)
266             myprintf(("vcread: signal msg (%d, %d)\n", 
267                       vmp->vm_opcode, vmp->vm_unique));
268         CODA_FREE((caddr_t)vmp->vm_data, (u_int)VC_IN_NO_DATA);
269         CODA_FREE((caddr_t)vmp, (u_int)sizeof(struct vmsg));
270         return(error);
271     }
272     
273     vmp->vm_flags |= VM_READ;
274     INSQUE(vmp->vm_chain, vcp->vc_replys);
275     
276     return(error);
277 }
278
279 int
280 vc_nb_write(dev, uiop, flag)   
281     struct cdev *dev;  
282     struct uio  *uiop; 
283     int          flag;
284 {
285     register struct vcomm *     vcp;
286     register struct vmsg *vmp;
287     struct coda_out_hdr *out;
288     u_long seq;
289     u_long opcode;
290     int buf[2];
291     int error = 0;
292
293     ENTRY;
294
295     vcp = &dev2coda_mntinfo(dev)->mi_vcomm;
296     
297     /* Peek at the opcode, unique without transfering the data. */
298     uiop->uio_rw = UIO_WRITE;
299     error = uiomove((caddr_t)buf, sizeof(int) * 2, uiop);
300     if (error) {
301         myprintf(("vcwrite: error (%d) on uiomove\n", error));
302         return(EINVAL);
303     }
304     
305     opcode = buf[0];
306     seq = buf[1];
307         
308     if (codadebug)
309         myprintf(("vcwrite got a call for %ld.%ld\n", opcode, seq));
310     
311     if (DOWNCALL(opcode)) {
312         union outputArgs pbuf;
313         
314         /* get the rest of the data. */
315         uiop->uio_rw = UIO_WRITE;
316         error = uiomove((caddr_t)&pbuf.coda_purgeuser.oh.result, sizeof(pbuf) - (sizeof(int)*2), uiop);
317         if (error) {
318             myprintf(("vcwrite: error (%d) on uiomove (Op %ld seq %ld)\n", 
319                       error, opcode, seq));
320             return(EINVAL);
321             }
322         
323         return handleDownCall(opcode, &pbuf);
324     }
325     
326     /* Look for the message on the (waiting for) reply queue. */
327     for (vmp = (struct vmsg *)GETNEXT(vcp->vc_replys);
328          !EOQ(vmp, vcp->vc_replys);
329          vmp = (struct vmsg *)GETNEXT(vmp->vm_chain))
330     {
331         if (vmp->vm_unique == seq) break;
332     }
333     
334     if (EOQ(vmp, vcp->vc_replys)) {
335         if (codadebug)
336             myprintf(("vcwrite: msg (%ld, %ld) not found\n", opcode, seq));
337         
338         return(ESRCH);
339         }
340     
341     /* Remove the message from the reply queue */
342     REMQUE(vmp->vm_chain);
343     
344     /* move data into response buffer. */
345     out = (struct coda_out_hdr *)vmp->vm_data;
346     /* Don't need to copy opcode and uniquifier. */
347     
348     /* get the rest of the data. */
349     if (vmp->vm_outSize < uiop->uio_resid) {
350         myprintf(("vcwrite: more data than asked for (%d < %d)\n",
351                   vmp->vm_outSize, uiop->uio_resid));
352         wakeup(&vmp->vm_sleep);         /* Notify caller of the error. */
353         return(EINVAL);
354     } 
355     
356     buf[0] = uiop->uio_resid;   /* Save this value. */
357     uiop->uio_rw = UIO_WRITE;
358     error = uiomove((caddr_t) &out->result, vmp->vm_outSize - (sizeof(int) * 2), uiop);
359     if (error) {
360         myprintf(("vcwrite: error (%d) on uiomove (op %ld seq %ld)\n", 
361                   error, opcode, seq));
362         return(EINVAL);
363     }
364     
365     /* I don't think these are used, but just in case. */
366     /* XXX - aren't these two already correct? -bnoble */
367     out->opcode = opcode;
368     out->unique = seq;
369     vmp->vm_outSize     = buf[0];       /* Amount of data transferred? */
370     vmp->vm_flags |= VM_WRITE;
371     wakeup(&vmp->vm_sleep);
372     
373     return(0);
374 }
375
376 int
377 vc_nb_ioctl(dev, cmd, addr, flag, td) 
378     struct cdev *dev;       
379     u_long        cmd;       
380     caddr_t       addr;      
381     int           flag;      
382     struct thread *td;
383 {
384     ENTRY;
385
386     switch(cmd) {
387     case CODARESIZE: {
388         struct coda_resize *data = (struct coda_resize *)addr;
389         return(coda_nc_resize(data->hashsize, data->heapsize, IS_DOWNCALL));
390         break;
391     }
392     case CODASTATS:
393         if (coda_nc_use) {
394             coda_nc_gather_stats();
395             return(0);
396         } else {
397             return(ENODEV);
398         }
399         break;
400     case CODAPRINT:
401         if (coda_nc_use) {
402             print_coda_nc();
403             return(0);
404         } else {
405             return(ENODEV);
406         }
407         break;
408     case CIOC_KERNEL_VERSION:
409         switch (*(u_int *)addr) {
410         case 0:
411                 *(u_int *)addr = coda_kernel_version;
412                 return 0;
413                 break;
414         case 1:
415         case 2:
416                 if (coda_kernel_version != *(u_int *)addr)
417                     return ENOENT;
418                 else
419                     return 0;
420         default:
421                 return ENOENT;
422         }
423         break;
424     default :
425         return(EINVAL);
426         break;
427     }
428 }
429
430 int
431 vc_nb_poll(dev, events, td)         
432     struct cdev *dev;    
433     int           events;   
434     struct thread *td;
435 {
436     register struct vcomm *vcp;
437     int event_msk = 0;
438
439     ENTRY;
440     
441     vcp = &dev2coda_mntinfo(dev)->mi_vcomm;
442     
443     event_msk = events & (POLLIN|POLLRDNORM);
444     if (!event_msk)
445         return(0);
446     
447     if (!EMPTY(vcp->vc_requests))
448         return(events & (POLLIN|POLLRDNORM));
449
450     selrecord(td, &(vcp->vc_selproc));
451     
452     return(0);
453 }
454
455 /*
456  * Statistics
457  */
458 struct coda_clstat coda_clstat;
459
460 /* 
461  * Key question: whether to sleep interuptably or uninteruptably when
462  * waiting for Venus.  The former seems better (cause you can ^C a
463  * job), but then GNU-EMACS completion breaks. Use tsleep with no
464  * timeout, and no longjmp happens. But, when sleeping
465  * "uninterruptibly", we don't get told if it returns abnormally
466  * (e.g. kill -9).  
467  */
468
469 int
470 coda_call(mntinfo, inSize, outSize, buffer) 
471      struct coda_mntinfo *mntinfo; int inSize; int *outSize; caddr_t buffer;
472 {
473         struct vcomm *vcp;
474         struct vmsg *vmp;
475         int error;
476 #ifdef  CTL_C
477         struct thread *td = curthread;
478         struct proc *p = td->td_proc;
479         sigset_t psig_omask;
480         sigset_t tempset;
481         int i;
482 #endif
483         if (mntinfo == NULL) {
484             /* Unlikely, but could be a race condition with a dying warden */
485             return ENODEV;
486         }
487
488         vcp = &(mntinfo->mi_vcomm);
489         
490         coda_clstat.ncalls++;
491         coda_clstat.reqs[((struct coda_in_hdr *)buffer)->opcode]++;
492
493         if (!VC_OPEN(vcp))
494             return(ENODEV);
495
496         CODA_ALLOC(vmp,struct vmsg *,sizeof(struct vmsg));
497         /* Format the request message. */
498         vmp->vm_data = buffer;
499         vmp->vm_flags = 0;
500         vmp->vm_inSize = inSize;
501         vmp->vm_outSize 
502             = *outSize ? *outSize : inSize; /* |buffer| >= inSize */
503         vmp->vm_opcode = ((struct coda_in_hdr *)buffer)->opcode;
504         vmp->vm_unique = ++vcp->vc_seq;
505         if (codadebug)
506             myprintf(("Doing a call for %d.%d\n", 
507                       vmp->vm_opcode, vmp->vm_unique));
508         
509         /* Fill in the common input args. */
510         ((struct coda_in_hdr *)buffer)->unique = vmp->vm_unique;
511
512         /* Append msg to request queue and poke Venus. */
513         INSQUE(vmp->vm_chain, vcp->vc_requests);
514         selwakeuppri(&(vcp->vc_selproc), coda_call_sleep);
515
516         /* We can be interrupted while we wait for Venus to process
517          * our request.  If the interrupt occurs before Venus has read
518          * the request, we dequeue and return. If it occurs after the
519          * read but before the reply, we dequeue, send a signal
520          * message, and return. If it occurs after the reply we ignore
521          * it. In no case do we want to restart the syscall.  If it
522          * was interrupted by a venus shutdown (vcclose), return
523          * ENODEV.  */
524
525         /* Ignore return, We have to check anyway */
526 #ifdef  CTL_C
527         /* This is work in progress.  Setting coda_pcatch lets tsleep reawaken
528            on a ^c or ^z.  The problem is that emacs sets certain interrupts
529            as SA_RESTART.  This means that we should exit sleep handle the
530            "signal" and then go to sleep again.  Mostly this is done by letting
531            the syscall complete and be restarted.  We are not idempotent and 
532            can not do this.  A better solution is necessary.
533          */
534         i = 0;
535         PROC_LOCK(p);
536         psig_omask = td->td_sigmask;
537         do {
538                 error = msleep(&vmp->vm_sleep, &p->p_mtx,
539                                (coda_call_sleep|coda_pcatch), "coda_call",
540                                hz*2);
541                 if (error == 0)
542                         break;
543                 else if (error == EWOULDBLOCK) {
544 #ifdef  CODA_VERBOSE
545                         printf("coda_call: tsleep TIMEOUT %d sec\n", 2+2*i);
546 #endif
547                 }
548                 else {
549                         SIGEMPTYSET(tempset);
550                         SIGADDSET(tempset, SIGIO);
551                         if (SIGSETEQ(td->td_siglist, tempset)) {
552                                 SIGADDSET(td->td_sigmask, SIGIO);
553 #ifdef  CODA_VERBOSE
554                                 printf("coda_call: tsleep returns %d SIGIO, cnt %d\n",
555                                        error, i);
556 #endif
557                         } else {
558                                 SIGDELSET(tempset, SIGIO);
559                                 SIGADDSET(tempset, SIGALRM);
560                                 if (SIGSETEQ(td->td_siglist, tempset)) {
561                                         SIGADDSET(td->td_sigmask, SIGALRM);
562 #ifdef  CODA_VERBOSE
563                                         printf("coda_call: tsleep returns %d SIGALRM, cnt %d\n",
564                                                error, i);
565 #endif
566                                 }
567                                 else {
568 #ifdef  CODA_VERBOSE
569                                         printf("coda_call: tsleep returns %d, cnt %d\n",
570                                                error, i);
571 #endif
572
573 #if notyet
574                                         tempset = td->td_siglist;
575                                         SIGSETNAND(tempset, td->td_sigmask);
576                                         printf("coda_call: siglist = %p, sigmask = %p, mask %p\n",
577                                                td->td_siglist, td->td_sigmask,
578                                                tempset);
579                                         break;
580                                         SIGSETOR(td->td_sigmask, td->td_siglist);
581                                         tempset = td->td_siglist;
582                                         SIGSETNAND(tempset, td->td_sigmask);
583                                         printf("coda_call: new mask, siglist = %p, sigmask = %p, mask %p\n",
584                                                td->td_siglist, td->td_sigmask,
585                                                tempset);
586 #endif
587                                 }
588                         }
589                 }
590         } while (error && i++ < 128 && VC_OPEN(vcp));
591         td->td_sigmask = psig_omask;
592         signotify(td);
593         PROC_UNLOCK(p);
594 #else
595         (void) tsleep(&vmp->vm_sleep, coda_call_sleep, "coda_call", 0);
596 #endif
597         if (VC_OPEN(vcp)) {     /* Venus is still alive */
598         /* Op went through, interrupt or not... */
599             if (vmp->vm_flags & VM_WRITE) {
600                 error = 0;
601                 *outSize = vmp->vm_outSize;
602             }
603
604             else if (!(vmp->vm_flags & VM_READ)) { 
605                 /* Interrupted before venus read it. */
606 #ifdef  CODA_VERBOSE
607                 if (1)
608 #else
609                 if (codadebug)
610 #endif
611                     myprintf(("interrupted before read: op = %d.%d, flags = %x\n",
612                            vmp->vm_opcode, vmp->vm_unique, vmp->vm_flags));
613                 REMQUE(vmp->vm_chain);
614                 error = EINTR;
615             }
616             
617             else {      
618                 /* (!(vmp->vm_flags & VM_WRITE)) means interrupted after
619                    upcall started */
620                 /* Interrupted after start of upcall, send venus a signal */
621                 struct coda_in_hdr *dog;
622                 struct vmsg *svmp;
623                 
624 #ifdef  CODA_VERBOSE
625                 if (1)
626 #else
627                 if (codadebug)
628 #endif
629                     myprintf(("Sending Venus a signal: op = %d.%d, flags = %x\n",
630                            vmp->vm_opcode, vmp->vm_unique, vmp->vm_flags));
631                 
632                 REMQUE(vmp->vm_chain);
633                 error = EINTR;
634                 
635                 CODA_ALLOC(svmp, struct vmsg *, sizeof (struct vmsg));
636
637                 CODA_ALLOC((svmp->vm_data), char *, sizeof (struct coda_in_hdr));
638                 dog = (struct coda_in_hdr *)svmp->vm_data;
639                 
640                 svmp->vm_flags = 0;
641                 dog->opcode = svmp->vm_opcode = CODA_SIGNAL;
642                 dog->unique = svmp->vm_unique = vmp->vm_unique;
643                 svmp->vm_inSize = sizeof (struct coda_in_hdr);
644 /*??? rvb */    svmp->vm_outSize = sizeof (struct coda_in_hdr);
645                 
646                 if (codadebug)
647                     myprintf(("coda_call: enqueing signal msg (%d, %d)\n",
648                            svmp->vm_opcode, svmp->vm_unique));
649                 
650                 /* insert at head of queue! */
651                 INSQUE(svmp->vm_chain, vcp->vc_requests);
652                 selwakeuppri(&(vcp->vc_selproc), coda_call_sleep);
653             }
654         }
655
656         else {  /* If venus died (!VC_OPEN(vcp)) */
657             if (codadebug)
658                 myprintf(("vcclose woke op %d.%d flags %d\n",
659                        vmp->vm_opcode, vmp->vm_unique, vmp->vm_flags));
660             
661                 error = ENODEV;
662         }
663
664         CODA_FREE(vmp, sizeof(struct vmsg));
665
666         if (outstanding_upcalls > 0 && (--outstanding_upcalls == 0))
667                 wakeup(&outstanding_upcalls);
668
669         if (!error)
670                 error = ((struct coda_out_hdr *)buffer)->result;
671         return(error);
672 }