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: