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