Coverage Report

Created: 2026-09-14 06:25

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
295k
{
81
295k
  BG(CurrentStatFile)=NULL;
82
295k
  BG(CurrentLStatFile)=NULL;
83
295k
  return SUCCESS;
84
295k
}
85
/* }}} */
86
87
PHP_RSHUTDOWN_FUNCTION(filestat) /* {{{ */
88
295k
{
89
295k
  if (BG(CurrentStatFile)) {
90
0
    zend_string_release(BG(CurrentStatFile));
91
0
    BG(CurrentStatFile) = NULL;
92
0
  }
93
295k
  if (BG(CurrentLStatFile)) {
94
0
    zend_string_release(BG(CurrentLStatFile));
95
0
    BG(CurrentLStatFile) = NULL;
96
0
  }
97
295k
  return SUCCESS;
98
295k
}
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
      RETURN_BOOL(wrapper->wops->stream_metadata(wrapper, filename, option, value, NULL));
343
0
    } else {
344
0
#ifndef PHP_WIN32
345
/* On Windows, we expect regular chgrp to fail silently by default */
346
0
      php_error_docref(NULL, E_WARNING, "Cannot call chgrp() for a non-standard stream");
347
0
#endif
348
0
      RETURN_FALSE;
349
0
    }
350
0
  }
351
352
#ifdef PHP_WIN32
353
  /* We have no native chgrp on Windows, nothing left to do if stream doesn't have own implementation */
354
  RETURN_FALSE;
355
#else
356
0
  if (group_str) {
357
0
    if (php_get_gid_by_name(ZSTR_VAL(group_str), &gid) != SUCCESS) {
358
0
      php_error_docref(NULL, E_WARNING, "Unable to find gid for %s", ZSTR_VAL(group_str));
359
0
      RETURN_FALSE;
360
0
    }
361
0
  } else {
362
0
    gid = (gid_t) group_long;
363
0
  }
364
365
  /* Check the basedir */
366
0
  if (php_check_open_basedir(filename)) {
367
0
    RETURN_FALSE;
368
0
  }
369
370
0
  if (do_lchgrp) {
371
0
#ifdef HAVE_LCHOWN
372
0
    ret = VCWD_LCHOWN(filename, -1, gid);
373
0
#endif
374
0
  } else {
375
0
    ret = VCWD_CHOWN(filename, -1, gid);
376
0
  }
377
0
  if (ret == -1) {
378
0
    php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
379
0
    RETURN_FALSE;
380
0
  }
381
382
0
  php_clear_stat_cache(0, NULL, 0);
383
384
0
  RETURN_TRUE;
