Coverage Report

Created: 2025-06-13 06:43

/src/php-src/Zend/zend_virtual_cwd.c
Line
Count
Source (jump to first uncovered line)
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
300k
{
243
300k
  if (CWDG(cwd).cwd != NULL) {
244
24
    CWD_STATE_FREE(&CWDG(cwd));
245
24
    CWDG(cwd).cwd = NULL;
246
24
  }
247
300k
}
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
195k
{
491
195k
  size_t i, j;
492
195k
  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
195k
  zend_stat_t st = {0};
504
195k
#endif
505
195k
  realpath_cache_bucket *bucket;
506
195k
  char *tmp;
507
195k
  ALLOCA_FLAG(use_heap)
508
509
195k
  while (1) {
510
195k
    if (len <= start) {
511
0
      if (link_is_dir) {
512
0
        *link_is_dir = 1;
513
0
      }
514
0
      return start;
515
0
    }
516
517
195k
    i = len;
518
1.60M
    while (i > start && !IS_SLASH(path[i-1])) {
519
1.40M
      i--;
520
1.40M
    }
521
195k
    assert(i < MAXPATHLEN);
522
523
195k
    if (i == len ||
524
195k
      (i + 1 == len && path[i] == '.')) {
525
      /* remove double slashes and '.' */
526
52
      len = EXPECTED(i > 0) ? i - 1 : 0;
527
52
      is_dir = 1;
528
52
      continue;
529
195k
    } else if (i + 2 == len && path[i] == '.' && path[i+1] == '.') {
530
      /* remove '..' and previous directory */
531
0
      is_dir = 1;
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, 1, 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
195k
    path[len] = 0;
574
575
195k
    save = (use_realpath != CWD_EXPAND);
576
577
195k
    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
195k
    if (save && php_sys_lstat(path, &st) < 0) {
893
18.1k
      if (use_realpath == CWD_REALPATH) {
894
        /* file not found */
895
11.0k
        return (size_t)-1;
896
11.0k
      }
897
      /* continue resolution anyway but don't save result in the cache */
898
7.07k
      save = 0;
899
7.07k
    }
900
901
183k
    tmp = do_alloca(len+1, use_heap);
902
183k
    memcpy(tmp, path, len+1);
903
904
183k
    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
183k
    } else {
935
183k
      if (save) {
936
176k
        directory = S_ISDIR(st.st_mode);
937
176k
        if (link_is_dir) {
938
0
          *link_is_dir = directory;
939
0
        }
940
176k
        if (is_dir && !directory) {
941
          /* not a directory */
942
0
          free_alloca(tmp, use_heap);
943
0
          return (size_t)-1;
944
0
        }
945
176k
      }
946
183k
#endif
947
183k
      if (i <= start + 1) {
948
88.0k
        j = start;
949
95.8k
      } else {
950
        /* some leading directories may be inaccessible */
951
95.8k
        j = tsrm_realpath_r(path, start, i-1, ll, t, save ? CWD_FILEPATH : use_realpath, 1, NULL);
952
95.8k
        if (j > start && j != (size_t)-1) {
953
95.8k
          path[j++] = DEFAULT_SLASH;
954
95.8k
        }
955
95.8k
      }
956
#ifdef ZEND_WIN32
957
      if (j == (size_t)-1 || j + len >= MAXPATHLEN - 1 + i) {
958
        free_alloca(tmp, use_heap);
959
        FREE_PATHW()
960
        return (size_t)-1;
961
      }
962
      if (save) {
963
        size_t sz;
964
        char *tmp_path = php_win32_ioutil_conv_w_to_any(dataw.cFileName, PHP_WIN32_CP_IGNORE_LEN, &sz);
965
        if (!tmp_path) {
966
          free_alloca(tmp, use_heap);
967
          FREE_PATHW()
968
          return (size_t)-1;
969
        }
970
        i = sz;
971
        memcpy(path+j, tmp_path, i+1);
972
        free(tmp_path);
973
        j += i;
974
      } else {
975
        /* use the original file or directory name as it wasn't found */
976
        memcpy(path+j, tmp+i, len-i+1);
977
        j += (len-i);
978
      }
979
    }
980
#else
981
183k
      if (j == (size_t)-1 || j + len >= MAXPATHLEN - 1 + i) {
982
0
        free_alloca(tmp, use_heap);
983
0
        return (size_t)-1;
984
0
      }
985
183k
      memcpy(path+j, tmp+i, len-i+1);
986
183k
      j += (len-i);
987
183k
    }
988
183k
#endif
989
990
183k
    if (save && start && CWDG(realpath_cache_size_limit)) {
991
      /* save absolute path in the cache */
992
0
      realpath_cache_add(tmp, len, path, j, directory, *t);
993
0
    }
994
995
183k
    free_alloca(tmp, use_heap);
996
#ifdef ZEND_WIN32
997
    FREE_PATHW()
998
#undef FREE_PATHW
999
#endif
1000
183k
    return j;
1001
183k
  }
1002
195k
}
1003
/* }}} */
1004
1005
/* Resolve path relatively to state and put the real path into state */
1006
/* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */
1007
CWD_API int virtual_file_ex(cwd_state *state, const char *path, verify_path_func verify_path, int use_realpath) /* {{{ */
1008
99.1k
{
1009
99.1k
  size_t path_length = strlen(path);
1010
99.1k
  char resolved_path[MAXPATHLEN];
1011
99.1k
  size_t start = 1;
1012
99.1k
  int ll = 0;
1013
99.1k
  time_t t;
1014
99.1k
  int ret;
1015
99.1k
  bool add_slash;
1016
99.1k
  void *tmp;
1017
1018
99.1k
  if (!path_length || path_length >= MAXPATHLEN-1) {
1019
#ifdef ZEND_WIN32
1020
    SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_PARAMETER);
1021
#else
1022
0
    errno = EINVAL;
1023
0
#endif
1024
0
    return 1;
1025
0
  }
1026
1027
#if VIRTUAL_CWD_DEBUG
1028
  fprintf(stderr,"cwd = %s path = %s\n", state->cwd, path);
1029
#endif
1030
1031
  /* cwd_length can be 0 when getcwd() fails.
1032
   * This can happen under solaris when a dir does not have read permissions
1033
   * but *does* have execute permissions */
1034
99.1k
  if (!IS_ABSOLUTE_PATH(path, path_length)) {
1035
3.85k
    if (state->cwd_length == 0) {
1036
      /* resolve relative path */
1037
0
      start = 0;
1038
0
      memcpy(resolved_path , path, path_length + 1);
1039
3.85k
    } else {
1040
3.85k
      size_t state_cwd_length = state->cwd_length;
1041
1042
#ifdef ZEND_WIN32
1043
      if (IS_SLASH(path[0])) {
1044
        if (state->cwd[1] == ':') {
1045
          /* Copy only the drive name */
1046
          state_cwd_length = 2;
1047
        } else if (IS_UNC_PATH(state->cwd, state->cwd_length)) {
1048
          /* Copy only the share name */
1049
          state_cwd_length = 2;
1050
          while (IS_SLASH(state->cwd[state_cwd_length])) {
1051
            state_cwd_length++;
1052
          }
1053
          while (state->cwd[state_cwd_length] &&
1054
              !IS_SLASH(state->cwd[state_cwd_length])) {
1055
            state_cwd_length++;
1056
          }
1057
          while (IS_SLASH(state->cwd[state_cwd_length])) {
1058
            state_cwd_length++;
1059
          }
1060
          while (state->cwd[state_cwd_length] &&
1061
              !IS_SLASH(state->cwd[state_cwd_length])) {
1062
            state_cwd_length++;
1063
          }
1064
        }
1065
      }
1066
#endif
1067
3.85k
      if (path_length + state_cwd_length + 1 >= MAXPATHLEN-1) {
1068
#ifdef ZEND_WIN32
1069
        SET_ERRNO_FROM_WIN32_CODE(ERROR_BUFFER_OVERFLOW);
1070
#else
1071
0
        errno = ENAMETOOLONG;
1072
0
#endif
1073
0
        return 1;
1074
0
      }
1075
3.85k
      memcpy(resolved_path, state->cwd, state_cwd_length);
1076
3.85k
      if (resolved_path[state_cwd_length-1] == DEFAULT_SLASH) {
1077
0
        memcpy(resolved_path + state_cwd_length, path, path_length + 1);
1078
0
        path_length += state_cwd_length;
1079
3.85k
      } else {
1080
3.85k
        resolved_path[state_cwd_length] = DEFAULT_SLASH;
1081
3.85k
        memcpy(resolved_path + state_cwd_length + 1, path, path_length + 1);
1082
3.85k
        path_length += state_cwd_length + 1;
1083
3.85k
      }
1084
3.85k
    }
1085
95.2k
  } else {
1086
#ifdef ZEND_WIN32
1087
    if (path_length > 2 && path[1] == ':' && !IS_SLASH(path[2])) {
1088
      resolved_path[0] = path[0];
1089
      resolved_path[1] = ':';
1090
      resolved_path[2] = DEFAULT_SLASH;
1091
      memcpy(resolved_path + 3, path + 2, path_length - 1);
1092
      path_length++;
1093
    } else
1094
#endif
1095
95.2k
    memcpy(resolved_path, path, path_length + 1);
1096
95.2k
  }
1097
1098
#ifdef ZEND_WIN32
1099
  if (memchr(resolved_path, '*', path_length) ||
1100
    memchr(resolved_path, '?', path_length)) {
1101
    SET_ERRNO_FROM_WIN32_CODE(ERROR_INVALID_NAME);
1102
    return 1;
1103
  }
1104
#endif
1105
1106
#ifdef ZEND_WIN32
1107
  if (IS_UNC_PATH(resolved_path, path_length)) {
1108
    /* skip UNC name */
1109
    resolved_path[0] = DEFAULT_SLASH;
1110
    resolved_path[1] = DEFAULT_SLASH;
1111
    start = 2;
1112
    while (!IS_SLASH(resolved_path[start])) {
1113
      if (resolved_path[start] == 0) {
1114
        goto verify;
1115
      }
1116
      resolved_path[start] = toupper(resolved_path[start]);
1117
      start++;
1118
    }
1119
    resolved_path[start++] = DEFAULT_SLASH;
1120
    while (!IS_SLASH(resolved_path[start])) {
1121
      if (resolved_path[start] == 0) {
1122
        goto verify;
1123
      }
1124
      resolved_path[start] = toupper(resolved_path[start]);
1125
      start++;
1126
    }
1127
    resolved_path[start++] = DEFAULT_SLASH;
1128
  } else if (IS_ABSOLUTE_PATH(resolved_path, path_length)) {
1129
    /* skip DRIVE name */
1130
    resolved_path[0] = toupper(resolved_path[0]);
1131
    resolved_path[2] = DEFAULT_SLASH;
1132
    if (path_length == 2) {
1133
      resolved_path[3] = '\0';
1134
    }
1135
    start = 3;
1136
  }
1137
#endif
1138
1139
99.1k
  add_slash = (use_realpath != CWD_REALPATH) && path_length > 0 && IS_SLASH(resolved_path[path_length-1]);
1140
99.1k
  t = CWDG(realpath_cache_ttl) ? 0 : -1;
1141
99.1k
  path_length = tsrm_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0, NULL);
1142
1143
99.1k
  if (path_length == (size_t)-1) {
1144
#ifdef ZEND_WIN32
1145
    if (errno != EACCES) {
1146
      errno = ENOENT;
1147
    }
1148
#else
1149
11.0k
    errno = ENOENT;
1150
11.0k
#endif
1151
11.0k
    return 1;
1152
11.0k
  }
1153
1154
88.0k
  if (!start && !path_length) {
1155
0
    resolved_path[path_length++] = '.';
1156
0
  }
1157
1158
88.0k
  if (add_slash && path_length && !IS_SLASH(resolved_path[path_length-1])) {
1159
4
    if (path_length >= MAXPATHLEN-1) {
1160
0
      return -1;
1161
0
    }
1162
4
    resolved_path[path_length++] = DEFAULT_SLASH;
1163
4
  }
1164
88.0k
  resolved_path[path_length] = 0;
1165
1166
#ifdef ZEND_WIN32
1167
verify:
1168
#endif
1169
88.0k
  if (verify_path) {
1170
0
    cwd_state old_state;
1171
1172
0
    CWD_STATE_COPY(&old_state, state);
1173
0
    state->cwd_length = path_length;
1174
1175
0
    tmp = erealloc(state->cwd, state->cwd_length+1);
1176
0
    state->cwd = (char *) tmp;
1177
1178
0
    memcpy(state->cwd, resolved_path, state->cwd_length+1);
1179
0
    if (verify_path(state)) {
1180
0
      CWD_STATE_FREE(state);
1181
0
      *state = old_state;
1182
0
      ret = 1;
1183
0
    } else {
1184
0
      CWD_STATE_FREE(&old_state);
1185
0
      ret = 0;
1186
0
    }
1187
88.0k
  } else {
1188
88.0k
    state->cwd_length = path_length;
1189
88.0k
    tmp = erealloc(state->cwd, state->cwd_length+1);
1190
88.0k
    state->cwd = (char *) tmp;
1191
1192
88.0k
    memcpy(state->cwd, resolved_path, state->cwd_length+1);
1193
88.0k
    ret = 0;
1194
88.0k
  }
1195
1196
#if VIRTUAL_CWD_DEBUG
1197
  fprintf (stderr, "virtual_file_ex() = %s\n",state->cwd);
1198
#endif
1199
88.0k
  return (ret);
1200
88.0k
}
1201
/* }}} */
1202
1203
CWD_API zend_result virtual_chdir(const char *path) /* {{{ */
1204
0
{
1205
0
  return virtual_file_ex(&CWDG(cwd), path, php_is_dir_ok, CWD_REALPATH) ? FAILURE : SUCCESS;
1206
0
}
1207
/* }}} */
1208
1209
1210
/* returns 0 for ok, 1 for empty string, -1 on error */
1211
CWD_API int virtual_chdir_file(const char *path, int (*p_chdir)(const char *path)) /* {{{ */
1212
0
{
1213
0
  size_t length = strlen(path);
1214
0
  char *temp;
1215
0
  int retval;
1216
0
  ALLOCA_FLAG(use_heap)
1217
1218
0
  if (length == 0) {
1219
0
    return 1; /* Can't cd to empty string */
1220
0
  }
1221
0
  while(--length < SIZE_MAX && !IS_SLASH(path[length])) {
1222
0
  }
1223
1224
0
  if (length == SIZE_MAX) {
1225
    /* No directory only file name */
1226
0
    errno = ENOENT;
1227
0
    return -1;
1228
0
  }
1229
1230
0
  if (length == COPY_WHEN_ABSOLUTE(path) && IS_ABSOLUTE_PATH(path, length+1)) { /* Also use trailing slash if this is absolute */
1231
0
    length++;
1232
0
  }
1233
0
  temp = (char *) do_alloca(length+1, use_heap);
1234
0
  memcpy(temp, path, length);
1235
0
  temp[length] = 0;
1236
#if VIRTUAL_CWD_DEBUG
1237
  fprintf (stderr, "Changing directory to %s\n", temp);
1238
#endif
1239
0
  retval = p_chdir(temp);
1240
0
  free_alloca(temp, use_heap);
1241
0
  return retval;
1242
0
}
1243
/* }}} */
1244
1245
CWD_API char *virtual_realpath(const char *path, char *real_path) /* {{{ */
1246
0
{
1247
0
  cwd_state new_state;
1248
0
  char *retval;
1249
0
  char cwd[MAXPATHLEN];
1250
1251
  /* realpath("") returns CWD */
1252
0
  if (!*path) {
1253
0
    new_state.cwd = (char*)emalloc(1);
1254
0
    new_state.cwd[0] = '\0';
1255
0
    new_state.cwd_length = 0;
1256
0
    if (VCWD_GETCWD(cwd, MAXPATHLEN)) {
1257
0
      path = cwd;
1258
0
    }
1259
0
  } else if (!IS_ABSOLUTE_PATH(path, strlen(path))) {
1260
0
    CWD_STATE_COPY(&new_state, &CWDG(cwd));
1261
0
  } else {
1262
0
    new_state.cwd = (char*)emalloc(1);
1263
0
    new_state.cwd[0] = '\0';
1264
0
    new_state.cwd_length = 0;
1265
0
  }
1266
1267
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)==0) {
1268
0
    size_t len = new_state.cwd_length>MAXPATHLEN-1?MAXPATHLEN-1:new_state.cwd_length;
1269
1270
0
    memcpy(real_path, new_state.cwd, len);
1271
0
    real_path[len] = '\0';
1272
0
    retval = real_path;
1273
0
  } else {
1274
0
    retval = NULL;
1275
0
  }
1276
1277
0
  CWD_STATE_FREE(&new_state);
1278
0
  return retval;
