Coverage Report

Created: 2022-10-31 07:00

/src/ghostpdl/base/gp_unifs.c
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (C) 2001-2021 Artifex Software, Inc.
2
   All Rights Reserved.
3
4
   This software is provided AS-IS with no warranty, either express or
5
   implied.
6
7
   This software is distributed under license and may not be copied,
8
   modified or distributed except as expressly authorized under the terms
9
   of the license contained in the file LICENSE in this distribution.
10
11
   Refer to licensing information at http://www.artifex.com or contact
12
   Artifex Software, Inc.,  1305 Grant Avenue - Suite 200, Novato,
13
   CA 94945, U.S.A., +1(415)492-9861, for further information.
14
*/
15
16
17
/* "Unix-like" file system platform routines for Ghostscript */
18
19
/* prevent gp.h from defining fopen */
20
#define fopen fopen
21
22
#include "stdio_.h"             /* for FILENAME_MAX */
23
#include "memory_.h"
24
#include "string_.h"
25
#include "gx.h"
26
27
#include "gp.h"
28
#include "gpmisc.h"
29
#include "gsstruct.h"
30
#include "gsutil.h"             /* for string_match */
31
#include "stat_.h"
32
#include "dirent_.h"
33
#include "unistd_.h"
34
#include <stdlib.h>             /* for mkstemp/mktemp */
35
36
#if !defined(HAVE_FSEEKO)
37
#define ftello ftell
38
#define fseeko fseek
39
#define ftello64 ftell
40
#define fseeko64 fseek
41
#endif
42
43
/* Provide a definition of the maximum path length in case the system
44
 * headers don't define it. This should be gp_file_name_sizeof from
45
 * gp.h once that value is properly sent in a system-dependent way.
46
 * HP-UX 11i 11.11 incorrectly defines FILENAME_MAX as 14.
47
 */
48
#ifdef FILENAME_MAX
49
#  if FILENAME_MAX < 80  /* arbitrary */
50
#    undef FILENAME_MAX
51
#  endif
52
#endif
53
#ifndef FILENAME_MAX
54
#  define FILENAME_MAX 1024
55
#endif
56
57
/* Library routines not declared in a standard header */
58
extern char *mktemp(char *);
59
60
/* ------ File naming and accessing ------ */
61
62
/* Define the default scratch file name prefix. */
63
const char gp_scratch_file_name_prefix[] = "gs_";
64
65
/* Define the name of the null output file. */
66
const char gp_null_file_name[] = "/dev/null";
67
68
/* Define the name that designates the current directory. */
69
const char gp_current_directory_name[] = ".";
70
71
/* Create and open a scratch file with a given name prefix. */
72
/* Write the actual file name at fname. */
73
FILE *
74
gp_open_scratch_file_impl(const gs_memory_t *mem,
75
                          const char        *prefix,
76
                                char         fname[gp_file_name_sizeof],
77
                          const char        *mode,
78
                                int          remove)
