Coverage Report

Created: 2025-09-27 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/zend_virtual_cwd.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright (c) The PHP Group                                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to version 3.01 of the PHP license,      |
6
   | that is bundled with this package in the file LICENSE, and is        |
7
   | available through the world-wide-web at the following url:           |
8
   | https://www.php.net/license/3_01.txt                                 |
9
   | If you did not receive a copy of the PHP license and are unable to   |
10
   | obtain it through the world-wide-web, please send a note to          |
11
   | license@php.net so we can mail you a copy immediately.               |
12
   +----------------------------------------------------------------------+
13
   | Authors: Andi Gutmans <andi@php.net>                                 |
14
   |          Sascha Schumann <sascha@schumann.cx>                        |
15
   |          Pierre Joye <pierre@php.net>                                |
16
   +----------------------------------------------------------------------+
17
*/
18
19
#include <sys/types.h>
20
#include <sys/stat.h>
21
#include <string.h>
22
#include <stdio.h>
23
#include <limits.h>
24
#include <errno.h>
25
#include <stdlib.h>
26
#include <fcntl.h>
27
#include <time.h>
28
29
#include "zend.h"
30
#include "zend_virtual_cwd.h"
31
32
#ifdef ZEND_WIN32
33
#include <io.h>
34
#include "tsrm_win32.h"
35
# ifndef IO_REPARSE_TAG_SYMLINK
36
#  define IO_REPARSE_TAG_SYMLINK 0xA000000C
37
# endif
38
39
# ifndef IO_REPARSE_TAG_DEDUP
40
#  define IO_REPARSE_TAG_DEDUP   0x80000013
41
# endif
42
43
# ifndef IO_REPARSE_TAG_CLOUD
44
#  define IO_REPARSE_TAG_CLOUD    (0x9000001AL)
45
# endif
46
/* IO_REPARSE_TAG_CLOUD_1 through IO_REPARSE_TAG_CLOUD_F have values of 0x9000101AL
47
   to 0x9000F01AL, they can be checked against the mask. */
48
#ifndef IO_REPARSE_TAG_CLOUD_MASK
49
#define IO_REPARSE_TAG_CLOUD_MASK (0x0000F000L)
50
#endif
51
52
#ifndef IO_REPARSE_TAG_ONEDRIVE
53
#define IO_REPARSE_TAG_ONEDRIVE   (0x80000021L)
54
#endif
55
56
# ifndef IO_REPARSE_TAG_ACTIVISION_HSM
57
#  define IO_REPARSE_TAG_ACTIVISION_HSM (0x00000047L)
58
# endif
59
60
# ifndef IO_REPARSE_TAG_PROJFS
61
#  define IO_REPARSE_TAG_PROJFS (0x9000001CL)
62
# endif
63
64
# ifndef VOLUME_NAME_NT
65
#  define VOLUME_NAME_NT 0x2
66
# endif
67
68
# ifndef VOLUME_NAME_DOS
69
#  define VOLUME_NAME_DOS 0x0
70
# endif
71
72
# include <winioctl.h>
73
# include <winnt.h>
74
#endif
75
76
#define VIRTUAL_CWD_DEBUG 0
77
78
#include "TSRM.h"
79
80
/* Only need mutex for popen() in Windows because it doesn't chdir() on UNIX */
81
#if defined(ZEND_WIN32) && defined(ZTS)
82
MUTEX_T cwd_mutex;
83
#endif
84
85
#ifdef ZTS
86
ts_rsrc_id cwd_globals_id;
87
size_t     cwd_globals_offset;
88
#else
89
virtual_cwd_globals cwd_globals;
90
#endif
91
92
static cwd_state main_cwd_state; /* True global */
93
94
#ifndef ZEND_WIN32
95
#include <unistd.h>
96
#else
97
#include <direct.h>
98
#include "zend_globals.h"
99
#include "zend_globals_macros.h"
100
#endif
101
102
#define CWD_STATE_COPY(d, s)        \
103
32
  (d)->cwd_length = (s)->cwd_length;    \
104
32
  (d)->cwd = (char *) emalloc((s)->cwd_length+1);  \
105
32
  memcpy((d)->cwd, (s)->cwd, (s)->cwd_length+1);
106
107
#define CWD_STATE_FREE(s)     \
108
24
  efree((s)->cwd); \
109
24
  (s)->cwd_length = 0;
110
111
#ifdef ZEND_WIN32
112
# define CWD_STATE_FREE_ERR(state) do { \
113
    DWORD last_error = GetLastError(); \
114
    CWD_STATE_FREE(state); \
115
    SetLastError(last_error); \
116
  } while (0)
117
#else
118
0
# define CWD_STATE_FREE_ERR(state) CWD_STATE_FREE(state)
119
#endif
120
121
static int php_is_dir_ok(const cwd_state *state)  /* {{{ */
122
0
{
123
0
  zend_stat_t buf = {0};
124
125
0
  if (php_sys_stat(state->cwd, &buf) == 0 && S_ISDIR(buf.st_mode))
126
0
    return (0);
127
128
0
  return (1);
129
0
}
130
/* }}} */
131
132
static int php_is_file_ok(const cwd_state *state)  /* {{{ */
133
0
{
134
0
  zend_stat_t buf = {0};
135
136
0
  if (php_sys_stat(state->cwd, &buf) == 0 && S_ISREG(buf.st_mode))
137
0
    return (0);
138
139
0
  return (1);
140
0
}
141
/* }}} */
142
143
static void cwd_globals_ctor(virtual_cwd_globals *cwd_g) /* {{{ */
144
16
{
145
16
  CWD_STATE_COPY(&cwd_g->cwd, &main_cwd_state);
146
16
  cwd_g->realpath_cache_size = 0;
147
16
  cwd_g->realpath_cache_size_limit = REALPATH_CACHE_SIZE;
148
16
  cwd_g->realpath_cache_ttl = REALPATH_CACHE_TTL;
149
16
  memset(cwd_g->realpath_cache, 0, sizeof(cwd_g->realpath_cache));
150
16
}
151
/* }}} */
152
153
static void realpath_cache_clean_helper(uint32_t max_entries, realpath_cache_bucket **cache, zend_long *cache_size)
154
0
{
155
0
  uint32_t i;
156
157
0
  for (i = 0; i < max_entries; i++) {
158
0
    realpath_cache_bucket *p = cache[i];
159
0
    while (p != NULL) {
160
0
      realpath_cache_bucket *r = p;
161
0
      p = p->next;
162
0
      free(r);
163
0
    }
164
0
    cache[i] = NULL;
165
0
  }
166
0
  *cache_size = 0;
167
0
}
168
169
static void cwd_globals_dtor(virtual_cwd_globals *cwd_g) /* {{{ */
170
0
{
171
0
  realpath_cache_clean_helper(sizeof(cwd_g->realpath_cache)/sizeof(cwd_g->realpath_cache[0]), cwd_g->realpath_cache, &cwd_g->realpath_cache_size);
172
0
}
173
/* }}} */
174
175
void virtual_cwd_main_cwd_init(uint8_t reinit) /* {{{ */
176
16
{
177
16
  char cwd[MAXPATHLEN];
178
16
  char *result;
179
180
16
  if (reinit) {
181
0
    free(main_cwd_state.cwd);
182
0
  }
183
184
#ifdef ZEND_WIN32
185
  ZeroMemory(&cwd, sizeof(cwd));
186
  result = php_win32_ioutil_getcwd(cwd, sizeof(cwd));
187
#else
188
16
  result = getcwd(cwd, sizeof(cwd));
189
16
#endif
190
191
16
  if (!result) {
192
0
    cwd[0] = '\0';
193
0
  }
194
195
16
  main_cwd_state.cwd_length = strlen(cwd);
196
#ifdef ZEND_WIN32
197
  if (main_cwd_state.cwd_length >= 2 && cwd[1] == ':') {
198
    cwd[0] = toupper(cwd[0]);
199
  }
200
#endif
201
16
  main_cwd_state.cwd = strdup(cwd);
202
16
}
203
/* }}} */
204
205
CWD_API void virtual_cwd_startup(void) /* {{{ */
206
16
{
207
16
  virtual_cwd_main_cwd_init(0);
208
#ifdef ZTS
209
  ts_allocate_fast_id(&cwd_globals_id, &cwd_globals_offset, sizeof(virtual_cwd_globals), (ts_allocate_ctor) cwd_globals_ctor, (ts_allocate_dtor) cwd_globals_dtor);
210
#else
211
16
  cwd_globals_ctor(&cwd_globals);
212
16
#endif
213
214
#if (defined(ZEND_WIN32)) && defined(ZTS)
215
  cwd_mutex = tsrm_mutex_alloc();
216
#endif
217
16
}
218
/* }}} */
219
220
CWD_API void virtual_cwd_shutdown(void) /* {{{ */
221
0
{
222
0
#ifndef ZTS
223
0
  cwd_globals_dtor(&cwd_globals);
224
0
#endif
225
#if (defined(ZEND_WIN32)) && defined(ZTS)
226
  tsrm_mutex_free(cwd_mutex);
227
#endif
228
229
0
  free(main_cwd_state.cwd); /* Don't use CWD_STATE_FREE because the non global states will probably use emalloc()/efree() */
230
0
}
231
/* }}} */
232
233
CWD_API void virtual_cwd_activate(void) /* {{{ */
234
16
{
235
16
  if (CWDG(cwd).cwd == NULL) {
236
16
    CWD_STATE_COPY(&CWDG(cwd), &main_cwd_state);
237
16
  }
238
16
}
239
/* }}} */
240
241
CWD_API void virtual_cwd_deactivate(void) /* {{{ */
242
278k
{
243
278k
  if (CWDG(cwd).cwd != NULL) {
244
24
    CWD_STATE_FREE(&CWDG(cwd));
245
24
    CWDG(cwd).cwd = NULL;
246
24
  }
247
278k
}
248
/* }}} */
249
250
CWD_API char *virtual_getcwd_ex(size_t *length) /* {{{ */
251
0
{
252
0
  cwd_state *state;
253
254
0
  state = &CWDG(cwd);
255
256
0
  if (state->cwd_length == 0) {
257
0
    char *retval;
258
259
0
    *length = 1;
260
0
    retval = (char *) emalloc(2);
261
0
    retval[0] = DEFAULT_SLASH;
262
0
    retval[1] = '\0';
263
0
    return retval;
264
0
  }
265
266
#ifdef ZEND_WIN32
267
  /* If we have something like C: */
268
  if (state->cwd_length == 2 && state->cwd[state->cwd_length-1] == ':') {
269
    char *retval;
270
271
    *length = state->cwd_length+1;
272
    retval = (char *) emalloc(*length+1);
273
    memcpy(retval, state->cwd, *length);
274
    retval[0] = toupper(retval[0]);
275
    retval[*length-1] = DEFAULT_SLASH;
276
    retval[*length] = '\0';
277
    return retval;
278
  }
279
#endif
280
0
  if (!state->cwd) {
281
0
    *length = 0;
282
0
    return NULL;
283
0
  }
284
285
0
  *length = state->cwd_length;
286
0
  return estrdup(state->cwd);
287
0
}
288
/* }}} */
289
290
/* Same semantics as UNIX getcwd() */
291
CWD_API char *virtual_getcwd(char *buf, size_t size) /* {{{ */
292
0
{
293
0
  size_t length;
294
0
  char *cwd;
295
296
0
  cwd = virtual_getcwd_ex(&length);
297
298
0
  if (buf == NULL) {
299
0
    return cwd;
300
0
  }
301
0
  if (length > size-1) {
302
0
    efree(cwd);
303
0
    errno = ERANGE; /* Is this OK? */
304
0
    return NULL;
305
0
  }
306
0
  if (!cwd) {
307
0
    return NULL;
308
0
  }
309
0
  memcpy(buf, cwd, length+1);
310
0
  efree(cwd);
311
0
  return buf;
312
0
}
313
/* }}} */
314
315
#ifdef ZEND_WIN32
316
static inline zend_ulong realpath_cache_key(const char *path, size_t path_len) /* {{{ */
317
{
318
  zend_ulong h;
319
  size_t bucket_key_len;
320
  const char *bucket_key_start = tsrm_win32_get_path_sid_key(path, path_len, &bucket_key_len);
321
  const char *bucket_key = bucket_key_start;
322
  const char *e;
323
324
  if (!bucket_key) {
325
    return 0;
326
  }
327
328
  e = bucket_key + bucket_key_len;
329
  for (h = Z_UL(2166136261); bucket_key < e;) {
330
    h *= Z_UL(16777619);
331
    h ^= *bucket_key++;
332
  }
333
  if (bucket_key_start != path) {
334
    HeapFree(GetProcessHeap(), 0, (LPVOID)bucket_key_start);
335
  }
336
  return h;
337
}
338
/* }}} */
339
#else
340
static inline zend_ulong realpath_cache_key(const char *path, size_t path_len) /* {{{ */
341
0
{
342
0
  zend_ulong h;
343
0
  const char *e = path + path_len;
344
345
0
  for (h = Z_UL(2166136261); path < e;) {
346
0
    h *= Z_UL(16777619);
347
0
    h ^= *path++;
348
0
  }
349
350
0
  return h;
351
0
}
352
/* }}} */
353
#endif /* defined(ZEND_WIN32) */
354
355
CWD_API void realpath_cache_clean(void) /* {{{ */
356
0
{
357
0
  realpath_cache_clean_helper(sizeof(CWDG(realpath_cache))/sizeof(CWDG(realpath_cache)[0]), CWDG(realpath_cache), &CWDG(realpath_cache_size));
358
0
}
359
/* }}} */
360
361
CWD_API void realpath_cache_del(const char *path, size_t path_len) /* {{{ */
362
0
{
363
0
  zend_ulong key = realpath_cache_key(path, path_len);
364
0
  zend_ulong n = key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0]));
365
0
  realpath_cache_bucket **bucket = &CWDG(realpath_cache)[n];
366
367
0
  while (*bucket != NULL) {
368
0
    if (key == (*bucket)->key && path_len == (*bucket)->path_len &&
369
0
          memcmp(path, (*bucket)->path, path_len) == 0) {
370
0
      realpath_cache_bucket *r = *bucket;
371
0
      *bucket = (*bucket)->next;
372
373
      /* if the pointers match then only subtract the length of the path */
374
0
        if(r->path == r->realpath) {
375
0
        CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1;
376
0
      } else {
377
0
        CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1 + r->realpath_len + 1;
378
0
      }
379
380
0
      free(r);
381
0
      return;
382
0
    } else {
383
0
      bucket = &(*bucket)->next;
384
0
    }
385
0
  }
386
0
}
387
/* }}} */
388
389
static inline void realpath_cache_add(const char *path, size_t path_len, const char *realpath, size_t realpath_len, int is_dir, time_t t) /* {{{ */
390
0
{
391
0
  zend_long size = sizeof(realpath_cache_bucket) + path_len + 1;
392
0
  int same = 1;
393
394
0
  if (realpath_len != path_len ||
395
0
    memcmp(path, realpath, path_len) != 0) {
396
0
    size += realpath_len + 1;
397
0
    same = 0;
398
0
  }
399
400
0
  if (CWDG(realpath_cache_size) + size <= CWDG(realpath_cache_size_limit)) {
401
0
    realpath_cache_bucket *bucket = malloc(size);
402
0
    zend_ulong n;
403
404
0
    if (bucket == NULL) {
405
0
      return;
406
0
    }
407
408
0
    bucket->key = realpath_cache_key(path, path_len);
409
0
    bucket->path = (char*)bucket + sizeof(realpath_cache_bucket);
410
0
    memcpy(bucket->path, path, path_len+1);
411
0
    bucket->path_len = path_len;
412
0
    if (same) {
413
0
      bucket->realpath = bucket->path;
414
0
    } else {
415
0
      bucket->realpath = bucket->path + (path_len + 1);
416
0
      memcpy(bucket->realpath, realpath, realpath_len+1);
417
0
    }
418
0
    bucket->realpath_len = realpath_len;
419
0
    bucket->is_dir = is_dir > 0;
420
#ifdef ZEND_WIN32
421
    bucket->is_rvalid   = 0;
422
    bucket->is_readable = 0;
423
    bucket->is_wvalid   = 0;
424
    bucket->is_writable = 0;
425
#endif
426
0
    bucket->expires = t + CWDG(realpath_cache_ttl);
427
0
    n = bucket->key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0]));
428
0
    bucket->next = CWDG(realpath_cache)[n];
429
0
    CWDG(realpath_cache)[n] = bucket;
430
0
    CWDG(realpath_cache_size) += size;
431
0
  }
432
0
}
433
/* }}} */
434
435
static inline realpath_cache_bucket* realpath_cache_find(const char *path, size_t path_len, time_t t) /* {{{ */
436
0
{
437
0
  zend_ulong key = realpath_cache_key(path, path_len);
438
0
  zend_ulong n = key % (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0]));
439
0
  realpath_cache_bucket **bucket = &CWDG(realpath_cache)[n];
440
441
0
  while (*bucket != NULL) {
442
0
    if (CWDG(realpath_cache_ttl) && (*bucket)->expires < t) {
443
0
      realpath_cache_bucket *r = *bucket;
444
0
      *bucket = (*bucket)->next;
445
446
      /* if the pointers match then only subtract the length of the path */
447
0
        if(r->path == r->realpath) {
448
0
        CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1;
449
0
      } else {
450
0
        CWDG(realpath_cache_size) -= sizeof(realpath_cache_bucket) + r->path_len + 1 + r->realpath_len + 1;
451
0
      }
452
0
      free(r);
453
0
    } else if (key == (*bucket)->key && path_len == (*bucket)->path_len &&
454
0
          memcmp(path, (*bucket)->path, path_len) == 0) {
455
0
      return *bucket;
456
0
    } else {
457
0
      bucket = &(*bucket)->next;
458
0
    }
459
0
  }
460
0
  return NULL;
461
0
}
462
/* }}} */
463
464
CWD_API realpath_cache_bucket* realpath_cache_lookup(const char *path, size_t path_len, time_t t) /* {{{ */
465
0
{
466
0
  return realpath_cache_find(path, path_len, t);
467
0
}
468
/* }}} */
469
470
CWD_API zend_long realpath_cache_size(void)
471
0
{
472
0
  return CWDG(realpath_cache_size);
473
0
}
474
475
CWD_API zend_long realpath_cache_max_buckets(void)
476
0
{
477
0
  return (sizeof(CWDG(realpath_cache)) / sizeof(CWDG(realpath_cache)[0]));
478
0
}
479
480
CWD_API realpath_cache_bucket** realpath_cache_get_buckets(void)
481
0
{
482
0
  return CWDG(realpath_cache);
483
0
}
484
485
486
#undef LINK_MAX
487
0
#define LINK_MAX 32
488
489
static size_t tsrm_realpath_r(char *path, size_t start, size_t len, int *ll, time_t *t, int use_realpath, bool is_dir, int *link_is_dir) /* {{{ */
490
174k
{
491
174k
  size_t i, j;
492
174k
  int directory = 0, save;
493
#ifdef ZEND_WIN32
494
  WIN32_FIND_DATAW dataw;
495
  HANDLE hFind = INVALID_HANDLE_VALUE;
496
  ALLOCA_FLAG(use_heap_large)
497
  wchar_t *pathw = NULL;
498
  int may_retry_reparse_point;
499
#define FREE_PATHW() \
500
  do { free(pathw); } while(0);
501
502
#else
503
174k
  zend_stat_t st = {0};
504
174k
#endif
505
174k
  realpath_cache_bucket *bucket;
506
174k
  char *tmp;
507
174k
  ALLOCA_FLAG(use_heap)
508
509
175k
  while (1) {
510
175k
    if (len <= start) {
511
1
      if (link_is_dir) {
512
0
        *link_is_dir = 1;
513
0
      }
514
1
      return start;
515
1
    }
516
517
175k
    i = len;
518
1.46M
    while (i > start && !IS_SLASH(path[i-1])) {
519
1.28M
      i--;
520
1.28M
    }
521
175k
    assert(i < MAXPATHLEN);
522
523
175k
    if (i == len ||
524
174k
      (i + 1 == len && path[i] == '.')) {
525
      /* remove double slashes and '.' */
526
983
      len = EXPECTED(i > 0) ? i - 1 : 0;
527
983
      is_dir = true;
528
983
      continue;
529
174k
    } else if (i + 2 == len && path[i] == '.' && path[i+1] == '.') {
530
      /* remove '..' and previous directory */
531
0
      is_dir = true;
532
0
      if (link_is_dir) {
533
0
        *link_is_dir = 1;
534
0
      }
535
0
      if (i <= start + 1) {
536
0
        return start ? start : len;
537
0
      }
538
0
      j = tsrm_realpath_r(path, start, i-1, ll, t, use_realpath, true, NULL);
539
0
      if (j > start && j != (size_t)-1) {
540
0
        j--;
541
0
        assert(i < MAXPATHLEN);
542
0
        while (j > start && !IS_SLASH(path[j])) {
543
0
          j--;
544
0
        }
545
0
        assert(i < MAXPATHLEN);
546
0
        if (!start) {
547
          /* leading '..' must not be removed in case of relative path */
548
0
          if (j == 0 && path[0] == '.' && path[1] == '.' &&
549
0
              IS_SLASH(path[2])) {
550
0
            path[3] = '.';
551
0
            path[4] = '.';
552
0
            path[5] = DEFAULT_SLASH;
553
0
            j = 5;
554
0
          } else if (j > 0 &&
555
0
              path[j+1] == '.' && path[j+2] == '.' &&
556
0
              IS_SLASH(path[j+3])) {
557
0
            j += 4;
558
0
            path[j++] = '.';
559
0
            path[j++] = '.';
560
0
            path[j] = DEFAULT_SLASH;
561
0
          }
562
0
        }
563
0
      } else if (!start && !j) {
564
        /* leading '..' must not be removed in case of relative path */
565
0
        path[0] = '.';
566
0
        path[1] = '.';
567
0
        path[2] = DEFAULT_SLASH;
568
0
        j = 2;
569
0
      }
570
0
      return j;
571
0
    }
572
573
174k
    path[len] = 0;
574
575
174k
    save = (use_realpath != CWD_EXPAND);
576
577
174k
    if (start && save && CWDG(realpath_cache_size_limit)) {
578
      /* cache lookup for absolute path */
579
0
      if (!*t) {
580
0
        *t = time(0);
581
0
      }
582
0
      if ((bucket = realpath_cache_find(path, len, *t)) != NULL) {
583
0
        if (is_dir && !bucket->is_dir) {
584
          /* not a directory */
585
0
          return (size_t)-1;
586
0
        } else {
587
0
          if (link_is_dir) {
588
0
            *link_is_dir = bucket->is_dir;
589
0
          }
590
0
          memcpy(path, bucket->realpath, bucket->realpath_len + 1);
591
0
          return bucket->realpath_len;
592
0
        }
593
0
      }
594
0
    }
595
596
#ifdef ZEND_WIN32
597
retry_reparse_point:
598
    may_retry_reparse_point = 0;
599
    if (save) {
600
      pathw = php_win32_ioutil_any_to_w(path);
601
      if (!pathw) {
602
        return (size_t)-1;
603
      }
604
      PHP_WIN32_IOUTIL_CHECK_PATH_W(pathw, (size_t)-1, 1);
605
      hFind = FindFirstFileExW(pathw, FindExInfoBasic, &dataw, FindExSearchNameMatch, NULL, 0);
606
      if (INVALID_HANDLE_VALUE == hFind) {
607
        if (use_realpath == CWD_REALPATH) {
608
          /* file not found */
609
          FREE_PATHW()
610
          return (size_t)-1;
611
        }
612
        /* continue resolution anyway but don't save result in the cache */
613
        save = 0;
614
      } else {
615
        FindClose(hFind);
616
      }
617
    }
618
619
    tmp = do_alloca(len+1, use_heap);
620
    memcpy(tmp, path, len+1);
621
622
retry_reparse_tag_cloud:
623
    if(save &&
624
        !(IS_UNC_PATH(path, len) && len >= 3 && path[2] != '?') &&
625
                               (dataw.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
626
        ) {
627
      /* File is a reparse point. Get the target */
628
      HANDLE hLink = NULL;
629
      PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER * pbuffer;
630
      DWORD retlength = 0;
631
      size_t bufindex = 0;
632
      uint8_t isabsolute = 0;
633
      wchar_t * reparsetarget;
634
      BOOL isVolume = FALSE;
635
#if VIRTUAL_CWD_DEBUG
636
      char *printname = NULL;
637
#endif
638
      char *substitutename = NULL;
639
      size_t substitutename_len;
640
      size_t substitutename_off = 0;
641
      wchar_t tmpsubstname[MAXPATHLEN];
642
643
      if(++(*ll) > LINK_MAX) {
644
        free_alloca(tmp, use_heap);
645
        FREE_PATHW()
646
        return (size_t)-1;
647
      }
648
649
      hLink = CreateFileW(pathw,
650
          0,
651
          PHP_WIN32_IOUTIL_DEFAULT_SHARE_MODE,
652
          NULL,
653
          OPEN_EXISTING,
654
          FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS,
655
          NULL);
656
      if(hLink == INVALID_HANDLE_VALUE) {
657
        free_alloca(tmp, use_heap);
658
        FREE_PATHW()
659
        return (size_t)-1;
660
      }
661
662
      pbuffer = (PHP_WIN32_IOUTIL_REPARSE_DATA_BUFFER *)do_alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE, use_heap_large);
663
      if (pbuffer == NULL) {
664
        CloseHandle(hLink);
665
        free_alloca(tmp, use_heap);
666
        FREE_PATHW()
667
        return (size_t)-1;
668
      }
669
      if(!DeviceIoControl(hLink, FSCTL_GET_REPARSE_POINT, NULL, 0, pbuffer,  MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &retlength, NULL)) {
670
        BY_HANDLE_FILE_INFORMATION fileInformation;
671
672
        free_alloca(pbuffer, use_heap_large);
673
        if ((dataw.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
674
            (dataw.dwReserved0 & ~IO_REPARSE_TAG_CLOUD_MASK) == IO_REPARSE_TAG_CLOUD &&
675
            EG(windows_version_info).dwMajorVersion >= 10 &&
676
            EG(windows_version_info).dwMinorVersion == 0 &&
677
            EG(windows_version_info).dwBuildNumber >= 18362 &&
678
            GetFileInformationByHandle(hLink, &fileInformation) &&
679
            !(fileInformation.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
680
          dataw.dwFileAttributes = fileInformation.dwFileAttributes;
681
          CloseHandle(hLink);
682
          (*ll)--;
683
          goto retry_reparse_tag_cloud;
684
        }
685
        free_alloca(tmp, use_heap);
686
        CloseHandle(hLink);
687
        FREE_PATHW()
688
        return (size_t)-1;
689
      }
690
691
      CloseHandle(hLink);
692
693
      if(pbuffer->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
694
        may_retry_reparse_point = 1;
695
        reparsetarget = pbuffer->SymbolicLinkReparseBuffer.ReparseTarget;
696
        isabsolute = pbuffer->SymbolicLinkReparseBuffer.Flags == 0;
697
#if VIRTUAL_CWD_DEBUG
698
        printname = php_win32_ioutil_w_to_any(reparsetarget + pbuffer->MountPointReparseBuffer.PrintNameOffset  / sizeof(WCHAR));
699
        if (!printname) {
700
          free_alloca(pbuffer, use_heap_large);
701
          free_alloca(tmp, use_heap);
702
          FREE_PATHW()
703
          return (size_t)-1;
704
        }
705
#endif
706
707
        substitutename_len = pbuffer->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
708
        if (substitutename_len >= MAXPATHLEN) {
709
          free_alloca(pbuffer, use_heap_large);
710
          free_alloca(tmp, use_heap);
711
          FREE_PATHW()
712
          return (size_t)-1;
713
        }
714
        memcpy(tmpsubstname, reparsetarget + pbuffer->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR), pbuffer->MountPointReparseBuffer.SubstituteNameLength);
715
        tmpsubstname[substitutename_len] = L'\0';
716
        substitutename = php_win32_cp_conv_w_to_any(tmpsubstname, substitutename_len, &substitutename_len);
717
        if (!substitutename || substitutename_len >= MAXPATHLEN) {
718
          free_alloca(pbuffer, use_heap_large);
719
          free_alloca(tmp, use_heap);
720
          free(substitutename);
721
#if VIRTUAL_CWD_DEBUG
722
          free(printname);
723
#endif
724
          FREE_PATHW()
725
          return (size_t)-1;
726
        }
727
      }
728
      else if(pbuffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
729
        isabsolute = 1;
730
        reparsetarget = pbuffer->MountPointReparseBuffer.ReparseTarget;
731
#if VIRTUAL_CWD_DEBUG
732
        printname = php_win32_ioutil_w_to_any(reparsetarget + pbuffer->MountPointReparseBuffer.PrintNameOffset  / sizeof(WCHAR));
733
        if (!printname) {
734
          free_alloca(pbuffer, use_heap_large);
735
          free_alloca(tmp, use_heap);
736
          FREE_PATHW()
737
          return (size_t)-1;
738
        }
739
#endif
740
741
742
        substitutename_len = pbuffer->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
743
        if (substitutename_len >= MAXPATHLEN) {
744
          free_alloca(pbuffer, use_heap_large);
745
          free_alloca(tmp, use_heap);
746
          FREE_PATHW()
747
          return (size_t)-1;
748
        }
749
        memcpy(tmpsubstname, reparsetarget + pbuffer->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR), pbuffer->MountPointReparseBuffer.SubstituteNameLength);
750
        tmpsubstname[substitutename_len] = L'\0';
751
        substitutename = php_win32_cp_conv_w_to_any(tmpsubstname, substitutename_len, &substitutename_len);
752
        if (!substitutename || substitutename_len >= MAXPATHLEN) {
753
          free_alloca(pbuffer, use_heap_large);
754
          free_alloca(tmp, use_heap);
755
          free(substitutename);
756
#if VIRTUAL_CWD_DEBUG
757
          free(printname);
758
#endif
759
          FREE_PATHW()
760
          return (size_t)-1;
761
        }
762
      }
763
      else if (pbuffer->ReparseTag == IO_REPARSE_TAG_DEDUP ||
764
          /* Starting with 1709. */
765
          (pbuffer->ReparseTag & ~IO_REPARSE_TAG_CLOUD_MASK) == IO_REPARSE_TAG_CLOUD ||
766
          IO_REPARSE_TAG_ONEDRIVE == pbuffer->ReparseTag ||
767
          IO_REPARSE_TAG_ACTIVISION_HSM == pbuffer->ReparseTag ||
768
          IO_REPARSE_TAG_PROJFS == pbuffer->ReparseTag) {
769
        isabsolute = 1;
770
        substitutename = malloc((len + 1) * sizeof(char));
771
        if (!substitutename) {
772
          free_alloca(pbuffer, use_heap_large);
773
          free_alloca(tmp, use_heap);
774
          FREE_PATHW()
775
          return (size_t)-1;
776
        }
777
        memcpy(substitutename, path, len + 1);
778
        substitutename_len = len;
779
      } else {
780
        /* XXX this might be not the end, restart handling with REPARSE_GUID_DATA_BUFFER should be implemented. */
781
        free_alloca(pbuffer, use_heap_large);
782
        free_alloca(tmp, use_heap);
783
        FREE_PATHW()
784
        return (size_t)-1;
785
      }
786
787
      if(isabsolute && substitutename_len > 4) {
788
        /* Do not resolve volumes (for now). A mounted point can
789
           target a volume without a drive, it is not certain that
790
           all IO functions we use in php and its deps support
791
           path with volume GUID instead of the DOS way, like:
792
           d:\test\mnt\foo
793
           \\?\Volume{62d1c3f8-83b9-11de-b108-806e6f6e6963}\foo
794
        */
795
        if (strncmp(substitutename, "\\??\\Volume{",11) == 0
796
          || strncmp(substitutename, "\\\\?\\Volume{",11) == 0
797
          || strncmp(substitutename, "\\??\\UNC\\", 8) == 0
798
          ) {
799
          isVolume = TRUE;
800
          substitutename_off = 0;
801
        } else
802
          /* do not use the \??\ and \\?\ prefix*/
803
          if (strncmp(substitutename, "\\??\\", 4) == 0
804
            || strncmp(substitutename, "\\\\?\\", 4) == 0) {
805
          substitutename_off = 4;
806
        }
807
      }
808
809
      if (!isVolume) {
810
        char * tmp2 = substitutename + substitutename_off;
811
        for (bufindex = 0; bufindex + substitutename_off < substitutename_len; bufindex++) {
812
          *(path + bufindex) = *(tmp2 + bufindex);
813
        }
814
815
        *(path + bufindex) = 0;
816
        j = bufindex;
817
      } else {
818
        j = len;
819
      }
820
821
822
#if VIRTUAL_CWD_DEBUG
823
      fprintf(stderr, "reparse: print: %s ", printname);
824
      fprintf(stderr, "sub: %s ", substitutename);
825
      fprintf(stderr, "resolved: %s ", path);
826
      free(printname);
827
#endif
828
      free_alloca(pbuffer, use_heap_large);
829
      free(substitutename);
830
831
      if (may_retry_reparse_point) {
832
        DWORD attrs;
833
834
        FREE_PATHW()
835
        pathw = php_win32_ioutil_any_to_w(path);
836
        if (!pathw) {
837
          return (size_t)-1;
838
        }
839
        attrs = GetFileAttributesW(pathw);
840
        if (!isVolume && attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_REPARSE_POINT)) {
841
          free_alloca(tmp, use_heap);
842
          FREE_PATHW()
843
          goto retry_reparse_point;
844
        }
845
      }
846
847
      if(isabsolute == 1) {
848
        if (!((j == 3) && (path[1] == ':') && (path[2] == '\\'))) {
849
          /* use_realpath is 0 in the call below coz path is absolute*/
850
          j = tsrm_realpath_r(path, 0, j, ll, t, 0, is_dir, &directory);
851
          if(j == (size_t)-1) {
852
            free_alloca(tmp, use_heap);
853
            FREE_PATHW()
854
            return (size_t)-1;
855
          }
856
        }
857
      }
858
      else {
859
        if(i + j >= MAXPATHLEN - 1) {
860
          free_alloca(tmp, use_heap);
861
          FREE_PATHW()
862
          return (size_t)-1;
863
        }
864
865
        memmove(path+i, path, j+1);
866
        memcpy(path, tmp, i-1);
867
        path[i-1] = DEFAULT_SLASH;
868
        j  = tsrm_realpath_r(path, start, i + j, ll, t, use_realpath, is_dir, &directory);
869
        if(j == (size_t)-1) {
870
          free_alloca(tmp, use_heap);
871
          FREE_PATHW()
872
          return (size_t)-1;
873
        }
874
      }
875
      directory = (dataw.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
876
877
      if(link_is_dir) {
878
        *link_is_dir = directory;
879
      }
880
    }
881
    else {
882
      if (save) {
883
        directory = (dataw.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
884
        if (is_dir && !directory) {
885
          /* not a directory */
886
          free_alloca(tmp, use_heap);
887
          FREE_PATHW()
888
          return (size_t)-1;
889
        }
890
      }
891
#else
892
174k
    if (save && php_sys_lstat(path, &st) < 0) {
893
15.4k
      if (use_realpath == CWD_REALPATH) {
894
        /* file not found */
895
9.24k
        return (size_t)-1;
896
9.24k
      }
897
      /* continue resolution anyway but don't save result in the cache */
898
6.21k
      save = 0;
899
6.21k
    }
900
901
165k
    tmp = do_alloca(len+1, use_heap);
902
165k
    memcpy(tmp, path, len+1);
903
904
165k
    if (save && S_ISLNK(st.st_mode)) {
905
0
      if (++(*ll) > LINK_MAX || (j = (size_t)php_sys_readlink(tmp, path, MAXPATHLEN)) == (size_t)-1) {
906
        /* too many links or broken symlinks */
907
0
        free_alloca(tmp, use_heap);
908
0
        return (size_t)-1;
909
0
      }
910
0
      path[j] = 0;
911
0
      if (IS_ABSOLUTE_PATH(path, j)) {
912
0
        j = tsrm_realpath_r(path, 1, j, ll, t, use_realpath, is_dir, &directory);
913
0
        if (j == (size_t)-1) {
914
0
          free_alloca(tmp, use_heap);
915
0
          return (size_t)-1;
916
0
        }
917
0
      } else {
918
0
        if (i + j >= MAXPATHLEN-1) {
919
0
          free_alloca(tmp, use_heap);
920
0
          return (size_t)-1; /* buffer overflow */
921
0
        }
922
0
        memmove(path+i, path, j+1);
923
0
        memcpy(path, tmp, i-1);
924
0
        path[i-1] = DEFAULT_SLASH;
925
0
        j = tsrm_realpath_r(path, start, i + j, ll, t, use_realpath, is_dir, &directory);
926
0
        if (j == (size_t)-1) {
927
0
          free_alloca(tmp, use_heap);
928
0
          return (size_t)-1;
929
0
        }
930
0
      }
931
0
      if (link_is_dir) {
932
0
        *link_is_dir = directory;
933
0
      }
934
165k
    } else {
935
165k
      if (save) {
936
159k
        directory = S_ISDIR(st.st_mode);
937
159k
        if (link_is_dir) {
938
0
          *link_is_dir = directory;
939
0
        }
940
159k
        if (is_dir && !directory) {
941
          /* not a directory */
942
0
          free_alloca(tmp, use_heap);
943
0
          return (size_t)-1;
944
0
        }
945
159k
      }
946
165k
#endif
947
165k
      if (i <= start + 1) {
948
79.7k
        j = start;
949
85.9k
      } else {
950
        /* some leading directories may be inaccessible */
951
85.9k
        j = tsrm_realpath_r(path, start, i-1, ll, t, save ? CWD_FILEPATH : use_realpath, true,
952
85.9k
                NULL);
953
85.9k
        if (j > start && j != (size_t)-1) {
954
85.9k
          path[j++] = DEFAULT_SLASH;
955
85.9k
        }
956
85.9k
      }
957
#ifdef ZEND_WIN32
958
      if (j == (size_t)-1 || j + len >= MAXPATHLEN - 1 + i) {
959
        free_alloca(tmp, use_heap);
960
        FREE_PATHW()
961
        return (size_t)-1;
962
      }
963
      if (save) {
964
        size_t sz;
965
        char *tmp_path = php_win32_ioutil_conv_w_to_any(dataw.cFileName, PHP_WIN32_CP_IGNORE_LEN, &sz);
966
        if (!tmp_path) {
967
          free_alloca(tmp, use_heap);
968
          FREE_PATHW()
969
          return (size_t)-1;
970
        }
971
        i = sz;
972
        memcpy(path+j, tmp_path, i+1);
973
        free(tmp_path);
974
        j += i;
975
      } else {
976
        /* use the original file or directory name as it wasn't found */
977
        memcpy(path+j, tmp+i, len-i+1);
978
        j += (len-i);
979
      }
980
    }
981
#else
982
165k
      if (j == (size_t)-1 || j + len >= MAXPATHLEN - 1 + i) {
983
0
        free_alloca(tmp, use_heap);
984
0
        return (size_t)-1;
985
0
      }
986
165k
      memcpy(path+j, tmp+i, len-i+1);
987
165k
      j += (len-i);
988
165k
    }
989
165k
#endif
990
991
165k
    if (save && start && CWDG(realpath_cache_size_limit)) {
992
      /* save absolute path in the cache */
993
0
      realpath_cache_add(tmp, len, path, j, directory, *t);
994
0
    }
995
996
165k
    free_alloca(tmp, use_heap);
997
#ifdef ZEND_WIN32
998
    FREE_PATHW()
999
#undef FREE_PATHW
1000
#endif
1001
165k
    return j;
1002
165k
  }
1003
174k
}
1004
/* }}} */
1005
1006
/* Resolve path relatively to state and put the real path into state */
1007
/* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */
1008
CWD_API int virtual_file_ex(cwd_state *state, const char *path, verify_path_func verify_path, int use_realpath) /* {{{ */
1009
88.9k
{
1010
88.9k
  size_t path_length = strlen(path);
1011
88.9k
  char resolved_path[MAXPATHLEN];
1012
88.9k
  size_t start = 1;
1013
88.9k
  int ll = 0;
1014
88.9k
  time_t t;
1015
88.9k
  int ret;
1016
88.9k
  bool add_slash;
1017
88.9k
  void *tmp;
1018
1019
88.9k
  if (!path_length || path_length >= MAXPATHLEN-1) {
1020
#ifdef ZEND_WIN32
1021
    SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
1022
#else
1023
0
    errno = EINVAL;
1024
0
#endif
1025
0
    return 1;
1026
0
  }
1027
1028
#if VIRTUAL_CWD_DEBUG
1029
  fprintf(stderr,"cwd = %s path = %s\n", state->cwd, path);
1030
#endif
1031
1032
  /* cwd_length can be 0 when getcwd() fails.
1033
   * This can happen under solaris when a dir does not have read permissions
1034
   * but *does* have execute permissions */
1035
88.9k
  if (!IS_ABSOLUTE_PATH(path, path_length)) {
1036
2.91k
    if (state->cwd_length == 0) {
1037
      /* resolve relative path */
1038
0
      start = 0;
1039
0
      memcpy(resolved_path , path, path_length + 1);
1040
2.91k
    } else {
1041
2.91k
      size_t state_cwd_length = state->cwd_length;
1042
1043
#ifdef ZEND_WIN32
1044
      if (IS_SLASH(path[0])) {
1045
        if (state->cwd[1] == ':') {
1046
          /* Copy only the drive name */
1047
          state_cwd_length = 2;
1048
        } else if (IS_UNC_PATH(state->cwd, state->cwd_length)) {
1049
          /* Copy only the share name */
1050
          state_cwd_length = 2;
1051
          while (IS_SLASH(state->cwd[state_cwd_length])) {
1052
            state_cwd_length++;
1053
          }
1054
          while (state->cwd[state_cwd_length] &&
1055
              !IS_SLASH(state->cwd[state_cwd_length])) {
1056
            state_cwd_length++;
1057
          }
1058
          while (IS_SLASH(state->cwd[state_cwd_length])) {
1059
            state_cwd_length++;
1060
          }
1061
          while (state->cwd[state_cwd_length] &&
1062
              !IS_SLASH(state->cwd[state_cwd_length])) {
1063
            state_cwd_length++;
1064
          }
1065
        }
1066
      }
1067
#endif
1068
2.91k
      if (path_length + state_cwd_length + 1 >= MAXPATHLEN-1) {
1069
#ifdef ZEND_WIN32
1070
        SET_ERRNO_FROM_WIN32_CODE(ERROR_BUFFER_OVERFLOW);
1071
#else
1072
3
        errno = ENAMETOOLONG;
1073
3
#endif
1074
3
        return 1;
1075
3
      }
1076
2.90k
      memcpy(resolved_path, state->cwd, state_cwd_length);
1077
2.90k
      if (resolved_path[state_cwd_length-1] == DEFAULT_SLASH) {
1078
0
        memcpy(resolved_path + state_cwd_length, path, path_length + 1);
1079
0
        path_length += state_cwd_length;
1080
2.90k
      } else {
1081
2.90k
        resolved_path[state_cwd_length] = DEFAULT_SLASH;
1082
2.90k
        memcpy(resolved_path + state_cwd_length + 1, path, path_length + 1);
1083
2.90k
        path_length += state_cwd_length + 1;
1084
2.90k
      }
1085
2.90k
    }
1086
86.0k
  } else {
1087
#ifdef ZEND_WIN32
1088
    if (path_length > 2 && path[1] == ':' && !IS_SLASH(path[2])) {
1089
      resolved_path[0] = path[0];
1090
      resolved_path[1] = ':';
1091
      resolved_path[2] = DEFAULT_SLASH;
1092
      memcpy(resolved_path + 3, path + 2, path_length - 1);
1093
      path_length++;
1094
    } else
1095
#endif
1096
86.0k
    memcpy(resolved_path, path, path_length + 1);
1097
86.0k
  }
1098
1099
#ifdef ZEND_WIN32
1100
  if (memchr(resolved_path, '*', path_length) ||
1101
    memchr(resolved_path, '?', path_length)) {
1102
    SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_NAME);
1103
    return 1;
1104
  }
1105
#endif
1106
1107
#ifdef ZEND_WIN32
1108
  if (IS_UNC_PATH(resolved_path, path_length)) {
1109
    /* skip UNC name */
1110
    resolved_path[0] = DEFAULT_SLASH;
1111
    resolved_path[1] = DEFAULT_SLASH;
1112
    start = 2;
1113
    while (!IS_SLASH(resolved_path[start])) {
1114
      if (resolved_path[start] == 0) {
1115
        goto verify;
1116
      }
1117
      resolved_path[start] = toupper(resolved_path[start]);
1118
      start++;
1119
    }
1120
    resolved_path[start++] = DEFAULT_SLASH;
1121
    while (!IS_SLASH(resolved_path[start])) {
1122
      if (resolved_path[start] == 0) {
1123
        goto verify;
1124
      }
1125
      resolved_path[start] = toupper(resolved_path[start]);
1126
      start++;
1127
    }
1128
    resolved_path[start++] = DEFAULT_SLASH;
1129
  } else if (IS_ABSOLUTE_PATH(resolved_path, path_length)) {
1130
    /* skip DRIVE name */
1131
    resolved_path[0] = toupper(resolved_path[0]);
1132
    resolved_path[2] = DEFAULT_SLASH;
1133
    if (path_length == 2) {
1134
      resolved_path[3] = '\0';
1135
    }
1136
    start = 3;
1137
  }
1138
#endif
1139
1140
88.9k
  add_slash = (use_realpath != CWD_REALPATH) && path_length > 0 && IS_SLASH(resolved_path[path_length-1]);
1141
88.9k
  t = CWDG(realpath_cache_ttl) ? 0 : -1;
1142
88.9k
  path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, false, NULL);
