Coverage Report

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