Spell 'weird' the way English expects it.
[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.7 2003/08/07 21:17:40 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 "use_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.h"
72 #include "cnode.h"
73 #include "coda_namecache.h"
74 #include "coda_io.h"
75 #include "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 #ifdef  CTL_C
87 int coda_pcatch = PCATCH;
88 #else
89 #endif
90
91 #define ENTRY if(coda_psdev_print_entry) myprintf(("Entered %s\n",__FUNCTION__))
92
93 void vcodaattach(int n);
94
95 struct vmsg {
96     struct queue vm_chain;
97     caddr_t      vm_data;
98     u_short      vm_flags;
99     u_short      vm_inSize;     /* Size is at most 5000 bytes */
100     u_short      vm_outSize;
101     u_short      vm_opcode;     /* copied from data to save ptr lookup */
102     int          vm_unique;
103     caddr_t      vm_sleep;      /* Not used by Mach. */
104 };
105
106 #define VM_READ     1
107 #define VM_WRITE    2
108 #define VM_INTR     4
109
110 /* vcodaattach: do nothing */
111 void
112 vcodaattach(n)
113     int n;
114 {
115 }
116
117 int 
118 vc_nb_open(dev_t dev, int flag, int mode, d_thread_t *td)
119 {
120     struct vcomm *vcp;
121     
122     ENTRY;
123
124     if (minor(dev) >= NVCODA || minor(dev) < 0)
125         return(ENXIO);
126     
127     if (!coda_nc_initialized)
128         coda_nc_init();
129     
130     vcp = &coda_mnttbl[minor(dev)].mi_vcomm;
131     if (VC_OPEN(vcp))
132         return(EBUSY);
133     
134     bzero(&(vcp->vc_selproc), sizeof (struct selinfo));
135     INIT_QUEUE(vcp->vc_requests);
136     INIT_QUEUE(vcp->vc_replys);
137     MARK_VC_OPEN(vcp);
138     
139     coda_mnttbl[minor(dev)].mi_vfsp = NULL;
140     coda_mnttbl[minor(dev)].mi_rootvp = NULL;
141
142     return(0);
143 }
144
145 int 
146 vc_nb_close (dev_t dev, int flag, int mode, d_thread_t *td)
147 {
148     struct vcomm *vcp;
149     struct vmsg *vmp, *nvmp = NULL;
150     struct coda_mntinfo *mi;
151     int                 err;
152         
153     ENTRY;
154
155     if (minor(dev) >= NVCODA || minor(dev) < 0)
156         return(ENXIO);
157
158     mi = &coda_mnttbl[minor(dev)];
159     vcp = &(mi->mi_vcomm);
160     
161     if (!VC_OPEN(vcp))
162         panic("vcclose: not open");
163     
164     /* prevent future operations on this vfs from succeeding by auto-
165      * unmounting any vfs mounted via this device. This frees user or
166      * sysadm from having to remember where all mount points are located.
167      * Put this before WAKEUPs to avoid queuing new messages between
168      * the WAKEUP and the unmount (which can happen if we're unlucky)
169      */
170     if (!mi->mi_rootvp) {
171         /* just a simple open/close w no mount */
172         MARK_VC_CLOSED(vcp);
173         return 0;
174     }
175
176     /* Let unmount know this is for real */
177     VTOC(mi->mi_rootvp)->c_flags |= C_UNMOUNTING;
178     coda_unmounting(mi->mi_vfsp);
179
180     outstanding_upcalls = 0;
181     /* Wakeup clients so they can return. */
182     for (vmp = (struct vmsg *)GETNEXT(vcp->vc_requests);
183          !EOQ(vmp, vcp->vc_requests);
184          vmp = nvmp)
185     {
186         nvmp = (struct vmsg *)GETNEXT(vmp->vm_chain);
187         /* Free signal request messages and don't wakeup cause
188            no one is waiting. */
189         if (vmp->vm_opcode == CODA_SIGNAL) {
190             CODA_FREE((caddr_t)vmp->vm_data, (u_int)VC_IN_NO_DATA);
191             CODA_FREE((caddr_t)vmp, (u_int)sizeof(struct vmsg));
192             continue;
193         }
194         outstanding_upcalls++;  
195         wakeup(&vmp->vm_sleep);
196     }
197
198     for (vmp = (struct vmsg *)GETNEXT(vcp->vc_replys);
199          !EOQ(vmp, vcp->vc_replys);
200          vmp = (struct vmsg *)GETNEXT(vmp->vm_chain))
201     {
202         outstanding_upcalls++;  
203         wakeup(&vmp->vm_sleep);
204     }
205
206     MARK_VC_CLOSED(vcp);
207
208     if (outstanding_upcalls) {
209 #ifdef  CODA_VERBOSE
210         printf("presleep: outstanding_upcalls = %d\n", outstanding_upcalls);
211         (void) tsleep(&outstanding_upcalls, 0, "coda_umount", 0);
212         printf("postsleep: outstanding_upcalls = %d\n", outstanding_upcalls);
213 #else
214         (void) tsleep(&outstanding_upcalls, 0, "coda_umount", 0);
215 #endif
216     }
217
218     err = dounmount(mi->mi_vfsp, flag, td);
219     if (err)
220         myprintf(("Error %d unmounting vfs in vcclose(%d)\n", 
221                    err, minor(dev)));
222     return 0;
223 }
224
225 int 
226 vc_nb_read(dev, uiop, flag)   
227     dev_t        dev;  
228     struct uio  *uiop; 
229     int          flag;
230 {
231     struct vcomm *      vcp;
232     struct vmsg *vmp;
233     int error = 0;
234     
235     ENTRY;
236
237     if (minor(dev) >= NVCODA || minor(dev) < 0)
238         return(ENXIO);
239     
240     vcp = &coda_mnttbl[minor(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     dev_t        dev;  
282     struct uio  *uiop; 
283     int          flag;
284 {
285     struct vcomm *      vcp;
286     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     if (minor(dev) >= NVCODA || minor(dev) < 0)
296         return(ENXIO);
297     
298     vcp = &coda_mnttbl[minor(dev)].mi_vcomm;
299     
300     /* Peek at the opcode, unique without transfering the data. */
301     uiop->uio_rw = UIO_WRITE;
302     error = uiomove((caddr_t)buf, sizeof(int) * 2, uiop);
303     if (error) {
304         myprintf(("vcwrite: error (%d) on uiomove\n", error));
305         return(EINVAL);
306     }
307     
308     opcode = buf[0];
309     seq = buf[1];
310         
311     if (codadebug)
312         myprintf(("vcwrite got a call for %ld.%ld\n", opcode, seq));
313     
314     if (DOWNCALL(opcode)) {
315         union outputArgs pbuf;
316         
317         /* get the rest of the data. */
318         uiop->uio_rw = UIO_WRITE;
319         error = uiomove((caddr_t)&pbuf.coda_purgeuser.oh.result, sizeof(pbuf) - (sizeof(int)*2), uiop);
320         if (error) {
321             myprintf(("vcwrite: error (%d) on uiomove (Op %ld seq %ld)\n", 
322                       error, opcode, seq));
323             return(EINVAL);
324             }
325         
326         return handleDownCall(opcode, &pbuf);
327     }
328     
329     /* Look for the message on the (waiting for) reply queue. */
330     for (vmp = (struct vmsg *)GETNEXT(vcp->vc_replys);
331          !EOQ(vmp, vcp->vc_replys);
332          vmp = (struct vmsg *)GETNEXT(vmp->vm_chain))
333     {
334         if (vmp->vm_unique == seq) break;
335     }
336     
337     if (EOQ(vmp, vcp->vc_replys)) {
338         if (codadebug)
339             myprintf(("vcwrite: msg (%ld, %ld) not found\n", opcode, seq));
340         
341         return(ESRCH);
342         }
343     
344     /* Remove the message from the reply queue */
345     REMQUE(vmp->vm_chain);
346     
347     /* move data into response buffer. */
348     out = (struct coda_out_hdr *)vmp->vm_data;
349     /* Don't need to copy opcode and uniquifier. */
350     
351     /* get the rest of the data. */
352     if (vmp->vm_outSize < uiop->uio_resid) {
353         myprintf(("vcwrite: more data than asked for (%d < %d)\n",
354                   vmp->vm_outSize, uiop->uio_resid));
355         wakeup(&vmp->vm_sleep);         /* Notify caller of the error. */
356         return(EINVAL);
357     } 
358     
359     buf[0] = uiop->uio_resid;   /* Save this value. */
360     uiop->uio_rw = UIO_WRITE;
361     error = uiomove((caddr_t) &out->result, vmp->vm_outSize - (sizeof(int) * 2), uiop);
362     if (error) {
363         myprintf(("vcwrite: error (%d) on uiomove (op %ld seq %ld)\n", 
364                   error, opcode, seq));
365         return(EINVAL);
366     }
367     
368     /* I don't think these are used, but just in case. */
369     /* XXX - aren't these two already correct? -bnoble */
370     out->opcode = opcode;
371     out->unique = seq;
372     vmp->vm_outSize     = buf[0];       /* Amount of data transferred? */
373     vmp->vm_flags |= VM_WRITE;
374     wakeup(&vmp->vm_sleep);
375     
376     return(0);
377 }
378
379 int
380 vc_nb_ioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, d_thread_t *td)
381 {
382     ENTRY;
383
384     switch(cmd) {
385     case CODARESIZE: {
386         struct coda_resize *data = (struct coda_resize *)addr;
387         return(coda_nc_resize(data->hashsize, data->heapsize, IS_DOWNCALL));
388         break;
389     }
390     case CODASTATS:
391         if (coda_nc_use) {
392             coda_nc_gather_stats();
393             return(0);
394         } else {
395             return(ENODEV);
396         }
397         break;
398     case CODAPRINT:
399         if (coda_nc_use) {
400             print_coda_nc();
401             return(0);
402         } else {
403             return(ENODEV);
404         }
405         break;
406     case CIOC_KERNEL_VERSION:
407         switch (*(u_int *)addr) {
408         case 0:
409                 *(u_int *)addr = coda_kernel_version;
410                 return 0;
411                 break;
412         case 1:
413         case 2:
414                 if (coda_kernel_version != *(u_int *)addr)
415                     return ENOENT;
416                 else
417                     return 0;
418         default:
419                 return ENOENT;
420         }
421         break;
422     default :
423         return(EINVAL);
424         break;
425     }
426 }
427
428 int
429 vc_nb_poll(dev_t dev, int events, d_thread_t *td)
430 {
431     struct vcomm *vcp;
432     int event_msk = 0;
433
434     ENTRY;
435     
436     if (minor(dev) >= NVCODA || minor(dev) < 0)
437         return(ENXIO);
438     
439     vcp = &coda_mnttbl[minor(dev)].mi_vcomm;
440     
441     event_msk = events & (POLLIN|POLLRDNORM);
442     if (!event_msk)
443         return(0);
444     
445     if (!EMPTY(vcp->vc_requests))
446         return(events & (POLLIN|POLLRDNORM));
447
448     selrecord(td, &(vcp->vc_selproc));
449     
450     return(0);
451 }
452
453 /*
454  * Statistics
455  */
456 struct coda_clstat coda_clstat;
457
458 /* 
459  * Key question: whether to sleep interuptably or uninteruptably when
460  * waiting for Venus.  The former seems better (cause you can ^C a
461  * job), but then GNU-EMACS completion breaks. Use tsleep with no
462  * timeout, and no longjmp happens. But, when sleeping
463  * "uninterruptibly", we don't get told if it returns abnormally
464  * (e.g. kill -9).  
465  */
466
467 int
468 coda_call(mntinfo, inSize, outSize, buffer) 
469      struct coda_mntinfo *mntinfo; int inSize; int *outSize; caddr_t buffer;
470 {
471         struct vcomm *vcp;
472         struct vmsg *vmp;
473         int error;
474 #ifdef  CTL_C
475         struct proc *p = curproc;
476         sigset_t psig_omask = p->p_sigmask;
477         sigset_t tempset;
478         int i;
479 #endif
480         if (mntinfo == NULL) {
481             /* Unlikely, but could be a race condition with a dying warden */
482             return ENODEV;
483         }
484
485         vcp = &(mntinfo->mi_vcomm);
486         
487         coda_clstat.ncalls++;
488         coda_clstat.reqs[((struct coda_in_hdr *)buffer)->opcode]++;
489
490         if (!VC_OPEN(vcp))
491             return(ENODEV);
492
493         CODA_ALLOC(vmp,struct vmsg *,sizeof(struct vmsg));
494         /* Format the request message. */
495         vmp->vm_data = buffer;
496         vmp->vm_flags = 0;
497         vmp->vm_inSize = inSize;
498         vmp->vm_outSize 
499             = *outSize ? *outSize : inSize; /* |buffer| >= inSize */
500         vmp->vm_opcode = ((struct coda_in_hdr *)buffer)->opcode;
501         vmp->vm_unique = ++vcp->vc_seq;
502         if (codadebug)
503             myprintf(("Doing a call for %d.%d\n", 
504                       vmp->vm_opcode, vmp->vm_unique));
505         
506         /* Fill in the common input args. */
507         ((struct coda_in_hdr *)buffer)->unique = vmp->vm_unique;
508
509         /* Append msg to request queue and poke Venus. */
510         INSQUE(vmp->vm_chain, vcp->vc_requests);
511         selwakeup(&(vcp->vc_selproc));
512
513         /* We can be interrupted while we wait for Venus to process
514          * our request.  If the interrupt occurs before Venus has read
515          * the request, we dequeue and return. If it occurs after the
516          * read but before the reply, we dequeue, send a signal
517          * message, and return. If it occurs after the reply we ignore
518          * it. In no case do we want to restart the syscall.  If it
519          * was interrupted by a venus shutdown (vcclose), return
520          * ENODEV.  */
521
522         /* Ignore return, We have to check anyway */
523 #ifdef  CTL_C
524         /* This is work in progress.  Setting coda_pcatch lets tsleep reawaken
525            on a ^c or ^z.  The problem is that emacs sets certain interrupts
526            as SA_RESTART.  This means that we should exit sleep handle the
527            "signal" and then go to sleep again.  Mostly this is done by letting
528            the syscall complete and be restarted.  We are not idempotent and 
529            can not do this.  A better solution is necessary.
530          */
531         i = 0;
532         do {
533                 error = tsleep(&vmp->vm_sleep, coda_pcatch, "coda_call", hz*2);
534                 if (error == 0)
535                         break;
536                 else if (error == EWOULDBLOCK) {
537 #ifdef  CODA_VERBOSE
538                         printf("coda_call: tsleep TIMEOUT %d sec\n", 2+2*i);
539 #endif
540                 }
541                 else {
542                         SIGEMPTYSET(tempset);
543                         SIGADDSET(tempset, SIGIO);
544                         if (SIGSETEQ(p->p_siglist, tempset)) {
545                                 SIGADDSET(p->p_sigmask, SIGIO);
546 #ifdef  CODA_VERBOSE
547                                 printf("coda_call: tsleep returns %d SIGIO, cnt %d\n",
548                                        error, i);
549 #endif
550                         } else {
551                                 SIGDELSET(tempset, SIGIO);
552                                 SIGADDSET(tempset, SIGALRM);
553                                 if (SIGSETEQ(p->p_siglist, tempset)) {
554                                         SIGADDSET(p->p_sigmask, SIGALRM);
555 #ifdef  CODA_VERBOSE
556                                         printf("coda_call: tsleep returns %d SIGALRM, cnt %d\n",
557                                                error, i);
558 #endif
559                                 }
560                                 else {
561                                         printf("coda_call: tsleep returns %d, cnt %d\n",
562                                                error, i);
563
564 #if notyet
565                                         tempset = p->p_siglist;
566                                         SIGSETNAND(tempset, p->p_sigmask);
567                                         printf("coda_call: siglist = %p, sigmask = %p, mask %p\n",
568                                                p->p_siglist, p->p_sigmask,
569                                                tempset);
570                                         break;
571                                         SIGSETOR(p->p_sigmask, p->p_siglist);
572                                         tempset = p->p_siglist;
573                                         SIGSETNAND(tempset, p->p_sigmask);
574                                         printf("coda_call: new mask, siglist = %p, sigmask = %p, mask %p\n",
575                                                p->p_siglist, p->p_sigmask,
576                                                tempset);
577 #endif
578                                 }
579                         }
580                 }
581         } while (error && i++ < 128 && VC_OPEN(vcp));
582         p->p_sigmask = psig_omask;
583 #else
584         (void) tsleep(&vmp->vm_sleep, 0, "coda_call", 0);
585 #endif
586         if (VC_OPEN(vcp)) {     /* Venus is still alive */
587         /* Op went through, interrupt or not... */
588             if (vmp->vm_flags & VM_WRITE) {
589                 error = 0;
590                 *outSize = vmp->vm_outSize;
591             }
592
593             else if (!(vmp->vm_flags & VM_READ)) { 
594                 /* Interrupted before venus read it. */
595 #ifdef  CODA_VERBOSE
596                 if (1)
597 #else
598                 if (codadebug)
599 #endif
600                     myprintf(("interrupted before read: op = %d.%d, flags = %x\n",
601                            vmp->vm_opcode, vmp->vm_unique, vmp->vm_flags));
602                 REMQUE(vmp->vm_chain);
603                 error = EINTR;
604             }
605             
606             else {      
607                 /* (!(vmp->vm_flags & VM_WRITE)) means interrupted after
608                    upcall started */
609                 /* Interrupted after start of upcall, send venus a signal */
610                 struct coda_in_hdr *dog;
611                 struct vmsg *svmp;
612                 
613 #ifdef  CODA_VERBOSE
614                 if (1)
615 #else
616                 if (codadebug)
617 #endif
618                     myprintf(("Sending Venus a signal: op = %d.%d, flags = %x\n",
619                            vmp->vm_opcode, vmp->vm_unique, vmp->vm_flags));
620                 
621                 REMQUE(vmp->vm_chain);
622                 error = EINTR;
623                 
624                 CODA_ALLOC(svmp, struct vmsg *, sizeof (struct vmsg));
625
626                 CODA_ALLOC((svmp->vm_data), char *, sizeof (struct coda_in_hdr));
627                 dog = (struct coda_in_hdr *)svmp->vm_data;
628                 
629                 svmp->vm_flags = 0;
630                 dog->opcode = svmp->vm_opcode = CODA_SIGNAL;
631                 dog->unique = svmp->vm_unique = vmp->vm_unique;
632                 svmp->vm_inSize = sizeof (struct coda_in_hdr);
633 /*??? rvb */    svmp->vm_outSize = sizeof (struct coda_in_hdr);
634                 
635                 if (codadebug)
636                     myprintf(("coda_call: enqueing signal msg (%d, %d)\n",
637                            svmp->vm_opcode, svmp->vm_unique));
638                 
639                 /* insert at head of queue! */
640                 INSQUE(svmp->vm_chain, vcp->vc_requests);
641                 selwakeup(&(vcp->vc_selproc));
642             }
643         }
644
645         else {  /* If venus died (!VC_OPEN(vcp)) */
646             if (codadebug)
647                 myprintf(("vcclose woke op %d.%d flags %d\n",
648                        vmp->vm_opcode, vmp->vm_unique, vmp->vm_flags));
649             
650                 error = ENODEV;
651         }
652
653         CODA_FREE(vmp, sizeof(struct vmsg));
654
655         if (outstanding_upcalls > 0 && (--outstanding_upcalls == 0))
656                 wakeup(&outstanding_upcalls);
657
658         if (!error)
659                 error = ((struct coda_out_hdr *)buffer)->result;
660         return(error);
661 }