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