Project

General

Profile

Statistics
| Branch: | Revision:

scoutos / prex-0.9.0 / usr / server / fs / fatfs / fatfs_vnops.c @ 03e9c04a

History | View | Annotate | Download (15.1 KB)

1
/*
2
 * Copyright (c) 2005-2008, Kohsuke Ohtani
3
 * 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. Neither the name of the author nor the names of any co-contributors
14
 *    may be used to endorse or promote products derived from this software
15
 *    without specific prior written permission.
16
 *
17
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
 * SUCH DAMAGE.
28
 */
29

    
30
#include <sys/prex.h>
31

    
32
#include <sys/vnode.h>
33
#include <sys/file.h>
34
#include <sys/mount.h>
35
#include <sys/dirent.h>
36
#include <sys/buf.h>
37

    
38
#include <ctype.h>
39
#include <unistd.h>
40
#include <errno.h>
41
#include <string.h>
42
#include <stdlib.h>
43
#include <fcntl.h>
44

    
45
#include "fatfs.h"
46

    
47
/*
48
 *  Time bits: 15-11 hours (0-23), 10-5 min, 4-0 sec /2
49
 *  Date bits: 15-9 year - 1980, 8-5 month, 4-0 day
50
 */
51
#define TEMP_DATE   0x3021
52
#define TEMP_TIME   0
53

    
54
#define fatfs_open        ((vnop_open_t)vop_nullop)
55
#define fatfs_close        ((vnop_close_t)vop_nullop)
56
static int fatfs_read        (vnode_t, file_t, void *, size_t, size_t *);
57
static int fatfs_write        (vnode_t, file_t, void *, size_t, size_t *);
58
#define fatfs_seek        ((vnop_seek_t)vop_nullop)
59
#define fatfs_ioctl        ((vnop_ioctl_t)vop_einval)
60
#define fatfs_fsync        ((vnop_fsync_t)vop_nullop)
61
static int fatfs_readdir(vnode_t, file_t, struct dirent *);
62
static int fatfs_lookup        (vnode_t, char *, vnode_t);
63
static int fatfs_create        (vnode_t, char *, mode_t);
64
static int fatfs_remove        (vnode_t, vnode_t, char *);
65
static int fatfs_rename        (vnode_t, vnode_t, char *, vnode_t, vnode_t, char *);
66
static int fatfs_mkdir        (vnode_t, char *, mode_t);
67
static int fatfs_rmdir        (vnode_t, vnode_t, char *);
68
static int fatfs_getattr(vnode_t, struct vattr *);
69
static int fatfs_setattr(vnode_t, struct vattr *);
70
static int fatfs_inactive(vnode_t);
71
static int fatfs_truncate(vnode_t, off_t);
72

    
73
/*
74
 * vnode operations
75
 */
76
struct vnops fatfs_vnops = {
77
        fatfs_open,                /* open */
78
        fatfs_close,                /* close */
79
        fatfs_read,                /* read */
80
        fatfs_write,                /* write */
81
        fatfs_seek,                /* seek */
82
        fatfs_ioctl,                /* ioctl */
83
        fatfs_fsync,                /* fsync */
84
        fatfs_readdir,                /* readdir */
85
        fatfs_lookup,                /* lookup */
86
        fatfs_create,                /* create */
87
        fatfs_remove,                /* remove */
88
        fatfs_rename,                /* remame */
89
        fatfs_mkdir,                /* mkdir */
90
        fatfs_rmdir,                /* rmdir */
91
        fatfs_getattr,                /* getattr */
92
        fatfs_setattr,                /* setattr */
93
        fatfs_inactive,                /* inactive */
94
        fatfs_truncate,                /* truncate */
95
};
96

    
97
/*
98
 * Read one cluster to buffer.
99
 */
100
static int
101
fat_read_cluster(struct fatfsmount *fmp, u_long cluster)
102
{
103
        u_long sec;
104
        size_t size;
105

    
106
        sec = cl_to_sec(fmp, cluster);
107
        size = fmp->sec_per_cl * SEC_SIZE;
108
        return device_read(fmp->dev, fmp->io_buf, &size, sec);
109
}
110

    
111
/*
112
 * Write one cluster from buffer.
113
 */
114
static int
115
fat_write_cluster(struct fatfsmount *fmp, u_long cluster)
116
{
117
        u_long sec;
118
        size_t size;
119

    
120
        sec = cl_to_sec(fmp, cluster);
121
        size = fmp->sec_per_cl * SEC_SIZE;
122
        return device_write(fmp->dev, fmp->io_buf, &size, sec);
123
}
124

    
125
/*
126
 * Lookup vnode for the specified file/directory.
127
 * The vnode data will be set properly.
128
 */
129
static int
130
fatfs_lookup(vnode_t dvp, char *name, vnode_t vp)
131
{
132
        struct fatfsmount *fmp;
133
        struct fat_dirent *de;
134
        struct fatfs_node *np;
135
        int error;
136

    
137
        if (*name == '\0')
138
                return ENOENT;
139

    
140
        fmp = vp->v_mount->m_data;
141
        mutex_lock(&fmp->lock);
142

    
143
        DPRINTF(("fatfs_lookup: name=%s\n", name));
144

    
145
        np = vp->v_data;
146
        error = fatfs_lookup_node(dvp, name, np);
147
        if (error) {
148
                DPRINTF(("fatfs_lookup: failed!! name=%s\n", name));
149
                mutex_unlock(&fmp->lock);
150
                return error;
151
        }
152
        de = &np->dirent;
153
        vp->v_type = IS_DIR(de) ? VDIR : VREG;
154
        fat_attr_to_mode(de->attr, &vp->v_mode);
155
        vp->v_mode = ALLPERMS;
156
        vp->v_size = de->size;
157
        vp->v_blkno = de->cluster;
158

    
159
        DPRINTF(("fatfs_lookup: cl=%d\n", de->cluster));
160
        mutex_unlock(&fmp->lock);
161
        return 0;
162
}
163

    
164
static int
165
fatfs_read(vnode_t vp, file_t fp, void *buf, size_t size, size_t *result)
166
{
167
        struct fatfsmount *fmp;
168
        int nr_read, nr_copy, buf_pos, error;
169
        u_long cl, file_pos;
170

    
171
        DPRINTF(("fatfs_read: vp=%x\n", vp));
172

    
173
        *result = 0;
174
        fmp = vp->v_mount->m_data;
175

    
176
        if (vp->v_type == VDIR)
177
                return EISDIR;
178
        if (vp->v_type != VREG)
179
                return EINVAL;
180

    
181
        /* Check if current file position is already end of file. */
182
        file_pos = fp->f_offset;
183
        if (file_pos >= vp->v_size)
184
                return 0;
185

    
186
        mutex_lock(&fmp->lock);
187

    
188
        /* Get the actual read size. */
189
        if (vp->v_size - file_pos < size)
190
                size = vp->v_size - file_pos;
191

    
192
        /* Seek to the cluster for the file offset */
193
        error = fat_seek_cluster(fmp, vp->v_blkno, file_pos, &cl);
194
        if (error)
195
                goto out;
196

    
197
        /* Read and copy data */
198
        nr_read = 0;
199
        buf_pos = file_pos % fmp->cluster_size;
200
        do {
201
                if (fat_read_cluster(fmp, cl)) {
202
                        error = EIO;
203
                        goto out;
204
                }
205

    
206
                nr_copy = fmp->cluster_size;
207
                if (buf_pos > 0)
208
                        nr_copy -= buf_pos;
209
                if (buf_pos + size < fmp->cluster_size)
210
                        nr_copy = size;
211
                memcpy(buf, fmp->io_buf + buf_pos, nr_copy);
212

    
213
                file_pos += nr_copy;
214
                nr_read += nr_copy;
215
                size -= nr_copy;
216
                if (size <= 0)
217
                        break;
218

    
219
                error = fat_next_cluster(fmp, cl, &cl);
220
                if (error)
221
                        goto out;
222

    
223
                buf = (void *)((u_long)buf + nr_copy);
224
                buf_pos = 0;
225
        } while (!IS_EOFCL(fmp, cl));
226

    
227
        fp->f_offset = file_pos;
228
        *result = nr_read;
229
        error = 0;
230
 out:
231
        mutex_unlock(&fmp->lock);
232
        return error;
233
}
234

    
235
static int
236
fatfs_write(vnode_t vp, file_t fp, void *buf, size_t size, size_t *result)
237
{
238
        struct fatfsmount *fmp;
239
        struct fatfs_node *np;
240
        struct fat_dirent *de;
241
        int nr_copy, nr_write, buf_pos, i, cl_size, error;
242
        u_long file_pos, end_pos;
243
        u_long cl;
244

    
245
        DPRINTF(("fatfs_write: vp=%x\n", vp));
246

    
247
        *result = 0;
248
        fmp = vp->v_mount->m_data;
249

    
250
        if (vp->v_type == VDIR)
251
                return EISDIR;
252
        if (vp->v_type != VREG)
253
                return EINVAL;
254

    
255
        mutex_lock(&fmp->lock);
256

    
257
        /* Check if file position exceeds the end of file. */
258
        end_pos = vp->v_size;
259
        file_pos = (fp->f_flags & O_APPEND) ? end_pos : fp->f_offset;
260
        if (file_pos + size > end_pos) {
261
                /* Expand the file size before writing to it */
262
                end_pos = file_pos + size;
263
                error = fat_expand_file(fmp, vp->v_blkno, end_pos);
264
                if (error) {
265
                        error = EIO;
266
                        goto out;
267
                }
268

    
269
                /* Update directory entry */
270
                np = vp->v_data;
271
                de = &np->dirent;
272
                de->size = end_pos;
273
                error = fatfs_put_node(fmp, np);
274
                if (error)
275
                        goto out;
276
                vp->v_size = end_pos;
277
        }
278

    
279
        /* Seek to the cluster for the file offset */
280
        error = fat_seek_cluster(fmp, vp->v_blkno, file_pos, &cl);
281
        if (error)
282
                goto out;
283

    
284
        buf_pos = file_pos % fmp->cluster_size;
285
        cl_size = size / fmp->cluster_size + 1;
286
        nr_write = 0;
287
        i = 0;
288
        do {
289
                /* First and last cluster must be read before write */
290
                if (i == 0 || i == cl_size) {
291
                        if (fat_read_cluster(fmp, cl)) {
292
                                error = EIO;
293
                                goto out;
294
                        }
295
                }
296
                nr_copy = fmp->cluster_size;
297
                if (buf_pos > 0)
298
                        nr_copy -= buf_pos;
299
                if (buf_pos + size < fmp->cluster_size)
300
                        nr_copy = size;
301
                memcpy(fmp->io_buf + buf_pos, buf, nr_copy);
302

    
303
                if (fat_write_cluster(fmp, cl)) {
304
                        error = EIO;
305
                        goto out;
306
                }
307
                file_pos += nr_copy;
308
                nr_write += nr_copy;
309
                size -= nr_copy;
310
                if (size <= 0)
311
                        break;
312

    
313
                error = fat_next_cluster(fmp, cl, &cl);
314
                if (error)
315
                        goto out;
316

    
317
                buf = (void *)((u_long)buf + nr_copy);
318
                buf_pos = 0;
319
                i++;
320
        } while (!IS_EOFCL(fmp, cl));
321

    
322
        fp->f_offset = file_pos;
323

    
324
        /*
325
         * XXX: Todo!
326
         *    de.time = ?
327
         *    de.date = ?
328
         *    if (dirent_set(fp, &de))
329
         *        return EIO;
330
         */
331
        *result = nr_write;
332
        error = 0;
333
 out:
334
        mutex_unlock(&fmp->lock);
335
        return error;
336
}
337

    
338
static int
339
fatfs_readdir(vnode_t vp, file_t fp, struct dirent *dir)
340
{
341
        struct fatfsmount *fmp;
342
        struct fatfs_node np;
343
        struct fat_dirent *de;
344
        int error;
345

    
346
        fmp = vp->v_mount->m_data;
347
        mutex_lock(&fmp->lock);
348

    
349
        error = fatfs_get_node(vp, fp->f_offset, &np);
350
        if (error)
351
                goto out;
352
        de = &np.dirent;
353
        fat_restore_name((char *)&de->name, dir->d_name);
354

    
355
        if (de->attr & FA_SUBDIR)
356
                dir->d_type = DT_DIR;
357
        else if (de->attr & FA_DEVICE)
358
                dir->d_type = DT_BLK;
359
        else
360
                dir->d_type = DT_REG;
361

    
362
        dir->d_fileno = fp->f_offset;
363
        dir->d_namlen = strlen(dir->d_name);
364

    
365
        fp->f_offset++;
366
        error = 0;
367
 out:
368
        mutex_unlock(&fmp->lock);
369
        return error;
370
}
371

    
372
/*
373
 * Create empty file.
374
 */
