Coverage Report

Created: 2026-06-02 06:36

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