385
0
#endif
386
0
}
387
/* }}} */
388
389
/* {{{ Change file group */
390
PHP_FUNCTION(chgrp)
391
0
{
392
0
  php_do_chgrp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
393
0
}
394
/* }}} */
395
396
/* {{{ Change symlink group */
397
#ifdef HAVE_LCHOWN
398
PHP_FUNCTION(lchgrp)
399
0
{
400
0
  php_do_chgrp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
401
0
}
402
#endif
403
/* }}} */
404
405
#ifndef PHP_WIN32
406
PHPAPI zend_result php_get_uid_by_name(const char *name, uid_t *uid)
407
0
{
408
#if defined(ZTS) && defined(_SC_GETPW_R_SIZE_MAX) && defined(HAVE_GETPWNAM_R)
409
    struct passwd pw;
410
    struct passwd *retpwptr = NULL;
411
    long pwbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
412
    char *pwbuf;
413
    int err;
414
415
    if (pwbuflen < 1) {
416
      pwbuflen = 1024;
417
    }
418
# if ZEND_DEBUG
419
    /* Test retry logic */
420
    pwbuflen = 1;
421
# endif
422
    pwbuf = emalloc(pwbuflen);
423
424
try_again:
425
    err = getpwnam_r(name, &pw, pwbuf, pwbuflen, &retpwptr);
426
    if (err != 0 || retpwptr == NULL) {
427
      if (err == EAGAIN) {
428
        pwbuflen *= 2;
429
        pwbuf = erealloc(pwbuf, pwbuflen);
430
        goto try_again;
431
      }
432
      efree(pwbuf);
433
      return FAILURE;
434
    }
435
    efree(pwbuf);
436
    *uid = pw.pw_uid;
437
#else
438
0
    struct passwd *pw = getpwnam(name);
439
440
0
    if (!pw) {
441
0
      return FAILURE;
442
0
    }
443
0
    *uid = pw->pw_uid;
444
0
#endif
445
0
    return SUCCESS;
446
0
}
447
#endif
448
449
static void php_do_chown(INTERNAL_FUNCTION_PARAMETERS, int do_lchown) /* {{{ */
450
0
{
451
0
  char *filename;
452
0
  size_t filename_len;
453
0
  zend_string *user_str;
454
0
  zend_long user_long;
455
0
#if !defined(PHP_WIN32)
456
0
  uid_t uid;
457
0
  int ret;
458
0
#endif
459
0
  php_stream_wrapper *wrapper;
460
461
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
462
0
    Z_PARAM_PATH(filename, filename_len)
463
0
    Z_PARAM_STR_OR_LONG(user_str, user_long)
464
0
  ZEND_PARSE_PARAMETERS_END();
465
466
0
  wrapper = php_stream_locate_url_wrapper(filename, NULL, 0);
467
0
  if(wrapper != &php_plain_files_wrapper || strncasecmp("file://", filename, 7) == 0) {
468
0
    if(wrapper && wrapper->wops->stream_metadata) {
469
0
      int option;
470
0
      void *value;
471
0
      if (user_str) {
472
0
        option = PHP_STREAM_META_OWNER_NAME;
473
0
        value = ZSTR_VAL(user_str);
474
0
      } else {
475
0
        option = PHP_STREAM_META_OWNER;
476
0
        value = &user_long;
477
0
      }
478
479
0
      RETURN_BOOL(wrapper->wops->stream_metadata(wrapper, filename, option, value, NULL));
480
0
    } else {
481
0
#ifndef PHP_WIN32
482
/* On Windows, we expect regular chown to fail silently by default */
483
0
      php_error_docref(NULL, E_WARNING, "Cannot call chown() for a non-standard stream");
484
0
#endif
485
0
      RETURN_FALSE;
486
0
    }
487
0
  }
488
489
#ifdef PHP_WIN32
490
  /* We have no native chown on Windows, nothing left to do if stream doesn't have own implementation */
491
  RETURN_FALSE;
492
#else
493
494
0
  if (user_str) {
495
0
    if (php_get_uid_by_name(ZSTR_VAL(user_str), &uid) != SUCCESS) {
496
0
      php_error_docref(NULL, E_WARNING, "Unable to find uid for %s", ZSTR_VAL(user_str));
497
0
      RETURN_FALSE;
498
0
    }
499
0
  } else {
500
0
    uid = (uid_t) user_long;
501
0
  }
502
503
  /* Check the basedir */
504
0
  if (php_check_open_basedir(filename)) {
505
0
    RETURN_FALSE;
506
0
  }
507
508
0
  if (do_lchown) {
509
0
#ifdef HAVE_LCHOWN
510
0
    ret = VCWD_LCHOWN(filename, uid, -1);
511
0
#endif
512
0
  } else {
513
0
    ret = VCWD_CHOWN(filename, uid, -1);
514
0
  }
515
0
  if (ret == -1) {
516
0
    php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
517
0
    RETURN_FALSE;
518
0
  }
519
520
0
  php_clear_stat_cache(0, NULL, 0);
521
522
0
  RETURN_TRUE;
523
0
#endif
524
0
}
525
/* }}} */
526
527
528
/* {{{ Change file owner */
529
PHP_FUNCTION(chown)
530
0
{
531
0
  php_do_chown(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
532
0
}
533
/* }}} */
534
535
/* {{{ Change file owner */
536
#ifdef HAVE_LCHOWN
537
PHP_FUNCTION(lchown)
538
0
{
539
0
  RETVAL_TRUE;
540
0
  php_do_chown(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
541
0
}
542
#endif
543
/* }}} */
544
545
/* {{{ Change file mode */
546
PHP_FUNCTION(chmod)
547
0
{
548
0
  char *filename;
549
0
  size_t filename_len;
550
0
  zend_long mode;
551
0
  int ret;
552
0
  mode_t imode;
553
0
  php_stream_wrapper *wrapper;
554
555
0
  ZEND_PARSE_PARAMETERS_START(2, 2)
556
0
    Z_PARAM_PATH(filename, filename_len)
557
0
    Z_PARAM_LONG(mode)
558
0
  ZEND_PARSE_PARAMETERS_END();
559
560
0
  wrapper = php_stream_locate_url_wrapper(filename, NULL, 0);
561
0
  if(wrapper != &php_plain_files_wrapper || strncasecmp("file://", filename, 7) == 0) {
562
0
    if(wrapper && wrapper->wops->stream_metadata) {
563
0
      if(wrapper->wops->stream_metadata(wrapper, filename, PHP_STREAM_META_ACCESS, &mode, NULL)) {
564
0
        RETURN_TRUE;
565
0
      } else {
566
0
        RETURN_FALSE;
567
0
      }
568
0
    } else {
569
0
      php_error_docref(NULL, E_WARNING, "Cannot call chmod() for a non-standard stream");
570
0
      RETURN_FALSE;
571
0
    }
572
0
  }
573
574
  /* Check the basedir */
575
0
  if (php_check_open_basedir(filename)) {
576
0
    RETURN_FALSE;
577
0
  }
578
579
0
  imode = (mode_t) mode;
580
581
0
  ret = VCWD_CHMOD(filename, imode);
582
0
  if (ret == -1) {
583
0
    php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
584
0
    RETURN_FALSE;
585
0
  }
586
587
0
  php_clear_stat_cache(0, NULL, 0);
588
589
0
  RETURN_TRUE;
590
0
}
591
/* }}} */
592
593
#ifdef HAVE_UTIME
594
/* {{{ Set modification time of file */
595
PHP_FUNCTION(touch)
596
0
{
597
0
  char *filename;
598
0
  size_t filename_len;
599
0
  zend_long filetime = 0, fileatime = 0;
600
0
  bool filetime_is_null = 1, fileatime_is_null = 1;
601
0
  int ret;
602
0
  FILE *file;
603
0
  struct utimbuf newtimebuf;
604
0
  struct utimbuf *newtime = &newtimebuf;
605
0
  php_stream_wrapper *wrapper;
606
607
0
  ZEND_PARSE_PARAMETERS_START(1, 3)
608
0
    Z_PARAM_PATH(filename, filename_len)
609
0
    Z_PARAM_OPTIONAL
610
0
    Z_PARAM_LONG_OR_NULL(filetime, filetime_is_null)
611
0
    Z_PARAM_LONG_OR_NULL(fileatime, fileatime_is_null)
612
0
  ZEND_PARSE_PARAMETERS_END();
613
614
0
  if (!filename_len) {
615
0
    RETURN_FALSE;
616
0
  }
617
618
0
  if (filetime_is_null && fileatime_is_null) {
619
0
    newtime = NULL;
620
0
  } else if (!filetime_is_null && fileatime_is_null) {
621
0
    newtime->modtime = newtime->actime = filetime;
622
0
  } else if (filetime_is_null && !fileatime_is_null) {
623
0
    zend_argument_value_error(2, "cannot be null when argument #3 ($atime) is an integer");
624
0
    RETURN_THROWS();
625
0
  } else {
626
0
    newtime->modtime = filetime;
627
0
    newtime->actime = fileatime;
628
0
  }
629
630
0
  wrapper = php_stream_locate_url_wrapper(filename, NULL, 0);
631
0
  if(wrapper != &php_plain_files_wrapper || strncasecmp("file://", filename, 7) == 0) {
632
0
    if(wrapper && wrapper->wops->stream_metadata) {
633
0
      RETURN_BOOL(wrapper->wops->stream_metadata(wrapper, filename, PHP_STREAM_META_TOUCH, newtime, NULL));
634
0
    } else {
635
0
      php_stream *stream;
636
0
      if(!filetime_is_null || !fileatime_is_null) {
637
0
        php_error_docref(NULL, E_WARNING, "Cannot call touch() for a non-standard stream");
638
0
        RETURN_FALSE;
639
0
      }
640
0
      stream = php_stream_open_wrapper_ex(filename, "c", REPORT_ERRORS, NULL, NULL);
641
0
      if(stream != NULL) {
642
0
        php_stream_close(stream);
643
0
        RETURN_TRUE;
644
0
      } else {
645
0
        RETURN_FALSE;
646
0
      }
647
0
    }
648
0
  }
649
650
  /* Check the basedir */
651
0
  if (php_check_open_basedir(filename)) {
652
0
    RETURN_FALSE;
653
0
  }
654
655
  /* create the file if it doesn't exist already */
656
0
  if (VCWD_ACCESS(filename, F_OK) != 0) {
657
0
    file = VCWD_FOPEN(filename, "w");
658
0
    if (file == NULL) {
659
0
      php_error_docref(NULL, E_WARNING, "Unable to create file %s because %s", filename, strerror(errno));
660
0
      RETURN_FALSE;
661
0
    }
662
0
    fclose(file);
663
0
  }
664
665
0
  ret = VCWD_UTIME(filename, newtime);
666
0
  if (ret == -1) {
667
0
    php_error_docref(NULL, E_WARNING, "Utime failed: %s", strerror(errno));
668
0
    RETURN_FALSE;
669
0
  }
670
671
0
  php_clear_stat_cache(0, NULL, 0);
672
673
0
  RETURN_TRUE;
674
0
}
675
/* }}} */
676
#endif
677
678
/* {{{ php_clear_stat_cache() */
679
PHPAPI void php_clear_stat_cache(bool clear_realpath_cache, const char *filename, size_t filename_len)
680
171
{
681
  /* always clear CurrentStatFile and CurrentLStatFile even if filename is not NULL
682
   * as it may contain outdated data (e.g. "nlink" for a directory when deleting a file
683
   * in this directory, as shown by lstat_stat_variation9.phpt) */
684
171
  if (BG(CurrentStatFile)) {
685
0
    zend_string_release(BG(CurrentStatFile));
686
0
    BG(CurrentStatFile) = NULL;
687
0
  }
688
171
  if (BG(CurrentLStatFile)) {
689
0
    zend_string_release(BG(CurrentLStatFile));
690
0
    BG(CurrentLStatFile) = NULL;
691
0
  }
692
171
  if (clear_realpath_cache) {
693
0
    if (filename != NULL) {
694
0
      realpath_cache_del(filename, filename_len);
695
0
    } else {
696
0
      realpath_cache_clean();
697
0
    }
698
0
  }
699
171
}
700
/* }}} */
701
702
/* {{{ Clear file stat cache */
703
PHP_FUNCTION(clearstatcache)
704
0
{
705
0
  bool  clear_realpath_cache = 0;
706
0
  char      *filename             = NULL;
707
0
  size_t     filename_len         = 0;
708
709
0
  ZEND_PARSE_PARAMETERS_START(0, 2)
710
0
    Z_PARAM_OPTIONAL
711
0
    Z_PARAM_BOOL(clear_realpath_cache)
712
0
    Z_PARAM_PATH(filename, filename_len)
713
0
  ZEND_PARSE_PARAMETERS_END();
714
715
0
  php_clear_stat_cache(clear_realpath_cache, filename, filename_len);
716
0
}
717
/* }}} */
718
719
534
#define IS_LINK_OPERATION(__t) ((__t) == FS_TYPE || (__t) == FS_IS_LINK || (__t) == FS_LSTAT || (__t) == FS_LPERMS)
720
534
#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)
721
1.93k
#define IS_ABLE_CHECK(__t) ((__t) == FS_IS_R || (__t) == FS_IS_W || (__t) == FS_IS_X)
722
504
#define IS_ACCESS_CHECK(__t) (IS_ABLE_CHECK(type) || (__t) == FS_EXISTS)
723
724
/* {{{ php_stat */
725
PHPAPI void php_stat(zend_string *filename, int type, zval *return_value)
726
526
{
727
526
  php_stream_statbuf ssb = {0};
728
526
  zend_stat_t *stat_sb = &ssb.sb;
729
526
  int flags = 0, rmask=S_IROTH, wmask=S_IWOTH, xmask=S_IXOTH; /* access rights defaults to other */
730
526
  const char *local = NULL;
731
526
  php_stream_wrapper *wrapper = NULL;
732
733
526
  ZEND_ASSERT(!zend_str_has_nul_byte(filename));
734
  /* Quick check for empty file paths */
735
526
  if (!ZSTR_LEN(filename)) {
736
22
    RETURN_FALSE;
737
22
  }
738
504
  if (IS_ACCESS_CHECK(type)) {
739
0
    wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(filename), &local, 0);
740
0
    if (wrapper == &php_plain_files_wrapper) {
741
0
      if (php_check_open_basedir(local)) {
742
0
        RETURN_FALSE;
743
0
      }
744
745
0
      char realpath[MAXPATHLEN];
746
0
      const char *file_path_to_check;
747
      /* if the wrapper is not found, we need to expand path to match open behavior */
748
0
      if (EXPECTED(!php_is_stream_path(local) || expand_filepath(local, realpath) == NULL)) {
749
0
        file_path_to_check = local;
750
0
      } else {
751
0
        file_path_to_check = realpath;
752
0
      }
753
0
      switch (type) {
754
0
#ifdef F_OK
755
0
        case FS_EXISTS:
756
0
          RETURN_BOOL(VCWD_ACCESS(file_path_to_check, F_OK) == 0);
757
0
#endif
758
0
#ifdef W_OK
759
0
        case FS_IS_W:
760
0
          RETURN_BOOL(VCWD_ACCESS(file_path_to_check, W_OK) == 0);
761
0
#endif
762
0
#ifdef R_OK
763
0
        case FS_IS_R:
764
0
          RETURN_BOOL(VCWD_ACCESS(file_path_to_check, R_OK) == 0);
765
0
#endif
766
0
#ifdef X_OK
767
0
        case FS_IS_X:
768
0
          RETURN_BOOL(VCWD_ACCESS(file_path_to_check, X_OK) == 0);
769
0
#endif
770
0
      }
771
0
    }
772
0
  }
773
774
504
  if (IS_LINK_OPERATION(type)) {
775
0
    flags |= PHP_STREAM_URL_STAT_LINK;
776
0
  }
777
504
  if (IS_EXISTS_CHECK(type)) {
778
0
    flags |= PHP_STREAM_URL_STAT_QUIET;
779
0
  }
780
781
504
  do {
782
    /* Try to hit the cache first */
783
504
    if (flags & PHP_STREAM_URL_STAT_LINK) {
784
0
      if (filename == BG(CurrentLStatFile)
785
0
       || (BG(CurrentLStatFile)
786
0
        && zend_string_equal_content(filename, BG(CurrentLStatFile)))) {
787
0
        stat_sb = &BG(lssb).sb;
788
0
        break;
789
0
      }
790
504
    } else {
791
504
      if (filename == BG(CurrentStatFile)
792
504
       || (BG(CurrentStatFile)
793
0
        && zend_string_equal_content(filename, BG(CurrentStatFile)))) {
794
0
        stat_sb = &BG(ssb).sb;
795
0
        break;
796
0
      }
797
504
    }
798
799
504
    if (!wrapper) {
800
504
      if ((wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(filename), &local, 0)) == &php_plain_files_wrapper
801
12
       && php_check_open_basedir(local)) {
802
12
        RETURN_FALSE;
803
12
      }
804
504
    }
805
806
492
    if (!wrapper
807
466
     || !wrapper->wops->url_stat
808
466
     || wrapper->wops->url_stat(wrapper, local, flags | PHP_STREAM_URL_STAT_IGNORE_OPEN_BASEDIR, &ssb, NULL)) {
809
      /* Error Occurred */
810
30
      if (!IS_EXISTS_CHECK(type)) {
811
30
        php_error_docref(NULL, E_WARNING, "%sstat failed for %s", IS_LINK_OPERATION(type) ? "L" : "", ZSTR_VAL(filename));
812
30
      }
813
30
      RETURN_FALSE;
814
30
    }
815
816
    /* Drop into cache */
817
462
    if (flags & PHP_STREAM_URL_STAT_LINK) {
818
0
      if (BG(CurrentLStatFile)) {
819
0
        zend_string_release(BG(CurrentLStatFile));
820
0
      }
821
0
      BG(CurrentLStatFile) = zend_string_copy(filename);
822
0
      memcpy(&BG(lssb), &ssb, sizeof(php_stream_statbuf));
823
0
    }
824
462
    if (!(flags & PHP_STREAM_URL_STAT_LINK)
825
0
     || !S_ISLNK(ssb.sb.st_mode)) {
826
0
      if (BG(CurrentStatFile)) {
827
0
        zend_string_release(BG(CurrentStatFile));
828
0
      }
829
0
      BG(CurrentStatFile) = zend_string_copy(filename);
830
0
      memcpy(&BG(ssb), &ssb, sizeof(php_stream_statbuf));
831
0
    }
832
462
  } while (0);
833
834
462
  if (type >= FS_IS_W && type <= FS_IS_X) {
835
0
    if(stat_sb->st_uid==getuid()) {
836
0
      rmask=S_IRUSR;
837
0
      wmask=S_IWUSR;
838
0
      xmask=S_IXUSR;
839
0
    } else if(stat_sb->st_gid==getgid()) {
840
0
      rmask=S_IRGRP;
841
0
      wmask=S_IWGRP;
842
0
      xmask=S_IXGRP;
843
0
    } else {
844
0
      int   groups, n, i;
845
0
      gid_t *gids;
846
847
0
      groups = getgroups(0, NULL);
848
0
      if(groups > 0) {
849
0
        gids=(gid_t *)safe_emalloc(groups, sizeof(gid_t), 0);
850
0
        n=getgroups(groups, gids);
851
0
        for(i=0;i<n;i++){
852
0
          if(stat_sb->st_gid==gids[i]) {
853
0
            rmask=S_IRGRP;
854
0
            wmask=S_IWGRP;
855
0
            xmask=S_IXGRP;
856
0
            break;
857
0
          }
858
0
        }
859
0
        efree(gids);
860
0
      }
861
0
    }
862
0
  }
863
864
462
  if (IS_ABLE_CHECK(type) && getuid() == 0) {
865
    /* root has special perms on plain_wrapper */
866
0
    if (wrapper == &php_plain_files_wrapper) {
867
0
      if (type == FS_IS_X) {
868
0
        xmask = S_IXROOT;
869
0
      } else {
870
0
        RETURN_TRUE;
871
0
      }
872
0
    }
873
0
  }
874
875
462
  switch (type) {
876
0
  case FS_PERMS:
877
0
  case FS_LPERMS:
878
0
    RETURN_LONG((zend_long)stat_sb->st_mode);
879
0
  case FS_INODE:
880
0
    RETURN_LONG((zend_long)stat_sb->st_ino);
881
0
  case FS_SIZE:
882
0
    RETURN_LONG((zend_long)stat_sb->st_size);
883
0
  case FS_OWNER:
884
0
    RETURN_LONG((zend_long)stat_sb->st_uid);
885
0
  case FS_GROUP:
886
0
    RETURN_LONG((zend_long)stat_sb->st_gid);
887
0
  case FS_ATIME:
888
0
    RETURN_LONG((zend_long)stat_sb->st_atime);
889
0
  case FS_MTIME:
890
0
    RETURN_LONG((zend_long)stat_sb->st_mtime);
891
0
  case FS_CTIME:
892
0
    RETURN_LONG((zend_long)stat_sb->st_ctime);
893
0
  case FS_TYPE:
894
0
    if (S_ISLNK(stat_sb->st_mode)) {
895
0
      RETURN_STRING("link");
896
0
    }
897
0
    switch(stat_sb->st_mode & S_IFMT) {
898
0
    case S_IFIFO: RETURN_STRING("fifo");
899
0
    case S_IFCHR: RETURN_STRING("char");
900
0
    case S_IFDIR: RETURN_STRING("dir");
901
0
    case S_IFBLK: RETURN_STRING("block");
902
0
    case S_IFREG: RETURN_STR(ZSTR_KNOWN(ZEND_STR_FILE)); /* "file" */
903
0
#if defined(S_IFSOCK) && !defined(PHP_WIN32)
904
0
    case S_IFSOCK: RETURN_STRING("socket");
905
0
#endif
906
0
    }
907
0
    php_error_docref(NULL, E_NOTICE, "Unknown file type (%d)", stat_sb->st_mode&S_IFMT);
908
0
    RETURN_STRING("unknown");
909
0
  case FS_IS_W:
910
0
    RETURN_BOOL((stat_sb->st_mode & wmask) != 0);
911
0
  case FS_IS_R:
912
0
    RETURN_BOOL((stat_sb->st_mode & rmask) != 0);
913
0
  case FS_IS_X:
914
0
    RETURN_BOOL((stat_sb->st_mode & xmask) != 0);
915
0
  case FS_IS_FILE:
916
0
    RETURN_BOOL(S_ISREG(stat_sb->st_mode));
917
0
  case FS_IS_DIR:
918
0
    RETURN_BOOL(S_ISDIR(stat_sb->st_mode));
919
0
  case FS_IS_LINK:
920
0
    RETURN_BOOL(S_ISLNK(stat_sb->st_mode));
921
0
  case FS_EXISTS:
922
0
    RETURN_TRUE; /* the false case was done earlier */
923
0
  case FS_LSTAT:
924
    /* FALLTHROUGH */
925
0
  case FS_STAT: {
926
0
    char *stat_sb_names[] = {
927
0
      "dev", "ino", "mode", "nlink", "uid", "gid", "rdev",
928
0
      "size", "atime", "mtime", "ctime", "blksize", "blocks"
929
0
    };
930
0
    zval stat_dev, stat_ino, stat_mode, stat_nlink, stat_uid, stat_gid, stat_rdev,
931
0
      stat_size, stat_atime, stat_mtime, stat_ctime, stat_blksize, stat_blocks;
932
0
    zval *stat_sb_addresses[] = {
933
0
      &stat_dev, &stat_ino, &stat_mode, &stat_nlink, &stat_uid, &stat_gid, &stat_rdev,
934
0
      &stat_size, &stat_atime, &stat_mtime, &stat_ctime, &stat_blksize, &stat_blocks
935
0
    };
936
0
    size_t i, size_stat_sb = sizeof(stat_sb_addresses) / sizeof(*stat_sb_addresses);
937
938
0
    array_init(return_value);
939
940
0
    ZVAL_LONG(&stat_dev, stat_sb->st_dev);
941
0
    ZVAL_LONG(&stat_ino, stat_sb->st_ino);
942
0
    ZVAL_LONG(&stat_mode, stat_sb->st_mode);
943
0
    ZVAL_LONG(&stat_nlink, stat_sb->st_nlink);
944
0
    ZVAL_LONG(&stat_uid, stat_sb->st_uid);
945
0
    ZVAL_LONG(&stat_gid, stat_sb->st_gid);
946
0
#ifdef HAVE_STRUCT_STAT_ST_RDEV
947
0
    ZVAL_LONG(&stat_rdev, stat_sb->st_rdev);
948
#else
949
    ZVAL_LONG(&stat_rdev, -1);
950
#endif
951
0
    ZVAL_LONG(&stat_size, stat_sb->st_size);
952
0
    ZVAL_LONG(&stat_atime, stat_sb->st_atime);
953
0
    ZVAL_LONG(&stat_mtime, stat_sb->st_mtime);
954
0
    ZVAL_LONG(&stat_ctime, stat_sb->st_ctime);
955
0
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
956
0
    ZVAL_LONG(&stat_blksize, stat_sb->st_blksize);
957
#else
958
    ZVAL_LONG(&stat_blksize,-1);
959
#endif
960
0
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
961
0
    ZVAL_LONG(&stat_blocks, stat_sb->st_blocks);
962
#else
963
    ZVAL_LONG(&stat_blocks,-1);
964
#endif
965
0
    for (i = 0; i < size_stat_sb; i++) {
966
      /* Store numeric indexes in proper order */
967
0
      zend_hash_next_index_insert(Z_ARRVAL_P(return_value), stat_sb_addresses[i]);
968
0
    }
969
970
0
    for (i = 0; i < size_stat_sb; i++) {
971
      /* Store string indexes referencing the same zval */
972
0
      zend_hash_str_add_new(Z_ARRVAL_P(return_value), stat_sb_names[i], strlen(stat_sb_names[i]), stat_sb_addresses[i]);
973
0
    }
974
975
0
    return;
976
0
      }
977
462
  }
978
0
  php_error_docref(NULL, E_WARNING, "Didn't understand stat call");
979
0
  RETURN_FALSE;
980
0
}
981
/* }}} */
982
983
/* another quickie macro to make defining similar functions easier */
984
/* {{{ FileFunction(name, funcnum) */
985
#define FileFunction(name, funcnum) \
986
531
ZEND_NAMED_FUNCTION(name) { \
987
531
  zend_string *filename; \
988
531
  \
989
1.59k
  ZEND_PARSE_PARAMETERS_START(1, 1) \
990
2.12k
    Z_PARAM_PATH_STR(filename) \
991
2.12k
  ZEND_PARSE_PARAMETERS_END(); \
992
531
  \
993
531
  php_stat(filename, funcnum, return_value); \
994
526
}
995
/* }}} */
996
997
/* {{{ Get file permissions */
998
0
FileFunction(PHP_FN(fileperms), FS_PERMS)
999
/* }}} */
1000
1001
/* {{{ Get file inode */
1002
0
FileFunction(PHP_FN(fileinode), FS_INODE)
1003
/* }}} */
1004
1005
/* {{{ Get file size */
1006
0
FileFunction(PHP_FN(filesize), FS_SIZE)
1007
/* }}} */
1008
1009
/* {{{ Get file owner */
1010
0
FileFunction(PHP_FN(fileowner), FS_OWNER)
1011
/* }}} */
1012
1013
/* {{{ Get file group */
1014
0
FileFunction(PHP_FN(filegroup), FS_GROUP)
1015
/* }}} */
1016
1017
/* {{{ Get last access time of file */
1018
0
FileFunction(PHP_FN(fileatime), FS_ATIME)
1019
/* }}} */
1020
1021
/* {{{ Get last modification time of file */
1022
0
FileFunction(PHP_FN(filemtime), FS_MTIME)
1023
/* }}} */
1024
1025
/* {{{ Get inode modification time of file */
1026
0
FileFunction(PHP_FN(filectime), FS_CTIME)
1027
/* }}} */
1028
1029
/* {{{ Get file type */
1030
0
FileFunction(PHP_FN(filetype), FS_TYPE)
1031
/* }}} */
1032
1033
/* {{{ Returns true if file can be written */
1034
0
FileFunction(PHP_FN(is_writable), FS_IS_W)
1035
/* }}} */
1036
1037
/* {{{ Returns true if file can be read */
1038
0
FileFunction(PHP_FN(is_readable), FS_IS_R)
1039
/* }}} */
1040
1041
/* {{{ Returns true if file is executable */
1042
0
FileFunction(PHP_FN(is_executable), FS_IS_X)
1043
/* }}} */
1044
1045
/* {{{ Returns true if file is a regular file */
1046
0
FileFunction(PHP_FN(is_file), FS_IS_FILE)
1047
/* }}} */
1048
1049
/* {{{ Returns true if file is directory */
1050
0
FileFunction(PHP_FN(is_dir), FS_IS_DIR)
1051
/* }}} */
1052
1053
/* {{{ Returns true if file is symbolic link */
1054
0
FileFunction(PHP_FN(is_link), FS_IS_LINK)
1055
/* }}} */
1056
1057
/* {{{ Returns true if filename exists */
1058
0
FileFunction(PHP_FN(file_exists), FS_EXISTS)
1059
/* }}} */
1060
1061
/* {{{ Give information about a file or symbolic link */
1062
0
FileFunction(PHP_FN(lstat), FS_LSTAT)
1063
/* }}} */
1064
1065
/* {{{ Give information about a file */
1066
531
FileFunction(PHP_FN(stat), FS_STAT)
1067
/* }}} */
1068
1069
/* {{{ Get current size of realpath cache */
1070
PHP_FUNCTION(realpath_cache_size)
1071
0
{
1072
0
  ZEND_PARSE_PARAMETERS_NONE();
1073
1074
0
  RETURN_LONG(realpath_cache_size());
1075
0
}
1076
1077
/* {{{ Get current size of realpath cache */
1078
PHP_FUNCTION(realpath_cache_get)
1079
0
{
1080
0
  realpath_cache_bucket **buckets = realpath_cache_get_buckets(), **end = buckets + realpath_cache_max_buckets();
1081
1082
0
  ZEND_PARSE_PARAMETERS_NONE();
1083
1084
0
  array_init(return_value);
1085
0
  while(buckets < end) {
1086
0
    realpath_cache_bucket *bucket = *buckets;
1087
0
    while(bucket) {
1088
0
      zval entry;
1089
1090
0
      array_init(&entry);
1091
1092
      /* bucket->key is unsigned long */
1093
0
      if (ZEND_LONG_MAX >= bucket->key) {
1094
0
        add_assoc_long_ex(&entry, "key", sizeof("key") - 1, bucket->key);
1095
0
      } else {
1096
0
        add_assoc_double_ex(&entry, "key", sizeof("key") - 1, (double)bucket->key);
1097
0
      }
1098
0
      add_assoc_bool_ex(&entry, "is_dir", sizeof("is_dir") - 1, bucket->is_dir);
1099
0
      add_assoc_stringl_ex(&entry, "realpath", sizeof("realpath") - 1, bucket->realpath, bucket->realpath_len);
1100
0
      add_assoc_long_ex(&entry, "expires", sizeof("expires") - 1, bucket->expires);
1101
#ifdef PHP_WIN32
1102
      add_assoc_bool_ex(&entry, "is_rvalid", sizeof("is_rvalid") - 1, bucket->is_rvalid);
1103
      add_assoc_bool_ex(&entry, "is_wvalid", sizeof("is_wvalid") - 1, bucket->is_wvalid);
1104
      add_assoc_bool_ex(&entry, "is_readable", sizeof("is_readable") - 1, bucket->is_readable);
1105
      add_assoc_bool_ex(&entry, "is_writable", sizeof("is_writable") - 1, bucket->is_writable);
1106
#endif
1107
0
      zend_hash_str_update(Z_ARRVAL_P(return_value), bucket->path, bucket->path_len, &entry);
1108
0
      bucket = bucket->next;
1109
0
    }
1110
0
    buckets++;
1111
0
  }
1112
0
}