|
|
| version 1.114, 2002/08/05 13:47:37 | version 1.123, 2002/11/21 09:18:19 |
|---|---|
| Line 8 | Line 8 |
| static const char* IDENT_COMMON_C="$Date$"; | static const char* IDENT_COMMON_C="$Date$"; |
| #include "pa_common.h" | #include "pa_common.h" |
| #include "pa_types.h" | |
| #include "pa_exception.h" | #include "pa_exception.h" |
| #include "pa_pool.h" | |
| #include "pa_globals.h" | #include "pa_globals.h" |
| #include "pa_value.h" | |
| #include "pa_hash.h" | |
| #include "pa_string.h" | |
| #ifdef WIN32 | #ifdef WIN32 |
| # include <windows.h> | # include <windows.h> |
| Line 28 static const char* IDENT_COMMON_C="$Date | Line 23 static const char* IDENT_COMMON_C="$Date |
| #ifndef _O_BINARY | #ifndef _O_BINARY |
| # define _O_BINARY 0 | # define _O_BINARY 0 |
| #endif | #endif |
| #ifndef O_TRUNC | |
| # define O_TRUNC 0 | |
| #endif | |
| // locking constants | // locking constants |
| Line 119 char *file_read_text(Pool& pool, const S | Line 111 char *file_read_text(Pool& pool, const S |
| void *result; size_t size; | void *result; size_t size; |
| return file_read(pool, file_spec, result, size, true, fail_on_read_problem)?(char *)result:0; | return file_read(pool, file_spec, result, size, true, fail_on_read_problem)?(char *)result:0; |
| } | } |
| #ifndef DOXYGEN | |
| struct File_read_action_info { | |
| void **data; size_t *data_size; | |
| }; | |
| #endif | |
| static void file_read_action(Pool& pool, | |
| int f, | |
| const String& file_spec, const char *fname, bool as_text, | |
| void *context) { | |
| File_read_action_info& info=*static_cast<File_read_action_info *>(context); | |
| struct stat finfo; | |
| if(stat(fname, &finfo)!=0) | |
| throw Exception("file.missing", // hardly possible: we just opened it OK | |
| &file_spec, | |
| "stat failed: %s (%d), actual filename '%s'", | |
| strerror(errno), errno, fname); | |
| if(size_t to_read_size=(size_t)finfo.st_size) { | |
| *info.data=pool.malloc(to_read_size+(as_text?1:0), 3); | |
| *info.data_size=(size_t)read(f, *info.data, to_read_size); | |
| if(ssize_t(*info.data_size)<0 || *info.data_size>to_read_size) | |
| throw Exception(0, | |
| &file_spec, | |
| "read failed: actually read %lu bytes count not in [0..%lu] valid range", | |
| *info.data_size, to_read_size); | |
| } else { // empty file | |
| if(as_text) { | |
| *info.data=pool.malloc(1); | |
| *(char*)(*info.data)=0; | |
| } else | |
| *info.data=0; | |
| *info.data_size=0; | |
| return; | |
| } | |
| if(as_text) { | |
| fix_line_breaks((char *)(*info.data), *info.data_size); | |
| // note: after fixing | |
| ((char*&)(*info.data))[*info.data_size]=0; | |
| } | |
| } | |
| bool file_read(Pool& pool, const String& file_spec, | bool file_read(Pool& pool, const String& file_spec, |
| void*& data, size_t& data_size, bool as_text, | void*& data, size_t& data_size, bool as_text, |
| bool fail_on_read_problem, | bool fail_on_read_problem) { |
| size_t offset, size_t limit) { | File_read_action_info info={&data, &data_size}; |
| return file_read_action_under_lock(pool, file_spec, | |
| "read", file_read_action, &info, | |
| as_text, fail_on_read_problem); | |
| } | |
| bool file_read_action_under_lock(Pool& pool, const String& file_spec, | |
| const char *action_name, File_read_action action, void *context, | |
| bool as_text, | |
| bool fail_on_read_problem) { | |
| const char *fname=file_spec.cstr(String::UL_FILE_SPEC); | const char *fname=file_spec.cstr(String::UL_FILE_SPEC); |
| //printf("file_read(%s)\n", fname); | |
| int f; | int f; |
| struct stat finfo; | |
| // first open, next stat: | // first open, next stat: |
| // directory update of NTFS hard links performed on open. | // directory update of NTFS hard links performed on open. |
| Line 136 bool file_read(Pool& pool, const String& | Line 179 bool file_read(Pool& pool, const String& |
| // directory entry of b.html in NTFS not updated at once, | // directory entry of b.html in NTFS not updated at once, |
| // they delay update till open, so we would receive "!^test[" string | // they delay update till open, so we would receive "!^test[" string |
| // if would do stat, next open. | // if would do stat, next open. |
| // later: it seems, even this does not help sometimes | |
| if((f=open(fname, O_RDONLY|(as_text?_O_TEXT:_O_BINARY)))>=0) { | if((f=open(fname, O_RDONLY|(as_text?_O_TEXT:_O_BINARY)))>=0) { |
| if(lock_shared_blocking(f)!=0) { | try { |
| Exception e("file.lock", | if(lock_shared_blocking(f)!=0) |
| &file_spec, | throw Exception("file.lock", |
| "shared lock failed: %s (%d), actual filename '%s'", | &file_spec, |
| strerror(errno), errno, fname); | "shared lock failed: %s (%d), actual filename '%s'", |
| unlock(f); | strerror(errno), errno, fname); |
| close(f); | |
| if(fail_on_read_problem) | |
| throw e; | |
| return false; | |
| } | |
| if(stat(fname, &finfo)!=0) { | |
| Exception e("file.missing", | |
| &file_spec, | |
| "stat failed: %s (%d), actual filename '%s'", | |
| strerror(errno), errno, fname); | |
| unlock(f); | |
| close(f); | |
| if(fail_on_read_problem) | |
| throw e; | |
| return false; | |
| } | |
| #ifdef NO_FOREIGN_GROUP_FILES | #ifdef NO_FOREIGN_GROUP_FILES |
| if(finfo.st_gid/*foreign?*/!=getegid()) { | if(finfo.st_gid/*foreign?*/!=getegid()) { |
| Exception e("parser.runtime", | throw Exception("parser.runtime", |
| &file_spec, | &file_spec, |
| "parser reading files of foreign group disabled [recompile parser without --disable-foreign-group-files configure option], actual filename '%s'", | "parser reading files of foreign group disabled [recompile parser without --disable-foreign-group-files configure option], actual filename '%s'", |
| fname); | fname); |
| unlock(f); | |
| close(f); | |
| if(fail_on_read_problem) | |
| throw e; | |
| return false; | |
| } | |
| #endif | #endif |
| size_t max_size=limit?min(offset+limit, (size_t)finfo.st_size)-offset:finfo.st_size; | |
| if(!max_size) { // eof | |
| if(as_text) { | |
| data=pool.malloc(1); | |
| *(char*)data=0; | |
| } else | |
| data=0; | |
| data_size=0; | |
| } else { | |
| data=pool.malloc(max_size+(as_text?1:0), 3); | |
| if(offset) | |
| lseek(f, offset, SEEK_SET); | |
| data_size=read(f, data, max_size); | |
| } | |
| unlock(f); | |
| close(f); | |
| if(!max_size) // eof | |
| return true; | |
| if(int(data_size)<0 || data_size>max_size) | action(pool, f, file_spec, fname, as_text, context); |
| throw Exception(0, | } catch(...) { |
| &file_spec, | unlock(f);close(f); |
| "read failed: actually read %d bytes count not in [0..%lu] valid range", | if(fail_on_read_problem) |
| data_size, (unsigned long)max_size); //never | /*re*/throw; |
| return false; | |
| } | |
| if(as_text) { | unlock(f);close(f); |
| fix_line_breaks((char *)data, data_size); | |
| // note: after fixing | |
| ((char*&)data)[data_size]=0; | |
| } | |
| return true; | return true; |
| } | } else { |
| if(fail_on_read_problem) | if(fail_on_read_problem) |
| throw Exception(errno==EACCES?"file.access":errno==ENOENT?"file.missing":0, | throw Exception(errno==EACCES?"file.access":errno==ENOENT?"file.missing":0, |
| &file_spec, | &file_spec, |
| "read failed: %s (%d), actual filename '%s'", | "%s failed: %s (%d), actual filename '%s'", |
| strerror(errno), errno, fname); | action_name, strerror(errno), errno, fname); |
| return false; | return false; |
| } | |
| } | } |
| static void create_dir_for_file(const String& file_spec) { | static void create_dir_for_file(const String& file_spec) { |
| Line 223 static void create_dir_for_file(const St | Line 227 static void create_dir_for_file(const St |
| bool file_write_action_under_lock( | bool file_write_action_under_lock( |
| const String& file_spec, | const String& file_spec, |
| const char *action_name, void (*action)(int, void *), void *context, | const char *action_name, File_write_action action, void *context, |
| bool as_text, | bool as_text, |
| bool do_append, | bool do_append, |
| bool do_block, | bool do_block, |
| Line 236 bool file_write_action_under_lock( | Line 240 bool file_write_action_under_lock( |
| if((f=open(fname, | if((f=open(fname, |
| O_CREAT|O_RDWR | O_CREAT|O_RDWR |
| |(as_text?_O_TEXT:_O_BINARY) | |(as_text?_O_TEXT:_O_BINARY) |
| |(do_append?O_APPEND:O_TRUNC), 0664))>=0) { | |(do_append?O_APPEND:0), 0664))>=0) { |
| if((do_block?lock_exclusive_blocking(f):lock_exclusive_nonblocking(f))!=0) { | if((do_block?lock_exclusive_blocking(f):lock_exclusive_nonblocking(f))!=0) { |
| Exception e("file.lock", | Exception e("file.lock", |
| &file_spec, | &file_spec, |
| Line 251 bool file_write_action_under_lock( | Line 255 bool file_write_action_under_lock( |
| try { | try { |
| action(f, context); | action(f, context); |
| } catch(...) { | } catch(...) { |
| #if O_TRUNC==0 | |
| if(!do_append) | if(!do_append) |
| ftruncate(f, tell(f)); | ftruncate(f, tell(f)); // one can not use O_TRUNC, read lower |
| #endif | unlock(f);close(f); |
| unlock(f); | |
| close(f); | |
| /*re*/throw; | /*re*/throw; |
| } | } |
| #if O_TRUNC==0 | |
| if(!do_append) | if(!do_append) |
| ftruncate(f, tell(f)); | ftruncate(f, tell(f)); // O_TRUNC truncates even exclusevely write-locked file [thanks to Igor Milyakov <virtan@rotabanner.com> for discovering] |
| #endif | unlock(f);close(f); |
| unlock(f); | |
| close(f); | |
| return true; | return true; |
| } else | } else |
| throw Exception(errno==EACCES?"file.access":0, | throw Exception(errno==EACCES?"file.access":0, |
| Line 283 struct File_write_action_info { | Line 281 struct File_write_action_info { |
| static void file_write_action(int f, void *context) { | static void file_write_action(int f, void *context) { |
| File_write_action_info& info=*static_cast<File_write_action_info *>(context); | File_write_action_info& info=*static_cast<File_write_action_info *>(context); |
| if(info.size) { | if(info.size) { |
| size_t written=write(f, info.data, info.size); | int written=write(f, info.data, info.size); |
| if(written!=info.size) | if(written<0) |
| throw Exception(0, | throw Exception(0, |
| 0, | 0, |
| "write failed: %s (%d), number of bytes written not equal to requested (%u!=%u)", | "write failed: %s (%d)", strerror(errno), errno); |
| strerror(errno), errno, | |
| written, info.size); | |
| } | } |
| } | } |
| void file_write( | void file_write( |
| Line 343 void file_move(const String& old_spec, c | Line 339 void file_move(const String& old_spec, c |
| } | } |
| bool entry_exists(const char *fname, struct stat *afinfo) { | |
| struct stat lfinfo; | |
| bool result=stat(fname, &lfinfo)==0; | |
| if(afinfo) | |
| *afinfo=lfinfo; | |
| return result; | |
| } | |
| bool entry_exists(const String& file_spec) { | |
| const char *fname=file_spec.cstr(String::UL_FILE_SPEC); | |
| return entry_exists(fname, 0); | |
| } | |
| static bool entry_readable(const String& file_spec, bool need_dir) { | static bool entry_readable(const String& file_spec, bool need_dir) { |
| const char *fname=file_spec.cstr(String::UL_FILE_SPEC); | char *fname=file_spec.cstr(String::UL_FILE_SPEC); |
| if(need_dir) { | |
| size_t size=strlen(fname); | |
| while(size) { | |
| char c=fname[size-1]; | |
| if(c=='/' || c=='\\') | |
| fname[--size]=0; | |
| else | |
| break; | |
| } | |
| } | |
| struct stat finfo; | struct stat finfo; |
| if(access(fname, R_OK)==0 && stat(fname, &finfo)==0) { | if(access(fname, R_OK)==0 && entry_exists(fname, &finfo)) { |
| bool is_dir=(finfo.st_mode&S_IFDIR) != 0; | bool is_dir=(finfo.st_mode&S_IFDIR) != 0; |
| return is_dir==need_dir; | return is_dir==need_dir; |
| } | } |
| Line 508 char *unescape_chars(Pool& pool, const c | Line 527 char *unescape_chars(Pool& pool, const c |
| return s; | return s; |
| } | } |
| /// used by attributed_meaning_to_string / append_attribute_subattribute | |
| struct Attributed_meaning_info { | |
| String *header; // header line being constructed | |
| String::Untaint_lang lang; // language in which to append to that line | |
| }; | |
| static void append_attribute_subattribute(const Hash::Key& akey, Hash::Val *avalue, | |
| void *info) { | |
| if(akey==VALUE_NAME) | |
| return; | |
| Attributed_meaning_info& ami=*static_cast<Attributed_meaning_info *>(info); | |
| // ...; charset=windows1251 | |
| *ami.header << "; "; | |
| ami.header->append(akey, ami.lang); | |
| *ami.header << "="; | |
| ami.header->append(static_cast<Value *>(avalue)->as_string(), ami.lang); | |
| } | |
| const String& attributed_meaning_to_string(Value& meaning, | |
| String::Untaint_lang lang) { | |
| String &result=*new(meaning.pool()) String(meaning.pool()); | |
| if(Hash *hash=meaning.get_hash(0)) { | |
| // $value(value) $subattribute(subattribute value) | |
| if(Value *value=static_cast<Value *>(hash->get(*value_name))) | |
| result.append(value->as_string(), lang, true); | |
| Attributed_meaning_info attributed_meaning_info={ | |
| &result, | |
| lang | |
| }; | |
| hash->for_each(append_attribute_subattribute, &attributed_meaning_info); | |
| } else // result value | |
| result.append(meaning.as_string(), lang, true); | |
| return result; | |
| } | |
| #ifdef WIN32 | #ifdef WIN32 |
| void back_slashes_to_slashes(char *s) { | void back_slashes_to_slashes(char *s) { |
| if(s) | if(s) |