Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.git] / sys / vfs / 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: src/sys/coda/coda_psdev.c,v 1.13 1999/09/29 15:03:46 marcel Exp $
31  * $DragonFly: src/sys/vfs/coda/Attic/coda_psdev.c,v 1.2 2003/06/17 04:28:19 dillon Exp $
32  * 
33  */
34
35 /* 
36  * Mach Operating System
37  * Copyright (c) 1989 Carnegie-Mellon University
38  * All rights reserved.  The CMU software License Agreement specifies
39  * the terms and conditions for use and redistribution.
40  */
41
42 /*
43  * This code was written for the Coda file system at Carnegie Mellon
44  * University.  Contributers include David Steere, James Kistler, and
45  * M. Satyanarayanan.  */
46
47 /* 
48  * These routines define the psuedo device for communication between
49  * Coda's Venus and Minicache in Mach 2.6. They used to be in cfs_subr.c, 
50  * but I moved them to make it easier to port the Minicache without 
51  * porting coda. -- DCS 10/12/94
52  */
53
54 /* These routines are the device entry points for Venus. */
55
56 extern int coda_nc_initialized;    /* Set if cache has been initialized */
57
58 #include <vcoda.h>
59
60 #include <sys/param.h>
61 #include <sys/systm.h>
62 #include <sys/kernel.h>
63 #include <sys/proc.h>
64 #include <sys/malloc.h>
65 #include <sys/mount.h>
66 #include <sys/file.h>
67 #include <sys/ioccom.h>
68 #include <sys/poll.h>
69 #include <sys/conf.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",__FUNCTION__))
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, p)    
120     dev_t        dev;      
121     int          flag;     
122     int          mode;     
123     struct proc *p;             /* 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, p)    
152     dev_t        dev;      
153     int          flag;     
154     int          mode;     
155     struct proc *p;
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, p);
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, p) 
390     dev_t         dev;       
391     u_long        cmd;       
392     caddr_t       addr;      
393     int           flag;      
394     struct proc  *p;
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, p)         
444     dev_t         dev;    
445     int           events;   
446     struct proc  *p;
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(p, &(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 proc *p = curproc;
493         sigset_t psig_omask = p->p_sigmask;
494         sigset_t tempset;
495         int i;
496 #endif
497         if (mntinfo == NULL) {
498             /* Unlikely, but could be a race condition with a dying warden */
499             return ENODEV;
500         }
501
502         vcp = &(mntinfo->mi_vcomm);
503         
504         coda_clstat.ncalls++;
505         coda_clstat.reqs[((struct coda_in_hdr *)buffer)->opcode]++;
506
507         if (!VC_OPEN(vcp))
508             return(ENODEV);
509
510         CODA_ALLOC(vmp,struct vmsg *,sizeof(struct vmsg));
511         /* Format the request message. */
512         vmp->vm_data = buffer;
513         vmp->vm_flags = 0;
514         vmp->vm_inSize = inSize;
515         vmp->vm_outSize 
516             = *outSize ? *outSize : inSize; /* |buffer| >= inSize */
517         vmp->vm_opcode = ((struct coda_in_hdr *)buffer)->opcode;
518         vmp->vm_unique = ++vcp->vc_seq;
519         if (codadebug)
520             myprintf(("Doing a call for %d.%d\n", 
521                       vmp->vm_opcode, vmp->vm_unique));
522         
523         /* Fill in the common input args. */
524         ((struct coda_in_hdr *)buffer)->unique = vmp->vm_unique;
525
526         /* Append msg to request queue and poke Venus. */
527         INSQUE(vmp->vm_chain, vcp->vc_requests);
528         selwakeup(&(vcp->vc_selproc));
529
530         /* We can be interrupted while we wait for Venus to process
531          * our request.  If the interrupt occurs before Venus has read
532          * the request, we dequeue and return. If it occurs after the
533          * read but before the reply, we dequeue, send a signal
534          * message, and return. If it occurs after the reply we ignore
535          * it. In no case do we want to restart the syscall.  If it
536          * was interrupted by a venus shutdown (vcclose), return
537          * ENODEV.  */
538
539         /* Ignore return, We have to check anyway */
540 #ifdef  CTL_C
541         /* This is work in progress.  Setting coda_pcatch lets tsleep reawaken
542            on a ^c or ^z.  The problem is that emacs sets certain interrupts
543            as SA_RESTART.  This means that we should exit sleep handle the
544            "signal" and then go to sleep again.  Mostly this is done by letting
545            the syscall complete and be restarted.  We are not idempotent and 
546            can not do this.  A better solution is necessary.
547          */
548         i = 0;
549         do {
550                 error = tsleep(&vmp->vm_sleep,
551                                (coda_call_sleep|coda_pcatch), "coda_call",
552                                hz*2);
553                 if (error == 0)
554                         break;
555                 else if (error == EWOULDBLOCK) {
556 #ifdef  CODA_VERBOSE
557                         printf("coda_call: tsleep TIMEOUT %d sec\n", 2+2*i);
558 #endif
559                 }
560                 else {
561                         SIGEMPTYSET(tempset);
562                         SIGADDSET(tempset, SIGIO);
563                         if (SIGSETEQ(p->p_siglist, tempset)) {
564                                 SIGADDSET(p->p_sigmask, SIGIO);
565 #ifdef  CODA_VERBOSE
566                                 printf("coda_call: tsleep returns %d SIGIO, cnt %d\n",
567                                        error, i);
568 #endif
569                         } else {
570                                 SIGDELSET(tempset, SIGIO);
571                                 SIGADDSET(tempset, SIGALRM);
572                                 if (SIGSETEQ(p->p_siglist, tempset)) {
573                                         SIGADDSET(p->p_sigmask, SIGALRM);
574 #ifdef  CODA_VERBOSE
575                                         printf("coda_call: tsleep returns %d SIGALRM, cnt %d\n",
576                                                error, i);
577 #endif
578                                 }
579                                 else {
580                                         printf("coda_call: tsleep returns %d, cnt %d\n",
581                                                error, i);
582
583 #if notyet
584                                         tempset = p->p_siglist;
585                                         SIGSETNAND(tempset, p->p_sigmask);
586                                         printf("coda_call: siglist = %p, sigmask = %p, mask %p\n",
587                                                p->p_siglist, p->p_sigmask,
588                                                tempset);
589                                         break;
590                                         SIGSETOR(p->p_sigmask, p->p_siglist);
591                                         tempset = p->p_siglist;
592                                         SIGSETNAND(tempset, p->p_sigmask);
593                                         printf("coda_call: new mask, siglist = %p, sigmask = %p, mask %p\n",
594                                                p->p_siglist, p->p_sigmask,
595                                                tempset);
596 #endif
597                                 }
598                         }
599                 }
600         } while (error && i++ < 128 && VC_OPEN(vcp));
601         p->p_sigmask = psig_omask;
602 #else
603         (void) tsleep(&vmp->vm_sleep, coda_call_sleep, "coda_call", 0);
604 #endif
605         if (VC_OPEN(vcp)) {     /* Venus is still alive */
606         /* Op went through, interrupt or not... */
607             if (vmp->vm_flags & VM_WRITE) {
608                 error = 0;
609                 *outSize = vmp->vm_outSize;
610             }
611
612             else if (!(vmp->vm_flags & VM_READ)) { 
613                 /* Interrupted before venus read it. */
614 #ifdef  CODA_VERBOSE
615                 if (1)
616 #else
617                 if (codadebug)
618 #endif
619                     myprintf(("interrupted before read: op = %d.%d, flags = %x\n",
620                            vmp->vm_opcode, vmp->vm_unique, vmp->vm_flags));
621                 REMQUE(vmp->vm_chain);
622                 error = EINTR;
623             }
624             
625             else {      
626                 /* (!(vmp->vm_flags & VM_WRITE)) means interrupted after
627                    upcall started */
628                 /* Interrupted after start of upcall, send venus a signal */
629                 struct coda_in_hdr *dog;
630                 struct vmsg *svmp;
631                 
632 #ifdef  CODA_VERBOSE
633                 if (1)
634 #else
635                 if (codadebug)
636 #endif
637                     myprintf(("Sending Venus a signal: op = %d.%d, flags = %x\n",
638                            vmp->vm_opcode, vmp->vm_unique, vmp->vm_flags));
639                 
640                 REMQUE(vmp->vm_chain);
641                 error = EINTR;
642                 
643                 CODA_ALLOC(svmp, struct vmsg *, sizeof (struct vmsg));
644
645                 CODA_ALLOC((svmp->vm_data), char *, sizeof (struct coda_in_hdr));
646                 dog = (struct coda_in_hdr *)svmp->vm_data;
647                 
648                 svmp->vm_flags = 0;
649                 dog->opcode = svmp->vm_opcode = CODA_SIGNAL;
650                 dog->unique = svmp->vm_unique = vmp->vm_unique;
651                 svmp->vm_inSize = sizeof (struct coda_in_hdr);
652 /*??? rvb */    svmp->vm_outSize = sizeof (struct coda_in_hdr);
653                 
654                 if (codadebug)
655                     myprintf(("coda_call: enqueing signal msg (%d, %d)\n",
656                            svmp->vm_opcode, svmp->vm_unique));
657                 
658                 /* insert at head of queue! */
659                 INSQUE(svmp->vm_chain, vcp->vc_requests);
660                 selwakeup(&(vcp->vc_selproc));
661             }
662         }
663
664         else {  /* If venus died (!VC_OPEN(vcp)) */
665             if (codadebug)
666                 myprintf(("vcclose woke op %d.%d flags %d\n",
667                        vmp->vm_opcode, vmp->vm_unique, vmp->vm_flags));
668             
669                 error = ENODEV;
670         }
671
672         CODA_FREE(vmp, sizeof(struct vmsg));
673
674         if (outstanding_upcalls > 0 && (--outstanding_upcalls == 0))
675                 wakeup(&outstanding_upcalls);
676
677         if (!error)
678                 error = ((struct coda_out_hdr *)buffer)->result;
679         return(error);
680 }