375
static int
376
fatfs_create(vnode_t dvp, char *name, mode_t mode)
377
{
378
        struct fatfsmount *fmp;
379
        struct fatfs_node np;
380
        struct fat_dirent *de;
381
        u_long cl;
382
        int error;
383

    
384
        DPRINTF(("fatfs_create: %s\n", name));
385

    
386
        if (!S_ISREG(mode))
387
                return EINVAL;
388

    
389
        if (!fat_valid_name(name))
390
                return EINVAL;
391

    
392
        fmp = dvp->v_mount->m_data;
393
        mutex_lock(&fmp->lock);
394

    
395
        /* Allocate free cluster for new file. */
396
        error = fat_alloc_cluster(fmp, 0, &cl);
397
        if (error)
398
                goto out;
399

    
400
        de = &np.dirent;
401
        memset(de, 0, sizeof(struct fat_dirent));
402
        fat_convert_name(name, (char *)de->name);
403
        de->cluster = cl;
404
        de->time = TEMP_TIME;
405
        de->date = TEMP_DATE;
406
        fat_mode_to_attr(mode, &de->attr);
407
        error = fatfs_add_node(dvp, &np);
408
        if (error)
409
                goto out;
410
        error = fat_set_cluster(fmp, cl, fmp->fat_eof);
411
 out:
412
        mutex_unlock(&fmp->lock);
413
        return error;
414
}
415

    
416
static int
417
fatfs_remove(vnode_t dvp, vnode_t vp, char *name)
418
{
419
        struct fatfsmount *fmp;
420
        struct fatfs_node np;
421
        struct fat_dirent *de;
422
        int error;
423

    
424
        if (*name == '\0')
425
                return ENOENT;
426

    
427
        fmp = dvp->v_mount->m_data;
428
        mutex_lock(&fmp->lock);
429

    
430
        error = fatfs_lookup_node(dvp, name, &np);
431
        if (error)
432
                goto out;
433
        de = &np.dirent;
434
        if (IS_DIR(de)) {
435
                error = EISDIR;
436
                goto out;
437
        }
438
        if (!IS_FILE(de)) {
439
                error = EPERM;
440
                goto out;
441
        }
442

    
443
        /* Remove clusters */
444
        error = fat_free_clusters(fmp, de->cluster);
445
        if (error)
446
                goto out;
447

    
448
        /* remove directory */
449
        de->name[0] = 0xe5;
450
        error = fatfs_put_node(fmp, &np);
451
 out:
452
        mutex_unlock(&fmp->lock);
453
        return error;
454
}
455

    
456
static int
457
fatfs_rename(vnode_t dvp1, vnode_t vp1, char *name1,
458
             vnode_t dvp2, vnode_t vp2, char *name2)