79
145k
{       /* The -8 is for XXXXXX plus a possible final / and -. */
80
#ifdef GS_NO_FILESYSTEM
81
    return NULL;
82
#else
83
145k
    int prefix_length = strlen(prefix);
84
145k
    int len = gp_file_name_sizeof - prefix_length - 8;
85
145k
    FILE *fp;
86
87
145k
    if (gp_file_name_is_absolute(prefix, prefix_length))
88
0
        *fname = 0;
89
145k
    else if (gp_gettmpdir(fname, &len) != 0)
90
145k
        strcpy(fname, "/tmp/");
91
0
    else {
92
0
        if (strlen(fname) != 0 && fname[strlen(fname) - 1] != '/')
93
0
            strcat(fname, "/");
94
0
    }
95
145k
    if (strlen(fname) + prefix_length + 8 >= gp_file_name_sizeof)
96
0
        return 0;               /* file name too long */
97
145k
    strcat(fname, prefix);
98
    /* Prevent trailing X's in path from being converted by mktemp. */
99
145k
    if (*fname != 0 && fname[strlen(fname) - 1] == 'X')
100
0
        strcat(fname, "-");
101
145k
    strcat(fname, "XXXXXX");
102
103
145k
#ifdef HAVE_MKSTEMP
104
145k
    {
105
145k
        int file;
106
145k
        char ofname[gp_file_name_sizeof];
107
108
        /* save the old filename template in case mkstemp fails */
109
145k
        memcpy(ofname, fname, gp_file_name_sizeof);
110
145k
#  ifdef HAVE_MKSTEMP64
111
145k
        file = mkstemp64(fname);
112
#  else
113
        file = mkstemp(fname);
114
#  endif
115
145k
        if (file < 0) {
116
0
            emprintf1(mem, "**** Could not open temporary file %s\n", ofname);
117
0
            return NULL;
118
0
        }
119
#  if defined(O_LARGEFILE) && defined(__hpux)
120
        fcntl(file, F_SETFD, fcntl(file, F_GETFD) | O_LARGEFILE);
121
#  else
122
        /* Fixme : what to do with b64 and 32-bit mkstemp? Unimplemented. */
123
145k
#  endif
124
125
145k
        fp = fdopen(file, mode);
126
145k
        if (fp == NULL) {
127
0
      close(file);
128
0
  }
129
145k
    }
130
#else
131
    /* Coverity thinks that any use of mktemp() is insecure. But if we
132
    reach here then there is no mkstemp() alternative available, so there's
133
    not much we can do. Haven't been able to disable this - e.g. '//
134
    coverity[SECURE_TEMP]' doesn't have any affect. */
135
    mktemp(fname);
136
    fp = gp_fopentemp(fname, mode);
137
#endif
138
145k
    if (fp == NULL)
139
0
        emprintf1(mem, "**** Could not open temporary file %s\n", fname);
140
141
145k
    if (remove)
142
40.7k
        unlink(fname); /* unlink, not gp_unlink here. */
143
144
145k
    return fp;
145
145k
#endif
146
145k
}
147
148
/* Open a file with the given name, as a stream of uninterpreted bytes. */
149
FILE *
150
gp_fopen_impl(gs_memory_t *mem, const char *fname, const char *mode)
151
21.2M
{
152
21.2M
#if defined(HAVE_FILE64)
153
21.2M
    return fopen64(fname, mode);
154
#else
155
    return fopen(fname, mode);
156
#endif
157
21.2M
}
158
159
int
160
gp_unlink_impl(gs_memory_t *mem, const char *fname)
161
187k
{
162
187k
    return unlink(fname);
163
187k
}
164
165
int
166
gp_rename_impl(gs_memory_t *mem, const char *from, const char *to)
167
0
{
168
0
    return rename(from, to);
169
0
}
170
171
int gp_stat_impl(const gs_memory_t *mem, const char *path, struct stat *buf)
172
0
{
173
0
    return stat(path, buf);
174
0
}
175
176
int gp_can_share_fdesc(void)
177
194M
{
178
194M
#if defined(HAVE_PREAD_PWRITE) && HAVE_PREAD_PWRITE == 1
179
194M
    return 1;
180
#else
181
    return 0; /* can't share FILE * descriptors w/o pread due to seek..read..seek */
182
#endif
183
194M
}
184
185
FILE *gp_fdup_impl(FILE *f, const char *mode)
186
0
{
187
#ifdef GS_NO_FILESYSTEM
188
    return NULL;
189
#else
190
0
    int fd = fileno(f);
191
0
    if (fd < 0)
192
0
        return NULL;
193
0
    fd = dup(fd);
194
0
    if (fd < 0)
195
0
        return NULL;
196
0
    return fdopen(fd, mode);
197
0
#endif
198
0
}
199
200
int gp_pread_impl(char *buf, size_t count, gs_offset_t offset, FILE *f)
201
1.02M
{
202
#ifdef GS_NO_FILESYSTEM
203
    return 0;
204
#elif defined(HAVE_PREAD_PWRITE) && HAVE_PREAD_PWRITE == 1
205
1.02M
    return pread(fileno(f), buf, count, offset);
206
#else
207
    int c;
208
    int64_t os, curroff = gp_ftell_impl(f);
209
    if (curroff < 0) return curroff;
210
211
    os = gp_fseek_impl(f, offset, 0);
212
    if (os < 0) return os;
213
214
    c = fread(buf, 1, count, f);
215
    if (c < 0) return c;
216
217
    os = gp_fseek_impl(f, curroff, 0);
218
    if (os < 0) return os;
219
220
    return c;
221
#endif
222
1.02M
}
223
224
int gp_pwrite_impl(const char *buf, size_t count, gs_offset_t offset, FILE *f)
225
6.28M
{
226
#ifdef GS_NO_FILESYSTEM
227
    return 0;
228
#elif defined(HAVE_PREAD_PWRITE) && HAVE_PREAD_PWRITE == 1
229
6.28M
    return pwrite(fileno(f), buf, count, offset);
230
#else
231
    int c;
232
    int64_t os, curroff = gp_ftell_impl(f);
233
    if (curroff < 0) return curroff;
234
235
    os = gp_fseek_impl(f, offset, 0);
236
    if (os < 0) return os;
237
238
    c = fwrite(buf, 1, count, f);
239
    if (c < 0) return c;
240
241
    os = gp_fseek_impl(f, curroff, 0);
242
    if (os < 0) return os;
243
244
    return c;
245
#endif
246
6.28M
}
247
248
/* Set a file into binary or text mode. */
249
int
250
gp_setmode_binary_impl(FILE * pfile, bool mode) /* lgtm [cpp/useless-expression] */
251
34.9k
{
252
34.9k
    return 0;                   /* Noop under Unix */
253
34.9k
}
254
255
/* ------ File enumeration ------ */
256
257
/* Thanks to Fritz Elfert (Fritz_Elfert@wue.maus.de) for */
258
/* the original version of the following code, and Richard Mlynarik */
259
/* (mly@adoc.xerox.com) for an improved version. */
260
261
#ifdef GS_NO_FILESYSTEM
262
struct file_enum_s {
263
    int dummy;
264
};
265
266
static file_enum dummy_enum;
267
#else
268
typedef struct dirstack_s dirstack;
269
struct dirstack_s {
270
    dirstack *next;
271
    DIR *entry;
272
};
273
274
gs_private_st_ptrs1(st_dirstack, dirstack, "dirstack",
275
                    dirstack_enum_ptrs, dirstack_reloc_ptrs, next);
276
277
struct file_enum_s {
278
    DIR *dirp;                  /* pointer to current open directory   */
279
    char *pattern;              /* original pattern                    */
280
    char *work;                 /* current path                        */
281
    int worklen;                /* strlen (work)                       */
282
    dirstack *dstack;           /* directory stack                     */
283
    int patlen;
284
    int pathead;                /* how much of pattern to consider
285
                                 *  when listing files in current directory */
286
    bool first_time;
287
    gs_memory_t *memory;
288
};
289
gs_private_st_ptrs3(st_file_enum, struct file_enum_s, "file_enum",
290
          file_enum_enum_ptrs, file_enum_reloc_ptrs, pattern, work, dstack);
291
292
/* Private procedures */
293
294
/* Do a wild-card match. */
295
#ifdef DEBUG
296
static bool
297
wmatch(const byte * str, uint len, const byte * pstr, uint plen,
298
       const gs_memory_t *mem)
299
{
300
    bool match = string_match(str, len, pstr, plen, NULL);
301
302
    if (gs_debug_c('e')) {
303
        int i;
304
        dmlputs(mem, "[e]string_match(\"");
305
        for (i=0; i<len; i++)
306
            errprintf(mem, "%c", str[i]);
307
        dmputs(mem, "\", \"");
308
        for (i=0; i<plen; i++)
309
            errprintf(mem, "%c", pstr[i]);
310
        dmprintf1(mem, "\") = %s\n", (match ? "TRUE" : "false"));
311
    }
312
    return match;
313
}
314
#else
315
0
#define wmatch(S,L,PS,PL,M) string_match(S,L,PS,PL,NULL)
316
#endif
317
318
/* Search a string backward for a character. */
319
/* (This substitutes for strrchr, which some systems don't provide.) */
320
static char *
321
rchr(char *str, char ch, int len)
322
981k
{
323
981k
    register char *p = str + len;
324
325
1.96M
    while (p > str)
326
1.96M
        if (*--p == ch)
327
981k
            return p;
328
0
    return 0;
329
981k
}
330
331
/* Pop a directory from the enumeration stack. */
332
static bool
333
popdir(file_enum * pfen)
334
981k
{
335
981k
    dirstack *d = pfen->dstack;
336
337
981k
    if (d == 0)
338
981k
        return false;
339
0
    pfen->dirp = d->entry;
340
0
    pfen->dstack = d->next;
341
0
    gs_free_object(pfen->memory, d, "gp_enumerate_files(popdir)");
342
0
    return true;
343
981k
}
344
#endif
345
346
/* Initialize an enumeration. */
347
file_enum *
348
gp_enumerate_files_init_impl(gs_memory_t * mem, const char *pat, uint patlen)
349
981k
{
350
#ifdef GS_NO_FILESYSTEM
351
    return &dummy_enum;
352
#else
353
981k
    file_enum *pfen;
354
981k
    char *p;
355
981k
    char *work;
356
357
    /* Reject attempts to enumerate paths longer than the */
358
    /* system-dependent limit. */
359
981k
    if (patlen > FILENAME_MAX)
360
0
        return 0;
361
362
    /* Reject attempts to enumerate with a pattern containing zeroes. */
363
981k
    {
364
981k
        const char *p1;
365
366
47.9M
        for (p1 = pat; p1 < pat + patlen; p1++)
367
46.9M
            if (*p1 == 0)
368
0
                return 0;
369
981k
    }
370
    /* >>> Should crunch strings of repeated "/"'s in pat to a single "/"
371
     * >>>  to match stupid unix filesystem "conventions" */
372
373
981k
    pfen = gs_alloc_struct(mem, file_enum, &st_file_enum,
374
981k
                           "gp_enumerate_files");
375
981k
    if (pfen == 0)
376
0
        return 0;
377
378
    /* pattern and work could be allocated as strings, */
379
    /* but it's simpler for GC and freeing to allocate them as bytes. */
380
381
981k
    pfen->memory = mem;
382
981k
    pfen->dstack = 0;
383
981k
    pfen->first_time = true;
384
981k
    pfen->patlen = patlen;
385
981k
    pfen->work = 0;
386
981k
    pfen->pattern =
387
981k
        (char *)gs_alloc_bytes(mem, patlen + 1,
388
981k
                               "gp_enumerate_files(pattern)");
389
981k
    if (pfen->pattern == 0)
390
0
        goto fail1;
391
981k
    memcpy(pfen->pattern, pat, patlen);
392
981k
    pfen->pattern[patlen] = 0;
393
394
981k
    work = (char *)gs_alloc_bytes(mem, FILENAME_MAX + 1,
395
981k
                                  "gp_enumerate_files(work)");
396
981k
    if (work == 0)
397
0
        goto fail2;
398
981k
    pfen->work = work;
399
400
981k
    p = work;
401
981k
    memcpy(p, pat, patlen);
402
981k
    p += patlen;
403
981k
    *p = 0;
404
405
    /* Remove directory specifications beyond the first wild card. */
406
    /* Some systems don't have strpbrk, so we code it open. */
407
981k
    p = pfen->work;
408
46.9M
    while (*p != '*' && *p != '?' && *p != 0)
409
45.9M
        p++;
410
1.96M
    while (*p != '/' && *p != 0)
411
981k
        p++;
412
981k
    if (*p == '/')
413
0
        *p = 0;
414
    /* Substring for first wildcard match */
415
981k
    pfen->pathead = p - work;
416
417
    /* Select the next higher directory-level. */
418
981k
    p = rchr(work, '/', p - work);
419
981k
    if (!p) {                   /* No directory specification */
420
0
        work[0] = 0;
421
0
        pfen->worklen = 0;
422
981k
    } else {
423
981k
        if (p == work) {        /* Root directory -- don't turn "/" into "" */
424
0
            p++;
425
0
        }
426
981k
        *p = 0;
427
981k
        pfen->worklen = p - work;
428
981k
    }
429
430
981k
    return pfen;
431
432
0
fail2:
433
0
    gs_free_object(mem, pfen->pattern, "gp_enumerate_files(pattern)");
434
0
fail1:
435
0
    gs_free_object(mem, pfen, "gp_enumerate_files");
436
0
    return NULL;
437
0
#endif
438
0
}
439
440
/* Enumerate the next file. */
441
uint
442
gp_enumerate_files_next_impl(gs_memory_t * mem, file_enum * pfen, char *ptr, uint maxlen)
443
981k
{
444
#ifdef GS_NO_FILESYSTEM
445
    return ~(uint)0;
446
#else
447
981k
    const dir_entry *de;
448
981k
    char *work = pfen->work;
449
981k
    int worklen = pfen->worklen;
450
981k
    char *pattern = pfen->pattern;
451
981k
    int pathead = pfen->pathead;
452
981k
    int len;
453
454
981k
    if (pfen->first_time) {
455
981k
        pfen->dirp = ((worklen == 0) ? opendir(".") : opendir(work));
456
981k
        if_debug1m('e', pfen->memory, "[e]file_enum:First-Open '%s'\n", work);
457
981k
        pfen->first_time = false;
458
981k
        if (pfen->dirp == 0) {  /* first opendir failed */
459
981k
            gp_enumerate_files_close(mem, pfen);
460
981k
            return ~(uint) 0;
461
981k
        }
462
981k
    }
463
0
  top:de = readdir(pfen->dirp);
464
0
    if (de == 0) {              /* No more entries in this directory */
465
0
        char *p;
466
467
0
        if_debug0m('e', pfen->memory, "[e]file_enum:Closedir\n");
468
0
        closedir(pfen->dirp);
469
        /* Back working directory and matching pattern up one level */
470
0
        p = rchr(work, '/', worklen);
471
0
        if (p != 0) {
472
0
            if (p == work)
473
0
                p++;
474
0
            *p = 0;
475
0
            worklen = p - work;
476
0
        } else
477
0
            worklen = 0;
478
0
        if (pathead != pfen->patlen) {
479
0
            p = rchr(pattern, '/', pathead);
480
0
            if (p != 0)
481
0
                pathead = p - pattern;
482
0
            else
483
0
                pathead = 0;
484
0
        }
485
486
0
        if (popdir(pfen)) {     /* Back up the directory tree. */
487
0
            if_debug1m('e', pfen->memory, "[e]file_enum:Dir popped '%s'\n", work);
488
0
            goto top;
489
0
        } else {
490
0
            if_debug0m('e', pfen->memory, "[e]file_enum:Dirstack empty\n");
491
0
            gp_enumerate_files_close(mem, pfen);
492
0
            return ~(uint) 0;
493
0
        }
494
0
    }
495
    /* Skip . and .. */
496
0
    len = strlen(de->d_name);
497
0
    if (len <= 2 && (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")))
498
0
        goto top;
499
0
    if (len + worklen + 1 > FILENAME_MAX)
500
        /* Should be an error, I suppose */
501
0
        goto top;
502
0
    if (worklen == 0) {         /* "Current" directory (evil un*x kludge) */
503
0
        memcpy(work, de->d_name, len + 1);
504
0
    } else if (worklen == 1 && work[0] == '/') {        /* Root directory */
505
0
        memcpy(work + 1, de->d_name, len + 1);
506
0
        len = len + 1;
507
0
    } else {
508
0
        work[worklen] = '/';
509
0
        memcpy(work + worklen + 1, de->d_name, len + 1);
510
0
        len = worklen + 1 + len;
511
0
    }
512
513
    /* Test for a match at this directory level */
514
0
    if (!wmatch((byte *) work, len, (byte *) pattern, pathead, pfen->memory))
515
0
        goto top;
516
517
    /* Perhaps descend into subdirectories */
518
0
    if (pathead < maxlen) {
519
        /* Using stat() to decide whether this item is a directory then opening
520
        using opendir(), results in Coverity complaining about races. Se
521
        instead we simple call opendir() immediately and look at whether it
522
        succeeded. */
523
0
        DIR *dp = opendir(work);
524
0
        if (!dp) {
525
            /* Not a directory. */
526
0
            goto winner;
527
0
        }
528
529
0
        if (pfen->patlen == pathead + 1) {      /* Listing "foo/?/" -- return this entry */
530
0
            closedir(dp);
531
0
            work[len++] = '/';
532
0
            goto winner;
533
0
        }
534
535
        /* >>> Should optimise the case in which the next level */
536
        /* >>> of directory has no wildcards. */
537
#ifdef DEBUG
538
        {
539
            char save_end = pattern[pathead];
540
541
            pattern[pathead] = 0;
542
            if_debug2m('e', pfen->memory, "[e]file_enum:fname='%s', p='%s'\n",
543
                       work, pattern);
544
            pattern[pathead] = save_end;
545
        }
546
#endif /* DEBUG */
547
0
        {                  /* Advance to the next directory-delimiter */
548
            /* in pattern */
549
0
            char *p;
550
0
            dirstack *d;
551
552
0
      if (pattern[pathead] == 0) {
553
0
                pathead = pfen->patlen;
554
0
      } else {
555
0
                for (p = pattern + pathead + 1;; p++) {
556
0
                    if (*p == 0) {  /* No more subdirectories to match */
557
0
                        pathead = pfen->patlen;
558
0
                        break;
559
0
                    } else if (*p == '/') {
560
0
                        pathead = p - pattern;
561
0
                        break;
562
0
                    }
563
0
                }
564
0
            }
565
566
            /* Push a directory onto the enumeration stack. */
567
0
            d = gs_alloc_struct(pfen->memory, dirstack,
568
0
                                &st_dirstack,
569
0
                                "gp_enumerate_files(pushdir)");
570
0
            if (d != 0) {
571
0
                d->next = pfen->dstack;
572
0
                d->entry = pfen->dirp;
573
0
                pfen->dstack = d;
574
0
            } else
575
0
                DO_NOTHING;     /* >>> gs_error_VMerror!!! */
576
577
0
            if_debug1m('e', pfen->memory, "[e]file_enum:Dir pushed '%s'\n",
578
0
                       work);
579
0
            worklen = len;
580
0
            pfen->dirp = dp;
581
0
            goto top;
582
0
        }
583
0
    }
584
0
  winner:
585
    /* We have a winner! */
586
0
    pfen->worklen = worklen;
587
0
    pfen->pathead = pathead;
588
0
    memcpy(ptr, work, len > maxlen ? maxlen : len);
589
590
0
    return len;
591
0
#endif
592
0
}
593
594
/* Clean up the file enumeration. */
595
void
596
gp_enumerate_files_close_impl(gs_memory_t * mem, file_enum * pfen)
597
981k
{
598
#ifdef GS_NO_FILESYSTEM
599
    /* No cleanup necessary */
600
#else
601
981k
    gs_memory_t *mem2 = pfen->memory;
602
981k
    (void)mem;
603
604
981k
    if_debug0m('e', mem2, "[e]file_enum:Cleanup\n");
605
981k
    while (popdir(pfen))        /* clear directory stack */
606
981k
        DO_NOTHING;
607
981k
    gs_free_object(mem2, (byte *) pfen->work,
608
981k
                   "gp_enumerate_close(work)");
609
981k
    gs_free_object(mem2, (byte *) pfen->pattern,
610
981k
                   "gp_enumerate_files_close(pattern)");
611
981k
    gs_free_object(mem2, pfen, "gp_enumerate_files_close");
612
981k
#endif
613
981k
}
614
615
/* Test-cases:
616
   (../?*r*?/?*.ps) {==} 100 string filenameforall
617
   (../?*r*?/?*.ps*) {==} 100 string filenameforall
618
   (../?*r*?/) {==} 100 string filenameforall
619
   (/t*?/?*.ps) {==} 100 string filenameforall
620
 */
621
622
/* --------- 64 bit file access ----------- */
623
624
gs_offset_t gp_ftell_impl(FILE *strm)
625
890k
{
626
890k
#if defined(HAVE_FILE64)
627
890k
    return ftello64(strm);
628
#else
629
    return ftello(strm);
630
#endif
631
890k
}
632
633
int gp_fseek_impl(FILE *strm, gs_offset_t offset, int origin)
634
7.60M
{
635
7.60M
#if defined(HAVE_FILE64)
636
7.60M
    return fseeko64(strm, offset, origin);
637
#else
638
    off_t offset1 = (off_t)offset;
639
640
    if (offset != offset1)
641
        return -1;
642
    return fseeko(strm, offset1, origin);
643
#endif
644
7.60M
}
645
646
bool gp_fseekable_impl(FILE *f)
647
0
{
648
0
    struct stat s;
649
0
    int fno;
650
651
0
    fno = fileno(f);
652
0
    if (fno < 0)
653
0
        return(false);
654
655
0
    if (fstat(fno, &s) < 0)
656
0
        return(false);
657
658
0
    return((bool)S_ISREG(s.st_mode));
659
0
}