Coverage Report

Created: 2026-06-02 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/standard/filestat.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright © The PHP Group and Contributors.                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Author:  Jim Winstead <jimw@php.net>                                 |
12
   +----------------------------------------------------------------------+
13
 */
14
15
#include "php.h"
16
#include "fopen_wrappers.h"
17
#include "php_globals.h"
18
19
#include <stdlib.h>
20
#include <sys/stat.h>
21
#include <string.h>
22
#include <errno.h>
23
#include <ctype.h>
24
#include <time.h>
25
26
#ifdef HAVE_UNISTD_H
27
# include <unistd.h>
28
#endif
29
30
#ifdef HAVE_SYS_PARAM_H
31
# include <sys/param.h>
32
#endif
33
34
#ifdef HAVE_SYS_VFS_H
35
# include <sys/vfs.h>
36
#endif
37
38
#if defined(__APPLE__)
39
  /*
40
   Apple statvfs has an integer overflow in libc copying to statvfs.
41
   cvt_statfs_to_statvfs(struct statfs *from, struct statvfs *to) {
42
   to->f_blocks = (fsblkcnt_t)from->f_blocks;
43
   */
44
#  undef HAVE_SYS_STATVFS_H
45
#  undef HAVE_STATVFS
46
#endif
47
48
#if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
49
# include <sys/statvfs.h>
50
#elif defined(HAVE_SYS_STATFS_H) && defined(HAVE_STATFS)
51
# include <sys/statfs.h>
52
#elif defined(HAVE_SYS_MOUNT_H) && defined(HAVE_STATFS)
53
# include <sys/mount.h>
54
#endif
55
56
#ifdef HAVE_PWD_H
57
# ifdef PHP_WIN32
58
#  include "win32/pwd.h"
59
# else
60
#  include <pwd.h>
61
# endif
62
#endif
63
64
#ifdef HAVE_GRP_H
65
# include <grp.h>
66
#endif
67
68
#ifdef HAVE_UTIME
69
# ifdef PHP_WIN32
70
#  include <sys/utime.h>
71
# else
72
#  include <utime.h>
73
# endif
74
#endif
75
76
#ifdef PHP_WIN32
77
#include "win32/winutil.h"
78
#endif
79
80
#include "basic_functions.h"
81
#include "php_filestat.h"
82
83
PHP_RINIT_FUNCTION(filestat) /* {{{ */
84
44.4k
{
85
44.4k
  BG(CurrentStatFile)=NULL;
86
44.4k
  BG(CurrentLStatFile)=NULL;
87
44.4k
  return SUCCESS;
88
44.4k
}
89
/* }}} */
90
91
PHP_RSHUTDOWN_FUNCTION(filestat) /* {{{ */
92
44.4k
{
93
44.4k
  if (BG(CurrentStatFile)) {
94
0
    zend_string_release(BG(CurrentStatFile));
95
0
    BG(CurrentStatFile) = NULL;
96
0
  }
97
44.4k
  if (BG(CurrentLStatFile)) {
98
0
    zend_string_release(BG(CurrentLStatFile));
99
0
    BG(CurrentLStatFile) = NULL;
100
0
  }
101
44.4k
  return SUCCESS;
102
44.4k
}
103
/* }}} */
104
105
static zend_result php_disk_total_space(char *path, double *space) /* {{{ */
106
#if defined(PHP_WIN32) /* {{{ */
107
{
108
  ULARGE_INTEGER FreeBytesAvailableToCaller;
109
  ULARGE_INTEGER TotalNumberOfBytes;
110
  ULARGE_INTEGER TotalNumberOfFreeBytes;
111
  PHP_WIN32_IOUTIL_INIT_W(path)
112
113
  if (GetDiskFreeSpaceExW(pathw, &FreeBytesAvailableToCaller, &TotalNumberOfBytes, &TotalNumberOfFreeBytes) == 0) {
114
    char *err = php_win_err();
115
    php_error_docref(NULL, E_WARNING, "%s", err);
116
    php_win_err_free(err);
117
    PHP_WIN32_IOUTIL_CLEANUP_W()
118
    return FAILURE;
119
  }
120
121
  /* i know - this is ugly, but i works <thies@thieso.net> */
122
  *space = TotalNumberOfBytes.HighPart * (double) (((zend_ulong)1) << 31) * 2.0 + TotalNumberOfBytes.LowPart;
123
124
  PHP_WIN32_IOUTIL_CLEANUP_W()
125
126
  return SUCCESS;
127
}
128
/* }}} */
129
#else /* {{{ if !defined(PHP_WIN32) */
130
0
{
131
0
  double bytestotal = 0;
132
0
#if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
133
0
  struct statvfs buf;
134
#elif (defined(HAVE_SYS_STATFS_H) || defined(HAVE_SYS_MOUNT_H)) && defined(HAVE_STATFS)
135
  struct statfs buf;
136
#endif
137
138
0
#if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
139
0
  if (statvfs(path, &buf)) {
140
0
    php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
141
0
    return FAILURE;
142
0
  }
143
0
  if (buf.f_frsize) {
144
0
    bytestotal = (((double)buf.f_blocks) * ((double)buf.f_frsize));
145
0
  } else {
146
0
    bytestotal = (((double)buf.f_blocks) * ((double)buf.f_bsize));
147
0
  }
148
149
#elif (defined(HAVE_SYS_STATFS_H) || defined(HAVE_SYS_MOUNT_H)) && defined(HAVE_STATFS)
150
  if (statfs(path, &buf)) {
151
    php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
152
    return FAILURE;
153
  }
154
  bytestotal = (((double)buf.f_bsize) * ((double)buf.f_blocks));
155
#endif
156
157
0
  *space = bytestotal;
158
0
  return SUCCESS;
159
0
}
160
#endif
161
/* }}} */
162
/* }}} */
163
164
/* {{{ Get total disk space for filesystem that path is on */
165
PHP_FUNCTION(disk_total_space)
166
0
{
167
0
  double bytestotal;
168
0
  char *path, fullpath[MAXPATHLEN];
169
0
  size_t path_len;
170
171
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
172
0
    Z_PARAM_PATH(path, path_len)
173
0
  ZEND_PARSE_PARAMETERS_END();
174
175
0
  if (!expand_filepath(path, fullpath)) {
176
0
    RETURN_FALSE;
177
0
  }
178
179
0
  if (php_check_open_basedir(fullpath)) {
180
0
    RETURN_FALSE;
181
0
  }
182
183
0
  if (php_disk_total_space(fullpath, &bytestotal) == SUCCESS) {
184
0
    RETURN_DOUBLE(bytestotal);
185
0
  }
186
0
  RETURN_FALSE;
187
0
}
188
/* }}} */
189
190
static zend_result php_disk_free_space(char *path, double *space) /* {{{ */
191
#if defined(PHP_WIN32) /* {{{ */
192
{
193
  ULARGE_INTEGER FreeBytesAvailableToCaller;
194
  ULARGE_INTEGER TotalNumberOfBytes;
195
  ULARGE_INTEGER TotalNumberOfFreeBytes;
196
  PHP_WIN32_IOUTIL_INIT_W(path)
197
198
  if (GetDiskFreeSpaceExW(pathw, &FreeBytesAvailableToCaller, &TotalNumberOfBytes, &TotalNumberOfFreeBytes) == 0) {
199
    char *err = php_win_err();
200
    php_error_docref(NULL, E_WARNING, "%s", err);
201
    php_win_err_free(err);
202
    PHP_WIN32_IOUTIL_CLEANUP_W()
203
    return FAILURE;
204
  }
205
206
  *space = FreeBytesAvailableToCaller.HighPart * (double) (1ULL << 32)  + FreeBytesAvailableToCaller.LowPart;
207
208
  PHP_WIN32_IOUTIL_CLEANUP_W()
209
210
  return SUCCESS;
211
}
212
#else /* {{{ if !defined(PHP_WIN32) */
213
0
{
214
0
  double bytesfree = 0;
215
0
#if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
216
0
  struct statvfs buf;
217
#elif (defined(HAVE_SYS_STATFS_H) || defined(HAVE_SYS_MOUNT_H)) && defined(HAVE_STATFS)
218
  struct statfs buf;
219
#endif
220
221
0
#if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
222
0
  if (statvfs(path, &buf)) {
223
0
    php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
224
0
    return FAILURE;
225
0
  }
226
0
  if (buf.f_frsize) {
227
0
    bytesfree = (((double)buf.f_bavail) * ((double)buf.f_frsize));
228
0
  } else {
229
0
    bytesfree = (((double)buf.f_bavail) * ((double)buf.f_bsize));
230
0
  }
231
#elif (defined(HAVE_SYS_STATFS_H) || defined(HAVE_SYS_MOUNT_H)) && defined(HAVE_STATFS)
232
  if (statfs(path, &buf)) {
233
    php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
234
    return FAILURE;
235
  }
236
  bytesfree = (((double)buf.f_bsize) * ((double)buf.f_bavail));
237
#endif
238
239
0
  *space = bytesfree;
240
0
  return SUCCESS;
241
0
}
242
#endif
243
/* }}} */
244
/* }}} */
245
246
/* {{{ Get free disk space for filesystem that path is on */
247
PHP_FUNCTION(disk_free_space)
248
0
{
249
0
  double bytesfree;
250
0
  char *path, fullpath[MAXPATHLEN];
251
0
  size_t path_len;
252
253
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
254
0
    Z_PARAM_PATH(path, path_len)
255
0
  ZEND_PARSE_PARAMETERS_END();
256
257
0
  if (!expand_filepath(path, fullpath)) {
258
0
    RETURN_FALSE;
259
0
  }
260
261
0
  if (php_check_open_basedir(fullpath)) {
262
0
    RETURN_FALSE;
263
0
  }
264
265
0
  if (php_disk_free_space(fullpath, &bytesfree) == SUCCESS) {
266
0
    RETURN_DOUBLE(bytesfree);
267
0
  }
268
0
  RETURN_FALSE;
269
0
}
270
/* }}} */
271
272
#ifndef PHP_WIN32
273
PHPAPI zend_result php_get_gid_by_name(const char *name, gid_t *gid)
274
0
{
275
#if defined(ZTS) && defined(HAVE_GETGRNAM_R) && defined(_SC_GETGR_R_SIZE_MAX)
276
    struct group gr;
277
    struct group *retgrptr;
278
    long grbuflen = sysconf(_SC_GETGR_R_SIZE_MAX);
279
    char *grbuf;
280
    int err;
281
282
    if (grbuflen < 1) {
283
      grbuflen = 1024;
284
    }
285
# if ZEND_DEBUG
286
    /* Test retry logic */
287
    grbuflen = 1;
288
# endif
289
    grbuf = emalloc(grbuflen);
290
291
try_again:
292
    err = getgrnam_r(name, &gr, grbuf, grbuflen, &retgrptr);
293
    if (err != 0 || retgrptr == NULL) {
294
      if (err == ERANGE) {
295
        grbuflen *= 2;
296
        grbuf = erealloc(grbuf, grbuflen);
297
        goto try_again;
298
      }
299
      efree(grbuf);
300
      return FAILURE;
301
    }
302
    efree(grbuf);
303
    *gid = gr.gr_gid;
304
#else
305
0
    struct group *gr = getgrnam(name);
306
307
0
    if (!gr) {
308
0
      return FAILURE;
309
0
    }
310
0
    *gid = gr->gr_gid;
311
0
#endif
312
0
    return SUCCESS;
313
0
}
314
#endif
315
316
static void php_do_chgrp(INTERNAL_FUNCTION_PARAMETERS, int do_lchgrp) /* {{{ */
317
0
{
318
0
  char *filename;
319
0
  size_t filename_len;
320
0
  zend_string *group_str;
321
0
  zend_long group_long;
322
0
#if !defined(PHP_WIN32)
323
0
  gid_t gid;
324
0
  int ret;
325
0
#endif
326
0
  php_stream_wrapper *wrapper;
327
328
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
329
0
    Z_PARAM_PATH(filename, filename_len)
330
0
    Z_PARAM_STR_OR_LONG(group_str, group_long)
331
0
  ZEND_PARSE_PARAMETERS_END();
332
333
0
  wrapper = php_stream_locate_url_wrapper(filename, NULL, 0);
334
0
  if(wrapper != &php_plain_files_wrapper || strncasecmp("file://", filename, 7) == 0) {
335
0
    if(wrapper && wrapper->wops->stream_metadata) {
336
0
      int option;
337
0
      void *value;
338
0
      if (group_str) {
339
0
        option = PHP_STREAM_META_GROUP_NAME;
340
0
        value = ZSTR_VAL(group_str);
341
0
      } else {
342
0
        option = PHP_STREAM_META_GROUP;
343
0
        value = &group_long;
344
0
      }
345
346
0
      if(wrapper->wops->stream_metadata(wrapper, filename, option, value, NULL)) {
347
0
        RETURN_TRUE;
348
0
      } else {
349
0
        RETURN_FALSE;
350
0
      }
351
0
    } else {
352
0
#ifndef PHP_WIN32
353
/* On Windows, we expect regular chgrp to fail silently by default */
354
0
      php_error_docref(NULL, E_WARNING, "Cannot call chgrp() for a non-standard stream");
355
0
#endif
356
0
      RETURN_FALSE;
357
0
    }
358
0
  }
359
360
#ifdef PHP_WIN32
361
  /* We have no native chgrp on Windows, nothing left to do if stream doesn't have own implementation */
362
  RETURN_FALSE;
363
#else
364
0
  if (group_str) {
365
0
    if (php_get_gid_by_name(ZSTR_VAL(group_str), &gid) != SUCCESS) {
366
0
      php_error_docref(NULL, E_WARNING, "Unable to find gid for %s", ZSTR_VAL(group_str));
367
0
      RETURN_FALSE;
368
0
    }
369
0
  } else {
370
0
    gid = (gid_t) group_long;
371
0
  }
372
373
  /* Check the basedir */
374
0
  if (php_check_open_basedir(filename)) {
375
0
    RETURN_FALSE;
376
0
  }
377
378
0
  if (do_lchgrp) {
379
0
#ifdef HAVE_LCHOWN
380
0
    ret = VCWD_LCHOWN(filename, -1, gid);
381
0
#endif
382
0
  } else {
383
0
    ret = VCWD_CHOWN(filename, -1, gid);
384
0
  }
385
0
  if (ret == -1) {
386
0
    php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
387
0
    RETURN_FALSE;
388
0
  }
389
390
0
  php_clear_stat_cache(0, NULL, 0);
391
392
0
  RETURN_TRUE;
393
0
#endif
394
0
}
395
/* }}} */
396
397
/* {{{ Change file group */
398
PHP_FUNCTION(chgrp)
399
0
{
400
0
  php_do_chgrp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
401
0
}
402
/* }}} */
403
404
/* {{{ Change symlink group */
405
#ifdef HAVE_LCHOWN
406
PHP_FUNCTION(lchgrp)
407
0
{
408
0
  php_do_chgrp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
409
0
}
410
#endif
411
/* }}} */
412
413
#ifndef PHP_WIN32
414
PHPAPI zend_result php_get_uid_by_name(const char *name, uid_t *uid)
415
0
{
416
#if defined(ZTS) && defined(_SC_GETPW_R_SIZE_MAX) && defined(HAVE_GETPWNAM_R)
417
    struct passwd pw;
418
    struct passwd *retpwptr = NULL;
419
    long pwbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
420
    char *pwbuf;
421
    int err;
422
423
    if (pwbuflen < 1) {
424
      pwbuflen = 1024;
425
    }
426
# if ZEND_DEBUG
427
    /* Test retry logic */
428
    pwbuflen = 1;
429
# endif
430
    pwbuf = emalloc(pwbuflen);
431
432
try_again:
433
    err = getpwnam_r(name, &pw, pwbuf, pwbuflen, &retpwptr);
434
    if (err != 0 || retpwptr == NULL) {
435
      if (err == EAGAIN) {
436
        pwbuflen *= 2;
437
        pwbuf = erealloc(pwbuf, pwbuflen);
438
        goto try_again;
439
      }
440
      efree(pwbuf);
441
      return FAILURE;
442
    }
443
    efree(pwbuf);
444
    *uid = pw.pw_uid;
445
#else
446
0
    struct passwd *pw = getpwnam(name);
447
448
0
    if (!pw) {
449
0
      return FAILURE;
450
0
    }
451
0
    *uid = pw->pw_uid;
452
0
#endif
453
0
    return SUCCESS;
454
0
}
455
#endif
456
457
static void php_do_chown(INTERNAL_FUNCTION_PARAMETERS, int do_lchown) /* {{{ */
458
0
{
459
0
  char *filename;
460
0
  size_t filename_len;
461
0
  zend_string *user_str;
462
0
  zend_long user_long;
463
0
#if !defined(PHP_WIN32)
464
0
  uid_t uid;
465
0
  int ret;
466
0
#endif
467
0
  php_stream_wrapper *wrapper;
468
469
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
470
0
    Z_PARAM_PATH(filename, filename_len)
471
0
    Z_PARAM_STR_OR_LONG(user_str, user_long)
472
0
  ZEND_PARSE_PARAMETERS_END();
473
474
0
  wrapper = php_stream_locate_url_wrapper(filename, NULL, 0);
475
0
  if(wrapper != &php_plain_files_wrapper || strncasecmp("file://", filename, 7) == 0) {
476
0
    if(wrapper && wrapper->wops->stream_metadata) {
477
0
      int option;
478
0
      void *value;
479
0
      if (user_str) {
480
0
        option = PHP_STREAM_META_OWNER_NAME;
481
0
        value = ZSTR_VAL(user_str);
482
0
      } else {
483
0
        option = PHP_STREAM_META_OWNER;
484
0
        value = &user_long;
485
0
      }
486
487
0
      if(wrapper->wops->stream_metadata(wrapper, filename, option, value, NULL)) {
488
0
        RETURN_TRUE;
489
0
      } else {
490
0
        RETURN_FALSE;
491
0
      }
492
0
    } else {
493
0
#ifndef PHP_WIN32
494
/* On Windows, we expect regular chown to fail silently by default */
495
0
      php_error_docref(NULL, E_WARNING, "Cannot call chown() for a non-standard stream");
496
0
#endif
497
0
      RETURN_FALSE;
498
0
    }
499
0
  }
500
501
#ifdef PHP_WIN32
502
  /* We have no native chown on Windows, nothing left to do if stream doesn't have own implementation */
503
  RETURN_FALSE;
504
#else
505
506
0
  if (user_str) {
507
0
    if (php_get_uid_by_name(ZSTR_VAL(user_str), &uid) != SUCCESS) {
508
0
      php_error_docref(NULL, E_WARNING, "Unable to find uid for %s", ZSTR_VAL(user_str));
509
0
      RETURN_FALSE;
510
0
    }
511
0
  } else {
512
0
    uid = (uid_t) user_long;
513
0
  }
514
515
  /* Check the basedir */
516
0
  if (php_check_open_basedir(filename)) {
517
0
    RETURN_FALSE;
518
0
  }
519
520
0
  if (do_lchown) {
521
0
#ifdef HAVE_LCHOWN
522
0
    ret = VCWD_LCHOWN(filename, uid, -1);
523
0
#endif
524
0
  } else {
525
0
    ret = VCWD_CHOWN(filename, uid, -1);
526
0
  }
527
0
  if (ret == -1) {
528
0
    php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
529
0
    RETURN_FALSE;
530
0
  }
531
532
0
  php_clear_stat_cache(0, NULL, 0);
533
534
0
  RETURN_TRUE;
535
0
#endif
536
0
}
537
/* }}} */
538
539
540
/* {{{ Change file owner */
541
PHP_FUNCTION(chown)
542
0
{
543
0
  php_do_chown(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
544
0
}
545
/* }}} */
546
547
/* {{{ Change file owner */
548
#ifdef HAVE_LCHOWN
549
PHP_FUNCTION(lchown)
550
0
{
551
0
  RETVAL_TRUE;
552
0
  php_do_chown(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
553
0
}
554
#endif
555
/* }}} */
556
557
/* {{{ Change file mode */
558
PHP_FUNCTION(chmod)
559
0
{
560
0
  char *filename;
561
0
  size_t filename_len;
562
0
  zend_long mode;
563
0
  int ret;
564
0
  mode_t imode;
565
0
  php_stream_wrapper *wrapper;
566
567
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
568
0
    Z_PARAM_PATH(filename, filename_len)
569
0
    Z_PARAM_LONG(mode)
570
0
  ZEND_PARSE_PARAMETERS_END();
571
572
0
  wrapper = php_stream_locate_url_wrapper(filename, NULL, 0);
573
0
  if(wrapper != &php_plain_files_wrapper || strncasecmp("file://", filename, 7) == 0) {
574
0
    if(wrapper && wrapper->wops->stream_metadata) {
575
0
      if(wrapper->wops->stream_metadata(wrapper, filename, PHP_STREAM_META_ACCESS, &mode, NULL)) {
576
0
        RETURN_TRUE;
577
0
      } else {
578
0
        RETURN_FALSE;
579
0
      }
580
0
    } else {
581
0
      php_error_docref(NULL, E_WARNING, "Cannot call chmod() for a non-standard stream");
582
0
      RETURN_FALSE;
583
0
    }
584
0
  }
585
586
  /* Check the basedir */
587
0
  if (php_check_open_basedir(filename)) {
588
0
    RETURN_FALSE;
589
0
  }
590
591
0
  imode = (mode_t) mode;
592
593
0
  ret = VCWD_CHMOD(filename, imode);
594
0
  if (ret == -1) {
595
0
    php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
596
0
    RETURN_FALSE;
597
0
  }
598
599
0
  php_clear_stat_cache(0, NULL, 0);
600
601
0
  RETURN_TRUE;
602
0
}
603
/* }}} */
604
605
#ifdef HAVE_UTIME
606
/* {{{ Set modification time of file */
607
PHP_FUNCTION(touch)
608
0
{
609
0
  char *filename;
610
0
  size_t filename_len;
611
0
  zend_long filetime = 0, fileatime = 0;
612
0
  bool filetime_is_null = 1, fileatime_is_null = 1;
613
0
  int ret;
614
0
  FILE *file;
615
0
  struct utimbuf newtimebuf;
616
0
  struct utimbuf *newtime = &newtimebuf;
617
0
  php_stream_wrapper *wrapper;
618
619
0
  ZEND_PARSE_PARAMETERS_START(1, 3)
620
0
    Z_PARAM_PATH(filename, filename_len)
621
0
    Z_PARAM_OPTIONAL
622
0
    Z_PARAM_LONG_OR_NULL(filetime, filetime_is_null)
623
0
    Z_PARAM_LONG_OR_NULL(fileatime, fileatime_is_null)
624
0
  ZEND_PARSE_PARAMETERS_END();
625
626
0
  if (!filename_len) {
627
0
    RETURN_FALSE;
628
0
  }
629
630
0
  if (filetime_is_null && fileatime_is_null) {
631
0
    newtime = NULL;
632
0
  } else if (!filetime_is_null && fileatime_is_null) {
633
0
    newtime->modtime = newtime->actime = filetime;
634
0
  } else if (filetime_is_null && !fileatime_is_null) {
635
0
    zend_argument_value_error(2, "cannot be null when argument #3 ($atime) is an integer");
636
0
    RETURN_THROWS();
637
0
  } else {
638
0
    newtime->modtime = filetime;
639
0
    newtime->actime = fileatime;
640
0
  }
641
642
0
  wrapper = php_stream_locate_url_wrapper(filename, NULL, 0);
643
0
  if(wrapper != &php_plain_files_wrapper || strncasecmp("file://", filename, 7) == 0) {
644
0
    if(wrapper && wrapper->wops->stream_metadata) {
645
0
      if(wrapper->wops->stream_metadata(wrapper, filename, PHP_STREAM_META_TOUCH, newtime, NULL)) {
646
0
        RETURN_TRUE;
647
0
      } else {
648
0
        RETURN_FALSE;
649
0
      }
650
0
    } else {
651
0
      php_stream *stream;
652
0
      if(!filetime_is_null || !fileatime_is_null) {
653
0
        php_error_docref(NULL, E_WARNING, "Cannot call touch() for a non-standard stream");
654
0
        RETURN_FALSE;
655
0
      }
656
0
      stream = php_stream_open_wrapper_ex(filename, "c", REPORT_ERRORS, NULL, NULL);
657
0
      if(stream != NULL) {
658
0
        php_stream_close(stream);
659
0
        RETURN_TRUE;
660
0
      } else {
661
0
        RETURN_FALSE;
662
0
      }
663
0
    }
664
0
  }
665
666
  /* Check the basedir */
667
0
  if (php_check_open_basedir(filename)) {
668
0
    RETURN_FALSE;
669
0
  }
670
671
  /* create the file if it doesn't exist already */
672
0
  if (VCWD_ACCESS(filename, F_OK) != 0) {
673
0
    file = VCWD_FOPEN(filename, "w");
674
0
    if (file == NULL) {
675
0
      php_error_docref(NULL, E_WARNING, "Unable to create file %s because %s", filename, strerror(errno));
676
0
      RETURN_FALSE;
677
0
    }
678
0
    fclose(file);
679
0
  }
680
681
0
  ret = VCWD_UTIME(filename, newtime);
682
0
  if (ret == -1) {
683
0
    php_error_docref(NULL, E_WARNING, "Utime failed: %s", strerror(errno));
684
0
    RETURN_FALSE;
685
0
  }
686
687
0
  php_clear_stat_cache(0, NULL, 0);
688
689
0
  RETURN_TRUE;
690
0
}
691
/* }}} */
692
#endif
693
694
/* {{{ php_clear_stat_cache() */
695
PHPAPI void php_clear_stat_cache(bool clear_realpath_cache, const char *filename, size_t filename_len)
696
84
{
697
  /* always clear CurrentStatFile and CurrentLStatFile even if filename is not NULL
698
   * as it may contain outdated data (e.g. "nlink" for a directory when deleting a file
699
   * in this directory, as shown by lstat_stat_variation9.phpt) */
700
84
  if (BG(CurrentStatFile)) {
701
0
    zend_string_release(BG(CurrentStatFile));
702
0
    BG(CurrentStatFile) = NULL;
703
0
  }
704
84
  if (BG(CurrentLStatFile)) {
705
0
    zend_string_release(BG(CurrentLStatFile));
706
0
    BG(CurrentLStatFile) = NULL;
707
0
  }
708
84
  if (clear_realpath_cache) {
709
0
    if (filename != NULL) {
710
0
      realpath_cache_del(filename, filename_len);
711
0
    } else {
712
0
      realpath_cache_clean();
713
0
    }
714
0
  }
715
84
}
716
/* }}} */
717
718
/* {{{ Clear file stat cache */
719
PHP_FUNCTION(clearstatcache)
720
0
{
721
0
  bool  clear_realpath_cache = 0;
722
0
  char      *filename             = NULL;
723
0
  size_t     filename_len         = 0;
724
725
0
  ZEND_PARSE_PARAMETERS_START(0, 2)
726
0
    Z_PARAM_OPTIONAL
727
0
    Z_PARAM_BOOL(clear_realpath_cache)
728
0
    Z_PARAM_PATH(filename, filename_len)
729
0
  ZEND_PARSE_PARAMETERS_END();
730
731
0
  php_clear_stat_cache(clear_realpath_cache, filename, filename_len);
732
0
}
733
/* }}} */
734
735
218
#define IS_LINK_OPERATION(__t) ((__t) == FS_TYPE || (__t) == FS_IS_LINK || (__t) == FS_LSTAT || (__t) == FS_LPERMS)
736
230
#define IS_EXISTS_CHECK(__t) ((__t) == FS_EXISTS  || (__t) == FS_IS_W || (__t) == FS_IS_R || (__t) == FS_IS_X || (__t) == FS_IS_FILE || (__t) == FS_IS_DIR || (__t) == FS_IS_LINK || (__t) == FS_LPERMS)
737
716
#define IS_ABLE_CHECK(__t) ((__t) == FS_IS_R || (__t) == FS_IS_W || (__t) == FS_IS_X)
738
200
#define IS_ACCESS_CHECK(__t) (IS_ABLE_CHECK(type) || (__t) == FS_EXISTS)
739
740
/* {{{ php_stat */
741
PHPAPI void php_stat(zend_string *filename, int type, zval *return_value)
742
200
{
743
200
  php_stream_statbuf ssb = {0};
744
200
  zend_stat_t *stat_sb = &ssb.sb;
745
200
  int flags = 0, rmask=S_IROTH, wmask=S_IWOTH, xmask=S_IXOTH; /* access rights defaults to other */
746
200
  const char *local = NULL;
747
200
  php_stream_wrapper *wrapper = NULL;
748
749
200
  if (IS_ACCESS_CHECK(type)) {
750
0
    if (!ZSTR_LEN(filename) || zend_str_has_nul_byte(filename)) {
751
0
      if (ZSTR_LEN(filename) && !IS_EXISTS_CHECK(type)) {
752
0
        php_error_docref(NULL, E_WARNING, "Filename contains null byte");
753
0
      }
754
0
      RETURN_FALSE;
755
0
    }
756
757
0
    if ((wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(filename), &local, 0)) == &php_plain_files_wrapper
758
0
        && php_check_open_basedir(local)) {
759
0
      RETURN_FALSE;
760
0
    }
761
762
0
    if (wrapper == &php_plain_files_wrapper) {
763
0
      char realpath[MAXPATHLEN];
764
0
      const char *file_path_to_check;
765
      /* if the wrapper is not found, we need to expand path to match open behavior */
766
0
      if (EXPECTED(!php_is_stream_path(local) || expand_filepath(local, realpath) == NULL)) {
767
0
        file_path_to_check = local;
768
0
      } else {
769
0
        file_path_to_check = realpath;
770
0
      }
771
0
      switch (type) {
772
0
#ifdef F_OK
773
0
        case FS_EXISTS:
774
0
          RETURN_BOOL(VCWD_ACCESS(file_path_to_check, F_OK) == 0);
775
0
#endif
776
0
#ifdef W_OK
777
0
        case FS_IS_W:
778
0
          RETURN_BOOL(VCWD_ACCESS(file_path_to_check, W_OK) == 0);
779
0
#endif
780
0
#ifdef R_OK
781
0
        case FS_IS_R:
782
0
          RETURN_BOOL(VCWD_ACCESS(file_path_to_check, R_OK) == 0);
783
0
#endif
784
0
#ifdef X_OK
785
0
        case FS_IS_X:
786
0
          RETURN_BOOL(VCWD_ACCESS(file_path_to_check, X_OK) == 0);
787
0
#endif
788
0
      }
789
0
    }
790
0
  }
791
792
200
  if (IS_LINK_OPERATION(type)) {
793
0
    flags |= PHP_STREAM_URL_STAT_LINK;
794
0
  }
795
200
  if (IS_EXISTS_CHECK(type)) {
796
0
    flags |= PHP_STREAM_URL_STAT_QUIET;
797
0
  }
798
799
200
  do {
800
    /* Try to hit the cache first */
801
200
    if (flags & PHP_STREAM_URL_STAT_LINK) {
802
0
      if (filename == BG(CurrentLStatFile)
803
0
       || (BG(CurrentLStatFile)
804
0
        && zend_string_equal_content(filename, BG(CurrentLStatFile)))) {
805
0
        stat_sb = &BG(lssb).sb;
806
0
        break;
807
0
      }
808
200
    } else {
809
200
      if (filename == BG(CurrentStatFile)
810
200
       || (BG(CurrentStatFile)
811
0
        && zend_string_equal_content(filename, BG(CurrentStatFile)))) {
812
0
        stat_sb = &BG(ssb).sb;
813
0
        break;
814
0
      }
815
200
    }
816
817
200
    if (!wrapper) {
818
200
      if (!ZSTR_LEN(filename) || zend_str_has_nul_byte(filename)) {
819
12
        if (ZSTR_LEN(filename) && !IS_EXISTS_CHECK(type)) {
820
12
          php_error_docref(NULL, E_WARNING, "Filename contains null byte");
821
12
        }
822
12
        RETURN_FALSE;
823
12
      }
824
825
188
      if ((wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(filename), &local, 0)) == &php_plain_files_wrapper
826
12
       && php_check_open_basedir(local)) {
827
12
        RETURN_FALSE;
828
12
      }
829
188
    }
830
831
176
    if (!wrapper
832
158
     || !wrapper->wops->url_stat
833
158
     || wrapper->wops->url_stat(wrapper, local, flags | PHP_STREAM_URL_STAT_IGNORE_OPEN_BASEDIR, &ssb, NULL)) {
834
      /* Error Occurred */
835
18
      if (!IS_EXISTS_CHECK(type)) {
836
18
        php_error_docref(NULL, E_WARNING, "%sstat failed for %s", IS_LINK_OPERATION(type) ? "L" : "", ZSTR_VAL(filename));
837
18
      }
838
18
      RETURN_FALSE;
839
18
    }
840
841
    /* Drop into cache */
842
158
    if (flags & PHP_STREAM_URL_STAT_LINK) {
843
0
      if (BG(CurrentLStatFile)) {
844
0
        zend_string_release(BG(CurrentLStatFile));
845
0
      }
846
0
      BG(CurrentLStatFile) = zend_string_copy(filename);
847
0
      memcpy(&BG(lssb), &ssb, sizeof(php_stream_statbuf));
848
0
    }
849
158
    if (!(flags & PHP_STREAM_URL_STAT_LINK)
850
0
     || !S_ISLNK(ssb.sb.st_mode)) {
851
0
      if (BG(CurrentStatFile)) {
852
0
        zend_string_release(BG(CurrentStatFile));
853
0
      }
854
0
      BG(CurrentStatFile) = zend_string_copy(filename);
855
0
      memcpy(&BG(ssb), &ssb, sizeof(php_stream_statbuf));
856
0
    }
857
158
  } while (0);
858
859
158
  if (type >= FS_IS_W && type <= FS_IS_X) {
860
0
    if(stat_sb->st_uid==getuid()) {
861
0
      rmask=S_IRUSR;
862
0
      wmask=S_IWUSR;
863
0
      xmask=S_IXUSR;
864
0
    } else if(stat_sb->st_gid==getgid()) {
865
0
      rmask=S_IRGRP;
866
0
      wmask=S_IWGRP;
867
0
      xmask=S_IXGRP;
868
0
    } else {
869
0
      int   groups, n, i;
870
0
      gid_t *gids;
871
872
0
      groups = getgroups(0, NULL);
873
0
      if(groups > 0) {
874
0
        gids=(gid_t *)safe_emalloc(groups, sizeof(gid_t), 0);
875
0
        n=getgroups(groups, gids);
876
0
        for(i=0;i<n;i++){
877
0
          if(stat_sb->st_gid==gids[i]) {
878
0
            rmask=S_IRGRP;
879
0
            wmask=S_IWGRP;
880
0
            xmask=S_IXGRP;
881
0
            break;
882
0
          }
883
0
        }
884
0
        efree(gids);
885
0
      }
886
0
    }
887
0
  }
888
889
158
  if (IS_ABLE_CHECK(type) && getuid() == 0) {
890
    /* root has special perms on plain_wrapper */
891
0
    if (wrapper == &php_plain_files_wrapper) {
892
0
      if (type == FS_IS_X) {
893
0
        xmask = S_IXROOT;
894
0
      } else {
895
0
        RETURN_TRUE;
896
0
      }
897
0
    }
898
0
  }
899
900
158
  switch (type) {
901
0
  case FS_PERMS:
902
0
  case FS_LPERMS:
903
0
    RETURN_LONG((zend_long)stat_sb->st_mode);
904
0
  case FS_INODE:
905
0
    RETURN_LONG((zend_long)stat_sb->st_ino);
906
0
  case FS_SIZE:
907
0
    RETURN_LONG((zend_long)stat_sb->st_size);
908
0
  case FS_OWNER:
909
0
    RETURN_LONG((zend_long)stat_sb->st_uid);
910
0
  case FS_GROUP:
911
0
    RETURN_LONG((zend_long)stat_sb->st_gid);
912
0
  case FS_ATIME:
913
0
    RETURN_LONG((zend_long)stat_sb->st_atime);
914
0
  case FS_MTIME:
915
0
    RETURN_LONG((zend_long)stat_sb->st_mtime);
916
0
  case FS_CTIME:
917
0
    RETURN_LONG((zend_long)stat_sb->st_ctime);
918
0
  case FS_TYPE:
919
0
    if (S_ISLNK(stat_sb->st_mode)) {
920
0
      RETURN_STRING("link");
921
0
    }
922
0
    switch(stat_sb->st_mode & S_IFMT) {
923
0
    case S_IFIFO: RETURN_STRING("fifo");
924
0
    case S_IFCHR: RETURN_STRING("char");
925
0
    case S_IFDIR: RETURN_STRING("dir");
926
0
    case S_IFBLK: RETURN_STRING("block");
927
0
    case S_IFREG: RETURN_STR(ZSTR_KNOWN(ZEND_STR_FILE)); /* "file" */
928
0
#if defined(S_IFSOCK) && !defined(PHP_WIN32)
929
0
    case S_IFSOCK: RETURN_STRING("socket");
930
0
#endif
931
0
    }
932
0
    php_error_docref(NULL, E_NOTICE, "Unknown file type (%d)", stat_sb->st_mode&S_IFMT);
933
0
    RETURN_STRING("unknown");
934
0
  case FS_IS_W:
935
0
    RETURN_BOOL((stat_sb->st_mode & wmask) != 0);
936
0
  case FS_IS_R:
937
0
    RETURN_BOOL((stat_sb->st_mode & rmask) != 0);
938
0
  case FS_IS_X:
939
0
    RETURN_BOOL((stat_sb->st_mode & xmask) != 0);
940
0
  case FS_IS_FILE:
941
0
    RETURN_BOOL(S_ISREG(stat_sb->st_mode));
942
0
  case FS_IS_DIR:
943
0
    RETURN_BOOL(S_ISDIR(stat_sb->st_mode));
944
0
  case FS_IS_LINK:
945
0
    RETURN_BOOL(S_ISLNK(stat_sb->st_mode));
946
0
  case FS_EXISTS:
947
0
    RETURN_TRUE; /* the false case was done earlier */
948
0
  case FS_LSTAT:
949
    /* FALLTHROUGH */
950
0
  case FS_STAT: {
951
0
    char *stat_sb_names[] = {
952
0
      "dev", "ino", "mode", "nlink", "uid", "gid", "rdev",
953
0
      "size", "atime", "mtime", "ctime", "blksize", "blocks"
954
0
    };
955
0
    zval stat_dev, stat_ino, stat_mode, stat_nlink, stat_uid, stat_gid, stat_rdev,
956
0
      stat_size, stat_atime, stat_mtime, stat_ctime, stat_blksize, stat_blocks;
957
0
    zval *stat_sb_addresses[] = {
958
0
      &stat_dev, &stat_ino, &stat_mode, &stat_nlink, &stat_uid, &stat_gid, &stat_rdev,
959
0
      &stat_size, &stat_atime, &stat_mtime, &stat_ctime, &stat_blksize, &stat_blocks
960
0
    };
961
0
    size_t i, size_stat_sb = sizeof(stat_sb_addresses) / sizeof(*stat_sb_addresses);
962
963
0
    array_init(return_value);
964
965
0
    ZVAL_LONG(&stat_dev, stat_sb->st_dev);
966
0
    ZVAL_LONG(&stat_ino, stat_sb->st_ino);
967
0
    ZVAL_LONG(&stat_mode, stat_sb->st_mode);
968
0
    ZVAL_LONG(&stat_nlink, stat_sb->st_nlink);
969
0
    ZVAL_LONG(&stat_uid, stat_sb->st_uid);
970
0
    ZVAL_LONG(&stat_gid, stat_sb->st_gid);
971
0
#ifdef HAVE_STRUCT_STAT_ST_RDEV
972
0
    ZVAL_LONG(&stat_rdev, stat_sb->st_rdev);
973
#else
974
    ZVAL_LONG(&stat_rdev, -1);
975
#endif
976
0
    ZVAL_LONG(&stat_size, stat_sb->st_size);
977
0
    ZVAL_LONG(&stat_atime, stat_sb->st_atime);
978
0
    ZVAL_LONG(&stat_mtime, stat_sb->st_mtime);
979
0
    ZVAL_LONG(&stat_ctime, stat_sb->st_ctime);
980
0
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
981
0
    ZVAL_LONG(&stat_blksize, stat_sb->st_blksize);
982
#else
983
    ZVAL_LONG(&stat_blksize,-1);
984
#endif
985
0
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
986
0
    ZVAL_LONG(&stat_blocks, stat_sb->st_blocks);
987
#else
988
    ZVAL_LONG(&stat_blocks,-1);
989
#endif
990
0
    for (i = 0; i < size_stat_sb; i++) {
991
      /* Store numeric indexes in proper order */
992
0
      zend_hash_next_index_insert(Z_ARRVAL_P(return_value), stat_sb_addresses[i]);
993
0
    }
994
995
0
    for (i = 0; i < size_stat_sb; i++) {
996
      /* Store string indexes referencing the same zval */
997
0
      zend_hash_str_add_new(Z_ARRVAL_P(return_value), stat_sb_names[i], strlen(stat_sb_names[i]), stat_sb_addresses[i]);
998
0
    }
999
1000
0
    return;
1001
0
      }
1002
158
  }
1003
0
  php_error_docref(NULL, E_WARNING, "Didn't understand stat call");
1004
0
  RETURN_FALSE;
1005
0
}
1006
/* }}} */
1007
1008
/* another quickie macro to make defining similar functions easier */
1009
/* {{{ FileFunction(name, funcnum) */
1010
#define FileFunction(name, funcnum) \
1011
200
ZEND_NAMED_FUNCTION(name) { \
1012
200
  zend_string *filename; \
1013
200
  \
1014
600
  ZEND_PARSE_PARAMETERS_START(1, 1) \
1015
800
    Z_PARAM_STR(filename) \
1016
800
  ZEND_PARSE_PARAMETERS_END(); \
1017
200
  \
1018
200
  php_stat(filename, funcnum, return_value); \
1019
200
}
1020
/* }}} */
1021
1022
/* {{{ Get file permissions */
1023
0
FileFunction(PHP_FN(fileperms), FS_PERMS)
1024
/* }}} */
1025
1026
/* {{{ Get file inode */
1027
0
FileFunction(PHP_FN(fileinode), FS_INODE)
1028
/* }}} */
1029
1030
/* {{{ Get file size */
1031
0
FileFunction(PHP_FN(filesize), FS_SIZE)
1032
/* }}} */
1033
1034
/* {{{ Get file owner */
1035
0
FileFunction(PHP_FN(fileowner), FS_OWNER)
1036
/* }}} */
1037
1038
/* {{{ Get file group */
1039
0
FileFunction(PHP_FN(filegroup), FS_GROUP)
1040
/* }}} */
1041
1042
/* {{{ Get last access time of file */
1043
0
FileFunction(PHP_FN(fileatime), FS_ATIME)
1044
/* }}} */
1045
1046
/* {{{ Get last modification time of file */
1047
0
FileFunction(PHP_FN(filemtime), FS_MTIME)
1048
/* }}} */
1049
1050
/* {{{ Get inode modification time of file */
1051
0
FileFunction(PHP_FN(filectime), FS_CTIME)
1052
/* }}} */
1053
1054
/* {{{ Get file type */
1055
0
FileFunction(PHP_FN(filetype), FS_TYPE)
1056
/* }}} */
1057
1058
/* {{{ Returns true if file can be written */
1059
0
FileFunction(PHP_FN(is_writable), FS_IS_W)
1060
/* }}} */
1061
1062
/* {{{ Returns true if file can be read */
1063
0
FileFunction(PHP_FN(is_readable), FS_IS_R)
1064
/* }}} */
1065
1066
/* {{{ Returns true if file is executable */
1067
0
FileFunction(PHP_FN(is_executable), FS_IS_X)
1068
/* }}} */
1069
1070
/* {{{ Returns true if file is a regular file */
1071
0
FileFunction(PHP_FN(is_file), FS_IS_FILE)
1072
/* }}} */
1073
1074
/* {{{ Returns true if file is directory */
1075
0
FileFunction(PHP_FN(is_dir), FS_IS_DIR)
1076
/* }}} */
1077
1078
/* {{{ Returns true if file is symbolic link */
1079
0
FileFunction(PHP_FN(is_link), FS_IS_LINK)
1080
/* }}} */
1081
1082
/* {{{ Returns true if filename exists */
1083
0
FileFunction(PHP_FN(file_exists), FS_EXISTS)
1084
/* }}} */
1085
1086
/* {{{ Give information about a file or symbolic link */
1087
0
FileFunction(PHP_FN(lstat), FS_LSTAT)
1088
/* }}} */
1089
1090
/* {{{ Give information about a file */
1091
200
FileFunction(PHP_FN(stat), FS_STAT)
1092
/* }}} */
1093
1094
/* {{{ Get current size of realpath cache */
1095
PHP_FUNCTION(realpath_cache_size)
1096
0
{
1097
0
  ZEND_PARSE_PARAMETERS_NONE();
1098
1099
0
  RETURN_LONG(realpath_cache_size());
1100
0
}
1101
1102
/* {{{ Get current size of realpath cache */
1103
PHP_FUNCTION(realpath_cache_get)
1104
0
{
1105
0
  realpath_cache_bucket **buckets = realpath_cache_get_buckets(), **end = buckets + realpath_cache_max_buckets();
1106
1107
0
  ZEND_PARSE_PARAMETERS_NONE();
1108
1109
0
  array_init(return_value);
1110
0
  while(buckets < end) {
1111
0
    realpath_cache_bucket *bucket = *buckets;
1112
0
    while(bucket) {
1113
0
      zval entry;
1114
1115
0
      array_init(&entry);
1116
1117
      /* bucket->key is unsigned long */
1118
0
      if (ZEND_LONG_MAX >= bucket->key) {
1119
0
        add_assoc_long_ex(&entry, "key", sizeof("key") - 1, bucket->key);
1120
0
      } else {
1121
0
        add_assoc_double_ex(&entry, "key", sizeof("key") - 1, (double)bucket->key);
1122
0
      }
1123
0
      add_assoc_bool_ex(&entry, "is_dir", sizeof("is_dir") - 1, bucket->is_dir);
1124
0
      add_assoc_stringl_ex(&entry, "realpath", sizeof("realpath") - 1, bucket->realpath, bucket->realpath_len);
1125
0
      add_assoc_long_ex(&entry, "expires", sizeof("expires") - 1, bucket->expires);
1126
#ifdef PHP_WIN32
1127
      add_assoc_bool_ex(&entry, "is_rvalid", sizeof("is_rvalid") - 1, bucket->is_rvalid);
1128
      add_assoc_bool_ex(&entry, "is_wvalid", sizeof("is_wvalid") - 1, bucket->is_wvalid);
1129
      add_assoc_bool_ex(&entry, "is_readable", sizeof("is_readable") - 1, bucket->is_readable);
1130
      add_assoc_bool_ex(&entry, "is_writable", sizeof("is_writable") - 1, bucket->is_writable);
1131
#endif
1132
0
      zend_hash_str_update(Z_ARRVAL_P(return_value), bucket->path, bucket->path_len, &entry);
1133
0
      bucket = bucket->next;
1134
0
    }
1135
0
    buckets++;
1136
0
  }
1137
0
}