459
{
460
        struct fatfsmount *fmp;
461
        struct fatfs_node np1;
462
        struct fat_dirent *de1, *de2;
463
        int error;
464

    
465
        fmp = dvp1->v_mount->m_data;
466
        mutex_lock(&fmp->lock);
467

    
468
        error = fatfs_lookup_node(dvp1, name1, &np1);
469
        if (error)
470
                goto out;
471
        de1 = &np1.dirent;
472

    
473
        if (IS_FILE(de1)) {
474
                /* Remove destination file, first */
475
                error = fatfs_remove(dvp2, vp1, name2);
476
                if (error == EIO)
477
                        goto out;
478

    
479
                /* Change file name of directory entry */
480
                fat_convert_name(name2, (char *)de1->name);
481

    
482
                /* Same directory ? */
483
                if (dvp1 == dvp2) {
484
                        /* Change the name of existing file */
485
                        error = fatfs_put_node(fmp, &np1);
486
                        if (error)
487
                                goto out;
488
                } else {
489
                        /* Create new directory entry */
490
                        error = fatfs_add_node(dvp2, &np1);
491
                        if (error)
492
                                goto out;
493

    
494
                        /* Remove souce file */
495
                        error = fatfs_remove(dvp1, vp2, name1);
496
                        if (error)
497
                                goto out;
498
                }
499
        } else {
500

    
501
                /* remove destination directory */
502
                error = fatfs_rmdir(dvp2, NULL, name2);
503
                if (error == EIO)
504
                        goto out;
505

    
506
                /* Change file name of directory entry */
507
                fat_convert_name(name2, (char *)de1->name);
508

    
509
                /* Same directory ? */
510
                if (dvp1 == dvp2) {
511
                        /* Change the name of existing directory */
512
                        error = fatfs_put_node(fmp, &np1);
513
                        if (error)
514
                                goto out;
515
                } else {
516
                        /* Create new directory entry */
517
                        error = fatfs_add_node(dvp2, &np1);
518
                        if (error)
519
                                goto out;
520

    
521
                        /* Update "." and ".." for renamed directory */
522
                        if (fat_read_cluster(fmp, de1->cluster)) {
523
                                error = EIO;
524
                                goto out;
525
                        }
526

    
527
                        de2 = (struct fat_dirent *)fmp->io_buf;
528
                        de2->cluster = de1->cluster;
529
                        de2->time = TEMP_TIME;
530
                        de2->date = TEMP_DATE;
531
                        de2++;
532
                        de2->cluster = dvp2->v_blkno;
533
                        de2->time = TEMP_TIME;
534
                        de2->date = TEMP_DATE;
535

    
536
                        if (fat_write_cluster(fmp, de1->cluster)) {
537
                                error = EIO;
538
                                goto out;
539
                        }
540

    
541
                        /* Remove souce directory */
542
                        error = fatfs_rmdir(dvp1, NULL, name1);
543
                        if (error)
544
                                goto out;
545
                }
546
        }
547
 out:
548
        mutex_unlock(&fmp->lock);
549
        return error;
550
}
551

    
552
static int
553
fatfs_mkdir(vnode_t dvp, char *name, mode_t mode)
554
{
555
        struct fatfsmount *fmp;
556
        struct fatfs_node np;
557
        struct fat_dirent *de;
558
        u_long cl;
559
        int error;
560

    
561
        if (!S_ISDIR(mode))
562
                return EINVAL;
563

    
564
        if (!fat_valid_name(name))
565
                return ENOTDIR;
566

    
567
        fmp = dvp->v_mount->m_data;
568
        mutex_lock(&fmp->lock);
569

    
570
        /* Allocate free cluster for directory data */
571
        error = fat_alloc_cluster(fmp, 0, &cl);
572
        if (error)
573
                goto out;
574

    
575
        memset(&np, 0, sizeof(struct fatfs_node));
576
        de = &np.dirent;
577
        fat_convert_name(name, (char *)&de->name);
578
        de->cluster = cl;
579
        de->time = TEMP_TIME;
580
        de->date = TEMP_DATE;
581
        fat_mode_to_attr(mode, &de->attr);
582
        error = fatfs_add_node(dvp, &np);
583
        if (error)
584
                goto out;
585

    
586
        /* Initialize "." and ".." for new directory */
587
        memset(fmp->io_buf, 0, fmp->cluster_size);
588

    
589
        de = (struct fat_dirent *)fmp->io_buf;
590
        memcpy(de->name, ".          ", 11);
591
        de->attr = FA_SUBDIR;
592
        de->cluster = cl;
593
        de->time = TEMP_TIME;
594
        de->date = TEMP_DATE;
595
        de++;
596
        memcpy(de->name, "..         ", 11);
597
        de->attr = FA_SUBDIR;
598
        de->cluster = dvp->v_blkno;
599
        de->time = TEMP_TIME;
600
        de->date = TEMP_DATE;
601

    
602
        if (fat_write_cluster(fmp, cl)) {
603
                error = EIO;
604
                goto out;
605
        }
606
        /* Add eof */
607
        error = fat_set_cluster(fmp, cl, fmp->fat_eof);
608
 out:
609
        mutex_unlock(&fmp->lock);
610
        return error;
611
}
612

    
613
/*
614
 * remove can be done only with empty directory
615
 */