1279
0
}
1280
/* }}} */
1281
1282
/* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */
1283
CWD_API int virtual_filepath_ex(const char *path, char **filepath, verify_path_func verify_path) /* {{{ */
1284
0
{
1285
0
  cwd_state new_state;
1286
0
  int retval;
1287
1288
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1289
0
  retval = virtual_file_ex(&new_state, path, verify_path, CWD_FILEPATH);
1290
1291
0
  *filepath = new_state.cwd;
1292
1293
0
  return retval;
1294
1295
0
}
1296
/* }}} */
1297
1298
/* returns 0 for ok, 1 for error, -1 if (path_length >= MAXPATHLEN-1) */
1299
CWD_API int virtual_filepath(const char *path, char **filepath) /* {{{ */
1300
0
{
1301
0
  return virtual_filepath_ex(path, filepath, php_is_file_ok);
1302
0
}
1303
/* }}} */
1304
1305
CWD_API FILE *virtual_fopen(const char *path, const char *mode) /* {{{ */
1306
0
{
1307
0
  cwd_state new_state;
1308
0
  FILE *f;
1309
1310
0
  if (path[0] == '\0') { /* Fail to open empty path */
1311
0
    return NULL;
1312
0
  }
1313
1314
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1315
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) {
1316
0
    CWD_STATE_FREE_ERR(&new_state);
1317
0
    return NULL;
1318
0
  }
1319
1320
#ifdef ZEND_WIN32
1321
  f = php_win32_ioutil_fopen(new_state.cwd, mode);
1322
#else
1323
0
  f = fopen(new_state.cwd, mode);
1324
0
#endif
1325
1326
0
  CWD_STATE_FREE_ERR(&new_state);
1327
1328
0
  return f;
1329
0
}
1330
/* }}} */
1331
1332
CWD_API int virtual_access(const char *pathname, int mode) /* {{{ */
1333
0
{
1334
0
  cwd_state new_state;
1335
0
  int ret;
1336
1337
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1338
0
  if (virtual_file_ex(&new_state, pathname, NULL, CWD_REALPATH)) {
1339
0
    CWD_STATE_FREE_ERR(&new_state);
1340
0
    return -1;
1341
0
  }
1342
1343
#if defined(ZEND_WIN32)
1344
  ret = tsrm_win32_access(new_state.cwd, mode);
1345
#else
1346
0
  ret = access(new_state.cwd, mode);
1347
0
#endif
1348
1349
0
  CWD_STATE_FREE_ERR(&new_state);
1350
1351
0
  return ret;
1352
0
}
1353
/* }}} */
1354
1355
#ifdef HAVE_UTIME
1356
CWD_API int virtual_utime(const char *filename, struct utimbuf *buf) /* {{{ */
1357
0
{
1358
0
  cwd_state new_state;
1359
0
  int ret;
1360
1361
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1362
0
  if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) {
1363
0
    CWD_STATE_FREE_ERR(&new_state);
1364
0
    return -1;
1365
0
  }
1366
1367
#ifdef ZEND_WIN32
1368
  ret = win32_utime(new_state.cwd, buf);
1369
#else
1370
0
  ret = utime(new_state.cwd, buf);
1371
0
#endif
1372
1373
0
  CWD_STATE_FREE_ERR(&new_state);
1374
0
  return ret;
1375
0
}
1376
/* }}} */
1377
#endif
1378
1379
CWD_API int virtual_chmod(const char *filename, mode_t mode) /* {{{ */
1380
0
{
1381
0
  cwd_state new_state;
1382
0
  int ret;
1383
1384
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1385
0
  if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) {
1386
0
    CWD_STATE_FREE_ERR(&new_state);
1387
0
    return -1;
1388
0
  }
1389
1390
#ifdef ZEND_WIN32
1391
  {
1392
    mode_t _tmp = mode;
1393
1394
    mode = 0;
1395
1396
    if (_tmp & _S_IREAD) {
1397
      mode |= _S_IREAD;
1398
    }
1399
    if (_tmp & _S_IWRITE) {
1400
      mode |= _S_IWRITE;
1401
    }
1402
    ret = php_win32_ioutil_chmod(new_state.cwd, mode);
1403
  }
1404
#else
1405
0
  ret = chmod(new_state.cwd, mode);
1406
0
#endif
1407
1408
0
  CWD_STATE_FREE_ERR(&new_state);
1409
0
  return ret;
1410
0
}
1411
/* }}} */
1412
1413
#if !defined(ZEND_WIN32)
1414
CWD_API int virtual_chown(const char *filename, uid_t owner, gid_t group, int link) /* {{{ */
1415
0
{
1416
0
  cwd_state new_state;
1417
0
  int ret;
1418
1419
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1420
0
  if (virtual_file_ex(&new_state, filename, NULL, CWD_REALPATH)) {
1421
0
    CWD_STATE_FREE_ERR(&new_state);
1422
0
    return -1;
1423
0
  }
1424
1425
0
  if (link) {
1426
0
#ifdef HAVE_LCHOWN
1427
0
    ret = lchown(new_state.cwd, owner, group);
1428
#else
1429
    ret = -1;
1430
#endif
1431
0
  } else {
1432
0
    ret = chown(new_state.cwd, owner, group);
1433
0
  }
1434
1435
0
  CWD_STATE_FREE_ERR(&new_state);
1436
0
  return ret;
1437
0
}
1438
/* }}} */
1439
#endif
1440
1441
CWD_API int virtual_open(const char *path, int flags, ...) /* {{{ */
1442
0
{
1443
0
  cwd_state new_state;
1444
0
  int f;
1445
1446
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1447
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_FILEPATH)) {
1448
0
    CWD_STATE_FREE_ERR(&new_state);
1449
0
    return -1;
1450
0
  }
1451
1452
0
  if (flags & O_CREAT) {
1453
0
    mode_t mode;
1454
0
    va_list arg;
1455
1456
0
    va_start(arg, flags);
1457
0
    mode = (mode_t) va_arg(arg, int);
1458
0
    va_end(arg);
1459
1460
#ifdef ZEND_WIN32
1461
    f = php_win32_ioutil_open(new_state.cwd, flags, mode);
1462
#else
1463
0
    f = open(new_state.cwd, flags, mode);
1464
0
#endif
1465
0
  } else {
1466
#ifdef ZEND_WIN32
1467
    f = php_win32_ioutil_open(new_state.cwd, flags);
1468
#else
1469
0
    f = open(new_state.cwd, flags);
1470
0
#endif
1471
0
  }
1472
0
  CWD_STATE_FREE_ERR(&new_state);
1473
0
  return f;
1474
0
}
1475
/* }}} */
1476
1477
CWD_API int virtual_creat(const char *path, mode_t mode) /* {{{ */
1478
0
{
1479
0
  cwd_state new_state;
1480
0
  int f;
1481
1482
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1483
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_FILEPATH)) {
1484
0
    CWD_STATE_FREE_ERR(&new_state);
1485
0
    return -1;
1486
0
  }
1487
1488
0
  f = creat(new_state.cwd,  mode);
1489
1490
0
  CWD_STATE_FREE_ERR(&new_state);
1491
0
  return f;
1492
0
}
1493
/* }}} */
1494
1495
CWD_API int virtual_rename(const char *oldname, const char *newname) /* {{{ */
1496
0
{
1497
0
  cwd_state old_state;
1498
0
  cwd_state new_state;
1499
0
  int retval;
1500
1501
0
  CWD_STATE_COPY(&old_state, &CWDG(cwd));
1502
0
  if (virtual_file_ex(&old_state, oldname, NULL, CWD_EXPAND)) {
1503
0
    CWD_STATE_FREE_ERR(&old_state);
1504
0
    return -1;
1505
0
  }
1506
0
  oldname = old_state.cwd;
1507
1508
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1509
0
  if (virtual_file_ex(&new_state, newname, NULL, CWD_EXPAND)) {
1510
0
    CWD_STATE_FREE_ERR(&old_state);
1511
0
    CWD_STATE_FREE_ERR(&new_state);
1512
0
    return -1;
1513
0
  }
1514
0
  newname = new_state.cwd;
1515
1516
  /* rename on windows will fail if newname already exists.
1517
     MoveFileEx has to be used */
1518
#ifdef ZEND_WIN32
1519
  /* MoveFileEx returns 0 on failure, other way 'round for this function */
1520
  retval = php_win32_ioutil_rename(oldname, newname);
1521
#else
1522
0
  retval = rename(oldname, newname);
1523
0
#endif
1524
1525
0
  CWD_STATE_FREE_ERR(&old_state);
1526
0
  CWD_STATE_FREE_ERR(&new_state);
1527
1528
0
  return retval;
1529
0
}
1530
/* }}} */
1531
1532
CWD_API int virtual_stat(const char *path, zend_stat_t *buf) /* {{{ */
1533
0
{
1534
0
  cwd_state new_state;
1535
0
  int retval;
1536
1537
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1538
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) {
1539
0
    CWD_STATE_FREE_ERR(&new_state);
1540
0
    return -1;
1541
0
  }
1542
1543
0
  retval = php_sys_stat(new_state.cwd, buf);
1544
1545
0
  CWD_STATE_FREE_ERR(&new_state);
1546
0
  return retval;
1547
0
}
1548
/* }}} */
1549
1550
CWD_API int virtual_lstat(const char *path, zend_stat_t *buf) /* {{{ */
1551
0
{
1552
0
  cwd_state new_state;
1553
0
  int retval;
1554
1555
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1556
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) {
1557
0
    CWD_STATE_FREE_ERR(&new_state);
1558
0
    return -1;
1559
0
  }
1560
1561
0
  retval = php_sys_lstat(new_state.cwd, buf);
1562
1563
0
  CWD_STATE_FREE_ERR(&new_state);
1564
0
  return retval;
1565
0
}
1566
/* }}} */
1567
1568
CWD_API int virtual_unlink(const char *path) /* {{{ */
1569
0
{
1570
0
  cwd_state new_state;
1571
0
  int retval;
1572
1573
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1574
0
  if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) {
1575
0
    CWD_STATE_FREE_ERR(&new_state);
1576
0
    return -1;
1577
0
  }
1578
1579
#ifdef ZEND_WIN32
1580
  retval = php_win32_ioutil_unlink(new_state.cwd);
1581
#else
1582
0
  retval = unlink(new_state.cwd);
1583
0
#endif
1584
1585
0
  CWD_STATE_FREE_ERR(&new_state);
1586
0
  return retval;
1587
0
}
1588
/* }}} */
1589
1590
CWD_API int virtual_mkdir(const char *pathname, mode_t mode) /* {{{ */
1591
0
{
1592
0
  cwd_state new_state;
1593
0
  int retval;
1594
1595
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1596
0
  if (virtual_file_ex(&new_state, pathname, NULL, CWD_FILEPATH)) {
1597
0
    CWD_STATE_FREE_ERR(&new_state);
1598
0
    return -1;
1599
0
  }
1600
1601
#ifdef ZEND_WIN32
1602
  retval = php_win32_ioutil_mkdir(new_state.cwd, mode);
1603
#else
1604
0
  retval = mkdir(new_state.cwd, mode);
1605
0
#endif
1606
0
  CWD_STATE_FREE_ERR(&new_state);
1607
0
  return retval;
1608
0
}
1609
/* }}} */
1610
1611
CWD_API int virtual_rmdir(const char *pathname) /* {{{ */
1612
0
{
1613
0
  cwd_state new_state;
1614
0
  int retval;
1615
1616
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1617
0
  if (virtual_file_ex(&new_state, pathname, NULL, CWD_EXPAND)) {
1618
0
    CWD_STATE_FREE_ERR(&new_state);
1619
0
    return -1;
1620
0
  }
1621
1622
#ifdef ZEND_WIN32
1623
  retval = php_win32_ioutil_rmdir(new_state.cwd);
1624
#else
1625
0
  retval = rmdir(new_state.cwd);
1626
0
#endif
1627
0
  CWD_STATE_FREE_ERR(&new_state);
1628
0
  return retval;
1629
0
}
1630
/* }}} */
1631
1632
#ifdef ZEND_WIN32
1633
DIR *opendir(const char *name);
1634
#endif
1635
1636
CWD_API DIR *virtual_opendir(const char *pathname) /* {{{ */
1637
0
{
1638
0
  cwd_state new_state;
1639
0
  DIR *retval;
1640
1641
0
  CWD_STATE_COPY(&new_state, &CWDG(cwd));
1642
0
  if (virtual_file_ex(&new_state, pathname, NULL, CWD_REALPATH)) {
1643
0
    CWD_STATE_FREE_ERR(&new_state);
1644
0
    return NULL;
1645
0
  }
1646
1647
0
  retval = opendir(new_state.cwd);
1648
1649
0
  CWD_STATE_FREE_ERR(&new_state);
1650
0
  return retval;
1651
0
}
1652
/* }}} */
1653
1654
#ifdef ZEND_WIN32
1655
CWD_API FILE *virtual_popen(const char *command, const char *type) /* {{{ */
1656
{
1657
  return popen_ex(command, type, CWDG(cwd).cwd, NULL);
1658
}
1659
/* }}} */
1660
#else /* Unix */
1661
CWD_API FILE *virtual_popen(const char *command, const char *type) /* {{{ */
1662
0
{
1663
0
  size_t command_length;
1664
0
  int dir_length, extra = 0;
1665
0
  char *command_line;
1666
0
  char *ptr, *dir;
1667
0
  FILE *retval;
1668
1669
0
  command_length = strlen(command);
1670
1671
0
  dir_length = CWDG(cwd).cwd_length;
1672
0
  dir = CWDG(cwd).cwd;
1673
0
  while (dir_length > 0) {
1674
0
    if (*dir == '\'') extra+=3;
1675
0
    dir++;
1676
0
    dir_length--;
1677
0
  }
1678
0
  dir_length = CWDG(cwd).cwd_length;
1679
0
  dir = CWDG(cwd).cwd;
1680
1681
0
  ptr = command_line = (char *) emalloc(command_length + sizeof("cd '' ; ") + dir_length + extra+1+1);
1682
0
  ptr = zend_mempcpy(ptr, "cd ", sizeof("cd ") - 1);
1683
1684
0
  if (CWDG(cwd).cwd_length == 0) {
1685
0
    *ptr++ = DEFAULT_SLASH;
1686
0
  } else {
1687
0
    *ptr++ = '\'';
1688
0
    while (dir_length > 0) {
1689
0
      switch (*dir) {
1690
0
      case '\'':
1691
0
        *ptr++ = '\'';
1692
0
        *ptr++ = '\\';
1693
0
        *ptr++ = '\'';
1694
0
        ZEND_FALLTHROUGH;
1695
0
      default:
1696
0
        *ptr++ = *dir;
1697
0
      }
1698
0
      dir++;
1699
0
      dir_length--;
1700
0
    }
1701
0
    *ptr++ = '\'';
1702
0
  }
1703
1704
0
  *ptr++ = ' ';
1705
0
  *ptr++ = ';';
1706
0
  *ptr++ = ' ';
1707
1708
0
  memcpy(ptr, command, command_length+1);
1709
0
  retval = popen(command_line, type);
1710
1711
0
  efree(command_line);
1712
0
  return retval;
1713
0
}
1714
/* }}} */
1715
#endif
1716
1717
CWD_API char *tsrm_realpath(const char *path, char *real_path) /* {{{ */
1718
94.3k
{
1719
94.3k
  cwd_state new_state;
1720
94.3k
  char cwd[MAXPATHLEN];
1721
1722
  /* realpath("") returns CWD */
1723
94.3k
  if (!*path) {
1724
0
    new_state.cwd = (char*)emalloc(1);
1725
0
    new_state.cwd[0] = '\0';
1726
0
    new_state.cwd_length = 0;
1727
0
    if (VCWD_GETCWD(cwd, MAXPATHLEN)) {
1728
0
      path = cwd;
1729
0
    }
1730
94.3k
  } else if (!IS_ABSOLUTE_PATH(path, strlen(path)) &&
1731
94.3k
          VCWD_GETCWD(cwd, MAXPATHLEN)) {
1732
1.96k
    new_state.cwd = estrdup(cwd);
1733
1.96k
    new_state.cwd_length = strlen(cwd);
1734
92.4k
  } else {
1735
92.4k
    new_state.cwd = (char*)emalloc(1);
1736
92.4k
    new_state.cwd[0] = '\0';
1737
92.4k
    new_state.cwd_length = 0;
1738
92.4k
  }
1739
1740
94.3k
  if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) {
1741
11.0k
    efree(new_state.cwd);
1742
11.0k
    return NULL;
1743
11.0k
  }
1744
1745
83.3k
  if (real_path) {
1746
2.22k
    size_t copy_len = new_state.cwd_length>MAXPATHLEN-1 ? MAXPATHLEN-1 : new_state.cwd_length;
1747
2.22k
    memcpy(real_path, new_state.cwd, copy_len);
1748
2.22k
    real_path[copy_len] = '\0';
1749
2.22k
    efree(new_state.cwd);
1750
2.22k
    return real_path;
1751
81.1k
  } else {
1752
81.1k
    return new_state.cwd;
1753
81.1k
  }
1754
83.3k
}
1755
/* }}} */