1143
1144
88.9k
  if (path_length == (size_t)-1) {
1145
#ifdef ZEND_WIN32
1146
    if (errno != EACCES) {
1147
      errno = ENOENT;
1148
    }
1149
#else
1150
9.24k
    errno = ENOENT;
1151
9.24k
#endif
1152
9.24k
    return 1;
1153
9.24k
  }
1154
1155
79.7k
  if (!start && !path_length) {
1156
0
    resolved_path[path_length++] = '.';
1157
0
  }
1158
1159
79.7k
  if (add_slash && path_length && !IS_SLASH(resolved_path[path_length-1])) {
1160
8
    if (path_length >= MAXPATHLEN-1) {
1161
0
      return -1;
1162
0
    }
1163
8
    resolved_path[path_length++] = DEFAULT_SLASH;
1164
8
  }
1165
79.7k
  resolved_path[path_length] = 0;
1166
1167
#ifdef ZEND_WIN32
1168
verify:
1169
#endif
1170
79.7k
  if (verify_path) {
1171
0
    cwd_state old_state;
1172
1173
0
    CWD_STATE_COPY(&old_state, state);
1174
0
    state->cwd_length = path_length;
1175
1176
0
    tmp = erealloc(state->cwd, state->cwd_length+1);
1177
0
    state->cwd = (char *) tmp;
1178
1179
0
    memcpy(state->cwd, resolved_path, state->cwd_length+1);
1180
0
    if (verify_path(state)) {
1181
0
      CWD_STATE_FREE(state);
1182
0
      *state = old_state;
1183
0
      ret = 1;
1184
0
    } else {
1185
0
      CWD_STATE_FREE(&old_state);
1186
0
      ret = 0;
1187
0
    }
1188
79.7k
  } else {
1189
79.7k
    state->cwd_length = path_length;
1190
79.7k
    tmp = erealloc(state->cwd, state->cwd_length+1);
1191
79.7k
    state->cwd = (char *) tmp;
1192
1193
79.7k
    memcpy(state->cwd, resolved_path, state->cwd_length+1);
1194
79.7k
    ret = 0;
1195
79.7k
  }
1196
1197
#if VIRTUAL_CWD_DEBUG
1198
  fprintf (stderr, "virtual_file_ex() = %s\n",state->cwd);
1199
#endif
1200
79.7k
  return (ret);
1201
79.7k
}
1202
/* }}} */
1203
1204
CWD_API zend_result virtual_chdir(const char *path) /* {{{ */
1205
0
{
1206
0
  return virtual_file_ex(&CWDG(cwd), path, php_is_dir_ok, CWD_REALPATH) ? FAILURE : SUCCESS;
1207
0
}
1208
/* }}} */
1209
1210
1211
/* returns 0 for ok, 1 for empty string, -1 on error */
1212
CWD_API int virtual_chdir_file(const char *path, int (*p_chdir)(const char *path)) /* {{{ */
1213
0
{
1214
0
  size_t length = strlen(path);
1215
0
  char *temp;
1216
0
  int retval;
1217
0
  ALLOCA_FLAG(use_heap)
1218
1219
0
  if (length == 0) {
1220
0
    return 1; /* Can't cd to empty string */
1221
0
  }
1222
0
  while(--length < SIZE_MAX && !IS_SLASH(path[length])) {
1223
0
  }
1224
1225
0
  if (length == SIZE_MAX) {
1226
    /* No directory only file name */
1227
0
    errno = ENOENT;
1228
0
    return -1;
1229
0
  }
1230
1231
0
  if (length == COPY_WHEN_ABSOLUTE(path) && IS_ABSOLUTE_PATH(path, length+1)) { /* Also use trailing slash if this is absolute */
1232
0
    length++;
1233
0
  }
1234
0
  temp = (char *) do_alloca(length+1, use_heap);
1235
0
  memcpy(temp, path, length);
1236
0
  temp[length] = 0;
1237
#if VIRTUAL_CWD_DEBUG
1238
  fprintf (stderr, "Changing directory to %s\n", temp);
1239
#endif
1240
0
  retval = p_chdir(temp);
1241
0
  free_alloca(temp, use_heap);
1242
0
  return retval;
1243
0
}
1244
/* }}} */
1245
1246
CWD_API char *virtual_realpath(const char *path, char *real_path) /* {{{ */
1247
0
{
1248
0
  cwd_state new_state;
1249
0
  char *retval;
1250
0
  char cwd[MAXPATHLEN];
1251
1252
  /* realpath("") returns CWD */
1253
0
  if (!*path) {
1254
0
    new_state.cwd = (char*)emalloc(1);
1255
0
    new_state.cwd[0] = '\0';
1256
0
    new_state.cwd_length = 0;
1257
0
    if (VCWD_GETCWD(cwd, MAXPATHLEN)) {
1258
0
      path = cwd;
1259
0
    }
1260
0
  } else if (!IS_ABSOLUTE_PATH(path, strlen(path))) {
1261
0
    CWD_STATE_COPY(&new_state, &CWDG(cwd));
1262
0
  } else {
1263
0
    new_state.cwd = (char*)emalloc(1);
1264
0
    new_state.cwd[0] = '\0';
1265
0
    new_state.cwd_length = 0;
1266
0
  }
1267
1268
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)==0) {
1269
0
    size_t len = new_state.cwd_length>MAXPATHLEN-1?MAXPATHLEN-1:new_state.cwd_length;
1270
1271
0
    memcpy(real_path, new_state.cwd, len);
1272
0
    real_path[len] = '\0';
1273
0
    retval = real_path;
1274
0
  } else {
1275
0
    retval = NULL;
1276
0
  }
1277
1278
0
  CWD_STATE_FREE(&new_state);
1279
0
  return retval;
1280
0
}
1281
/* }}} */
1282
1283
/* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */
1284
CWD_API int virtual_filepath_ex(const char *path, char **filepath, verify_path_func verify_path) /* {{{ */
1285
0
{
1286
0
  cwd_state new_state;
1287
0
  int retval;
1288
1289
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1290
0
  retval = virtual_file_ex(&new_state, path, verify_path, CWD_FILEPATH);
1291
1292
0
  *filepath = new_state.cwd;
1293
1294
0
  return retval;
1295
1296
0
}
1297
/* }}} */
1298
1299
/* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */
1300
CWD_API int virtual_filepath(const char *path, char **filepath) /* {{{ */
1301
0
{
1302
0
  return virtual_filepath_ex(path, filepath, php_is_file_ok);
1303
0
}
1304
/* }}} */
1305
1306
CWD_API FILE *virtual_fopen(const char *path, const char *mode) /* {{{ */
1307
0
{
1308
0
  cwd_state new_state;
1309
0
  FILE *f;
1310
1311
0
  if (path[0] == '\0') { /* Fail to open empty path */
1312
0
    return NULL;
1313
0
  }
1314
1315
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1316
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) {
1317
0
    CWD_STATE_FREE_ERR(&new_state);
1318
0
    return NULL;
1319
0
  }
1320
1321
#ifdef ZEND_WIN32
1322
  f = php_win32_ioutil_fopen(new_state.cwd, mode);
1323
#else
1324
0
  f = fopen(new_state.cwd, mode);
1325
0
#endif
1326
1327
0
  CWD_STATE_FREE_ERR(&new_state);
1328
1329
0
  return f;
1330
0
}
1331
/* }}} */
1332
1333
CWD_API int virtual_access(const char *pathname, int mode) /* {{{ */
1334
0
{
1335
0
  cwd_state new_state;
1336
0
  int ret;
1337
1338
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1339
0
  if (virtual_file_ex(&new_state, pathname, NULL, CWD_REALPATH)) {
1340
0
    CWD_STATE_FREE_ERR(&new_state);
1341
0
    return -1;
1342
0
  }
1343
1344
#if defined(ZEND_WIN32)
1345
  ret = tsrm_win32_access(new_state.cwd, mode);
1346
#else
1347
0
  ret = access(new_state.cwd, mode);
1348
0
#endif
1349
1350
0
  CWD_STATE_FREE_ERR(&new_state);
1351
1352
0
  return ret;
1353
0
}
1354
/* }}} */
1355
1356
#ifdef HAVE_UTIME
1357
CWD_API int virtual_utime(const char *filename, struct utimbuf *buf) /* {{{ */
1358
0
{
1359
0
  cwd_state new_state;
1360
0
  int ret;
1361
1362
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1363
0
  if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) {
1364
0
    CWD_STATE_FREE_ERR(&new_state);
1365
0
    return -1;
1366
0
  }
1367
1368
#ifdef ZEND_WIN32
1369
  ret = win32_utime(new_state.cwd, buf);
1370
#else
1371
0
  ret = utime(new_state.cwd, buf);
1372
0
#endif
1373
1374
0
  CWD_STATE_FREE_ERR(&new_state);
1375
0
  return ret;
1376
0
}
1377
/* }}} */
1378
#endif
1379
1380
CWD_API int virtual_chmod(const char *filename, mode_t mode) /* {{{ */
1381
0
{
1382
0
  cwd_state new_state;
1383
0
  int ret;
1384
1385
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1386
0
  if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) {
1387
0
    CWD_STATE_FREE_ERR(&new_state);
1388
0
    return -1;
1389
0
  }
1390
1391
#ifdef ZEND_WIN32
1392
  {
1393
    mode_t _tmp = mode;
1394
1395
    mode = 0;
1396
1397
    if (_tmp & _S_IREAD) {
1398
      mode |= _S_IREAD;
1399
    }
1400
    if (_tmp & _S_IWRITE) {
1401
      mode |= _S_IWRITE;
1402
    }
1403
    ret = php_win32_ioutil_chmod(new_state.cwd, mode);
1404
  }
1405
#else
1406
0
  ret = chmod(new_state.cwd, mode);
1407
0
#endif
1408
1409
0
  CWD_STATE_FREE_ERR(&new_state);
1410
0
  return ret;
1411
0
}
1412
/* }}} */
1413
1414
#if !defined(ZEND_WIN32)
1415
CWD_API int virtual_chown(const char *filename, uid_t owner, gid_t group, int link) /* {{{ */
1416
0
{
1417
0
  cwd_state new_state;
1418
0
  int ret;
1419
1420
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1421
0
  if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) {
1422
0
    CWD_STATE_FREE_ERR(&new_state);
1423
0
    return -1;
1424
0
  }
1425
1426
0
  if (link) {
1427
0
#ifdef HAVE_LCHOWN
1428
0
    ret = lchown(new_state.cwd, owner, group);
1429
#else
1430
    ret = -1;
1431
#endif
1432
0
  } else {
1433
0
    ret = chown(new_state.cwd, owner, group);
1434
0
  }
1435
1436
0
  CWD_STATE_FREE_ERR(&new_state);
1437
0
  return ret;
1438
0
}
1439
/* }}} */
1440
#endif
1441
1442
CWD_API int virtual_open(const char *path, int flags, ...) /* {{{ */
1443
0
{
1444
0
  cwd_state new_state;
1445
0
  int f;
1446
1447
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1448
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_FILEPATH)) {
1449
0
    CWD_STATE_FREE_ERR(&new_state);
1450
0
    return -1;
1451
0
  }
1452
1453
0
  if (flags & O_CREAT) {
1454
0
    mode_t mode;
1455
0
    va_list arg;
1456
1457
0
    va_start(arg, flags);
1458
0
    mode = (mode_t) va_arg(arg, int);
1459
0
    va_end(arg);
1460
1461
#ifdef ZEND_WIN32
1462
    f = php_win32_ioutil_open(new_state.cwd, flags, mode);
1463
#else
1464
0
    f = open(new_state.cwd, flags, mode);
1465
0
#endif
1466
0
  } else {
1467
#ifdef ZEND_WIN32
1468
    f = php_win32_ioutil_open(new_state.cwd, flags);
1469
#else
1470
0
    f = open(new_state.cwd, flags);
1471
0
#endif
1472
0
  }
1473
0
  CWD_STATE_FREE_ERR(&new_state);
1474
0
  return f;
1475
0
}
1476
/* }}} */
1477
1478
CWD_API int virtual_creat(const char *path, mode_t mode) /* {{{ */
1479
0
{
1480
0
  cwd_state new_state;
1481
0
  int f;
1482
1483
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1484
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_FILEPATH)) {
1485
0
    CWD_STATE_FREE_ERR(&new_state);
1486
0
    return -1;
1487
0
  }
1488
1489
0
  f = creat(new_state.cwd,  mode);
1490
1491
0
  CWD_STATE_FREE_ERR(&new_state);
1492
0
  return f;
1493
0
}
1494
/* }}} */
1495
1496
CWD_API int virtual_rename(const char *oldname, const char *newname) /* {{{ */
1497
0
{
1498
0
  cwd_state old_state;
1499
0
  cwd_state new_state;
1500
0
  int retval;
1501
1502
0
  CWD_STATE_COPY(&old_state, &CWDG(cwd));
1503
0
  if (virtual_file_ex(&old_state, oldname, NULL, CWD_EXPAND)) {
1504
0
    CWD_STATE_FREE_ERR(&old_state);
1505
0
    return -1;
1506
0
  }
1507
0
  oldname = old_state.cwd;
1508
1509
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1510
0
  if (virtual_file_ex(&new_state, newname, NULL, CWD_EXPAND)) {
1511
0
    CWD_STATE_FREE_ERR(&old_state);
1512
0
    CWD_STATE_FREE_ERR(&new_state);
1513
0
    return -1;
1514
0
  }
1515
0
  newname = new_state.cwd;
1516
1517
  /* rename on windows will fail if newname already exists.
1518
     MoveFileEx has to be used */
1519
#ifdef ZEND_WIN32
1520
  /* MoveFileEx returns 0 on failure, other way 'round for this function */
1521
  retval = php_win32_ioutil_rename(oldname, newname);
1522
#else
1523
0
  retval = rename(oldname, newname);
1524
0
#endif
1525
1526
0
  CWD_STATE_FREE_ERR(&old_state);
1527
0
  CWD_STATE_FREE_ERR(&new_state);
1528
1529
0
  return retval;
1530
0
}
1531
/* }}} */
1532
1533
CWD_API int virtual_stat(const char *path, zend_stat_t *buf) /* {{{ */
1534
0
{
1535
0
  cwd_state new_state;
1536
0
  int retval;
1537
1538
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1539
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) {
1540
0
    CWD_STATE_FREE_ERR(&new_state);
1541
0
    return -1;
1542
0
  }
1543
1544
0
  retval = php_sys_stat(new_state.cwd, buf);
1545
1546
0
  CWD_STATE_FREE_ERR(&new_state);
1547
0
  return retval;
1548
0
}
1549
/* }}} */
1550
1551
CWD_API int virtual_lstat(const char *path, zend_stat_t *buf) /* {{{ */
1552
0
{
1553
0
  cwd_state new_state;
1554
0
  int retval;
1555
1556
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1557
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) {
1558
0
    CWD_STATE_FREE_ERR(&new_state);
1559
0
    return -1;
1560
0
  }
1561
1562
0
  retval = php_sys_lstat(new_state.cwd, buf);
1563
1564
0
  CWD_STATE_FREE_ERR(&new_state);
1565
0
  return retval;
1566
0
}
1567
/* }}} */
1568
1569
CWD_API int virtual_unlink(const char *path) /* {{{ */
1570
0
{
1571
0
  cwd_state new_state;
1572
0
  int retval;
1573
1574
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1575
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) {
1576
0
    CWD_STATE_FREE_ERR(&new_state);
1577
0
    return -1;
1578
0
  }
1579
1580
#ifdef ZEND_WIN32
1581
  retval = php_win32_ioutil_unlink(new_state.cwd);
1582
#else
1583
0
  retval = unlink(new_state.cwd);
1584
0
#endif
1585
1586
0
  CWD_STATE_FREE_ERR(&new_state);
1587
0
  return retval;
1588
0
}
1589
/* }}} */
1590
1591
CWD_API int virtual_mkdir(const char *pathname, mode_t mode) /* {{{ */
1592
0
{
1593
0
  cwd_state new_state;
1594
0
  int retval;
1595
1596
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1597
0
  if (virtual_file_ex(&new_state, pathname, NULL, CWD_FILEPATH)) {
1598
0
    CWD_STATE_FREE_ERR(&new_state);
1599
0
    return -1;
1600
0
  }
1601
1602
#ifdef ZEND_WIN32
1603
  retval = php_win32_ioutil_mkdir(new_state.cwd, mode);
1604
#else
1605
0
  retval = mkdir(new_state.cwd, mode);
1606
0
#endif
1607
0
  CWD_STATE_FREE_ERR(&new_state);
1608
0
  return retval;
1609
0
}
1610
/* }}} */
1611
1612
CWD_API int virtual_rmdir(const char *pathname) /* {{{ */
1613
0
{
1614
0
  cwd_state new_state;
1615
0
  int retval;
1616
1617
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1618
0
  if (virtual_file_ex(&new_state, pathname, NULL, CWD_EXPAND)) {
1619
0
    CWD_STATE_FREE_ERR(&new_state);
1620
0
    return -1;
1621
0
  }
1622
1623
#ifdef ZEND_WIN32
1624
  retval = php_win32_ioutil_rmdir(new_state.cwd);
1625
#else
1626
0
  retval = rmdir(new_state.cwd);
1627
0
#endif
1628
0
  CWD_STATE_FREE_ERR(&new_state);
1629
0
  return retval;
1630
0
}
1631
/* }}} */
1632
1633
#ifdef ZEND_WIN32
1634
DIR *opendir(const char *name);
1635
#endif
1636
1637
CWD_API DIR *virtual_opendir(const char *pathname) /* {{{ */
1638
0
{
1639
0
  cwd_state new_state;
1640
0
  DIR *retval;
1641
1642
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1643
0
  if (virtual_file_ex(&new_state, pathname, NULL, CWD_REALPATH)) {
1644
0
    CWD_STATE_FREE_ERR(&new_state);
1645
0
    return NULL;
1646
0
  }
1647
1648
0
  retval = opendir(new_state.cwd);
1649
1650
0
  CWD_STATE_FREE_ERR(&new_state);
1651
0
  return retval;
1652
0
}
1653
/* }}} */
1654
1655
#ifdef ZEND_WIN32
1656
CWD_API FILE *virtual_popen(const char *command, const char *type) /* {{{ */
1657
{
1658
  return popen_ex(command, type, CWDG(cwd).cwd, NULL);
1659
}
1660
/* }}} */
1661
#else /* Unix */
1662
CWD_API FILE *virtual_popen(const char *command, const char *type) /* {{{ */
1663
0
{
1664
0
  size_t command_length;
1665
0
  int dir_length, extra = 0;
1666
0
  char *command_line;
1667
0
  char *ptr, *dir;
1668
0
  FILE *retval;
1669
1670
0
  command_length = strlen(command);
1671
1672
0
  dir_length = CWDG(cwd).cwd_length;
1673
0
  dir = CWDG(cwd).cwd;
1674
0
  while (dir_length > 0) {
1675
0
    if (*dir == '\'') extra+=3;
1676
0
    dir++;
1677
0
    dir_length--;
1678
0
  }
1679
0
  dir_length = CWDG(cwd).cwd_length;
1680
0
  dir = CWDG(cwd).cwd;
1681
1682
0
  ptr = command_line = (char *) emalloc(command_length + sizeof("cd '' ; ") + dir_length + extra+1+1);
1683
0
  ptr = zend_mempcpy(ptr, "cd ", sizeof("cd ") - 1);
1684
1685
0
  if (CWDG(cwd).cwd_length == 0) {
1686
0
    *ptr++ = DEFAULT_SLASH;
1687
0
  } else {
1688
0
    *ptr++ = '\'';
1689
0
    while (dir_length > 0) {
1690
0
      switch (*dir) {
1691
0
      case '\'':
1692
0
        *ptr++ = '\'';
1693
0
        *ptr++ = '\\';
1694
0
        *ptr++ = '\'';
1695
0
        ZEND_FALLTHROUGH;
1696
0
      default:
1697
0
        *ptr++ = *dir;
1698
0
      }
1699
0
      dir++;
1700
0
      dir_length--;
1701
0
    }
1702
0
    *ptr++ = '\'';
1703
0
  }
1704
1705
0
  *ptr++ = ' ';
1706
0
  *ptr++ = ';';
1707
0
  *ptr++ = ' ';
1708
1709
0
  memcpy(ptr, command, command_length+1);
1710
0
  retval = popen(command_line, type);
1711
1712
0
  efree(command_line);
1713
0
  return retval;
1714
0
}
1715
/* }}} */
1716
#endif
1717
1718
CWD_API char *tsrm_realpath(const char *path, char *real_path) /* {{{ */
1719
85.0k
{
1720
85.0k
  cwd_state new_state;
1721
85.0k
  char cwd[MAXPATHLEN];
1722
1723
  /* realpath("") returns CWD */
1724
85.0k
  if (!*path) {
1725
0
    new_state.cwd = (char*)emalloc(1);
1726
0
    new_state.cwd[0] = '\0';
1727
0
    new_state.cwd_length = 0;
1728
0
    if (VCWD_GETCWD(cwd, MAXPATHLEN)) {
1729
0
      path = cwd;
1730
0
    }
1731
85.0k
  } else if (!IS_ABSOLUTE_PATH(path, strlen(path)) &&
1732
1.48k
          VCWD_GETCWD(cwd, MAXPATHLEN)) {
1733
1.48k
    new_state.cwd = estrdup(cwd);
1734
1.48k
    new_state.cwd_length = strlen(cwd);
1735
83.5k
  } else {
1736
83.5k
    new_state.cwd = (char*)emalloc(1);
1737
83.5k
    new_state.cwd[0] = '\0';
1738
83.5k
    new_state.cwd_length = 0;
1739
83.5k
  }
1740
1741
85.0k
  if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) {
1742
9.24k
    efree(new_state.cwd);
1743
9.24k
    return NULL;
1744
9.24k
  }
1745
1746
75.7k
  if (real_path) {
1747
1.80k
    size_t copy_len = new_state.cwd_length>MAXPATHLEN-1 ? MAXPATHLEN-1 : new_state.cwd_length;
1748
1.80k
    memcpy(real_path, new_state.cwd, copy_len);
1749
1.80k
    real_path[copy_len] = '\0';
1750
1.80k
    efree(new_state.cwd);
1751
1.80k
    return real_path;
1752
73.9k
  } else {
1753
73.9k
    return new_state.cwd;
1754
73.9k
  }
1755
75.7k
}
1756
/* }}} */