Annotation of parser3/src/main/pa_file.C, revision 1.14
1.1 moko 1: /** @file
2: Parser: file and file system related functions.
3:
4: Copyright (c) 2000-2026 Art. Lebedev Studio (https://www.artlebedev.com)
5: Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
6: */
7:
8: #include "pa_file.h"
9: #include "pa_dir.h"
1.13 moko 10: #include "pa_os.h"
1.1 moko 11: #include "pa_charsets.h"
12: #include "pa_http.h"
13: #include "pa_convert_utf.h"
14:
15: #ifdef _MSC_VER
16: #include <windows.h>
17: #include <direct.h>
18: #endif
19:
1.14 ! moko 20: volatile const char * IDENT_PA_FILE_C="$Id: pa_file.C,v 1.13 2026/11/03 15:51:20 moko Exp $" IDENT_PA_FILE_H;
1.8 moko 21:
22: // a tainted path scheme (file://, http:// or parser://) would be accepted
23: //#define IGNORE_FILE_PREFIX_LANGUAGES
1.1 moko 24:
25: // some maybe-undefined constants
26:
27: #ifndef _O_TEXT
28: # define _O_TEXT 0
29: #endif
30: #ifndef _O_BINARY
31: # define _O_BINARY 0
32: #endif
33:
34: #ifdef HAVE_FTRUNCATE
35: # define PA_O_TRUNC 0
36: #else
37: # ifdef _O_TRUNC
38: # define PA_O_TRUNC _O_TRUNC
39: # else
40: # error you must have either ftruncate function or _O_TRUNC bit declared
41: # endif
42: #endif
43:
44: #ifdef WIN32
45:
46: bool is_os_absolute_path(const String& path) {
47: return is_os_absolute_path(path.cstr());
48: }
49:
50: bool is_os_absolute_path(const char* path) {
51: return path[0] && path[0]!=':' && path[1]==':' // DRIVE: (including drive-relative paths, for compatibility)
1.4 moko 52: || path[0]=='\\' && path[1]=='\\'; // UNC, only '\\' is supported as '//' is a document root relative path
1.1 moko 53: }
54: #endif
55:
1.5 moko 56: uint path_scheme(const String& path) {
57: if(path.starts_with("file://"))
58: return PA_SCHEME_FILE;
59: if(path.starts_with("http://"))
60: return PA_SCHEME_HTTP;
61: if(path.starts_with("parser://"))
62: return PA_SCHEME_PARSER;
63: return PA_SCHEME_NONE;
64: }
65:
1.9 moko 66: bool clean_path_scheme(uint scheme, const String& path) {
67: #ifdef IGNORE_FILE_PREFIX_LANGUAGES
68: return true;
69: #else
70: return path.check_lang(String::L_AS_IS, 0, scheme==PA_SCHEME_PARSER ? 9 /* "parser://" */ : 7 /* "file://" or "http://" */);
71: #endif
72: }
73:
1.5 moko 74: void check_path_scheme(uint scheme, const String& path, uint allowed) {
75: if(scheme!=PA_SCHEME_FILE && !(allowed&scheme))
76: throw Exception(PARSER_RUNTIME, &path, "unsupported path scheme");
1.9 moko 77: if(!clean_path_scheme(scheme, path))
1.8 moko 78: throw Exception(PARSER_RUNTIME, &path, "tainted path scheme");
1.5 moko 79: }
80:
1.10 moko 81: int path_cmp(const char* path, const char* dir, size_t length) {
82: #ifdef WIN32
83: return strncasecmp(path, dir, length); // mostly for the drive letter
84: #else
85: return strncmp(path, dir, length);
86: #endif
87: }
88:
1.11 moko 89: const char* actual_filename(const String& file_spec, const char* fname) {
90: return strcmp(file_spec.cstr(), fname) ? pa_strcat(", actual filename '", fname, "'") : "";
91: }
92:
1.5 moko 93: const String* file_uri_to_path(const String& uri) {
94: const String* rest=&uri.mid(7 /* "file://" */, uri.length());
95: if(rest->starts_with("localhost/"))
96: rest=&rest->mid(9 /* "localhost", keeping the slash */, rest->length());
97: if(rest->first_char()=='/') {
98: // file:///C:/path and file://localhost/C:/path: the slash before a drive name is not a part of the path
99: const String& disk=rest->mid(1, rest->length());
100: return is_os_absolute_path(disk) ? &disk : rest;
101: }
102: if(is_os_absolute_path(*rest))
103: return rest; // file://C:/path, not a standard form but in use
1.7 moko 104: #ifdef WIN32
105: if(rest->pos('/')!=STRING_NOT_FOUND) { // file://server/share/path is the UNC path \\server\share\path
106: String& result=*new String("//");
107: result << *rest;
108: return &result;
109: }
110: #endif
1.5 moko 111: return 0;
112: }
113:
1.1 moko 114: // functions
115:
116: #ifdef _MSC_VER
117:
1.12 moko 118: // externs
119:
120: const UTF16* pa_utf16_encode(const char* in, Charset& source_charset);
1.1 moko 121: #define PA_UTF16_ENC(value) (const wchar_t *)pa_utf16_encode(value, pa_thread_request().charsets.source())
122:
1.12 moko 123: // non-UTF-8 name should be converted to an error without throw
124: #define PA_UTF16_VAR(var, value, error, failed) const wchar_t* var=PA_UTF16_ENC(value); if(!var){ errno=error; return failed; }
125:
1.1 moko 126: int pa_stat(const char *pathname, struct stat *buffer){
1.12 moko 127: PA_UTF16_VAR(wpathname, pathname, ENOENT, -1)
128: return _wstat64(wpathname, buffer);
1.1 moko 129: }
130:
131: int pa_open(const char *pathname, int flags, int mode){
1.12 moko 132: PA_UTF16_VAR(wpathname, pathname, (flags & _O_CREAT) ? EINVAL : ENOENT, -1)
133: return _wopen(wpathname, flags, mode);
1.1 moko 134: }
135:
136: FILE *pa_fopen(const char *pathname, const char *mode){
1.12 moko 137: PA_UTF16_VAR(wpathname, pathname, *mode=='r' ? ENOENT : EINVAL, 0)
138: PA_UTF16_VAR(wmode, mode, EINVAL, 0)
139: return _wfopen(wpathname, wmode);
1.1 moko 140: }
141:
142: int pa_mkdir(const char *pathname, int){
1.12 moko 143: PA_UTF16_VAR(wpathname, pathname, EINVAL, -1)
144: return _wmkdir(wpathname);
1.1 moko 145: }
146:
147: int pa_rmdir(const char *pathname){
1.12 moko 148: PA_UTF16_VAR(wpathname, pathname, ENOENT, -1)
149: return _wrmdir(wpathname);
1.1 moko 150: }
151:
1.13 moko 152: static bool pa_is_dir(const wchar_t* wpathname){
153: DWORD attributes=GetFileAttributesW(wpathname);
154: return attributes!=INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY);
155: }
156:
1.1 moko 157: int pa_rename(const char *oldpath, const char *newpath){
1.12 moko 158: PA_UTF16_VAR(woldpath, oldpath, ENOENT, -1)
159: PA_UTF16_VAR(wnewpath, newpath, ENOENT, -1)
1.13 moko 160: if(pa_set_errno(MoveFileExW(woldpath, wnewpath, MOVEFILE_REPLACE_EXISTING)))
161: return 0;
162: // like rename on unix, a directory replaces the empty one: windows does not replace directories at all
163: if(errno==EACCES && pa_is_dir(woldpath) && pa_is_dir(wnewpath) && _wrmdir(wnewpath)==0)
164: return pa_set_errno(MoveFileExW(woldpath, wnewpath, MOVEFILE_REPLACE_EXISTING)) ? 0 : -1;
165: return -1;
1.1 moko 166: }
167:
168: int pa_unlink(const char *pathname){
1.12 moko 169: PA_UTF16_VAR(wpathname, pathname, ENOENT, -1)
170: return _wunlink(wpathname);
1.1 moko 171: }
172:
173: #else
174:
175: #define pa_mkdir mkdir
176: #define pa_rmdir rmdir
177: #define pa_rename rename
178: #define pa_unlink unlink
179:
180: #endif
181:
182: /// these options were handled but not checked elsewhere, now check them
183: int pa_get_valid_file_options_count(HashStringValue& options) {
184: int result=0;
185: if(options.get(PA_SQL_LIMIT_NAME))
186: result++;
187: if(options.get(PA_SQL_OFFSET_NAME))
188: result++;
189: if(options.get(PA_COLUMN_SEPARATOR_NAME))
190: result++;
191: if(options.get(PA_COLUMN_ENCLOSER_NAME))
192: result++;
193: if(options.get(PA_CHARSET_NAME))
194: result++;
195: return result;
196: }
197:
198: #ifndef DOXYGEN
199: struct File_read_action_info {
200: char **data; size_t *data_size;
201: char* buf; uint64_t offset; size_t limit;
1.3 moko 202: };
1.1 moko 203: #endif
204:
205: static void file_read_action(struct stat& finfo, int f, const String& file_spec, void *context) {
1.3 moko 206: File_read_action_info& info = *static_cast<File_read_action_info *>(context);
1.1 moko 207: size_t to_read_size = check_file_size(info.limit && info.limit < (size_t)finfo.st_size ? info.limit : (size_t)finfo.st_size, &file_spec);
208: if(to_read_size) {
209: if(info.offset)
210: pa_lseek(f, info.offset, SEEK_SET); // seek never fails as POSIX allows the file offset to be set beyond the EOF
211: *info.data = info.buf ? info.buf : (char *)pa_malloc_atomic(to_read_size+1);
212: ssize_t result = read(f, *info.data, to_read_size);
213: if(result<0)
214: throw Exception("file.read", &file_spec, "read failed: %s (%d)", strerror(errno), errno);
215: *info.data_size = result;
216: } else { // empty file
217: // for both, text and binary: for text we need that terminator, for binary we need nonzero pointer to be able to save such files
218: *info.data = (char *)pa_malloc_atomic(1);
219: *(char*)(*info.data) = 0;
220: *info.data_size = 0;
221: return;
222: }
223: }
224:
225: File_read_result file_read_binary(const String& file_spec, bool fail_on_read_problem, char* buf, uint64_t offset, size_t limit) {
226: File_read_result result = {false, 0, 0, 0};
227: File_read_action_info info = {&result.str, &result.length, buf, offset, limit};
228:
229: result.success = file_read_action_under_lock(file_spec, "read", file_read_action, &info, 0, fail_on_read_problem);
230: return result;
231: }
232:
1.3 moko 233: File_read_result file_read(Request_charsets& charsets, const String& file_spec,
1.1 moko 234: bool as_text, HashStringValue *options,
235: bool fail_on_read_problem,
236: size_t offset = 0, size_t limit = 0, bool transcode_text_result = true) {
237: File_read_result result = {false, 0, 0, 0};
238: if(options){
239: int valid_options = pa_get_valid_file_options_count(*options);
240: if(valid_options != options->count())
241: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
242: }
243:
1.3 moko 244: File_read_action_info info = {&result.str, &result.length, 0, offset, limit};
1.1 moko 245:
1.3 moko 246: result.success = file_read_action_under_lock(file_spec, "read", file_read_action, &info, as_text, fail_on_read_problem);
1.1 moko 247:
248: if(as_text){
249: if(result.success){
250: Charset* asked_charset = 0;
251: if(options)
252: if(Value* vcharset_name = options->get(PA_CHARSET_NAME))
253: asked_charset = &pa_charsets.get(vcharset_name->as_string());
254:
255: asked_charset = pa_charsets.checkBOM(result.str, result.length, asked_charset);
256:
257: if(result.length && transcode_text_result && asked_charset){ // length must be checked because transcode returns CONST string in case length==0, which contradicts hacking few lines below
258: String::C body = String::C(result.str, result.length);
259: body=Charset::transcode(body, *asked_charset, charsets.source());
260:
261: result.str = const_cast<char*>(body.str); // hacking a little
262: result.length = body.length;
263: }
264: }
265: if(result.length)
266: fix_line_breaks(result.str, result.length);
267: }
268:
269: return result;
270: }
271:
1.3 moko 272: File_read_result file_load(Request& r, const String& file_spec,
1.1 moko 273: bool as_text, HashStringValue *options,
274: bool fail_on_read_problem,
275: bool transcode_text_result) {
276:
277: size_t offset = 0;
278: size_t limit = 0;
279:
280: if(options){
281: if(Value *voffset = (Value *)options->get(sql_offset_name))
282: offset = r.process(*voffset).as_int();
283: if(Value *vlimit = (Value *)options->get(sql_limit_name))
284: limit = r.process(*vlimit).as_int();
285: // no check on options count here
286: }
287:
288: if(file_spec.starts_with("http://")) {
289: if(offset || limit)
290: throw Exception(PARSER_RUNTIME, 0, "offset and load options are not supported for HTTP:// file load");
291:
292: // fail on read problem
293: File_read_http_result http = pa_internal_file_read_http(r, file_spec, as_text, options, transcode_text_result);
294:
295: File_read_result result = {true, http.str, http.length, http.headers};
296: return result;
297: } else
298: return file_read(r.charsets, file_spec, as_text, options, fail_on_read_problem, offset, limit, transcode_text_result);
299: }
300:
301: char* file_read_text(Request_charsets& charsets, const String& file_spec, bool fail_on_read_problem) {
302: File_read_result file = file_read(charsets, file_spec, true, 0, fail_on_read_problem);
303: return file.success ? file.str : 0;
304: }
305:
306: char* file_load_text(Request& r, const String& file_spec, bool fail_on_read_problem, HashStringValue* options, bool transcode_result) {
307: File_read_result file = file_load(r, file_spec, true, options, fail_on_read_problem, transcode_result);
308: return file.success ? file.str : 0;
309: }
310:
311: #ifdef PA_SAFE_MODE
312: void check_safe_mode(struct stat finfo, const String& file_spec, const char* fname) {
313: if(finfo.st_uid/*foreign?*/!=geteuid()
314: && finfo.st_gid/*foreign?*/!=getegid())
315: throw Exception(PARSER_RUNTIME,
316: &file_spec,
317: "parser is in safe mode: reading files of foreign group and user disabled "
318: "[recompile parser with --disable-safe-mode configure option], "
319: "actual filename '%s', fuid(%d)!=euid(%d) or fgid(%d)!=egid(%d)",
320: fname, finfo.st_uid, geteuid(), finfo.st_gid, getegid()
321: );
322: }
323: #else
324: void check_safe_mode(struct stat, const String&, const char*) {
325: }
326: #endif
327:
328:
329: bool file_read_action_under_lock(const String& file_spec, const char* action_name, File_read_action action, void *context,
330: bool as_text, bool fail_on_read_problem) {
331:
332: const char* fname=file_spec.taint_cstr(String::L_FILE_SPEC);
333: int f;
334:
335: // first open, next stat:
336: // directory update of NTFS hard links performed on open.
1.3 moko 337: // ex:
1.1 moko 338: // a.html:^test[] and b.html hardlink to a.html
339: // user inserts ! before ^test in a.html
1.3 moko 340: // directory entry of b.html in NTFS not updated at once,
1.1 moko 341: // they delay update till open, so we would receive "!^test[" string
342: // if would do stat, next open.
343: // later: it seems, even this does not help sometimes
344: if((f=pa_open(fname, O_RDONLY | (as_text ? _O_TEXT : _O_BINARY) ))>=0) {
345: try {
346: int pa_errno=pa_lock_shared_blocking(f);
347: if(pa_errno!=0)
1.11 moko 348: throw Exception("file.lock", &file_spec, "shared lock failed: %s (%d)%s", strerror(pa_errno), pa_errno, actual_filename(file_spec, fname));
1.1 moko 349:
350: struct stat finfo;
351: if(pa_fstat(f, &finfo)!=0)
352: throw Exception("file.missing", // hardly possible: we just opened it OK
1.11 moko 353: &file_spec, "stat failed: %s (%d)%s", strerror(errno), errno, actual_filename(file_spec, fname));
1.1 moko 354:
355: check_safe_mode(finfo, file_spec, fname);
356:
1.3 moko 357: action(finfo, f, file_spec, context);
1.1 moko 358: } catch(...) {
1.3 moko 359: pa_unlock(f);close(f);
1.1 moko 360: if(fail_on_read_problem)
361: rethrow;
362: return false;
1.3 moko 363: }
1.1 moko 364:
1.3 moko 365: pa_unlock(f);close(f);
1.1 moko 366: return true;
367: } else {
368: if(fail_on_read_problem)
369: throw Exception(errno==EACCES ? "file.access" : (errno==ENOENT || errno==ENOTDIR || errno==ENODEV) ? "file.missing" : 0,
1.11 moko 370: &file_spec, "%s failed: %s (%d)%s", action_name, strerror(errno), errno, actual_filename(file_spec, fname));
1.1 moko 371: return false;
372: }
373: }
374:
375: void create_dir_for_file(const String& file_spec) {
376: const char *str=file_spec.taint_cstr(String::L_FILE_SPEC);
377: if(str[0]){
378: const char *pos=str+1;
1.6 moko 379: while((pos=strchr(pos, '/')) && pos[1]) { // to avoid trailing /, see #1166
1.1 moko 380: pa_mkdir(pa_strdup(str,pos-str), 0775);
381: pos++;
382: }
383: }
384: }
385:
386: bool file_write_action_under_lock(const String& file_spec, const char* action_name, File_write_action action, void *context,
387: bool as_text, bool do_append, bool do_block, bool fail_on_lock_problem) {
388:
389: const char* fname=file_spec.taint_cstr(String::L_FILE_SPEC);
390: int f;
391: if(access(fname, W_OK)!=0) // no
392: create_dir_for_file(file_spec);
393:
394: if((f=pa_open(fname, O_CREAT | O_RDWR | (as_text ? _O_TEXT : _O_BINARY) | (do_append ? O_APPEND : PA_O_TRUNC), 0664))>=0) {
395: int pa_errno=do_block ? pa_lock_exclusive_blocking(f) : pa_lock_exclusive_nonblocking(f);
396: if(pa_errno!=0) {
1.11 moko 397: Exception e("file.lock", &file_spec, "exclusive lock failed: %s (%d)%s", strerror(pa_errno), pa_errno, actual_filename(file_spec, fname));
1.1 moko 398: close(f);
399: if(fail_on_lock_problem)
400: throw e;
401: return false;
402: }
403:
404: try {
405: #if (defined(HAVE_FCHMOD) && defined(PA_SAFE_MODE))
406: struct stat finfo;
407: if(pa_fstat(f, &finfo)==0 && finfo.st_mode & 0111)
408: fchmod(f, finfo.st_mode & 0666/*clear executable bits*/); // backward: ignore errors if any
409: #endif
410: action(f, context);
411: } catch(...) {
412: #ifdef HAVE_FTRUNCATE
413: if(!do_append)
414: PA_UNUSED int ignore_result=ftruncate(f, lseek(f, 0, SEEK_CUR)); // one cannot use O_TRUNC, read lower
415: #endif
416: pa_unlock(f);close(f);
417: rethrow;
418: }
419:
420: #ifdef HAVE_FTRUNCATE
421: if(!do_append)
422: PA_UNUSED int ignore_result=ftruncate(f, lseek(f, 0, SEEK_CUR)); // O_TRUNC truncates even exclusevely write-locked file [thanks to Igor Milyakov <virtan@rotabanner.com> for discovering]
423: #endif
424: pa_unlock(f);close(f);
425: return true;
426: } else
1.11 moko 427: throw Exception(errno==EACCES ? "file.access" : 0, &file_spec, "%s failed: %s (%d)%s", action_name, strerror(errno), errno, actual_filename(file_spec, fname));
1.1 moko 428: // here should be nothing, see rethrow above
429: }
430:
431: #ifndef DOXYGEN
432: struct File_write_action_info {
433: const char* str;
434: size_t length;
435: };
436: #endif
437:
438: // a single write() must stay below INT_MAX as windows _write() reports written bytes as int
439: #define FILE_WRITE_CHUNK_SIZE (256*0x100000)
440:
441: static const char* bytes_left(size_t rest) {
442: return rest ? pa_strcat(", ", pa_uitoa(rest), " bytes left") : "";
443: }
444:
445: static void file_write_action(int f, void *context) {
446: File_write_action_info& info=*static_cast<File_write_action_info *>(context);
447:
448: const char* data=info.str;
449: size_t left=info.length;
450:
451: while(left) {
452: size_t portion=min(left, (size_t)FILE_WRITE_CHUNK_SIZE);
453: ssize_t written=write(f, data, portion);
454: if(written<0)
455: throw Exception("file.write", 0, "error writing %s bytes: %s (%d)%s", pa_uitoa(portion), strerror(errno), errno, bytes_left(left-portion));
456: if(written==0)
457: throw Exception("file.write", 0, "error writing %s bytes: zero bytes written%s", pa_uitoa(portion), bytes_left(left-portion));
458: data+=written;
459: left-=written;
460: }
461: }
462:
463: void file_write(
464: Request_charsets& charsets,
465: const String& file_spec,
466: const char* data,
1.3 moko 467: size_t size,
468: bool as_text,
1.1 moko 469: bool do_append,
470: Charset* asked_charset) {
471:
472: if(as_text && asked_charset){
473: String::C body=String::C(data, size);
474: body=Charset::transcode(body, charsets.source(), *asked_charset);
475: data=body.str;
476: size=body.length;
477: };
478:
479: File_write_action_info info={data, size};
480:
481: file_write_action_under_lock(
482: file_spec,
483: "write",
484: file_write_action,
485: &info,
486: as_text,
487: do_append);
488: }
489:
490: static size_t get_dir(char* fname, size_t helper_length){
491: bool dir=false;
492: size_t pos=0;
493: for(pos=helper_length; pos; pos--){
494: char c=fname[pos-1];
1.6 moko 495: if(c=='/'){
1.1 moko 496: fname[pos-1]=0;
497: dir=true;
498: } else if(dir) break;
499: }
500: return pos;
501: }
502:
503: bool entry_exists(const char* fname, struct stat *afinfo) {
504: struct stat lfinfo;
505: bool result=pa_stat(fname, &lfinfo)==0;
506: if(afinfo)
507: *afinfo=lfinfo;
508: return result;
509: }
510:
511: bool entry_exists(const String& file_spec) {
1.3 moko 512: return entry_exists(file_spec.taint_cstr(String::L_FILE_SPEC), 0);
1.1 moko 513: }
514:
515: static bool entry_ifdir(char *fname, bool need_dir) {
516: if(need_dir){
517: size_t size=strlen(fname);
518: while(size) {
519: char c=fname[size-1];
1.6 moko 520: if(c=='/')
1.1 moko 521: fname[--size]=0;
522: else
523: break;
524: }
525: }
526:
527: struct stat finfo;
528: if(entry_exists(fname, &finfo)) {
529: bool is_dir=(finfo.st_mode&S_IFDIR) != 0;
530: return is_dir==need_dir;
531: }
532: return false;
533: }
534:
535: static bool entry_ifdir(const String& file_spec, bool need_dir) {
536: return entry_ifdir(file_spec.taint_cstrm(String::L_FILE_SPEC), need_dir);
537: }
538:
539: // throws nothing! [this is required in file_move & file_delete]
540: static void rmdir(const String& file_spec, size_t pos_after) {
541: char* dir_spec=file_spec.taint_cstrm(String::L_FILE_SPEC);
542: size_t length=strlen(dir_spec);
543: while( (length=get_dir(dir_spec, length)) && (length > pos_after) ){
544: #ifdef _MSC_VER
545: if(!entry_ifdir(dir_spec, true))
546: break;
547: DWORD attrs=GetFileAttributesW(PA_UTF16_ENC(dir_spec));
548: if(
549: (attrs==INVALID_FILE_ATTRIBUTES)
550: || !(attrs & FILE_ATTRIBUTE_DIRECTORY)
551: || (attrs & FILE_ATTRIBUTE_REPARSE_POINT)
552: )
553: break;
554: #endif
555: if(pa_rmdir(dir_spec))
556: break;
557: };
558: }
559:
560: bool file_delete(const String& file_spec, bool fail_on_problem, bool keep_empty_dirs) {
1.3 moko 561: const char* fname=file_spec.taint_cstr(String::L_FILE_SPEC);
1.1 moko 562: if(pa_unlink(fname)!=0) {
563: if(fail_on_problem)
1.14 ! moko 564: throw Exception(errno==EACCES ? "file.access" : errno==ENOENT ? "file.missing" : 0,
1.11 moko 565: &file_spec, "unlink failed: %s (%d)%s", strerror(errno), errno, actual_filename(file_spec, fname));
1.1 moko 566: else
567: return false;
568: }
569:
570: if(!keep_empty_dirs)
1.3 moko 571: rmdir(file_spec, 1);
1.1 moko 572:
573: return true;
574: }
575:
1.14 ! moko 576: bool dir_delete(const String& dir_spec, bool fail_on_problem) {
! 577: const char* dname=dir_spec.taint_cstr(String::L_FILE_SPEC);
! 578: if(pa_rmdir(dname)!=0) {
! 579: if(fail_on_problem)
! 580: throw Exception(errno==EACCES ? "file.access" : errno==ENOENT ? "file.missing" : 0,
! 581: &dir_spec, "rmdir failed: %s (%d)%s", strerror(errno), errno, actual_filename(dir_spec, dname));
! 582: else
! 583: return false;
! 584: }
! 585: return true;
! 586: }
! 587:
1.1 moko 588: void file_move(const String& old_spec, const String& new_spec, bool keep_empty_dirs) {
1.3 moko 589: const char* old_spec_cstr=old_spec.taint_cstr(String::L_FILE_SPEC);
590: const char* new_spec_cstr=new_spec.taint_cstr(String::L_FILE_SPEC);
1.1 moko 591:
1.3 moko 592: create_dir_for_file(new_spec);
1.1 moko 593:
594: if(pa_rename(old_spec_cstr, new_spec_cstr)!=0)
595: throw Exception(errno==EACCES ? "file.access" : errno==ENOENT ? "file.missing" : 0,
1.11 moko 596: &old_spec, "rename to '%s' failed: %s (%d)%s", new_spec_cstr, strerror(errno), errno, actual_filename(old_spec, old_spec_cstr));
1.1 moko 597:
598: if(!keep_empty_dirs)
1.3 moko 599: rmdir(old_spec, 1);
1.1 moko 600: }
601:
602:
603: bool file_exist(const String& file_spec) {
1.3 moko 604: return entry_ifdir(file_spec, false);
1.1 moko 605: }
606:
607: bool dir_exists(const String& file_spec) {
1.3 moko 608: return entry_ifdir(file_spec, true);
1.1 moko 609: }
610:
611: const String* file_exist(const String& path, const String& name) {
612: String& result=*new String(path);
1.6 moko 613: if(path.last_char() != '/')
1.1 moko 614: result << "/";
615: result << name;
616: return file_exist(result)?&result:0;
617: }
618:
619: bool file_executable(const String& file_spec) {
620: return access(file_spec.taint_cstr(String::L_FILE_SPEC), X_OK)==0;
621: }
622:
623: bool file_stat(const String& file_spec, uint64_t& rsize, time_t& ratime, time_t& rmtime, time_t& rctime, bool fail_on_read_problem) {
1.3 moko 624: const char* fname=file_spec.taint_cstr(String::L_FILE_SPEC);
1.1 moko 625: struct stat finfo;
626: if(pa_stat(fname, &finfo)!=0) {
627: if(fail_on_read_problem)
1.11 moko 628: throw Exception("file.missing", &file_spec, "getting file size failed: %s (%d)%s", strerror(errno), errno, actual_filename(file_spec, fname));
1.1 moko 629: else
630: return false;
631: }
632: rsize=finfo.st_size;
633: ratime=(time_t)finfo.st_atime;
634: rmtime=(time_t)finfo.st_mtime;
635: rctime=(time_t)finfo.st_ctime;
636: return true;
637: }
638:
639: size_t check_file_size(uint64_t size, const String* file_spec){
640: if(size > (uint64_t)pa_file_size_limit)
1.2 moko 641: throw Exception(PARSER_RUNTIME, file_spec, "content size of %s bytes exceeds the limit (%s bytes)", pa_uitoa(size), pa_uitoa(pa_file_size_limit));
1.1 moko 642: return (size_t)size;
643: }
644:
1.3 moko 645: /**
1.1 moko 646: String related functions
647: */
648:
649: const char *pa_filename(const char *path){
650: const char *slash=strrpbrk(path, "/\\");
651: return slash ? slash+1 : path;
652: }
E-mail: