xref: /php-src/ext/phar/dirstream.c (revision 1f14d58c)
1 /*
2   +----------------------------------------------------------------------+
3   | phar:// stream wrapper support                                       |
4   +----------------------------------------------------------------------+
5   | Copyright (c) The PHP Group                                          |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | https://www.php.net/license/3_01.txt                                 |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Gregory Beaver <cellog@php.net>                             |
16   |          Marcus Boerger <helly@php.net>                              |
17   +----------------------------------------------------------------------+
18 */
19 
20 #define PHAR_DIRSTREAM 1
21 #include "phar_internal.h"
22 #include "dirstream.h"
23 
24 void phar_dostat(phar_archive_data *phar, phar_entry_info *data, php_stream_statbuf *ssb, bool is_dir);
25 
26 static const php_stream_ops phar_dir_ops = {
27 	phar_dir_write, /* write */
28 	phar_dir_read,  /* read  */
29 	phar_dir_close, /* close */
30 	phar_dir_flush, /* flush */
31 	"phar dir",
32 	phar_dir_seek,  /* seek */
33 	NULL,           /* cast */
34 	NULL,           /* stat */
35 	NULL, /* set option */
36 };
37 
38 /**
39  * Used for closedir($fp) where $fp is an opendir('phar://...') directory handle
40  */
phar_dir_close(php_stream * stream,int close_handle)41 static int phar_dir_close(php_stream *stream, int close_handle)  /* {{{ */
42 {
43 	HashTable *data = (HashTable *)stream->abstract;
44 
45 	if (data) {
46 		zend_hash_destroy(data);
47 		FREE_HASHTABLE(data);
48 		stream->abstract = NULL;
49 	}
50 
51 	return 0;
52 }
53 /* }}} */
54 
55 /**
56  * Used for seeking on a phar directory handle
57  */
phar_dir_seek(php_stream * stream,zend_off_t offset,int whence,zend_off_t * newoffset)58 static int phar_dir_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) /* {{{ */
59 {
60 	HashTable *data = (HashTable *)stream->abstract;
61 
62 	if (!data) {
63 		return -1;
64 	}
65 
66 	if (whence == SEEK_END) {
67 		whence = SEEK_SET;
68 		offset = zend_hash_num_elements(data) + offset;
69 	}
70 
71 	if (whence == SEEK_SET) {
72 		zend_hash_internal_pointer_reset(data);
73 	}
74 
75 	if (offset < 0) {
76 		return -1;
77 	} else {
78 		*newoffset = 0;
79 		while (*newoffset < offset && zend_hash_move_forward(data) == SUCCESS) {
80 			++(*newoffset);
81 		}
82 		return 0;
83 	}
84 }
85 /* }}} */
86 
87 /**
88  * Used for readdir() on an opendir()ed phar directory handle
89  */
phar_dir_read(php_stream * stream,char * buf,size_t count)90 static ssize_t phar_dir_read(php_stream *stream, char *buf, size_t count) /* {{{ */
91 {
92 	HashTable *data = (HashTable *)stream->abstract;
93 	zend_string *str_key;
94 	zend_ulong unused;
95 
96 	if (count != sizeof(php_stream_dirent)) {
97 		return -1;
98 	}
99 
100 	if (HASH_KEY_NON_EXISTENT == zend_hash_get_current_key(data, &str_key, &unused)) {
101 		return 0;
102 	}
103 
104 	zend_hash_move_forward(data);
105 
106 	php_stream_dirent *dirent = (php_stream_dirent *) buf;
107 
108 	if (sizeof(dirent->d_name) <= ZSTR_LEN(str_key)) {
109 		return 0;
110 	}
111 
112 	memset(dirent, 0, sizeof(php_stream_dirent));
113 	PHP_STRLCPY(dirent->d_name, ZSTR_VAL(str_key), sizeof(dirent->d_name), ZSTR_LEN(str_key));
114 
115 	return sizeof(php_stream_dirent);
116 }
117 /* }}} */
118 
119 /**
120  * Dummy: Used for writing to a phar directory (i.e. not used)
121  */
phar_dir_write(php_stream * stream,const char * buf,size_t count)122 static ssize_t phar_dir_write(php_stream *stream, const char *buf, size_t count) /* {{{ */
123 {
124 	return -1;
125 }
126 /* }}} */
127 
128 /**
129  * Dummy: Used for flushing writes to a phar directory (i.e. not used)
130  */
phar_dir_flush(php_stream * stream)131 static int phar_dir_flush(php_stream *stream) /* {{{ */
132 {
133 	return EOF;
134 }
135 /* }}} */
136 
137 /**
138  * Used for sorting directories alphabetically
139  */
phar_compare_dir_name(Bucket * f,Bucket * s)140 static int phar_compare_dir_name(Bucket *f, Bucket *s)  /* {{{ */
141 {
142 	int result = zend_binary_strcmp(
143 		ZSTR_VAL(f->key), ZSTR_LEN(f->key), ZSTR_VAL(s->key), ZSTR_LEN(s->key));
144 	return ZEND_NORMALIZE_BOOL(result);
145 }
146 /* }}} */
147 
148 /**
149  * Create a opendir() directory stream handle by iterating over each of the
150  * files in a phar and retrieving its relative path.  From this, construct
151  * a list of files/directories that are "in" the directory represented by dir
152  */
phar_make_dirstream(const char * dir,size_t dirlen,const HashTable * manifest)153 static php_stream *phar_make_dirstream(const char *dir, size_t dirlen, const HashTable *manifest) /* {{{ */
154 {
155 	HashTable *data;
156 	char *entry;
157 
158 	ALLOC_HASHTABLE(data);
159 	zend_hash_init(data, 64, NULL, NULL, 0);
160 
161 	if ((*dir == '/' && dirlen == 1 && (manifest->nNumOfElements == 0)) || (dirlen >= sizeof(".phar")-1 && !memcmp(dir, ".phar", sizeof(".phar")-1))) {
162 		/* make empty root directory for empty phar */
163 		/* make empty directory for .phar magic directory */
164 		return php_stream_alloc(&phar_dir_ops, data, NULL, "r");
165 	}
166 
167 	zend_string *str_key;
168 	ZEND_HASH_MAP_FOREACH_STR_KEY(manifest, str_key) {
169 		size_t keylen = ZSTR_LEN(str_key);
170 		if (keylen <= dirlen) {
171 			if (keylen == 0 || keylen < dirlen || !strncmp(ZSTR_VAL(str_key), dir, dirlen)) {
172 				continue;
173 			}
174 		}
175 
176 		if (*dir == '/') {
177 			/* root directory */
178 			if (zend_string_starts_with_literal(str_key, ".phar")) {
179 				/* do not add any magic entries to this directory */
180 				continue;
181 			}
182 
183 			const char *has_slash = memchr(ZSTR_VAL(str_key), '/', keylen);
184 			if (has_slash) {
185 				/* the entry has a path separator and is a subdirectory */
186 				keylen = has_slash - ZSTR_VAL(str_key);
187 			}
188 			entry = safe_emalloc(keylen, 1, 1);
189 			memcpy(entry, ZSTR_VAL(str_key), keylen);
190 			entry[keylen] = '\0';
191 
192 			goto PHAR_ADD_ENTRY;
193 		} else {
194 			if (0 != memcmp(ZSTR_VAL(str_key), dir, dirlen)) {
195 				/* entry in directory not found */
196 				continue;
197 			} else {
198 				if (ZSTR_VAL(str_key)[dirlen] != '/') {
199 					continue;
200 				}
201 			}
202 		}
203 
204 		const char *save = ZSTR_VAL(str_key);
205 		save += dirlen + 1; /* seek to just past the path separator */
206 
207 		const char *has_slash = memchr(save, '/', keylen - dirlen - 1);
208 		if (has_slash) {
209 			/* is subdirectory */
210 			save -= dirlen + 1;
211 			entry = safe_emalloc(has_slash - save + dirlen, 1, 1);
212 			memcpy(entry, save + dirlen + 1, has_slash - save - dirlen - 1);
213 			keylen = has_slash - save - dirlen - 1;
214 			entry[keylen] = '\0';
215 		} else {
216 			/* is file */
217 			save -= dirlen + 1;
218 			entry = safe_emalloc(keylen - dirlen, 1, 1);
219 			memcpy(entry, save + dirlen + 1, keylen - dirlen - 1);
220 			entry[keylen - dirlen - 1] = '\0';
221 			keylen = keylen - dirlen - 1;
222 		}
223 PHAR_ADD_ENTRY:
224 		if (keylen) {
225 			/**
226 			 * Add an empty element to avoid duplicates
227 			 *
228 			 * This is used to get a unique listing of virtual directories within a phar,
229 			 * for iterating over opendir()ed phar directories.
230 			 */
231 			zval dummy;
232 
233 			ZVAL_NULL(&dummy);
234 			zend_hash_str_update(data, entry, keylen, &dummy);
235 		}
236 
237 		efree(entry);
238 	} ZEND_HASH_FOREACH_END();
239 
240 	if (FAILURE != zend_hash_has_more_elements(data)) {
241 		zend_hash_sort(data, phar_compare_dir_name, 0);
242 		return php_stream_alloc(&phar_dir_ops, data, NULL, "r");
243 	} else {
244 		return php_stream_alloc(&phar_dir_ops, data, NULL, "r");
245 	}
246 }
247 /* }}}*/
248 
249 /**
250  * Open a directory handle within a phar archive
251  */
phar_wrapper_open_dir(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,zend_string ** opened_path,php_stream_context * context STREAMS_DC)252 php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC) /* {{{ */
253 {
254 	php_url *resource = NULL;
255 	char *error;
256 	phar_archive_data *phar;
257 
258 	if ((resource = phar_parse_url(wrapper, path, mode, options)) == NULL) {
259 		php_stream_wrapper_log_error(wrapper, options, "phar url \"%s\" is unknown", path);
260 		return NULL;
261 	}
262 
263 	/* we must have at the very least phar://alias.phar/ */
264 	if (!resource->scheme || !resource->host || !resource->path) {
265 		if (resource->host && !resource->path) {
266 			php_stream_wrapper_log_error(wrapper, options, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", path, ZSTR_VAL(resource->host));
267 			php_url_free(resource);
268 			return NULL;
269 		}
270 		php_url_free(resource);
271 		php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\", must have at least phar://%s/", path, path);
272 		return NULL;
273 	}
274 
275 	if (!zend_string_equals_literal_ci(resource->scheme, "phar")) {
276 		php_url_free(resource);
277 		php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar url \"%s\"", path);
278 		return NULL;
279 	}
280 
281 	phar_request_initialize();
282 
283 	if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error)) {
284 		if (error) {
285 			php_stream_wrapper_log_error(wrapper, options, "%s", error);
286 			efree(error);
287 		} else {
288 			php_stream_wrapper_log_error(wrapper, options, "phar file \"%s\" is unknown", ZSTR_VAL(resource->host));
289 		}
290 		php_url_free(resource);
291 		return NULL;
292 	}
293 
294 	if (error) {
295 		efree(error);
296 	}
297 
298 	if (zend_string_equals(resource->path, ZSTR_CHAR('/'))) {
299 		/* root directory requested */
300 		php_url_free(resource);
301 		return phar_make_dirstream("/", strlen("/"), &phar->manifest);
302 	}
303 
304 	if (!HT_IS_INITIALIZED(&phar->manifest)) {
305 		php_url_free(resource);
306 		return NULL;
307 	}
308 
309 	const char *internal_file = ZSTR_VAL(resource->path) + 1; /* strip leading "/" */
310 	size_t internal_file_len = ZSTR_LEN(resource->path) - 1;
311 	phar_entry_info *entry = zend_hash_str_find_ptr(&phar->manifest, internal_file, internal_file_len);
312 	php_stream *ret;
313 
314 	if (NULL != entry && !entry->is_dir) {
315 		php_url_free(resource);
316 		return NULL;
317 	} else if (entry && entry->is_dir) {
318 		if (entry->is_mounted) {
319 			ret = php_stream_opendir(entry->tmp, options, context);
320 			php_url_free(resource);
321 			return ret;
322 		}
323 		ret = phar_make_dirstream(internal_file, internal_file_len, &phar->manifest);
324 		php_url_free(resource);
325 		return ret;
326 	} else {
327 		zend_string *str_key;
328 
329 		/* search for directory */
330 		ZEND_HASH_MAP_FOREACH_STR_KEY(&phar->manifest, str_key) {
331 			if (zend_string_starts_with_cstr(str_key, internal_file, internal_file_len)) {
332 				/* directory found */
333 				ret = phar_make_dirstream(internal_file, internal_file_len, &phar->manifest);
334 				php_url_free(resource);
335 				return ret;
336 			}
337 		} ZEND_HASH_FOREACH_END();
338 	}
339 
340 	php_url_free(resource);
341 	return NULL;
342 }
343 /* }}} */
344 
345 /**
346  * Make a new directory within a phar archive
347  */
phar_wrapper_mkdir(php_stream_wrapper * wrapper,const char * url_from,int mode,int options,php_stream_context * context)348 int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mode, int options, php_stream_context *context) /* {{{ */
349 {
350 	phar_entry_info entry, *e;
351 	phar_archive_data *phar = NULL;
352 	char *error, *arch, *entry2;
353 	size_t arch_len, entry_len;
354 	php_url *resource = NULL;
355 
356 	/* pre-readonly check, we need to know if this is a data phar */
357 	if (FAILURE == phar_split_fname(url_from, strlen(url_from), &arch, &arch_len, &entry2, &entry_len, 2, 2)) {
358 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", no phar archive specified", url_from);
359 		return 0;
360 	}
361 
362 	if (FAILURE == phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL)) {
363 		phar = NULL;
364 	}
365 
366 	efree(arch);
367 	efree(entry2);
368 
369 	if (PHAR_G(readonly) && (!phar || !phar->is_data)) {
370 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", write operations disabled", url_from);
371 		return 0;
372 	}
373 
374 	if ((resource = phar_parse_url(wrapper, url_from, "w", options)) == NULL) {
375 		return 0;
376 	}
377 
378 	/* we must have at the very least phar://alias.phar/internalfile.php */
379 	if (!resource->scheme || !resource->host || !resource->path) {
380 		php_url_free(resource);
381 		php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url_from);
382 		return 0;
383 	}
384 
385 	if (!zend_string_equals_literal_ci(resource->scheme, "phar")) {
386 		php_url_free(resource);
387 		php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url_from);
388 		return 0;
389 	}
390 
391 	if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error)) {
392 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", error retrieving phar information: %s", ZSTR_VAL(resource->path) + 1, ZSTR_VAL(resource->host), error);
393 		efree(error);
394 		php_url_free(resource);
395 		return 0;
396 	}
397 
398 	if ((e = phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, ZSTR_LEN(resource->path) - 1, 2, &error, 1))) {
399 		/* directory exists, or is a subdirectory of an existing file */
400 		if (e->is_temp_dir) {
401 			efree(e->filename);
402 			efree(e);
403 		}
404 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", directory already exists", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
405 		php_url_free(resource);
406 		return 0;
407 	}
408 
409 	if (error) {
410 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
411 		efree(error);
412 		php_url_free(resource);
413 		return 0;
414 	}
415 
416 	if (phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, ZSTR_LEN(resource->path) - 1, 0, &error, 1)) {
417 		/* entry exists as a file */
418 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", file already exists", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
419 		php_url_free(resource);
420 		return 0;
421 	}
422 
423 	if (error) {
424 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
425 		efree(error);
426 		php_url_free(resource);
427 		return 0;
428 	}
429 
430 	memset((void *) &entry, 0, sizeof(phar_entry_info));
431 
432 	/* strip leading "/" */
433 	if (phar->is_zip) {
434 		entry.is_zip = 1;
435 	}
436 
437 	entry.filename = estrdup(ZSTR_VAL(resource->path) + 1);
438 
439 	if (phar->is_tar) {
440 		entry.is_tar = 1;
441 		entry.tar_type = TAR_DIR;
442 	}
443 
444 	entry.filename_len = ZSTR_LEN(resource->path) - 1;
445 	php_url_free(resource);
446 	entry.is_dir = 1;
447 	entry.phar = phar;
448 	entry.is_modified = 1;
449 	entry.is_crc_checked = 1;
450 	entry.flags = PHAR_ENT_PERM_DEF_DIR;
451 	entry.old_flags = PHAR_ENT_PERM_DEF_DIR;
452 
453 	if (NULL == zend_hash_str_add_mem(&phar->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info))) {
454 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", adding to manifest failed", entry.filename, phar->fname);
455 		efree(error);
456 		efree(entry.filename);
457 		return 0;
458 	}
459 
460 	phar_flush(phar, &error);
461 
462 	if (error) {
463 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", entry.filename, phar->fname, error);
464 		zend_hash_str_del(&phar->manifest, entry.filename, entry.filename_len);
465 		efree(error);
466 		return 0;
467 	}
468 
469 	phar_add_virtual_dirs(phar, entry.filename, entry.filename_len);
470 	return 1;
471 }
472 /* }}} */
473 
474 /**
475  * Remove a directory within a phar archive
476  */
phar_wrapper_rmdir(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)477 int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context) /* {{{ */
478 {
479 	phar_entry_info *entry;
480 	phar_archive_data *phar = NULL;
481 	char *error, *arch, *entry2;
482 	size_t arch_len, entry_len;
483 	php_url *resource = NULL;
484 
485 	/* pre-readonly check, we need to know if this is a data phar */
486 	if (FAILURE == phar_split_fname(url, strlen(url), &arch, &arch_len, &entry2, &entry_len, 2, 2)) {
487 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\", no phar archive specified, or phar archive does not exist", url);
488 		return 0;
489 	}
490 
491 	if (FAILURE == phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL)) {
492 		phar = NULL;
493 	}
494 
495 	efree(arch);
496 	efree(entry2);
497 
498 	if (PHAR_G(readonly) && (!phar || !phar->is_data)) {
499 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot rmdir directory \"%s\", write operations disabled", url);
500 		return 0;
501 	}
502 
503 	if ((resource = phar_parse_url(wrapper, url, "w", options)) == NULL) {
504 		return 0;
505 	}
506 
507 	/* we must have at the very least phar://alias.phar/internalfile.php */
508 	if (!resource->scheme || !resource->host || !resource->path) {
509 		php_url_free(resource);
510 		php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url);
511 		return 0;
512 	}
513 
514 	if (!zend_string_equals_literal_ci(resource->scheme, "phar")) {
515 		php_url_free(resource);
516 		php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url);
517 		return 0;
518 	}
519 
520 	if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error)) {
521 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", error retrieving phar information: %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
522 		efree(error);
523 		php_url_free(resource);
524 		return 0;
525 	}
526 
527 	size_t path_len = ZSTR_LEN(resource->path) - 1;
528 
529 	if (!(entry = phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, path_len, 2, &error, 1))) {
530 		if (error) {
531 			php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
532 			efree(error);
533 		} else {
534 			php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", directory does not exist", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
535 		}
536 		php_url_free(resource);
537 		return 0;
538 	}
539 
540 	if (!entry->is_deleted) {
541 		zend_string *str_key;
542 
543 		ZEND_HASH_MAP_FOREACH_STR_KEY(&phar->manifest, str_key) {
544 			if (
545 				zend_string_starts_with_cstr(str_key, ZSTR_VAL(resource->path)+1, path_len)
546 				&& IS_SLASH(ZSTR_VAL(str_key)[path_len])
547 			) {
548 				php_stream_wrapper_log_error(wrapper, options, "phar error: Directory not empty");
549 				if (entry->is_temp_dir) {
550 					efree(entry->filename);
551 					efree(entry);
552 				}
553 				php_url_free(resource);
554 				return 0;
555 			}
556 		} ZEND_HASH_FOREACH_END();
557 
558 		ZEND_HASH_MAP_FOREACH_STR_KEY(&phar->virtual_dirs, str_key) {
559 			ZEND_ASSERT(str_key);
560 			if (
561 				zend_string_starts_with_cstr(str_key, ZSTR_VAL(resource->path)+1, path_len)
562 				&& IS_SLASH(ZSTR_VAL(str_key)[path_len])
563 			) {
564 				php_stream_wrapper_log_error(wrapper, options, "phar error: Directory not empty");
565 				if (entry->is_temp_dir) {
566 					efree(entry->filename);
567 					efree(entry);
568 				}
569 				php_url_free(resource);
570 				return 0;
571 			}
572 		} ZEND_HASH_FOREACH_END();
573 	}
574 
575 	if (entry->is_temp_dir) {
576 		zend_hash_str_del(&phar->virtual_dirs, ZSTR_VAL(resource->path)+1, path_len);
577 		efree(entry->filename);
578 		efree(entry);
579 	} else {
580 		entry->is_deleted = 1;
581 		entry->is_modified = 1;
582 		phar_flush(phar, &error);
583 
584 		if (error) {
585 			php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", entry->filename, phar->fname, error);
586 			php_url_free(resource);
587 			efree(error);
588 			return 0;
589 		}
590 	}
591 
592 	php_url_free(resource);
593 	return 1;
594 }
595 /* }}} */
596