Annotation of parser3/src/main/pa_common.C, revision 1.140.2.3
1.15 paf 1: /** @file
1.16 paf 2: Parser: commonly functions.
3:
1.102 paf 4: Copyright(c) 2001, 2002 ArtLebedev Group (http://www.artlebedev.com)
1.101 paf 5: Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
1.111 paf 6: */
1.16 paf 7:
1.140.2.3! paf 8: static const char* IDENT_COMMON_C="$Date: 2003/02/25 12:27:17 $";
1.1 paf 9:
10: #include "pa_common.h"
1.4 paf 11: #include "pa_exception.h"
1.14 paf 12: #include "pa_globals.h"
1.126 paf 13: #include "pa_hash.h"
14: #include "pa_vstring.h"
1.135 paf 15: #include "pa_vdate.h"
1.140.2.3! paf 16: #include "pa_request.h"
1.1 paf 17:
1.98 paf 18: #ifdef WIN32
19: # include <windows.h>
1.126 paf 20: #else
21: # define closesocket close
1.98 paf 22: #endif
23:
1.93 paf 24: // some maybe-undefined constants
25:
1.82 paf 26: #ifndef _O_TEXT
27: # define _O_TEXT 0
28: #endif
29: #ifndef _O_BINARY
30: # define _O_BINARY 0
1.47 paf 31: #endif
1.80 paf 32:
1.138 paf 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:
1.93 paf 43: // locking constants
44:
1.99 paf 45: #ifdef HAVE_FLOCK
46:
47: static int lock_shared_blocking(int fd) { return flock(fd, LOCK_SH); }
48: static int lock_exclusive_blocking(int fd) { return flock(fd, LOCK_EX); }
49: static int lock_exclusive_nonblocking(int fd) { return flock(fd, LOCK_EX || LOCK_NB); }
50: static int unlock(int fd) { return flock(fd, LOCK_UN); }
51:
1.98 paf 52: #else
1.99 paf 53: #ifdef HAVE__LOCKING
1.98 paf 54:
1.126 paf 55: #define FLOCK(operation) lseek(fd, 0, SEEK_SET); return _locking(fd, operation, 1)
1.99 paf 56: static int lock_shared_blocking(int fd) { FLOCK(_LK_LOCK); }
57: static int lock_exclusive_blocking(int fd) { FLOCK(_LK_LOCK); }
58: static int lock_exclusive_nonblocking(int fd) { FLOCK(_LK_NBLCK); }
59: static int unlock(int fd) { FLOCK(_LK_UNLCK); }
1.93 paf 60:
1.99 paf 61: #else
62: #ifdef HAVE_FCNTL
1.93 paf 63:
1.126 paf 64: #define FLOCK(cmd, arg) struct flock ls={arg, SEEK_SET}; return fcntl(fd, cmd, &ls)
1.99 paf 65: static int lock_shared_blocking(int fd) { FLOCK(F_SETLKW, F_RDLCK); }
66: static int lock_exclusive_blocking(int fd) { FLOCK(F_SETLKW, F_WRLCK); }
67: static int lock_exclusive_nonblocking(int fd) { FLOCK(F_SETLK, F_RDLCK); }
68: static int unlock(int fd) { FLOCK(F_SETLK, F_UNLCK); }
1.93 paf 69:
70: #else
71: #ifdef HAVE_LOCKF
1.99 paf 72:
1.126 paf 73: #define FLOCK(fd, operation) lseek(fd, 0, SEEK_SET); return lockf(fd, operation, 1)
1.99 paf 74: static int lock_shared_blocking(int fd) { FLOCK(F_LOCK); } // on intel solaris man doesn't have doc on shared blocking
75: static int lock_exclusive_blocking(int fd) { FLOCK(F_LOCK); }
76: static int lock_exclusive_nonblocking(int fd) { FLOCK(F_TLOCK); }
77: static int unlock(int fd) { FLOCK(F_TLOCK); }
78:
1.93 paf 79: #else
1.99 paf 80:
81: #error unable to find file locking func
82:
83: #endif
1.93 paf 84: #endif
85: #endif
86: #endif
87:
1.127 paf 88: #define DEFAULT_USER_AGENT "parser3"
89:
90:
1.126 paf 91: void fix_line_breaks(char* buf, size_t& size) {
1.139 paf 92: if(size==0)
93: return;
94:
1.87 paf 95: //_asm int 3;
1.126 paf 96: const char* const eob=buf+size;
97: char* dest=buf;
1.72 parser 98: // fix DOS: \r\n -> \n
99: // fix Macintosh: \r -> \n
1.126 paf 100: char* bol=buf;
1.137 paf 101: while(char* eol=(char*)memchr(bol, '\r', eob -bol)) {
1.72 parser 102: size_t len=eol-bol;
103: if(dest!=bol)
1.126 paf 104: memcpy(dest, bol, len);
1.72 parser 105: dest+=len;
1.126 paf 106: *dest++='\n';
1.72 parser 107:
1.126 paf 108: if(&eol[1]<eob && eol[1]=='\n') { // \r, \n = DOS
1.72 parser 109: bol=eol+2;
1.126 paf 110: size--;
111: } else // \r, not \n = Macintosh
1.72 parser 112: bol=eol+1;
113: }
114: // last piece without \r, including terminating 0
115: if(dest!=bol)
1.126 paf 116: memcpy(dest, bol, eob-bol);
1.72 parser 117: }
1.18 paf 118:
1.126 paf 119: char* file_read_text(Pool& pool, const String& file_spec,
120: bool fail_on_read_problem,
1.140.2.3! paf 121: Hash* options, Request* r,
! 122: Hash** out_fields) {
1.72 parser 123: void *result; size_t size;
1.140.2.3! paf 124: return file_read(pool, file_spec, result, size, true, options, r, out_fields, fail_on_read_problem)?(char *)result:0;
1.126 paf 125: }
126:
127: //http request stuff
128: /* ************************ http stuff *********************** */
129:
130: static bool set_addr(struct sockaddr_in *addr, const char* host, const short port){
131: memset(addr, 0, sizeof(*addr));
132: addr->sin_family=AF_INET;
133: addr->sin_port=htons(port);
134: if(host) {
135: if(struct hostent *hostIP=gethostbyname(host))
136: memcpy(&addr->sin_addr, hostIP->h_addr, hostIP->h_length);
137: else
138: return false;
139: } else
140: addr->sin_addr.s_addr=INADDR_ANY;
141: return true;
142: }
143:
144: static void http_read_response(String& response, int sock){
1.130 paf 145: bool check_status=true;
146: ssize_t EOLat=0;
1.126 paf 147: while(true) {
148: char *buf=(char *)response.pool().malloc(MAX_STRING);
149: ssize_t size=recv(sock, buf, MAX_STRING, 0);
150: if(size<=0)
151: break;
1.130 paf 152: response.APPEND_TAINTED(buf, size, "remote HTTP server response", 0);
153: if(check_status && (EOLat=response.pos("\r\n", 2))>=0) { // checking status in first response
154: check_status=false;
155:
156: const String& status_line=response.mid(0, (size_t)EOLat);
157: Array astatus(response.pool());
158: size_t pos_after_ref=0; status_line.split(astatus, &pos_after_ref, " ", 1);
159: const String* status_code=astatus.get_string(1);
160:
161: if(!status_code || *status_code!="200")
162: throw Exception("http.status",
163: status_code,
164: "invalid HTTP response status");
165: }
1.126 paf 166: }
1.130 paf 167: if(check_status)
168: throw Exception("http.response",
169: 0,
170: "bad response from host - no status found (size=%lu)", response.size());
1.126 paf 171: }
172:
173: /* ********************** request *************************** */
174:
175: #if defined(SIGALRM) && defined(HAVE_SIGSETJMP) && defined(HAVE_SIGLONGJMP)
1.140.2.1 paf 176: # define PA_USE_ALARM
1.126 paf 177: #endif
178:
1.140.2.1 paf 179: #ifdef PA_USE_ALARM
1.126 paf 180: static sigjmp_buf timeout_env;
181: static void timeout_handler(int sig){
182: siglongjmp(timeout_env, 1);
183: }
184: #endif
185:
186: static void http_request(String& response,
187: const String *origin_string,
188: const char* host, int port,
189: const char* request,
1.140.2.2 paf 190: int timeout) {
1.126 paf 191: if(!host)
192: throw Exception("http.host",
193: origin_string,
194: "zero hostname"); //never
195:
1.140.2.1 paf 196: #ifdef PA_USE_ALARM
1.126 paf 197: signal(SIGALRM, timeout_handler);
198: #endif
199: int sock=-1;
1.140.2.1 paf 200: #ifdef PA_USE_ALARM
201: if(sigsetjmp(timeout_env, 1)) {
202: // stupid gcc [2.95.4] generated bad code
203: // which failed to handle sigsetjmp+throw: crashed inside of pre-throw code.
204: // rewritten simplier [though duplicating closesocket code]
205: if(sock>=0)
206: closesocket(sock);
207: throw Exception("http.timeout",
208: origin_string,
209: "timeout occured while retrieving document");
1.140.2.2 paf 210: } else
1.140.2.1 paf 211: alarm(timeout);
212: #endif
1.126 paf 213: try {
1.140.2.1 paf 214: struct sockaddr_in dest;
215:
216: if(!set_addr(&dest, host, port))
217: throw Exception("http.host",
1.126 paf 218: origin_string,
1.140.2.1 paf 219: "can not resolve hostname \"%s\"", host);
1.126 paf 220:
1.140.2.1 paf 221: if((sock=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP/*0*/))<0)
222: throw Exception("http.connect",
223: origin_string,
224: "can not make socket: %s (%d)", strerror(errno), errno);
225: if(connect(sock, (struct sockaddr *)&dest, sizeof(dest)))
226: throw Exception("http.connect",
227: origin_string,
228: "can not connect to host \"%s\": %s (%d)", host, strerror(errno), errno);
229: size_t request_size=strlen(request);
230: if(send(sock, request, request_size, 0)!=(ssize_t)request_size)
231: throw Exception("http.connect",
232: origin_string,
233: "error sending request: %s (%d)", strerror(errno), errno);
234:
235: http_read_response(response, sock);
236: closesocket(sock);
237: #ifdef PA_USE_ALARM
238: alarm(0);
1.126 paf 239: #endif
240: } catch(...) {
241: if(sock>=0)
242: closesocket(sock);
1.140.2.1 paf 243: #ifdef PA_USE_ALARM
1.126 paf 244: alarm(0);
245: #endif
246: /*re*/throw;
247: }
248: }
249:
1.127 paf 250: #ifndef DOXYGEN
251: struct Http_pass_header_info {
252: String* request;
253: bool user_agent_specified;
254: };
255: #endif
256: static void http_pass_header(const Hash::Key& key, Hash::Val *value, void *info)
1.126 paf 257: {
1.127 paf 258: Http_pass_header_info& i=*static_cast<Http_pass_header_info *>(info);
259: Pool& pool=i.request->pool();
260:
1.135 paf 261: *(i.request)<<key<<": "
1.136 paf 262: << attributed_meaning_to_string(*static_cast<Value *>(value), String::UL_HTTP_HEADER, false)
1.135 paf 263: <<"\n";
264:
1.127 paf 265: if(key.change_case(pool, String::CC_UPPER)=="USER-AGENT")
266: i.user_agent_specified=true;
1.126 paf 267: }
268: static void file_read_http(Pool& pool, const String& file_spec,
269: void*& data, size_t& data_size,
1.127 paf 270: Hash *options=0, Hash** out_fields=0) {
1.126 paf 271: char host[MAX_STRING];
1.129 paf 272: const char* uri;
1.126 paf 273: int port;
274: const char* method="GET";
275: int timeout=2;
1.127 paf 276: Value *vheaders=0;
1.126 paf 277:
1.133 paf 278: String connect_string(pool);
279: // not in ^sql{... UL_SQL ...} spirit, but closer to ^file::load one
280: connect_string.append(file_spec, String::UL_URI); // tainted pieces -> URI pieces
281:
282: char* connect_string_cstr=connect_string.cstr(String::UL_UNSPECIFIED);
283: if(strncmp(connect_string_cstr, "http://", 7)!=0)
1.126 paf 284: throw Exception(0,
1.133 paf 285: &connect_string,
1.126 paf 286: "does not start with http://"); //never
1.133 paf 287: connect_string_cstr+=7;
1.126 paf 288:
1.133 paf 289: strncpy(host, connect_string_cstr, sizeof(host)-1); host[sizeof(host)-1]=0;
1.126 paf 290: char* host_uri=lsplit(host, '/');
1.133 paf 291: uri=host_uri?connect_string_cstr+(host_uri-1-host):"/";
1.126 paf 292: char* port_cstr=lsplit(host, ':');
293: char* error_pos=0;
294: port=port_cstr?strtol(port_cstr, &error_pos, 0):80;
295:
1.127 paf 296: if(options) {
297: int valid_options=0;
298: if(Value *vmethod=static_cast<Value *>(options->get(*http_method_name))) {
299: valid_options++;
300: method=vmethod->as_string().cstr();
301: }
302: if(Value *vtimeout=static_cast<Value *>(options->get(*http_timeout_name))) {
303: valid_options++;
304: timeout=vtimeout->as_int();
305: }
306: if(vheaders=static_cast<Value *>(options->get(*http_headers_name))) {
307: valid_options++;
308: }
309: if(valid_options!=options->size())
310: throw Exception("parser.runtime",
311: 0,
312: "invalid option passed");
1.133 paf 313: }
1.126 paf 314:
315: //making request
1.127 paf 316: String request(pool);
1.126 paf 317: request<< method <<" "<< uri <<" HTTP/1.0\nHost: "<< host<<"\n";
1.127 paf 318: bool user_agent_specified=false;
319: if(vheaders && !vheaders->is_string()) { // allow empty
1.133 paf 320: if(Hash *headers=vheaders->get_hash(&connect_string)) {
1.127 paf 321: Http_pass_header_info info={&request};
322: headers->for_each(http_pass_header, &info);
323: user_agent_specified=info.user_agent_specified;
324: } else
325: throw Exception("parser.runtime",
1.133 paf 326: &connect_string,
1.127 paf 327: "headers param must be hash");
328: };
329: if(!user_agent_specified) // defaulting
330: request << "user-agent: " DEFAULT_USER_AGENT "\n";
1.126 paf 331: request<<"\n";
332:
333: //sending request
334: String response(pool);
335: http_request(response,
1.133 paf 336: &connect_string, host, port, request.cstr(String::UL_UNSPECIFIED), timeout);
1.126 paf 337:
338: //processing results
339: int pos=response.pos("\r\n\r\n", 4);
340: if(pos<1){
341: throw Exception("http.response",
1.133 paf 342: &connect_string,
1.126 paf 343: "bad response from host - no headers found");
344: }
345: String header_block=response.mid(0, pos);
346: String body=response.mid(pos+4, response.size());
347:
348: Array aheaders(pool);
349: Hash& headers=*new(pool) Hash(pool);
350: size_t pos_after_ref=0;
351: header_block.split(aheaders, &pos_after_ref, "\r\n", 2);
352:
353: //processing status code
354: const String *status_line=aheaders.get_string(0);
355: if(!status_line){
356: throw Exception("http.response",
1.133 paf 357: &connect_string,
1.126 paf 358: "bad response from host - no status line ");
359: }
360: //processing headers
361: for(int i=1;i<aheaders.size();i++) {
362: if(const String *line=aheaders.get_string(i)) {
363: pos=line->pos(": ", 2);
364: if(pos<1)
365: throw Exception("http.response",
1.133 paf 366: &connect_string,
1.126 paf 367: "bad response from host - bad header \"%s\"", line->cstr());
368:
1.128 paf 369: headers.put(
370: line->mid(0, pos).change_case(pool, String::CC_UPPER),
371: new(pool) VString(line->mid(pos+2, line->size())));
1.126 paf 372: } else
373: throw Exception("http.response",
1.133 paf 374: &connect_string,
1.126 paf 375: "bad response from host - bad headers \"%s\"", header_block.cstr());
376: }
377:
378: // output response
379: data=body.cstr(); data_size=body.size();
1.130 paf 380: if(out_fields)
1.126 paf 381: *out_fields=&headers;
1.34 paf 382: }
1.123 paf 383:
384: #ifndef DOXYGEN
385: struct File_read_action_info {
386: void **data; size_t *data_size;
1.140.2.3! paf 387: Hash* options; Request* r;
1.126 paf 388: };
1.123 paf 389: #endif
1.126 paf 390: static void file_read_action(Pool& pool,
391: struct stat& finfo,
1.123 paf 392: int f,
1.126 paf 393: const String& file_spec, const char* fname, bool as_text,
1.123 paf 394: void *context) {
1.126 paf 395: File_read_action_info& info=*static_cast<File_read_action_info *>(context);
1.123 paf 396:
1.140.2.3! paf 397: int offset=0;
! 398: int limit=-1;
! 399: if(info.options) {
! 400: int valid_options=0;
! 401: if(Value *voffset=(Value *)info.options->get(*sql_offset_name)) {
! 402: valid_options++;
! 403: offset=info.r->process_to_value(*voffset).as_int();
! 404: }
! 405: if(Value *vlimit=(Value *)info.options->get(*sql_limit_name)) {
! 406: valid_options++;
! 407: limit=info.r->process_to_value(*vlimit).as_int();
! 408: }
! 409: if(valid_options!=info.options->size())
! 410: throw Exception("parser.runtime",
! 411: &file_spec,
! 412: "load called with invalid option");
! 413: }
! 414:
! 415: int m=finfo.st_size;
! 416: if(offset<0 || offset>=m || limit==0) { // bad options/empty file: returning empty file
1.123 paf 417: if(as_text) {
1.126 paf 418: *info.data=pool.malloc(1);
1.123 paf 419: *(char*)(*info.data)=0;
420: } else
421: *info.data=0;
422: *info.data_size=0;
423: return;
424: }
1.140.2.3! paf 425: if(limit<0 || limit>m)
! 426: limit=m;
! 427:
! 428: if(offset)
! 429: if(lseek(f, offset, SEEK_SET)<0)
! 430: throw Exception("file.read",
! 431: &file_spec,
! 432: "seek to %d position failed: %s (%d), actual filename '%s'",
! 433: offset, strerror(errno), errno, fname);
! 434:
! 435: *info.data=pool.malloc(limit+(as_text?1:0), 3);
! 436: *info.data_size=(size_t)read(f, *info.data, limit);
! 437:
! 438: if((ssize_t)*info.data_size<0)
! 439: throw Exception(0,
! 440: &file_spec,
! 441: "failed to read %d bytes from %d offset of %d size (stat)", // may be in text mode...
! 442: limit, offset, m);
1.126 paf 443: }
444: bool file_read(Pool& pool, const String& file_spec,
445: void*& data, size_t& data_size,
1.140.2.3! paf 446: bool as_text,
! 447: Hash* options, Request* r,
! 448: Hash** out_fields,
1.126 paf 449: bool fail_on_read_problem) {
450: bool result;
451: if(file_spec.starts_with("http://", 7)) {
452: // fail on read problem
1.140.2.3! paf 453: file_read_http(pool, file_spec, data, data_size, options, out_fields);
1.126 paf 454: result=true;
455: } else {
1.140.2.3! paf 456: File_read_action_info info={&data, &data_size, options, r};
1.126 paf 457: result=file_read_action_under_lock(pool, file_spec,
458: "read", file_read_action, &info,
459: as_text, fail_on_read_problem);
460: }
1.123 paf 461:
1.126 paf 462: if(result && as_text) {
1.131 paf 463: // UTF-8 signature: EF BB BF
464: if(data_size>=3) {
465: char *in=(char *)data;
466: if((in[0] == '\xEF') && (in[1] == '\xBB') &&
467: (in[2] == '\xBF')) {
468: data=in+3; data_size-=3;// skip prefix
469: }
470: }
471:
1.126 paf 472: fix_line_breaks((char *)(data), data_size);
1.123 paf 473: // note: after fixing
1.126 paf 474: ((char*&)(data))[data_size]=0;
1.123 paf 475: }
1.126 paf 476:
477: return result;
1.123 paf 478: }
479:
480: bool file_read_action_under_lock(Pool& pool, const String& file_spec,
1.126 paf 481: const char* action_name, File_read_action action, void *context,
1.140.2.3! paf 482: bool as_text, bool fail_on_read_problem) {
1.126 paf 483: const char* fname=file_spec.cstr(String::UL_FILE_SPEC);
1.33 paf 484: int f;
485:
486: // first open, next stat:
1.45 paf 487: // directory update of NTFS hard links performed on open.
1.33 paf 488: // ex:
489: // a.html:^test[] and b.html hardlink to a.html
490: // user inserts ! before ^test in a.html
1.126 paf 491: // directory entry of b.html in NTFS not updated at once,
1.35 paf 492: // they delay update till open, so we would receive "!^test[" string
493: // if would do stat, next open.
1.123 paf 494: // later: it seems, even this does not help sometimes
1.98 paf 495: if((f=open(fname, O_RDONLY|(as_text?_O_TEXT:_O_BINARY)))>=0) {
1.123 paf 496: try {
497: if(lock_shared_blocking(f)!=0)
1.126 paf 498: throw Exception("file.lock",
1.123 paf 499: &file_spec,
500: "shared lock failed: %s (%d), actual filename '%s'",
1.126 paf 501: strerror(errno), errno, fname);
1.123 paf 502:
1.124 paf 503: struct stat finfo;
504: if(stat(fname, &finfo)!=0)
505: throw Exception("file.missing", // hardly possible: we just opened it OK
506: &file_spec,
507: "stat failed: %s (%d), actual filename '%s'",
1.126 paf 508: strerror(errno), errno, fname);
1.124 paf 509:
1.140 paf 510: #ifdef PA_SAFE_MODE
511: if(finfo.st_uid/*foreign?*/!=geteuid()
512: && finfo.st_gid/*foreign?*/!=getegid())
1.126 paf 513: throw Exception("parser.runtime",
514: &file_spec,
1.140 paf 515: "parser is in safe mode: reading files of foreign group and user disabled [recompile parser with --disable-safe-mode configure option], actual filename '%s'",
1.126 paf 516: fname);
1.105 paf 517: #endif
1.32 paf 518:
1.126 paf 519: action(pool, finfo, f, file_spec, fname, as_text, context);
1.123 paf 520: } catch(...) {
1.126 paf 521: unlock(f);close(f);
1.123 paf 522: if(fail_on_read_problem)
523: /*re*/throw;
524: return false;
525: }
1.87 paf 526:
1.126 paf 527: unlock(f);close(f);
1.72 parser 528: return true;
1.118 paf 529: } else {
530: if(fail_on_read_problem)
1.126 paf 531: throw Exception(errno==EACCES?"file.access":errno==ENOENT?"file.missing":0,
1.118 paf 532: &file_spec,
1.123 paf 533: "%s failed: %s (%d), actual filename '%s'",
1.126 paf 534: action_name, strerror(errno), errno, fname);
1.118 paf 535: return false;
536: }
1.8 paf 537: }
538:
1.63 parser 539: static void create_dir_for_file(const String& file_spec) {
540: size_t pos_after=1;
541: int pos_before;
542: while((pos_before=file_spec.pos("/", 1, pos_after))>=0) {
1.126 paf 543: mkdir(file_spec.mid(0, pos_before).cstr(String::UL_FILE_SPEC), 0775);
1.63 parser 544: pos_after=pos_before+1;
545: }
546: }
547:
1.98 paf 548: bool file_write_action_under_lock(
1.28 paf 549: const String& file_spec,
1.126 paf 550: const char* action_name, File_write_action action, void *context,
551: bool as_text,
552: bool do_append,
553: bool do_block,
1.110 paf 554: bool fail_on_lock_problem) {
1.126 paf 555: const char* fname=file_spec.cstr(String::UL_FILE_SPEC);
1.28 paf 556: int f;
1.80 paf 557: if(access(fname, W_OK)!=0) // no
1.126 paf 558: create_dir_for_file(file_spec);
1.50 paf 559:
1.80 paf 560: if((f=open(fname,
561: O_CREAT|O_RDWR
562: |(as_text?_O_TEXT:_O_BINARY)
1.138 paf 563: |(do_append?O_APPEND:PA_O_TRUNC), 0664))>=0) {
1.99 paf 564: if((do_block?lock_exclusive_blocking(f):lock_exclusive_nonblocking(f))!=0) {
1.126 paf 565: Exception e("file.lock",
1.110 paf 566: &file_spec,
567: "shared lock failed: %s (%d), actual filename '%s'",
1.126 paf 568: strerror(errno), errno, fname);
569: close(f);
1.110 paf 570: if(fail_on_lock_problem)
571: throw e;
1.98 paf 572: return false;
573: }
1.96 paf 574:
575: try {
1.126 paf 576: action(f, context);
1.96 paf 577: } catch(...) {
1.138 paf 578: #ifdef HAVE_FTRUNCATE
1.104 paf 579: if(!do_append)
1.125 paf 580: ftruncate(f, lseek(f, 0, SEEK_CUR)); // one can not use O_TRUNC, read lower
1.138 paf 581: #endif
1.126 paf 582: unlock(f);close(f);
1.96 paf 583: /*re*/throw;
584: }
1.80 paf 585:
1.138 paf 586: #ifdef HAVE_FTRUNCATE
1.104 paf 587: if(!do_append)
1.125 paf 588: ftruncate(f, lseek(f, 0, SEEK_CUR)); // O_TRUNC truncates even exclusevely write-locked file [thanks to Igor Milyakov <virtan@rotabanner.com> for discovering]
1.138 paf 589: #endif
1.126 paf 590: unlock(f);close(f);
1.98 paf 591: return true;
1.80 paf 592: } else
1.126 paf 593: throw Exception(errno==EACCES?"file.access":0,
1.80 paf 594: &file_spec,
1.96 paf 595: "%s failed: %s (%d), actual filename '%s'",
1.126 paf 596: action_name, strerror(errno), errno, fname);
1.96 paf 597: // here should be nothing, see rethrow above
598: }
599:
600: #ifndef DOXYGEN
601: struct File_write_action_info {
602: const void *data; size_t size;
1.126 paf 603: };
1.96 paf 604: #endif
605: static void file_write_action(int f, void *context) {
1.126 paf 606: File_write_action_info& info=*static_cast<File_write_action_info *>(context);
1.113 paf 607: if(info.size) {
1.126 paf 608: int written=write(f, info.data, info.size);
1.116 paf 609: if(written<0)
1.126 paf 610: throw Exception(0,
611: 0,
612: "write failed: %s (%d)", strerror(errno), errno);
1.113 paf 613: }
1.96 paf 614: }
615: void file_write(
616: const String& file_spec,
617: const void *data, size_t size,
1.126 paf 618: bool as_text,
1.96 paf 619: bool do_append) {
1.126 paf 620: File_write_action_info info={data, size};
1.98 paf 621: file_write_action_under_lock(
1.96 paf 622: file_spec,
1.126 paf 623: "write", file_write_action, &info,
624: as_text,
625: do_append);
1.30 paf 626: }
627:
1.63 parser 628: // throws nothing! [this is required in file_move & file_delete]
1.50 paf 629: static void rmdir(const String& file_spec, size_t pos_after) {
630: int pos_before;
631: if((pos_before=file_spec.pos("/", 1, pos_after))>=0)
1.126 paf 632: rmdir(file_spec, pos_before+1);
1.50 paf 633:
1.126 paf 634: rmdir(file_spec.mid(0, pos_after-1/* / */).cstr(String::UL_FILE_SPEC));
1.50 paf 635: }
1.95 paf 636: bool file_delete(const String& file_spec, bool fail_on_read_problem) {
1.126 paf 637: const char* fname=file_spec.cstr(String::UL_FILE_SPEC);
1.54 parser 638: if(unlink(fname)!=0)
1.93 paf 639: if(fail_on_read_problem)
1.126 paf 640: throw Exception(errno==EACCES?"file.access":errno==ENOENT?"file.missing":0,
1.93 paf 641: &file_spec,
642: "unlink failed: %s (%d), actual filename '%s'",
1.126 paf 643: strerror(errno), errno, fname);
1.93 paf 644: else
645: return false;
1.50 paf 646:
1.126 paf 647: rmdir(file_spec, 1);
1.93 paf 648: return true;
1.60 parser 649: }
1.95 paf 650: void file_move(const String& old_spec, const String& new_spec) {
1.126 paf 651: const char* old_spec_cstr=old_spec.cstr(String::UL_FILE_SPEC);
652: const char* new_spec_cstr=new_spec.cstr(String::UL_FILE_SPEC);
1.63 parser 653:
1.126 paf 654: create_dir_for_file(new_spec);
1.63 parser 655:
1.60 parser 656: if(rename(old_spec_cstr, new_spec_cstr)!=0)
1.126 paf 657: throw Exception(errno==EACCES?"file.access":errno==ENOENT?"file.missing":0,
1.60 parser 658: &old_spec,
659: "rename failed: %s (%d), actual filename '%s' to '%s'",
1.126 paf 660: strerror(errno), errno, old_spec_cstr, new_spec_cstr);
1.63 parser 661:
1.126 paf 662: rmdir(old_spec, 1);
1.31 paf 663: }
664:
1.51 paf 665:
1.126 paf 666: bool entry_exists(const char* fname, struct stat *afinfo) {
1.118 paf 667: struct stat lfinfo;
668: bool result=stat(fname, &lfinfo)==0;
669: if(afinfo)
670: *afinfo=lfinfo;
671: return result;
1.119 paf 672: }
673:
674: bool entry_exists(const String& file_spec) {
1.126 paf 675: const char* fname=file_spec.cstr(String::UL_FILE_SPEC);
676: return entry_exists(fname, 0);
1.118 paf 677: }
678:
1.51 paf 679: static bool entry_readable(const String& file_spec, bool need_dir) {
1.126 paf 680: char* fname=file_spec.cstr(String::UL_FILE_SPEC);
1.120 paf 681: if(need_dir) {
1.126 paf 682: size_t size=strlen(fname);
1.120 paf 683: while(size) {
1.126 paf 684: char c=fname[size-1];
1.120 paf 685: if(c=='/' || c=='\\')
686: fname[--size]=0;
687: else
688: break;
689: }
690: }
1.51 paf 691: struct stat finfo;
1.118 paf 692: if(access(fname, R_OK)==0 && entry_exists(fname, &finfo)) {
1.109 paf 693: bool is_dir=(finfo.st_mode&S_IFDIR) != 0;
1.51 paf 694: return is_dir==need_dir;
695: }
696: return false;
697: }
1.31 paf 698: bool file_readable(const String& file_spec) {
1.126 paf 699: return entry_readable(file_spec, false);
1.51 paf 700: }
701: bool dir_readable(const String& file_spec) {
1.126 paf 702: return entry_readable(file_spec, true);
1.65 parser 703: }
704: String *file_readable(const String& path, const String& name) {
1.126 paf 705: String *result=new(path.pool()) String(path);
706: *result << "/";
1.65 parser 707: *result << name;
708: return file_readable(*result)?result:0;
1.43 paf 709: }
710: bool file_executable(const String& file_spec) {
1.64 parser 711: return access(file_spec.cstr(String::UL_FILE_SPEC), X_OK)==0;
1.44 paf 712: }
713:
1.64 parser 714: bool file_stat(const String& file_spec,
1.58 parser 715: size_t& rsize,
1.126 paf 716: time_t& ratime,
717: time_t& rmtime,
718: time_t& rctime,
1.64 parser 719: bool fail_on_read_problem) {
1.126 paf 720: Pool& pool=file_spec.pool();
721: const char* fname=file_spec.cstr(String::UL_FILE_SPEC);
1.44 paf 722: struct stat finfo;
723: if(stat(fname, &finfo)!=0)
1.64 parser 724: if(fail_on_read_problem)
1.126 paf 725: throw Exception("file.missing",
1.67 parser 726: &file_spec,
727: "getting file size failed: %s (%d), real filename '%s'",
1.126 paf 728: strerror(errno), errno, fname);
1.64 parser 729: else
730: return false;
1.58 parser 731: rsize=finfo.st_size;
732: ratime=finfo.st_atime;
733: rmtime=finfo.st_mtime;
734: rctime=finfo.st_ctime;
1.64 parser 735: return true;
1.18 paf 736: }
737:
1.126 paf 738: char* getrow(char* *row_ref, char delim) {
739: char* result=*row_ref;
1.8 paf 740: if(result) {
1.126 paf 741: *row_ref=strchr(result, delim);
1.8 paf 742: if(*row_ref)
743: *((*row_ref)++)=0;
744: else if(!*result)
745: return 0;
746: }
747: return result;
748: }
749:
1.126 paf 750: char* lsplit(char* string, char delim) {
1.23 paf 751: if(string) {
1.126 paf 752: char* v=strchr(string, delim);
1.8 paf 753: if(v) {
754: *v=0;
755: return v+1;
756: }
757: }
758: return 0;
759: }
760:
1.126 paf 761: char* lsplit(char* *string_ref, char delim) {
762: char* result=*string_ref;
763: char* next=lsplit(*string_ref, delim);
1.8 paf 764: *string_ref=next;
765: return result;
1.9 paf 766: }
767:
1.126 paf 768: char* rsplit(char* string, char delim) {
1.18 paf 769: if(string) {
1.126 paf 770: char* v=strrchr(string, delim);
1.18 paf 771: if(v) {
1.9 paf 772: *v=0;
773: return v+1;
774: }
775: }
776: return NULL;
1.10 paf 777: }
778:
1.37 paf 779: /// @todo less stupid type detection
1.126 paf 780: char* format(Pool& pool, double value, char* fmt) {
781: char local_buf[MAX_NUMBER];
1.108 paf 782: size_t size;
783:
1.10 paf 784: if(fmt)
785: if(strpbrk(fmt, "diouxX"))
786: if(strpbrk(fmt, "ouxX"))
1.126 paf 787: size=snprintf(local_buf, sizeof(local_buf), fmt, (uint)value);
1.10 paf 788: else
1.126 paf 789: size=snprintf(local_buf, sizeof(local_buf), fmt, (int)value);
1.10 paf 790: else
1.126 paf 791: size=snprintf(local_buf, sizeof(local_buf), fmt, value);
1.10 paf 792: else
1.126 paf 793: size=snprintf(local_buf, sizeof(local_buf), "%d", (int)value);
1.10 paf 794:
1.126 paf 795: char* pool_buf=(char *)pool.malloc(size+1, 4);
796: memcpy(pool_buf, local_buf, size+1);
1.108 paf 797: return pool_buf;
1.12 paf 798: }
799:
1.36 paf 800: size_t stdout_write(const void *buf, size_t size) {
1.12 paf 801: #ifdef WIN32
802: do{
1.126 paf 803: int chunk_written=fwrite(buf, 1, min(8*0x400, size), stdout);
1.12 paf 804: if(chunk_written<=0)
805: break;
806: size-=chunk_written;
1.36 paf 807: buf=((const char*)buf)+chunk_written;
1.126 paf 808: } while(size>0);
1.12 paf 809:
810: return size;
811: #else
1.126 paf 812: return fwrite(buf, 1, size, stdout);
1.12 paf 813: #endif
1.2 paf 814: }
1.14 paf 815:
1.126 paf 816: char* unescape_chars(Pool& pool, const char* cp, int len) {
817: char* s=(char *)pool.malloc(len + 1, 5);
1.14 paf 818: enum EscapeState {
1.33 paf 819: EscapeRest,
820: EscapeFirst,
1.14 paf 821: EscapeSecond
822: } escapeState=EscapeRest;
823: int escapedValue=0;
824: int srcPos=0;
825: int dstPos=0;
826: while(srcPos < len) {
1.126 paf 827: int ch=cp[srcPos];
1.14 paf 828: switch(escapeState) {
829: case EscapeRest:
830: if(ch=='%') {
831: escapeState=EscapeFirst;
832: } else if(ch=='+') {
1.126 paf 833: s[dstPos++]=' ';
1.14 paf 834: } else {
835: s[dstPos++]=ch;
836: }
837: break;
838: case EscapeFirst:
839: escapedValue=hex_value[ch] << 4;
840: escapeState=EscapeSecond;
841: break;
842: case EscapeSecond:
1.126 paf 843: escapedValue +=hex_value[ch];
1.14 paf 844: s[dstPos++]=escapedValue;
845: escapeState=EscapeRest;
846: break;
847: }
1.126 paf 848: srcPos++;
1.14 paf 849: }
850: s[dstPos]=0;
851: return s;
1.24 paf 852: }
853:
854: #ifdef WIN32
1.126 paf 855: void back_slashes_to_slashes(char* s) {
1.24 paf 856: if(s)
857: for(; *s; s++)
858: if(*s=='\\')
1.126 paf 859: *s='/';
1.24 paf 860: }
1.42 paf 861: /*
1.126 paf 862: void slashes_to_back_slashes(char* s) {
1.42 paf 863: if(s)
864: for(; *s; s++)
865: if(*s=='/')
1.126 paf 866: *s='\\';
1.42 paf 867: }
868: */
1.24 paf 869: #endif
1.41 paf 870:
1.126 paf 871: bool StrEqNc(const char* s1, const char* s2, bool strict) {
1.41 paf 872: while(true) {
873: if(!(*s1)) {
874: if(!(*s2))
875: return true;
876: else
877: return !strict;
878: } else if(!(*s2))
879: return !strict;
880: if(isalpha(*s1)) {
881: if(tolower(*s1) !=tolower(*s2))
882: return false;
883: } else if((*s1) !=(*s2))
884: return false;
1.126 paf 885: s1++;
886: s2++;
1.41 paf 887: }
1.57 parser 888: }
889:
1.84 paf 890: static bool isLeap(int year) {
1.57 parser 891: return !(
892: (year % 4) || ((year % 400) && !(year % 100))
1.126 paf 893: );
1.57 parser 894: }
895:
896: int getMonthDays(int year, int month) {
897: int monthDays[]={
1.126 paf 898: 31,
899: isLeap(year) ? 29 : 28,
900: 31,
901: 30,
902: 31,
903: 30,
904: 31,
905: 31,
906: 30,
907: 31,
908: 30,
1.57 parser 909: 31
1.126 paf 910: };
911: return monthDays[month];
1.41 paf 912: }
1.69 parser 913:
1.126 paf 914: void remove_crlf(char* start, char* end) {
915: for(char* p=start; p<end; p++)
1.69 parser 916: switch(*p) {
1.126 paf 917: case '\n': *p='|'; break;
918: case '\r': *p=' '; break;
1.69 parser 919: }
1.91 paf 920: }
921:
922:
923: /// must be last in this file
924: #undef vsnprintf
1.126 paf 925: int __vsnprintf(char* b, size_t s, const char* f, va_list l) {
1.91 paf 926: if(!s)
927: return 0;
928:
929: int r;
930: // note: on win32& maybe somewhere else
931: // vsnprintf do not writes terminating 0 in 'buffer full' case, reducing
932: --s;
933: #if _MSC_VER
934: /*
935: win32:
936: mk:@MSITStore:C:\Program%20Files\Microsoft%20Visual%20Studio\MSDN\2001APR\1033\vccore.chm::/html/_crt__vsnprintf.2c_._vsnwprintf.htm
937:
938: if the number of bytes to write exceeds buffer, then count bytes are written and –1 is returned
939: */
1.126 paf 940: r=_vsnprintf(b, s, f, l);
1.91 paf 941: if(r<0)
942: r=s;
943: #else
1.126 paf 944: r=vsnprintf(b, s, f, l);
1.91 paf 945: /*
946: solaris:
947: man vsnprintf
948:
949: The snprintf() function returns the number of characters
950: formatted, that is, the number of characters that would have
951: been written to the buffer if it were large enough. If the
952: value of n is 0 on a call to snprintf(), an unspecified
953: value less than 1 is returned.
954: */
955:
956: if(r<0)
957: r=0;
958: else if(r>s)
959: r=s;
960: #endif
961: b[r]=0;
962: return r;
963: }
964:
1.126 paf 965: int __snprintf(char* b, size_t s, const char* f, ...) {
1.91 paf 966: va_list l;
1.126 paf 967: va_start(l, f);
968: int r=__vsnprintf(b, s, f, l);
969: va_end(l);
1.91 paf 970: return r;
1.98 paf 971: }
972:
973: int pa_sleep(unsigned long secs, unsigned long usecs) {
1.126 paf 974: for (; usecs >= 1000000; ++secs, usecs -= 1000000);
1.98 paf 975:
976: #ifdef WIN32
1.126 paf 977: Sleep(secs * 1000 + usecs / 1000);
1.98 paf 978: return 0;
979: #else
980: struct timeval t;
981: t.tv_sec = secs;
982: t.tv_usec = usecs;
1.126 paf 983: return (select(0, NULL, NULL, NULL, &t) == -1 ? errno : 0);
1.98 paf 984: #endif
1.135 paf 985: }
986:
987:
988: // attributed meaning
989:
990: static size_t date_attribute(const VDate& vdate, char *buf, size_t buf_size) {
991: const char month_names[12][4]={
992: "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
993: const char days[7][4]={
994: "Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
995:
996: time_t when=vdate.get_time();
997: struct tm *tms=gmtime(&when);
998: if(!tms)
999: throw Exception(0,
1000: 0,
1001: "bad time in attribute value (seconds from epoch=%ld)", when);
1002: return snprintf(buf, MAX_STRING, "%s, %.2d-%s-%.4d %.2d:%.2d:%.2d GMT",
1003: days[tms->tm_wday],
1004: tms->tm_mday,month_names[tms->tm_mon],tms->tm_year+1900,
1005: tms->tm_hour,tms->tm_min,tms->tm_sec);
1006: }
1007: static void append_attribute_meaning(String& result,
1.136 paf 1008: Value& value, String::Untaint_lang lang, bool forced) {
1.135 paf 1009: if(const String *string=value.get_string())
1.136 paf 1010: result.append(string->join_chains(result.pool(), 0), lang, forced);
1.135 paf 1011: else
1012: if(Value *vdate=value.as(VDATE_TYPE, false)) {
1013: char *buf=(char *)result.malloc(MAX_STRING);
1014: size_t size=date_attribute(*static_cast<VDate *>(vdate),
1015: buf, MAX_STRING);
1016:
1017: result.APPEND_CLEAN(buf, size, "converted from date", 0);
1018: } else
1019: throw Exception("parser.runtime",
1020: &result,
1021: "trying to append here neither string nor date (%s)",
1022: value.type());
1023: }
1024: #ifndef DOXYGEN
1025: struct Attributed_meaning_info {
1026: String *header; // header line being constructed
1027: String::Untaint_lang lang; // language in which to append to that line
1.136 paf 1028: bool forced; // do they force that lang?
1.135 paf 1029: };
1030: #endif
1031: static void append_attribute_subattribute(const Hash::Key& akey, Hash::Val *avalue,
1032: void *info) {
1033: if(akey==VALUE_NAME)
1034: return;
1035:
1036: Attributed_meaning_info& ami=*static_cast<Attributed_meaning_info *>(info);
1037:
1038: // ...; charset=windows1251
1039: *ami.header << "; ";
1.136 paf 1040: ami.header->append(akey, ami.lang, ami.forced);
1.135 paf 1041: *ami.header << "=";
1.136 paf 1042: append_attribute_meaning(*ami.header, *static_cast<Value *>(avalue), ami.lang, ami.forced);
1.135 paf 1043: }
1044: const String& attributed_meaning_to_string(Value& meaning,
1.136 paf 1045: String::Untaint_lang lang, bool forced) {
1.135 paf 1046: String &result=*new(meaning.pool()) String(meaning.pool());
1047: if(Hash *hash=meaning.get_hash(0)) {
1048: // $value(value) $subattribute(subattribute value)
1049: if(Value *value=static_cast<Value *>(hash->get(*value_name)))
1.136 paf 1050: append_attribute_meaning(result, *value, lang, forced);
1.135 paf 1051:
1052: Attributed_meaning_info attributed_meaning_info={&result, lang};
1053: hash->for_each(append_attribute_subattribute, &attributed_meaning_info);
1054: } else // result value
1.136 paf 1055: append_attribute_meaning(result, meaning, lang, forced);
1.135 paf 1056:
1057: return result;
1.74 parser 1058: }
E-mail: