afscp: null-terminate root.cell dir if needed in dynroot mode
[openafs.git] / src / libafscp / afscp_dir.c
1 /* AUTORIGHTS
2 Copyright (C) 2003 - 2010 Chaskiel Grundman
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
9 1. Redistributions of source code must retain the above copyright
10    notice, this list of conditions and the following disclaimer.
11
12 2. Redistributions in binary form must reproduce the above copyright
13    notice, this list of conditions and the following disclaimer in the
14    documentation and/or other materials provided with the distribution.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 #include <afsconfig.h>
28 #include <afs/param.h>
29
30 #include <roken.h>
31
32 #include <search.h>
33
34 #include <afs/vlserver.h>
35 #include <afs/vldbint.h>
36 #include <afs/dir.h>
37 #ifdef AFS_NT40_ENV
38 #include <afs/errmap_nt.h>
39 #endif
40 #include "afscp.h"
41 #include "afscp_internal.h"
42
43 static int dirmode = DIRMODE_CELL;
44
45 int
46 afscp_SetDirMode(int mode)
47 {
48     if ((mode != DIRMODE_CELL) && (mode != DIRMODE_DYNROOT)) {
49         afscp_errno = EINVAL;
50         return -1;
51     }
52     dirmode = mode;
53     return 0;
54 }
55
56 /* comparison function for tsearch */
57 static int
58 dircompare(const void *a, const void *b)
59 {
60     const struct afscp_dircache *sa = a, *sb = b;
61     if (sa->me.fid.Vnode < sb->me.fid.Vnode)
62         return -1;
63     if (sa->me.fid.Vnode > sb->me.fid.Vnode)
64         return 1;
65     if (sa->me.fid.Unique < sb->me.fid.Unique)
66         return -1;
67     if (sa->me.fid.Unique > sb->me.fid.Unique)
68         return 1;
69     return 0;
70 }
71
72 /* make sure the dirstream contains the most up to date directory contents */
73 static int
74 _DirUpdate(struct afscp_dirstream *d)
75 {
76     struct AFSFetchStatus s;
77     int code;
78     struct afscp_volume *v;
79     struct afscp_dircache key, *stored;
80     void **cached;
81
82
83     code = afscp_GetStatus(&d->fid, &s);
84     if (code != 0) {
85         return code;
86     }
87
88     if (d->dirbuffer && d->dv == s.DataVersion) {
89         return 0;
90     }
91     v = afscp_VolumeById(d->fid.cell, d->fid.fid.Volume);
92     if (v == NULL) {
93         afscp_errno = ENOENT;
94         return -1;
95     }
96
97     memcpy(&key.me, &d->fid, sizeof(struct afscp_venusfid));
98     cached = tfind(&key, &v->dircache, dircompare);
99     if (cached != NULL) {
100         stored = *(struct afscp_dircache **)cached;
101         if (d->dv == s.DataVersion) {
102             d->dirbuffer = stored->dirbuffer;
103             d->buflen = stored->buflen;
104             d->dv = stored->dv;
105             return 0;
106         }
107         pthread_mutex_lock(&(stored->mtx));
108         tdelete(&key, &v->dircache, dircompare);
109         stored->nwaiters++;
110         while (stored->nwaiters > 1) {
111             pthread_cond_wait(&(stored->cv), &(stored->mtx));
112         }
113         stored->nwaiters--;
114         pthread_cond_destroy(&(stored->cv));
115         pthread_mutex_unlock(&(stored->mtx));
116         pthread_mutex_destroy(&(stored->mtx));
117         if (d->dirbuffer != stored->dirbuffer)
118             free(stored->dirbuffer);
119         free(stored);
120     }
121     if (s.Length > BIGMAXPAGES * AFS_PAGESIZE) {
122         afscp_errno = EFBIG;
123         return -1;
124     }
125     if (d->buflen != s.Length) {
126         char *new;
127         if (d->dirbuffer) {
128             new = realloc(d->dirbuffer, s.Length);
129         } else {
130             new = malloc(s.Length);
131         }
132         if (new != NULL) {
133             d->dirbuffer = new;
134         } else {
135             afscp_errno = ENOMEM;
136             return -1;
137         }
138         d->buflen = s.Length;
139     }
140
141     code = afscp_PRead(&d->fid, d->dirbuffer, s.Length, 0);
142     if (code < 0) {
143         return -1;
144     }
145     d->dv = s.DataVersion;
146     cached = tsearch(&key, &v->dircache, dircompare);
147     if (cached != NULL) {
148         stored = malloc(sizeof(struct afscp_dircache));
149         if (stored != NULL) {
150             memcpy(&stored->me, &d->fid, sizeof(struct afscp_venusfid));
151             stored->buflen = d->buflen;
152             stored->dirbuffer = d->dirbuffer;
153             stored->dv = d->dv;
154             stored->nwaiters = 0;
155             pthread_mutex_init(&(stored->mtx), NULL);
156             pthread_cond_init(&(stored->cv), NULL);
157             *(struct afscp_dircache **)cached = stored;
158         } else {
159             tdelete(&key, &v->dircache, dircompare);
160         }
161     }
162     return 0;
163 }
164
165 static struct DirEntry *
166 dir_get_entry(struct afscp_dirstream *d, int entry)
167 {
168     struct DirHeader *h = (struct DirHeader *)d->dirbuffer;
169     /* struct PageHeader *p; */
170     struct DirEntry *ret;
171     /* int fr; */
172     int pg, off;
173
174     pg = entry >> LEPP;
175     off = entry & (EPP - 1);
176
177     if (pg * AFS_PAGESIZE >= d->buflen) {       /* beyond end of file */
178         return NULL;
179     }
180     if (!off || (!pg && off < DHE + 1)) {       /* offset refers to metadata */
181         return NULL;
182     }
183     if (pg < MAXPAGES && h->alloMap[pg] == EPP) {       /* page is empty */
184         return NULL;
185     }
186     /* p = (struct PageHeader *)&d->dirbuffer[pg * AFS_PAGESIZE]; */
187     /* p is set but not referenced later */
188     /* fr = p->freebitmap[off >> 8]; */
189     /* fr is set but not referenced later */
190     ret = (struct DirEntry *)&d->dirbuffer[pg * AFS_PAGESIZE + 32 * off];
191     return ret;
192 }
193
194 struct afscp_dirstream *
195 afscp_OpenDir(const struct afscp_venusfid *fid)
196 {
197     struct afscp_dirstream *ret;
198     struct AFSFetchStatus s;
199     int code;
200
201     code = afscp_GetStatus(fid, &s);
202     if (code != 0) {
203         return NULL;
204     }
205
206     if (s.FileType != Directory) {
207         afscp_errno = ENOTDIR;
208         return NULL;
209     }
210     ret = calloc(1, sizeof(struct afscp_dirstream));
211     if (ret == NULL) {
212         afscp_errno = ENOMEM;
213         return NULL;
214     }
215     memmove(&ret->fid, fid, sizeof(struct afscp_venusfid));
216     code = _DirUpdate(ret);
217     if (code < 0) {
218         afscp_CloseDir(ret);
219         return NULL;
220     }
221     ret->hashent = -1;
222     ret->entry = 0;
223
224     return ret;
225 }
226
227 struct afscp_dirent *
228 afscp_ReadDir(struct afscp_dirstream *d)
229 {
230     struct DirHeader *h = (struct DirHeader *)d->dirbuffer;
231     struct DirEntry *info;
232     int ent;
233
234
235     ent = d->entry;
236     while (ent == 0 && d->hashent < NHASHENT - 1) {
237         d->hashent++;
238         ent = ntohs(h->hashTable[d->hashent]);
239     }
240     if (ent == 0) {
241         afscp_errno = 0;
242         return NULL;
243     }
244     info = dir_get_entry(d, ent);
245     if (info == NULL) {
246         afscp_errno = 0;
247         return NULL;
248     }
249     d->ret.vnode = ntohl(info->fid.vnode);
250     d->ret.unique = ntohl(info->fid.vunique);
251     strlcpy(d->ret.name, info->name, sizeof(d->ret.name));      /* guaranteed to be NULL terminated? */
252     d->entry = ntohs(info->next);
253
254     return &d->ret;
255 }
256
257 /* as it calls _DirUpdate, this may corrupt any previously returned dirent's */
258 int
259 afscp_RewindDir(struct afscp_dirstream *d)
260 {
261     _DirUpdate(d);
262     d->hashent = -1;
263     d->entry = 0;
264     return 0;
265 }
266
267 int
268 afscp_CloseDir(struct afscp_dirstream *d)
269 {
270     free(d);
271     return 0;
272 }
273
274 static int
275 namehash(const char *name)
276 {
277     int hval, tval;
278
279     hval = 0;
280     while (*name != '\0')
281         hval = (hval * 173) + *name++;
282     tval = hval & (NHASHENT - 1);
283     return tval ? (hval < 0 ? NHASHENT - tval : tval)
284         : 0;
285 }
286
287 struct afscp_venusfid *
288 afscp_DirLookup(struct afscp_dirstream *d, const char *name)
289 {
290     int code;
291     int hval, entry;
292     struct DirHeader *h = (struct DirHeader *)d->dirbuffer;
293     struct DirEntry *info;
294
295     code = _DirUpdate(d);
296     if (code != 0) {
297         return NULL;
298     }
299     hval = namehash(name);
300     entry = ntohs(h->hashTable[hval]);
301
302     while (entry != 0) {
303         info = dir_get_entry(d, entry);
304         if (info == NULL) {
305             afscp_errno = EIO;
306             return NULL;
307         }
308         if (strcmp(info->name, name) == 0)
309             break;
310         entry = ntohs(info->next);
311     }
312     if (entry != 0) {
313         return afscp_MakeFid(d->fid.cell, d->fid.fid.Volume,
314                              ntohl(info->fid.vnode),
315                              ntohl(info->fid.vunique));
316     } else {
317         afscp_errno = ENOENT;
318         return NULL;
319     }
320 }
321
322 struct afscp_venusfid *
323 afscp_ResolveName(const struct afscp_venusfid *dir, const char *name)
324 {
325     struct afscp_venusfid *ret;
326     struct afscp_dirstream *d;
327
328     d = afscp_OpenDir(dir);
329     if (d == NULL) {
330         return NULL;
331     }
332     ret = afscp_DirLookup(d, name);
333     afscp_CloseDir(d);
334     return ret;
335 }
336
337 static int
338 gettoproot(struct afscp_cell *cell, char *p, char **q,
339            struct afscp_venusfid **root)
340 {
341     struct afscp_volume *rootvol;
342     char *r;
343
344     if (dirmode == DIRMODE_DYNROOT && (strcmp(p, "/afs") == 0)) {
345         afscp_errno = EINVAL;
346         return 1;
347     }
348     if (strncmp(p, "/afs", 4) == 0) {
349         afs_dprintf(("gettoproot: path is absolute\n"));
350         p = &p[5];
351         while (*p == '/')
352             p++;
353         if (dirmode == DIRMODE_DYNROOT) {
354             int voltype;
355           retry_dot:
356             voltype = ROVOL;
357             if (*p == '.') {
358                 p++;
359                 voltype = RWVOL;
360             }
361             if (*p == '/') {
362                 while (*p == '/')
363                     p++;
364                 goto retry_dot;
365             }
366             if (*p == '.' || *p == 0) {
367                 afscp_errno = EINVAL;
368                 return 1;
369             }
370             r = p;
371             while (*r && *r != '/')
372                 r++;
373             if (*r)
374                 *r++ = 0;
375             *q = r;
376             afs_dprintf(("gettoproot: dynroot looking up cell %s\n", p));
377             cell = afscp_CellByName(p, NULL);
378             if (cell == NULL) {
379                 afs_dprintf(("gettoproot: no such cell\n"));
380                 afscp_errno = ENODEV;
381                 return 1;
382             }
383             rootvol = afscp_VolumeByName(cell, "root.cell", voltype);
384             if (!rootvol && voltype == ROVOL)
385                 rootvol = afscp_VolumeByName(cell, "root.cell", RWVOL);
386         } else {
387             *q = p;
388             rootvol = afscp_VolumeByName(cell, "root.afs", ROVOL);
389             if (!rootvol)
390                 rootvol = afscp_VolumeByName(cell, "root.afs", RWVOL);
391         }
392         if (!rootvol)
393             afs_dprintf(("gettoproot: volume not found\n"));
394     } else {
395         afs_dprintf(("gettoproot: path is relative\n"));
396         if (p[0] == '/') {
397             afscp_errno = EXDEV;
398             return 1;
399         }
400         rootvol = afscp_VolumeByName(cell, "root.cell", ROVOL);
401         if (!rootvol)
402             rootvol = afscp_VolumeByName(cell, "root.cell", RWVOL);
403         *q = p;
404     }
405     if (rootvol == NULL) {
406         afscp_errno = ENODEV;
407         return 1;
408     }
409     *root = afscp_MakeFid(cell, rootvol->id, 1, 1);
410     return 0;
411 }
412
413 static int
414 getvolumeroot(struct afscp_cell *cell, int voltype, const char *vname,
415               struct afscp_venusfid **root)
416 {
417     struct afscp_volume *vol;
418     vol = afscp_VolumeByName(cell, vname, voltype);
419     if (!vol && voltype == ROVOL)
420         vol = afscp_VolumeByName(cell, vname, RWVOL);
421     if (vol == NULL) {
422         afscp_errno = ENODEV;
423         return 1;
424     }
425     *root = afscp_MakeFid(cell, vol->id, 1, 1);
426     return 0;
427 }
428
429 typedef struct fidstack_s {
430     int alloc;
431     int count;
432     struct afscp_venusfid **entries;
433 } *fidstack;
434
435 static fidstack
436 fidstack_alloc(void)
437 {
438     fidstack ret;
439
440     ret = malloc(sizeof(struct fidstack_s));
441     if (ret == NULL) {
442         afscp_errno = ENOMEM;
443         return NULL;
444     }
445     ret->alloc = 10;
446     ret->count = 0;
447     ret->entries = malloc(ret->alloc * sizeof(struct afscp_venusfid *));
448     if (ret->entries == NULL) {
449         free(ret);
450         afscp_errno = ENOMEM;
451         return NULL;
452     }
453     return ret;
454 }
455
456 static void
457 fidstack_push(fidstack s, struct afscp_venusfid *entry)
458 {
459     struct afscp_venusfid **new;
460     if (s->count >= s->alloc) {
461         new = realloc(s->entries, (s->alloc + 10) *
462                       sizeof(struct afscp_venusfid *));
463         if (new == NULL) {
464             return;
465         }
466         s->entries = new;
467         s->alloc += 10;
468     }
469     s->entries[s->count++] = entry;
470     return;
471 }
472
473 static struct afscp_venusfid *
474 fidstack_pop(fidstack s)
475 {
476     if (s->count)
477         return s->entries[--s->count];
478     return NULL;
479 }
480
481 static void
482 fidstack_free(fidstack s)
483 {
484     int i;
485
486     for (i = 0; i < s->count; i++)
487         free(s->entries[i]);
488     free(s->entries);
489     free(s);
490 }
491
492 static struct afscp_venusfid *_ResolvePath(const struct afscp_venusfid *,
493                                            fidstack, char *, int);
494
495 static struct afscp_venusfid *
496 afscp_HandleLink(struct afscp_venusfid *in,
497                  const struct afscp_venusfid *parent, fidstack fids,
498                  int follow, const struct AFSFetchStatus *s, int terminal)
499 {
500     char *linkbuf, *linkbufq;
501     struct afscp_cell *cell;
502     struct afscp_volume *v;
503     struct afscp_venusfid *root, *ret;
504     int voltype;
505     int code;
506     ssize_t len;
507     if ((s->UnixModeBits & 0111) && (follow == 0) && terminal) {        /* normal link */
508         return in;
509     }
510     linkbuf = malloc(s->Length + 1);
511     code = afscp_PRead(in, linkbuf, s->Length, 0);
512     if (code < 0) {
513         free(linkbuf);
514         free(in);
515         return NULL;
516     }
517     if (code != s->Length) {
518         afscp_errno = EIO;
519         free(linkbuf);
520         free(in);
521         return NULL;
522     }
523     linkbuf[s->Length] = 0;
524     if (s->UnixModeBits & 0111) {       /* normal link */
525         afs_dprintf(("Recursing on symlink %s...\n", linkbuf));
526         if (linkbuf[0] == '/') {
527             if (gettoproot(in->cell, linkbuf, &linkbufq, &root)) {
528                 free(linkbuf);
529                 free(in);
530                 return NULL;
531             }
532             free(in);
533             ret = _ResolvePath(root, 0, linkbufq, 0);
534             free(root);
535         } else {
536             free(in);
537             ret = _ResolvePath(parent, fids, linkbuf, 0);
538         }
539         free(linkbuf);
540     } else {                    /* mountpoint */
541         afs_dprintf(("EvalMountPoint %s...\n", linkbuf));
542         linkbufq = strchr(linkbuf, ':');
543         cell = in->cell;
544         v = afscp_VolumeById(cell, in->fid.Volume);
545         free(in);
546         if (v == NULL) {
547             free(linkbuf);
548             afscp_errno = ENODEV;
549             return NULL;
550         }
551         voltype = v->voltype;
552         if (linkbuf[0] == '%')
553             voltype = RWVOL;
554         if (linkbufq == NULL) {
555             linkbufq = linkbuf + 1;
556         } else {
557             *linkbufq++ = 0;
558             cell = afscp_CellByName(linkbuf + 1, NULL);
559             if (linkbuf[0] != '%')
560                 voltype = ROVOL;
561         }
562         if (cell == NULL) {
563             free(linkbuf);
564             afscp_errno = ENODEV;
565             return NULL;
566         }
567         len = strnlen(linkbufq, s->Length + 1);
568         if (len < 2) {
569             free(linkbuf);
570             afscp_errno = ENODEV;
571             return NULL;
572         }
573         len = strnlen(linkbufq, s->Length + 1);
574         linkbufq[len - 1] = 0;  /* eliminate trailer */
575         if (getvolumeroot(cell, voltype, linkbufq, &ret)) {
576             free(linkbuf);
577             return NULL;
578         }
579         free(linkbuf);
580     }
581     return ret;
582 }
583
584 static struct afscp_venusfid *
585 _ResolvePath(const struct afscp_venusfid *start, fidstack infids,
586              char *path, int follow)
587 {
588     struct afscp_venusfid *ret, *cwd;
589     struct AFSFetchStatus s;
590     char *p, *q;
591     int code;
592     int linkcount;
593     fidstack fids;
594
595     p = path;
596     ret = cwd = afscp_DupFid(start);
597     fids = infids;
598     if (fids == NULL)
599         fids = fidstack_alloc();
600     if (fids == NULL) {
601         return NULL;
602     }
603
604     while (p && *p) {
605         q = strchr(p, '/');
606         if (q)
607             *q++ = 0;
608         if (strcmp(p, ".") == 0) {
609             /* do nothing */
610         } else if (strcmp(p, "..") == 0) {
611             ret = fidstack_pop(fids);
612             if (ret == NULL)
613                 ret = cwd;
614             else
615                 free(cwd);
616         } else {
617             ret = afscp_ResolveName(cwd, p);
618             if (ret == NULL) {
619                 afs_dprintf(("Lookup %s in %lu.%lu.%lu failed\n", p,
620                              cwd->fid.Volume, cwd->fid.Vnode,
621                              cwd->fid.Unique));
622                 free(cwd);
623                 if (infids == NULL)
624                     fidstack_free(fids);
625                 return NULL;
626             }
627             afs_dprintf(("Lookup %s in %lu.%lu.%lu->%lu.%lu.%lu\n", p,
628                          cwd->fid.Volume, cwd->fid.Vnode, cwd->fid.Unique,
629                          ret->fid.Volume, ret->fid.Vnode, ret->fid.Unique));
630             linkcount = 0;
631
632           retry:
633             if ((ret->fid.Vnode & 1) == 0) {    /* not a directory; check for link */
634                 code = afscp_GetStatus(ret, &s);
635                 if (code != 0) {
636                     if (infids == NULL)
637                         fidstack_free(fids);
638                     free(cwd);
639                     free(ret);
640                     return NULL;
641                 }
642                 if (s.FileType == SymbolicLink) {
643                     if (linkcount++ > 5) {
644                         afscp_errno = ELOOP;
645                         if (infids == NULL)
646                             fidstack_free(fids);
647                         free(cwd);
648                         free(ret);
649                         return NULL;
650                     }
651                     ret =
652                         afscp_HandleLink(ret, cwd, fids, follow, &s,
653                                          (q == NULL));
654                     if (ret == NULL) {
655                         free(cwd);
656                         if (infids == NULL)
657                             fidstack_free(fids);
658                         return NULL;
659                     }
660                     afs_dprintf(("   ....-> %lu.%lu.%lu\n", ret->fid.Volume,
661                                  ret->fid.Vnode, ret->fid.Unique));
662                     goto retry;
663                 } else {
664                     if (q != NULL) {
665                         afscp_errno = ENOTDIR;
666                         free(cwd);
667                         free(ret);
668                         if (infids == NULL)
669                             fidstack_free(fids);
670                         return NULL;
671                     }
672                 }
673             }
674             fidstack_push(fids, cwd);
675         }
676         cwd = ret;
677
678         while ((q != NULL) && (*q == '/'))
679             q++;
680         p = q;
681     }
682     if (infids == NULL)
683         fidstack_free(fids);
684     return ret;
685 }
686
687 /*!
688  * Resolve a path to a FID starting from the root volume
689  *
690  * \param[in]   path    full path
691  *
692  * \post Returns a venusfid representing the final element of path
693  *
694  * \note There are three cases:
695  *       (1) begins with /afs: start in root.afs of cell or home cell
696  *       (2) else begins with /: error
697  *       (3) else start in root.cell of cell or home cell
698  */
699 struct afscp_venusfid *
700 afscp_ResolvePath(const char *path)
701 {
702     struct afscp_venusfid *root, *ret;
703     struct afscp_cell *cell;
704     int code;
705     char *p, *q;
706     p = strdup(path);           /* so we can modify the string */
707     if (p == NULL) {
708         afscp_errno = ENOMEM;
709         return NULL;
710     }
711     cell = afscp_DefaultCell();
712     if (cell == NULL) {
713         free(p);
714         afscp_errno = EINVAL;
715         return NULL;
716     }
717     code = gettoproot(cell, p, &q, &root);
718     if (code != 0) {
719         free(p);
720         return NULL;
721     }
722     if (q && *q) {
723         ret = _ResolvePath(root, 0, q, 1);
724         free(root);
725     } else
726         ret = root;
727     free(p);
728     return ret;
729 }
730
731 /*!
732  * Resolve a path to a FID starting from the given volume
733  *
734  * \param[in]   v       volume structure containing id and cell info
735  * \param[in]   path    path relative to volume v
736  *
737  * \post Returns a venusfid representing the final element of path
738  */
739 struct afscp_venusfid *
740 afscp_ResolvePathFromVol(const struct afscp_volume *v, const char *path)
741 {
742     struct afscp_venusfid *root, *ret;
743     char *origp, *p;
744
745     /* so we can modify the string */
746     origp = p = strdup(path);
747     if (p == NULL) {
748         afscp_errno = ENOMEM;
749         return NULL;
750     }
751     root = afscp_MakeFid(v->cell, v->id, 1, 1);
752     while (*p == '/')
753         p++;
754     if (*p != '\0') {
755         ret = _ResolvePath(root, 0, p, 1);
756         free(root);
757     } else
758         ret = root;
759     free(origp);
760     return ret;
761 }