616
static int
617
fatfs_rmdir(vnode_t dvp, vnode_t vp, char *name)
618
{
619
        struct fatfsmount *fmp;
620
        struct fatfs_node np;
621
        struct fat_dirent *de;
622
        int error;
623

    
624
        if (*name == '\0')
625
                return ENOENT;
626

    
627
        fmp = dvp->v_mount->m_data;
628
        mutex_lock(&fmp->lock);
629

    
630
        error = fatfs_lookup_node(dvp, name, &np);
631
        if (error)
632
                goto out;
633

    
634
        de = &np.dirent;
635
        if (!IS_DIR(de)) {
636
                error = ENOTDIR;
637
                goto out;
638
        }
639

    
640
        /* Remove clusters */
641
        error = fat_free_clusters(fmp, de->cluster);
642
        if (error)
643
                goto out;
644

    
645
        /* remove directory */
646
        de->name[0] = 0xe5;
647

    
648
        error = fatfs_put_node(fmp, &np);
649
 out:
650
        mutex_unlock(&fmp->lock);
651
        return error;
652
}
653

    
654
static int
655
fatfs_getattr(vnode_t vp, struct vattr *vap)
656
{
657
        /* XXX */
658
        return 0;
659
}
660

    
661
static int
662
fatfs_setattr(vnode_t vp, struct vattr *vap)
663
{
664
        /* XXX */
665
        return 0;
666
}
667

    
668

    
669
static int
670
fatfs_inactive(vnode_t vp)
671
{
672

    
673
        free(vp->v_data);
674
        return 0;
675
}
676

    
677
static int
678
fatfs_truncate(vnode_t vp, off_t length)
679
{
680
        struct fatfsmount *fmp;
681
        struct fatfs_node *np;
682
        struct fat_dirent *de;
683
        int error;
684

    
685
        fmp = vp->v_mount->m_data;
686
        mutex_lock(&fmp->lock);
687

    
688
        np = vp->v_data;
689
        de = &np->dirent;
690

    
691
        if (length == 0) {
692
                /* Remove clusters */
693
                error = fat_free_clusters(fmp, de->cluster);
694
                if (error)
695
                        goto out;
696
        } else if (length > vp->v_size) {
697
                error = fat_expand_file(fmp, vp->v_blkno, length);
698
                if (error) {
699
                        error = EIO;
700
                        goto out;
701
                }
702
        }
703

    
704
        /* Update directory entry */
705
        de->size = length;
706
        error = fatfs_put_node(fmp, np);
707
        if (error)
708
                goto out;
709
        vp->v_size = length;
710
 out:
711
        mutex_unlock(&fmp->lock);
712
        return error;
713
}
714

    
715
int
716
fatfs_init(void)
717
{
718
        return 0;
719
}