]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/ufs/ufs/ufs_readwrite.c
This commit was generated by cvs2svn to compensate for changes in r53657,
[FreeBSD/FreeBSD.git] / sys / ufs / ufs / ufs_readwrite.c
1 /*-
2  * Copyright (c) 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  *      @(#)ufs_readwrite.c     8.11 (Berkeley) 5/8/95
34  * $FreeBSD$
35  */
36
37 #define BLKSIZE(a, b, c)        blksize(a, b, c)
38 #define FS                      struct fs
39 #define I_FS                    i_fs
40 #define READ                    ffs_read
41 #define READ_S                  "ffs_read"
42 #define WRITE                   ffs_write
43 #define WRITE_S                 "ffs_write"
44
45 #include <vm/vm.h>
46 #include <vm/vm_object.h>
47 #include <vm/vm_pager.h>
48 #include <vm/vm_map.h>
49 #include <vm/vnode_pager.h>
50 #include <sys/poll.h>
51
52 /*
53  * Vnode op for reading.
54  */
55 /* ARGSUSED */
56 int
57 READ(ap)
58         struct vop_read_args /* {
59                 struct vnode *a_vp;
60                 struct uio *a_uio;
61                 int a_ioflag;
62                 struct ucred *a_cred;
63         } */ *ap;
64 {
65         register struct vnode *vp;
66         register struct inode *ip;
67         register struct uio *uio;
68         register FS *fs;
69         struct buf *bp;
70         ufs_daddr_t lbn, nextlbn;
71         off_t bytesinfile;
72         long size, xfersize, blkoffset;
73         int error, orig_resid;
74         u_short mode;
75         int seqcount;
76         int ioflag;
77         vm_object_t object;
78
79         vp = ap->a_vp;
80         seqcount = ap->a_ioflag >> 16;
81         ip = VTOI(vp);
82         mode = ip->i_mode;
83         uio = ap->a_uio;
84         ioflag = ap->a_ioflag;
85
86 #ifdef DIAGNOSTIC
87         if (uio->uio_rw != UIO_READ)
88                 panic("%s: mode", READ_S);
89
90         if (vp->v_type == VLNK) {
91                 if ((int)ip->i_size < vp->v_mount->mnt_maxsymlinklen)
92                         panic("%s: short symlink", READ_S);
93         } else if (vp->v_type != VREG && vp->v_type != VDIR)
94                 panic("%s: type %d", READ_S, vp->v_type);
95 #endif
96         fs = ip->I_FS;
97         if ((u_int64_t)uio->uio_offset > fs->fs_maxfilesize)
98                 return (EFBIG);
99
100         orig_resid = uio->uio_resid;
101         if (orig_resid <= 0)
102                 return (0);
103
104         object = vp->v_object;
105
106         bytesinfile = ip->i_size - uio->uio_offset;
107         if (bytesinfile <= 0) {
108                 if ((vp->v_mount->mnt_flag & MNT_NOATIME) == 0)
109                         ip->i_flag |= IN_ACCESS;
110                 return 0;
111         }
112
113         if (object)
114                 vm_object_reference(object);
115
116 #ifdef ENABLE_VFS_IOOPT
117         /*
118          * If IO optimisation is turned on,
119          * and we are NOT a VM based IO request, 
120          * (i.e. not headed for the buffer cache)
121          * but there IS a vm object associated with it.
122          */
123         if ((ioflag & IO_VMIO) == 0 && (vfs_ioopt > 1) && object) {
124                 int nread, toread;
125
126                 toread = uio->uio_resid;
127                 if (toread > bytesinfile)
128                         toread = bytesinfile;
129                 if (toread >= PAGE_SIZE) {
130                         /*
131                          * Then if it's at least a page in size, try 
132                          * get the data from the object using vm tricks
133                          */
134                         error = uioread(toread, uio, object, &nread);
135                         if ((uio->uio_resid == 0) || (error != 0)) {
136                                 /*
137                                  * If we finished or there was an error
138                                  * then finish up (the reference previously
139                                  * obtained on object must be released).
140                                  */
141                                 if ((error == 0 ||
142                                     uio->uio_resid != orig_resid) &&
143                                     (vp->v_mount->mnt_flag & MNT_NOATIME) == 0)
144                                         ip->i_flag |= IN_ACCESS;
145
146                                 if (object)
147                                         vm_object_vndeallocate(object);
148                                 return error;
149                         }
150                 }
151         }
152 #endif
153
154         /*
155          * Ok so we couldn't do it all in one vm trick...
156          * so cycle around trying smaller bites..
157          */
158         for (error = 0, bp = NULL; uio->uio_resid > 0; bp = NULL) {
159                 if ((bytesinfile = ip->i_size - uio->uio_offset) <= 0)
160                         break;
161 #ifdef ENABLE_VFS_IOOPT
162                 if ((ioflag & IO_VMIO) == 0 && (vfs_ioopt > 1) && object) {
163                         /*
164                          * Obviously we didn't finish above, but we
165                          * didn't get an error either. Try the same trick again.
166                          * but this time we are looping.
167                          */
168                         int nread, toread;
169                         toread = uio->uio_resid;
170                         if (toread > bytesinfile)
171                                 toread = bytesinfile;
172
173                         /*
174                          * Once again, if there isn't enough for a
175                          * whole page, don't try optimising.
176                          */
177                         if (toread >= PAGE_SIZE) {
178                                 error = uioread(toread, uio, object, &nread);
179                                 if ((uio->uio_resid == 0) || (error != 0)) {
180                                         /*
181                                          * If we finished or there was an 
182                                          * error then finish up (the reference
183                                          * previously obtained on object must 
184                                          * be released).
185                                          */
186                                         if ((error == 0 ||
187                                             uio->uio_resid != orig_resid) &&
188                                             (vp->v_mount->mnt_flag &
189                                             MNT_NOATIME) == 0)
190                                                 ip->i_flag |= IN_ACCESS;
191                                         if (object)
192                                                 vm_object_vndeallocate(object);
193                                         return error;
194                                 }
195                                 /*
196                                  * To get here we didnt't finish or err.
197                                  * If we did get some data,
198                                  * loop to try another bite.
199                                  */
200                                 if (nread > 0) {
201                                         continue;
202                                 }
203                         }
204                 }
205 #endif
206
207                 lbn = lblkno(fs, uio->uio_offset);
208                 nextlbn = lbn + 1;
209
210                 /*
211                  * size of buffer.  The buffer representing the
212                  * end of the file is rounded up to the size of
213                  * the block type ( fragment or full block, 
214                  * depending ).
215                  */
216                 size = BLKSIZE(fs, ip, lbn);
217                 blkoffset = blkoff(fs, uio->uio_offset);
218                 
219                 /*
220                  * The amount we want to transfer in this iteration is
221                  * one FS block less the amount of the data before
222                  * our startpoint (duh!)
223                  */
224                 xfersize = fs->fs_bsize - blkoffset;
225
226                 /*
227                  * But if we actually want less than the block,
228                  * or the file doesn't have a whole block more of data,
229                  * then use the lesser number.
230                  */
231                 if (uio->uio_resid < xfersize)
232                         xfersize = uio->uio_resid;
233                 if (bytesinfile < xfersize)
234                         xfersize = bytesinfile;
235
236                 if (lblktosize(fs, nextlbn) >= ip->i_size)
237                         /*
238                          * Don't do readahead if this is the end of the file.
239                          */
240                         error = bread(vp, lbn, size, NOCRED, &bp);
241                 else if ((vp->v_mount->mnt_flag & MNT_NOCLUSTERR) == 0)
242                         /* 
243                          * Otherwise if we are allowed to cluster,
244                          * grab as much as we can.
245                          *
246                          * XXX  This may not be a win if we are not
247                          * doing sequential access.
248                          */
249                         error = cluster_read(vp, ip->i_size, lbn,
250                                 size, NOCRED, uio->uio_resid, seqcount, &bp);
251                 else if (seqcount > 1) {
252                         /*
253                          * If we are NOT allowed to cluster, then
254                          * if we appear to be acting sequentially,
255                          * fire off a request for a readahead
256                          * as well as a read. Note that the 4th and 5th
257                          * arguments point to arrays of the size specified in
258                          * the 6th argument.
259                          */
260                         int nextsize = BLKSIZE(fs, ip, nextlbn);
261                         error = breadn(vp, lbn,
262                             size, &nextlbn, &nextsize, 1, NOCRED, &bp);
263                 } else
264                         /*
265                          * Failing all of the above, just read what the 
266                          * user asked for. Interestingly, the same as
267                          * the first option above.
268                          */
269                         error = bread(vp, lbn, size, NOCRED, &bp);
270                 if (error) {
271                         brelse(bp);
272                         bp = NULL;
273                         break;
274                 }
275
276                 /*
277                  * We should only get non-zero b_resid when an I/O error
278                  * has occurred, which should cause us to break above.
279                  * However, if the short read did not cause an error,
280                  * then we want to ensure that we do not uiomove bad
281                  * or uninitialized data.
282                  */
283                 size -= bp->b_resid;
284                 if (size < xfersize) {
285                         if (size == 0)
286                                 break;
287                         xfersize = size;
288                 }
289
290 #ifdef ENABLE_VFS_IOOPT
291                 if (vfs_ioopt && object &&
292                     (bp->b_flags & B_VMIO) &&
293                     ((blkoffset & PAGE_MASK) == 0) &&
294                     ((xfersize & PAGE_MASK) == 0)) {
295                         /*
296                          * If VFS IO  optimisation is turned on,
297                          * and it's an exact page multiple
298                          * And a normal VM based op,
299                          * then use uiomiveco()
300                          */
301                         error =
302                                 uiomoveco((char *)bp->b_data + blkoffset,
303                                         (int)xfersize, uio, object);
304                 } else 
305 #endif
306                 {
307                         /*
308                          * otherwise use the general form
309                          */
310                         error =
311                                 uiomove((char *)bp->b_data + blkoffset,
312                                         (int)xfersize, uio);
313                 }
314
315                 if (error)
316                         break;
317
318                 if ((ioflag & IO_VMIO) &&
319                    (LIST_FIRST(&bp->b_dep) == NULL)) {
320                         /*
321                          * If there are no dependencies, and
322                          * it's VMIO, then we don't need the buf,
323                          * mark it available for freeing. The VM has the data.
324                          */
325                         bp->b_flags |= B_RELBUF;
326                         brelse(bp);
327                 } else {
328                         /*
329                          * Otherwise let whoever
330                          * made the request take care of
331                          * freeing it. We just queue
332                          * it onto another list.
333                          */
334                         bqrelse(bp);
335                 }
336         }
337
338         /* 
339          * This can only happen in the case of an error
340          * because the loop above resets bp to NULL on each iteration
341          * and on normal completion has not set a new value into it.
342          * so it must have come from a 'break' statement
343          */
344         if (bp != NULL) {
345                 if ((ioflag & IO_VMIO) &&
346                    (LIST_FIRST(&bp->b_dep) == NULL)) {
347                         bp->b_flags |= B_RELBUF;
348                         brelse(bp);
349                 } else {
350                         bqrelse(bp);
351                 }
352         }
353
354         if (object)
355                 vm_object_vndeallocate(object);
356         if ((error == 0 || uio->uio_resid != orig_resid) &&
357             (vp->v_mount->mnt_flag & MNT_NOATIME) == 0)
358                 ip->i_flag |= IN_ACCESS;
359         return (error);
360 }
361
362 /*
363  * Vnode op for writing.
364  */
365 int
366 WRITE(ap)
367         struct vop_write_args /* {
368                 struct vnode *a_vp;
369                 struct uio *a_uio;
370                 int a_ioflag;
371                 struct ucred *a_cred;
372         } */ *ap;
373 {
374         register struct vnode *vp;
375         register struct uio *uio;
376         register struct inode *ip;
377         register FS *fs;
378         struct buf *bp;
379         struct proc *p;
380         ufs_daddr_t lbn;
381         off_t osize;
382         int blkoffset, error, extended, flags, ioflag, resid, size, xfersize;
383         vm_object_t object;
384
385         extended = 0;
386         ioflag = ap->a_ioflag;
387         uio = ap->a_uio;
388         vp = ap->a_vp;
389         ip = VTOI(vp);
390
391         object = vp->v_object;
392         if (object)
393                 vm_object_reference(object);
394
395 #ifdef DIAGNOSTIC
396         if (uio->uio_rw != UIO_WRITE)
397                 panic("%s: mode", WRITE_S);
398 #endif
399
400         switch (vp->v_type) {
401         case VREG:
402                 if (ioflag & IO_APPEND)
403                         uio->uio_offset = ip->i_size;
404                 if ((ip->i_flags & APPEND) && uio->uio_offset != ip->i_size) {
405                         if (object)
406                                 vm_object_vndeallocate(object);
407                         return (EPERM);
408                 }
409                 /* FALLTHROUGH */
410         case VLNK:
411                 break;
412         case VDIR:
413                 panic("%s: dir write", WRITE_S);
414                 break;
415         default:
416                 panic("%s: type %p %d (%d,%d)", WRITE_S, vp, (int)vp->v_type,
417                         (int)uio->uio_offset,
418                         (int)uio->uio_resid
419                 );
420         }
421
422         fs = ip->I_FS;
423         if (uio->uio_offset < 0 ||
424             (u_int64_t)uio->uio_offset + uio->uio_resid > fs->fs_maxfilesize) {
425                 if (object)
426                         vm_object_vndeallocate(object);
427                 return (EFBIG);
428         }
429         /*
430          * Maybe this should be above the vnode op call, but so long as
431          * file servers have no limits, I don't think it matters.
432          */
433         p = uio->uio_procp;
434         if (vp->v_type == VREG && p &&
435             uio->uio_offset + uio->uio_resid >
436             p->p_rlimit[RLIMIT_FSIZE].rlim_cur) {
437                 psignal(p, SIGXFSZ);
438                 if (object)
439                         vm_object_vndeallocate(object);
440                 return (EFBIG);
441         }
442
443         resid = uio->uio_resid;
444         osize = ip->i_size;
445         flags = 0;
446         if ((ioflag & IO_SYNC) && !DOINGASYNC(vp))
447                 flags = B_SYNC;
448
449         if (object && (object->flags & OBJ_OPT)) {
450                 vm_freeze_copyopts(object,
451                         OFF_TO_IDX(uio->uio_offset),
452                         OFF_TO_IDX(uio->uio_offset + uio->uio_resid + PAGE_MASK));
453         }
454
455         for (error = 0; uio->uio_resid > 0;) {
456                 lbn = lblkno(fs, uio->uio_offset);
457                 blkoffset = blkoff(fs, uio->uio_offset);
458                 xfersize = fs->fs_bsize - blkoffset;
459                 if (uio->uio_resid < xfersize)
460                         xfersize = uio->uio_resid;
461
462                 if (uio->uio_offset + xfersize > ip->i_size)
463                         vnode_pager_setsize(vp, uio->uio_offset + xfersize);
464
465                 if (fs->fs_bsize > xfersize)
466                         flags |= B_CLRBUF;
467                 else
468                         flags &= ~B_CLRBUF;
469 /* XXX is uio->uio_offset the right thing here? */
470                 error = VOP_BALLOC(vp, uio->uio_offset, xfersize,
471                     ap->a_cred, flags, &bp);
472                 if (error != 0)
473                         break;
474
475                 if (uio->uio_offset + xfersize > ip->i_size) {
476                         ip->i_size = uio->uio_offset + xfersize;
477                         extended = 1;
478                 }
479
480                 size = BLKSIZE(fs, ip, lbn) - bp->b_resid;
481                 if (size < xfersize)
482                         xfersize = size;
483
484                 error =
485                     uiomove((char *)bp->b_data + blkoffset, (int)xfersize, uio);
486                 if ((ioflag & IO_VMIO) &&
487                    (LIST_FIRST(&bp->b_dep) == NULL))
488                         bp->b_flags |= B_RELBUF;
489
490                 if (ioflag & IO_SYNC) {
491                         (void)bwrite(bp);
492                 } else if (xfersize + blkoffset == fs->fs_bsize) {
493                         if ((vp->v_mount->mnt_flag & MNT_NOCLUSTERW) == 0) {
494                                 bp->b_flags |= B_CLUSTEROK;
495                                 cluster_write(bp, ip->i_size);
496                         } else {
497                                 bawrite(bp);
498                         }
499                 } else {
500                         bp->b_flags |= B_CLUSTEROK;
501                         bdwrite(bp);
502                 }
503                 if (error || xfersize == 0)
504                         break;
505                 ip->i_flag |= IN_CHANGE | IN_UPDATE;
506         }
507         /*
508          * If we successfully wrote any data, and we are not the superuser
509          * we clear the setuid and setgid bits as a precaution against
510          * tampering.
511          */
512         if (resid > uio->uio_resid && ap->a_cred && ap->a_cred->cr_uid != 0)
513                 ip->i_mode &= ~(ISUID | ISGID);
514         if (error) {
515                 if (ioflag & IO_UNIT) {
516                         (void)UFS_TRUNCATE(vp, osize,
517                             ioflag & IO_SYNC, ap->a_cred, uio->uio_procp);
518                         uio->uio_offset -= resid - uio->uio_resid;
519                         uio->uio_resid = resid;
520                 }
521         } else if (resid > uio->uio_resid && (ioflag & IO_SYNC))
522                 error = UFS_UPDATE(vp, 1);
523         if (!error)
524                 VN_POLLEVENT(vp, POLLWRITE | (extended ? POLLEXTEND : 0));
525
526         if (object)
527                 vm_object_vndeallocate(object);
528
529         return (error);
530 }
531
532
533 /*
534  * get page routine
535  */
536 int
537 ffs_getpages(ap)
538         struct vop_getpages_args *ap;
539 {
540         off_t foff, physoffset;
541         int i, size, bsize;
542         struct vnode *dp, *vp;
543         vm_object_t obj;
544         vm_pindex_t pindex, firstindex;
545         vm_page_t mreq;
546         int bbackwards, bforwards;
547         int pbackwards, pforwards;
548         int firstpage;
549         int reqlblkno;
550         daddr_t reqblkno;
551         int poff;
552         int pcount;
553         int rtval;
554         int pagesperblock;
555
556
557         pcount = round_page(ap->a_count) / PAGE_SIZE;
558         mreq = ap->a_m[ap->a_reqpage];
559         firstindex = ap->a_m[0]->pindex;
560
561         /*
562          * if ANY DEV_BSIZE blocks are valid on a large filesystem block,
563          * then the entire page is valid.  Since the page may be mapped,
564          * user programs might reference data beyond the actual end of file
565          * occuring within the page.  We have to zero that data.
566          */
567         if (mreq->valid) {
568                 if (mreq->valid != VM_PAGE_BITS_ALL)
569                         vm_page_zero_invalid(mreq, TRUE);
570                 for (i = 0; i < pcount; i++) {
571                         if (i != ap->a_reqpage) {
572                                 vm_page_free(ap->a_m[i]);
573                         }
574                 }
575                 return VM_PAGER_OK;
576         }
577
578         vp = ap->a_vp;
579         obj = vp->v_object;
580         bsize = vp->v_mount->mnt_stat.f_iosize;
581         pindex = mreq->pindex;
582         foff = IDX_TO_OFF(pindex) /* + ap->a_offset should be zero */;
583
584         if (bsize < PAGE_SIZE)
585                 return vnode_pager_generic_getpages(ap->a_vp, ap->a_m,
586                                                     ap->a_count,
587                                                     ap->a_reqpage);
588
589         /*
590          * foff is the file offset of the required page
591          * reqlblkno is the logical block that contains the page
592          * poff is the index of the page into the logical block
593          */
594         reqlblkno = foff / bsize;
595         poff = (foff % bsize) / PAGE_SIZE;
596
597         if ( VOP_BMAP( vp, reqlblkno, &dp, &reqblkno,
598                 &bforwards, &bbackwards) || (reqblkno == -1)) {
599                 for(i = 0; i < pcount; i++) {
600                         if (i != ap->a_reqpage)
601                                 vm_page_free(ap->a_m[i]);
602                 }
603                 if (reqblkno == -1) {
604                         if ((mreq->flags & PG_ZERO) == 0)
605                                 vm_page_zero_fill(mreq);
606                         vm_page_undirty(mreq);
607                         mreq->valid = VM_PAGE_BITS_ALL;
608                         return VM_PAGER_OK;
609                 } else {
610                         return VM_PAGER_ERROR;
611                 }
612         }
613
614         physoffset = (off_t)reqblkno * DEV_BSIZE + poff * PAGE_SIZE;
615         pagesperblock = bsize / PAGE_SIZE;
616         /*
617          * find the first page that is contiguous...
618          * note that pbackwards is the number of pages that are contiguous
619          * backwards.
620          */
621         firstpage = 0;
622         if (ap->a_count) {
623                 pbackwards = poff + bbackwards * pagesperblock;
624                 if (ap->a_reqpage > pbackwards) {
625                         firstpage = ap->a_reqpage - pbackwards;
626                         for(i=0;i<firstpage;i++)
627                                 vm_page_free(ap->a_m[i]);
628                 }
629
630         /*
631          * pforwards is the number of pages that are contiguous
632          * after the current page.
633          */
634                 pforwards = (pagesperblock - (poff + 1)) +
635                         bforwards * pagesperblock;
636                 if (pforwards < (pcount - (ap->a_reqpage + 1))) {
637                         for( i = ap->a_reqpage + pforwards + 1; i < pcount; i++)
638                                 vm_page_free(ap->a_m[i]);
639                         pcount = ap->a_reqpage + pforwards + 1;
640                 }
641
642         /*
643          * number of pages for I/O corrected for the non-contig pages at
644          * the beginning of the array.
645          */
646                 pcount -= firstpage;
647         }
648
649         /*
650          * calculate the size of the transfer
651          */
652
653         size = pcount * PAGE_SIZE;
654
655         if ((IDX_TO_OFF(ap->a_m[firstpage]->pindex) + size) >
656                 obj->un_pager.vnp.vnp_size)
657                 size = obj->un_pager.vnp.vnp_size -
658                         IDX_TO_OFF(ap->a_m[firstpage]->pindex);
659
660         physoffset -= foff;
661         rtval = VOP_GETPAGES(dp, &ap->a_m[firstpage], size,
662                 (ap->a_reqpage - firstpage), physoffset);
663
664         return (rtval);
665 }
666
667 /*
668  * put page routine
669  *
670  * XXX By default, wimp out... note that a_offset is ignored (and always
671  * XXX has been).
672  */
673 int
674 ffs_putpages(ap)
675         struct vop_putpages_args *ap;
676 {
677         return vnode_pager_generic_putpages(ap->a_vp, ap->a_m, ap->a_count,
678                 ap->a_sync, ap->a_rtvals);
679 }