vdr
1.7.27
|
00001 /* 00002 * tools.c: Various tools 00003 * 00004 * See the main source file 'vdr.c' for copyright information and 00005 * how to reach the author. 00006 * 00007 * $Id: tools.c 2.22 2012/02/18 15:30:35 kls Exp $ 00008 */ 00009 00010 #include "tools.h" 00011 #include <ctype.h> 00012 #include <dirent.h> 00013 #include <errno.h> 00014 extern "C" { 00015 #ifdef boolean 00016 #define HAVE_BOOLEAN 00017 #endif 00018 #include <jpeglib.h> 00019 #undef boolean 00020 } 00021 #include <stdlib.h> 00022 #include <sys/time.h> 00023 #include <sys/vfs.h> 00024 #include <time.h> 00025 #include <unistd.h> 00026 #include <utime.h> 00027 #include "i18n.h" 00028 #include "thread.h" 00029 00030 int SysLogLevel = 3; 00031 00032 #define MAXSYSLOGBUF 256 00033 00034 void syslog_with_tid(int priority, const char *format, ...) 00035 { 00036 va_list ap; 00037 char fmt[MAXSYSLOGBUF]; 00038 snprintf(fmt, sizeof(fmt), "[%d] %s", cThread::ThreadId(), format); 00039 va_start(ap, format); 00040 vsyslog(priority, fmt, ap); 00041 va_end(ap); 00042 } 00043 00044 int BCD2INT(int x) 00045 { 00046 return ((1000000 * BCDCHARTOINT((x >> 24) & 0xFF)) + 00047 (10000 * BCDCHARTOINT((x >> 16) & 0xFF)) + 00048 (100 * BCDCHARTOINT((x >> 8) & 0xFF)) + 00049 BCDCHARTOINT( x & 0xFF)); 00050 } 00051 00052 ssize_t safe_read(int filedes, void *buffer, size_t size) 00053 { 00054 for (;;) { 00055 ssize_t p = read(filedes, buffer, size); 00056 if (p < 0 && errno == EINTR) { 00057 dsyslog("EINTR while reading from file handle %d - retrying", filedes); 00058 continue; 00059 } 00060 return p; 00061 } 00062 } 00063 00064 ssize_t safe_write(int filedes, const void *buffer, size_t size) 00065 { 00066 ssize_t p = 0; 00067 ssize_t written = size; 00068 const unsigned char *ptr = (const unsigned char *)buffer; 00069 while (size > 0) { 00070 p = write(filedes, ptr, size); 00071 if (p < 0) { 00072 if (errno == EINTR) { 00073 dsyslog("EINTR while writing to file handle %d - retrying", filedes); 00074 continue; 00075 } 00076 break; 00077 } 00078 ptr += p; 00079 size -= p; 00080 } 00081 return p < 0 ? p : written; 00082 } 00083 00084 void writechar(int filedes, char c) 00085 { 00086 safe_write(filedes, &c, sizeof(c)); 00087 } 00088 00089 int WriteAllOrNothing(int fd, const uchar *Data, int Length, int TimeoutMs, int RetryMs) 00090 { 00091 int written = 0; 00092 while (Length > 0) { 00093 int w = write(fd, Data + written, Length); 00094 if (w > 0) { 00095 Length -= w; 00096 written += w; 00097 } 00098 else if (written > 0 && !FATALERRNO) { 00099 // we've started writing, so we must finish it! 00100 cTimeMs t; 00101 cPoller Poller(fd, true); 00102 Poller.Poll(RetryMs); 00103 if (TimeoutMs > 0 && (TimeoutMs -= t.Elapsed()) <= 0) 00104 break; 00105 } 00106 else 00107 // nothing written yet (or fatal error), so we can just return the error code: 00108 return w; 00109 } 00110 return written; 00111 } 00112 00113 char *strcpyrealloc(char *dest, const char *src) 00114 { 00115 if (src) { 00116 int l = max(dest ? strlen(dest) : 0, strlen(src)) + 1; // don't let the block get smaller! 00117 dest = (char *)realloc(dest, l); 00118 if (dest) 00119 strcpy(dest, src); 00120 else 00121 esyslog("ERROR: out of memory"); 00122 } 00123 else { 00124 free(dest); 00125 dest = NULL; 00126 } 00127 return dest; 00128 } 00129 00130 char *strn0cpy(char *dest, const char *src, size_t n) 00131 { 00132 char *s = dest; 00133 for ( ; --n && (*dest = *src) != 0; dest++, src++) ; 00134 *dest = 0; 00135 return s; 00136 } 00137 00138 char *strreplace(char *s, char c1, char c2) 00139 { 00140 if (s) { 00141 char *p = s; 00142 while (*p) { 00143 if (*p == c1) 00144 *p = c2; 00145 p++; 00146 } 00147 } 00148 return s; 00149 } 00150 00151 char *strreplace(char *s, const char *s1, const char *s2) 00152 { 00153 char *p = strstr(s, s1); 00154 if (p) { 00155 int of = p - s; 00156 int l = strlen(s); 00157 int l1 = strlen(s1); 00158 int l2 = strlen(s2); 00159 if (l2 > l1) { 00160 if (char *NewBuffer = (char *)realloc(s, l + l2 - l1 + 1)) 00161 s = NewBuffer; 00162 else { 00163 esyslog("ERROR: out of memory"); 00164 return s; 00165 } 00166 } 00167 char *sof = s + of; 00168 if (l2 != l1) 00169 memmove(sof + l2, sof + l1, l - of - l1 + 1); 00170 strncpy(sof, s2, l2); 00171 } 00172 return s; 00173 } 00174 00175 char *stripspace(char *s) 00176 { 00177 if (s && *s) { 00178 for (char *p = s + strlen(s) - 1; p >= s; p--) { 00179 if (!isspace(*p)) 00180 break; 00181 *p = 0; 00182 } 00183 } 00184 return s; 00185 } 00186 00187 char *compactspace(char *s) 00188 { 00189 if (s && *s) { 00190 char *t = stripspace(skipspace(s)); 00191 char *p = t; 00192 while (p && *p) { 00193 char *q = skipspace(p); 00194 if (q - p > 1) 00195 memmove(p + 1, q, strlen(q) + 1); 00196 p++; 00197 } 00198 if (t != s) 00199 memmove(s, t, strlen(t) + 1); 00200 } 00201 return s; 00202 } 00203 00204 cString strescape(const char *s, const char *chars) 00205 { 00206 char *buffer; 00207 const char *p = s; 00208 char *t = NULL; 00209 while (*p) { 00210 if (strchr(chars, *p)) { 00211 if (!t) { 00212 buffer = MALLOC(char, 2 * strlen(s) + 1); 00213 t = buffer + (p - s); 00214 s = strcpy(buffer, s); 00215 } 00216 *t++ = '\\'; 00217 } 00218 if (t) 00219 *t++ = *p; 00220 p++; 00221 } 00222 if (t) 00223 *t = 0; 00224 return cString(s, t != NULL); 00225 } 00226 00227 bool startswith(const char *s, const char *p) 00228 { 00229 while (*p) { 00230 if (*p++ != *s++) 00231 return false; 00232 } 00233 return true; 00234 } 00235 00236 bool endswith(const char *s, const char *p) 00237 { 00238 const char *se = s + strlen(s) - 1; 00239 const char *pe = p + strlen(p) - 1; 00240 while (pe >= p) { 00241 if (*pe-- != *se-- || (se < s && pe >= p)) 00242 return false; 00243 } 00244 return true; 00245 } 00246 00247 bool isempty(const char *s) 00248 { 00249 return !(s && *skipspace(s)); 00250 } 00251 00252 int numdigits(int n) 00253 { 00254 int res = 1; 00255 while (n >= 10) { 00256 n /= 10; 00257 res++; 00258 } 00259 return res; 00260 } 00261 00262 bool isnumber(const char *s) 00263 { 00264 if (!s || !*s) 00265 return false; 00266 do { 00267 if (!isdigit(*s)) 00268 return false; 00269 } while (*++s); 00270 return true; 00271 } 00272 00273 int64_t StrToNum(const char *s) 00274 { 00275 char *t = NULL; 00276 int64_t n = strtoll(s, &t, 10); 00277 if (t) { 00278 switch (*t) { 00279 case 'T': n *= 1024; 00280 case 'G': n *= 1024; 00281 case 'M': n *= 1024; 00282 case 'K': n *= 1024; 00283 } 00284 } 00285 return n; 00286 } 00287 00288 cString AddDirectory(const char *DirName, const char *FileName) 00289 { 00290 return cString::sprintf("%s/%s", DirName && *DirName ? DirName : ".", FileName); 00291 } 00292 00293 cString itoa(int n) 00294 { 00295 char buf[16]; 00296 snprintf(buf, sizeof(buf), "%d", n); 00297 return buf; 00298 } 00299 00300 bool EntriesOnSameFileSystem(const char *File1, const char *File2) 00301 { 00302 struct stat st; 00303 if (stat(File1, &st) == 0) { 00304 dev_t dev1 = st.st_dev; 00305 if (stat(File2, &st) == 0) 00306 return st.st_dev == dev1; 00307 else 00308 LOG_ERROR_STR(File2); 00309 } 00310 else 00311 LOG_ERROR_STR(File1); 00312 return false; 00313 } 00314 00315 int FreeDiskSpaceMB(const char *Directory, int *UsedMB) 00316 { 00317 if (UsedMB) 00318 *UsedMB = 0; 00319 int Free = 0; 00320 struct statfs statFs; 00321 if (statfs(Directory, &statFs) == 0) { 00322 double blocksPerMeg = 1024.0 * 1024.0 / statFs.f_bsize; 00323 if (UsedMB) 00324 *UsedMB = int((statFs.f_blocks - statFs.f_bfree) / blocksPerMeg); 00325 Free = int(statFs.f_bavail / blocksPerMeg); 00326 } 00327 else 00328 LOG_ERROR_STR(Directory); 00329 return Free; 00330 } 00331 00332 bool DirectoryOk(const char *DirName, bool LogErrors) 00333 { 00334 struct stat ds; 00335 if (stat(DirName, &ds) == 0) { 00336 if (S_ISDIR(ds.st_mode)) { 00337 if (access(DirName, R_OK | W_OK | X_OK) == 0) 00338 return true; 00339 else if (LogErrors) 00340 esyslog("ERROR: can't access %s", DirName); 00341 } 00342 else if (LogErrors) 00343 esyslog("ERROR: %s is not a directory", DirName); 00344 } 00345 else if (LogErrors) 00346 LOG_ERROR_STR(DirName); 00347 return false; 00348 } 00349 00350 bool MakeDirs(const char *FileName, bool IsDirectory) 00351 { 00352 bool result = true; 00353 char *s = strdup(FileName); 00354 char *p = s; 00355 if (*p == '/') 00356 p++; 00357 while ((p = strchr(p, '/')) != NULL || IsDirectory) { 00358 if (p) 00359 *p = 0; 00360 struct stat fs; 00361 if (stat(s, &fs) != 0 || !S_ISDIR(fs.st_mode)) { 00362 dsyslog("creating directory %s", s); 00363 if (mkdir(s, ACCESSPERMS) == -1) { 00364 LOG_ERROR_STR(s); 00365 result = false; 00366 break; 00367 } 00368 } 00369 if (p) 00370 *p++ = '/'; 00371 else 00372 break; 00373 } 00374 free(s); 00375 return result; 00376 } 00377 00378 bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks) 00379 { 00380 struct stat st; 00381 if (stat(FileName, &st) == 0) { 00382 if (S_ISDIR(st.st_mode)) { 00383 cReadDir d(FileName); 00384 if (d.Ok()) { 00385 struct dirent *e; 00386 while ((e = d.Next()) != NULL) { 00387 cString buffer = AddDirectory(FileName, e->d_name); 00388 if (FollowSymlinks) { 00389 struct stat st2; 00390 if (lstat(buffer, &st2) == 0) { 00391 if (S_ISLNK(st2.st_mode)) { 00392 int size = st2.st_size + 1; 00393 char *l = MALLOC(char, size); 00394 int n = readlink(buffer, l, size - 1); 00395 if (n < 0) { 00396 if (errno != EINVAL) 00397 LOG_ERROR_STR(*buffer); 00398 } 00399 else { 00400 l[n] = 0; 00401 dsyslog("removing %s", l); 00402 if (remove(l) < 0) 00403 LOG_ERROR_STR(l); 00404 } 00405 free(l); 00406 } 00407 } 00408 else if (errno != ENOENT) { 00409 LOG_ERROR_STR(FileName); 00410 return false; 00411 } 00412 } 00413 dsyslog("removing %s", *buffer); 00414 if (remove(buffer) < 0) 00415 LOG_ERROR_STR(*buffer); 00416 } 00417 } 00418 else { 00419 LOG_ERROR_STR(FileName); 00420 return false; 00421 } 00422 } 00423 dsyslog("removing %s", FileName); 00424 if (remove(FileName) < 0) { 00425 LOG_ERROR_STR(FileName); 00426 return false; 00427 } 00428 } 00429 else if (errno != ENOENT) { 00430 LOG_ERROR_STR(FileName); 00431 return false; 00432 } 00433 return true; 00434 } 00435 00436 bool RemoveEmptyDirectories(const char *DirName, bool RemoveThis) 00437 { 00438 cReadDir d(DirName); 00439 if (d.Ok()) { 00440 bool empty = true; 00441 struct dirent *e; 00442 while ((e = d.Next()) != NULL) { 00443 if (strcmp(e->d_name, "lost+found")) { 00444 cString buffer = AddDirectory(DirName, e->d_name); 00445 struct stat st; 00446 if (stat(buffer, &st) == 0) { 00447 if (S_ISDIR(st.st_mode)) { 00448 if (!RemoveEmptyDirectories(buffer, true)) 00449 empty = false; 00450 } 00451 else 00452 empty = false; 00453 } 00454 else { 00455 LOG_ERROR_STR(*buffer); 00456 empty = false; 00457 } 00458 } 00459 } 00460 if (RemoveThis && empty) { 00461 dsyslog("removing %s", DirName); 00462 if (remove(DirName) < 0) { 00463 LOG_ERROR_STR(DirName); 00464 return false; 00465 } 00466 } 00467 return empty; 00468 } 00469 else 00470 LOG_ERROR_STR(DirName); 00471 return false; 00472 } 00473 00474 int DirSizeMB(const char *DirName) 00475 { 00476 cReadDir d(DirName); 00477 if (d.Ok()) { 00478 int size = 0; 00479 struct dirent *e; 00480 while (size >= 0 && (e = d.Next()) != NULL) { 00481 cString buffer = AddDirectory(DirName, e->d_name); 00482 struct stat st; 00483 if (stat(buffer, &st) == 0) { 00484 if (S_ISDIR(st.st_mode)) { 00485 int n = DirSizeMB(buffer); 00486 if (n >= 0) 00487 size += n; 00488 else 00489 size = -1; 00490 } 00491 else 00492 size += st.st_size / MEGABYTE(1); 00493 } 00494 else { 00495 LOG_ERROR_STR(*buffer); 00496 size = -1; 00497 } 00498 } 00499 return size; 00500 } 00501 else 00502 LOG_ERROR_STR(DirName); 00503 return -1; 00504 } 00505 00506 char *ReadLink(const char *FileName) 00507 { 00508 if (!FileName) 00509 return NULL; 00510 char *TargetName = canonicalize_file_name(FileName); 00511 if (!TargetName) { 00512 if (errno == ENOENT) // file doesn't exist 00513 TargetName = strdup(FileName); 00514 else // some other error occurred 00515 LOG_ERROR_STR(FileName); 00516 } 00517 return TargetName; 00518 } 00519 00520 bool SpinUpDisk(const char *FileName) 00521 { 00522 for (int n = 0; n < 10; n++) { 00523 cString buf; 00524 if (DirectoryOk(FileName)) 00525 buf = cString::sprintf("%s/vdr-%06d", *FileName ? FileName : ".", n); 00526 else 00527 buf = cString::sprintf("%s.vdr-%06d", FileName, n); 00528 if (access(buf, F_OK) != 0) { // the file does not exist 00529 timeval tp1, tp2; 00530 gettimeofday(&tp1, NULL); 00531 int f = open(buf, O_WRONLY | O_CREAT, DEFFILEMODE); 00532 // O_SYNC doesn't work on all file systems 00533 if (f >= 0) { 00534 if (fdatasync(f) < 0) 00535 LOG_ERROR_STR(*buf); 00536 close(f); 00537 remove(buf); 00538 gettimeofday(&tp2, NULL); 00539 double seconds = (((long long)tp2.tv_sec * 1000000 + tp2.tv_usec) - ((long long)tp1.tv_sec * 1000000 + tp1.tv_usec)) / 1000000.0; 00540 if (seconds > 0.5) 00541 dsyslog("SpinUpDisk took %.2f seconds", seconds); 00542 return true; 00543 } 00544 else 00545 LOG_ERROR_STR(*buf); 00546 } 00547 } 00548 esyslog("ERROR: SpinUpDisk failed"); 00549 return false; 00550 } 00551 00552 void TouchFile(const char *FileName) 00553 { 00554 if (utime(FileName, NULL) == -1 && errno != ENOENT) 00555 LOG_ERROR_STR(FileName); 00556 } 00557 00558 time_t LastModifiedTime(const char *FileName) 00559 { 00560 struct stat fs; 00561 if (stat(FileName, &fs) == 0) 00562 return fs.st_mtime; 00563 return 0; 00564 } 00565 00566 off_t FileSize(const char *FileName) 00567 { 00568 struct stat fs; 00569 if (stat(FileName, &fs) == 0) 00570 return fs.st_size; 00571 return -1; 00572 } 00573 00574 // --- cTimeMs --------------------------------------------------------------- 00575 00576 cTimeMs::cTimeMs(int Ms) 00577 { 00578 if (Ms >= 0) 00579 Set(Ms); 00580 else 00581 begin = 0; 00582 } 00583 00584 uint64_t cTimeMs::Now(void) 00585 { 00586 #if _POSIX_TIMERS > 0 && defined(_POSIX_MONOTONIC_CLOCK) 00587 #define MIN_RESOLUTION 5 // ms 00588 static bool initialized = false; 00589 static bool monotonic = false; 00590 struct timespec tp; 00591 if (!initialized) { 00592 // check if monotonic timer is available and provides enough accurate resolution: 00593 if (clock_getres(CLOCK_MONOTONIC, &tp) == 0) { 00594 long Resolution = tp.tv_nsec; 00595 // require a minimum resolution: 00596 if (tp.tv_sec == 0 && tp.tv_nsec <= MIN_RESOLUTION * 1000000) { 00597 if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) { 00598 dsyslog("cTimeMs: using monotonic clock (resolution is %ld ns)", Resolution); 00599 monotonic = true; 00600 } 00601 else 00602 esyslog("cTimeMs: clock_gettime(CLOCK_MONOTONIC) failed"); 00603 } 00604 else 00605 dsyslog("cTimeMs: not using monotonic clock - resolution is too bad (%ld s %ld ns)", tp.tv_sec, tp.tv_nsec); 00606 } 00607 else 00608 esyslog("cTimeMs: clock_getres(CLOCK_MONOTONIC) failed"); 00609 initialized = true; 00610 } 00611 if (monotonic) { 00612 if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) 00613 return (uint64_t(tp.tv_sec)) * 1000 + tp.tv_nsec / 1000000; 00614 esyslog("cTimeMs: clock_gettime(CLOCK_MONOTONIC) failed"); 00615 monotonic = false; 00616 // fall back to gettimeofday() 00617 } 00618 #else 00619 # warning Posix monotonic clock not available 00620 #endif 00621 struct timeval t; 00622 if (gettimeofday(&t, NULL) == 0) 00623 return (uint64_t(t.tv_sec)) * 1000 + t.tv_usec / 1000; 00624 return 0; 00625 } 00626 00627 void cTimeMs::Set(int Ms) 00628 { 00629 begin = Now() + Ms; 00630 } 00631 00632 bool cTimeMs::TimedOut(void) 00633 { 00634 return Now() >= begin; 00635 } 00636 00637 uint64_t cTimeMs::Elapsed(void) 00638 { 00639 return Now() - begin; 00640 } 00641 00642 // --- UTF-8 support --------------------------------------------------------- 00643 00644 static uint SystemToUtf8[128] = { 0 }; 00645 00646 int Utf8CharLen(const char *s) 00647 { 00648 if (cCharSetConv::SystemCharacterTable()) 00649 return 1; 00650 #define MT(s, m, v) ((*(s) & (m)) == (v)) // Mask Test 00651 if (MT(s, 0xE0, 0xC0) && MT(s + 1, 0xC0, 0x80)) 00652 return 2; 00653 if (MT(s, 0xF0, 0xE0) && MT(s + 1, 0xC0, 0x80) && MT(s + 2, 0xC0, 0x80)) 00654 return 3; 00655 if (MT(s, 0xF8, 0xF0) && MT(s + 1, 0xC0, 0x80) && MT(s + 2, 0xC0, 0x80) && MT(s + 3, 0xC0, 0x80)) 00656 return 4; 00657 return 1; 00658 } 00659 00660 uint Utf8CharGet(const char *s, int Length) 00661 { 00662 if (cCharSetConv::SystemCharacterTable()) 00663 return (uchar)*s < 128 ? *s : SystemToUtf8[(uchar)*s - 128]; 00664 if (!Length) 00665 Length = Utf8CharLen(s); 00666 switch (Length) { 00667 case 2: return ((*s & 0x1F) << 6) | (*(s + 1) & 0x3F); 00668 case 3: return ((*s & 0x0F) << 12) | ((*(s + 1) & 0x3F) << 6) | (*(s + 2) & 0x3F); 00669 case 4: return ((*s & 0x07) << 18) | ((*(s + 1) & 0x3F) << 12) | ((*(s + 2) & 0x3F) << 6) | (*(s + 3) & 0x3F); 00670 default: ; 00671 } 00672 return *s; 00673 } 00674 00675 int Utf8CharSet(uint c, char *s) 00676 { 00677 if (c < 0x80 || cCharSetConv::SystemCharacterTable()) { 00678 if (s) 00679 *s = c; 00680 return 1; 00681 } 00682 if (c < 0x800) { 00683 if (s) { 00684 *s++ = ((c >> 6) & 0x1F) | 0xC0; 00685 *s = (c & 0x3F) | 0x80; 00686 } 00687 return 2; 00688 } 00689 if (c < 0x10000) { 00690 if (s) { 00691 *s++ = ((c >> 12) & 0x0F) | 0xE0; 00692 *s++ = ((c >> 6) & 0x3F) | 0x80; 00693 *s = (c & 0x3F) | 0x80; 00694 } 00695 return 3; 00696 } 00697 if (c < 0x110000) { 00698 if (s) { 00699 *s++ = ((c >> 18) & 0x07) | 0xF0; 00700 *s++ = ((c >> 12) & 0x3F) | 0x80; 00701 *s++ = ((c >> 6) & 0x3F) | 0x80; 00702 *s = (c & 0x3F) | 0x80; 00703 } 00704 return 4; 00705 } 00706 return 0; // can't convert to UTF-8 00707 } 00708 00709 int Utf8SymChars(const char *s, int Symbols) 00710 { 00711 if (cCharSetConv::SystemCharacterTable()) 00712 return Symbols; 00713 int n = 0; 00714 while (*s && Symbols--) { 00715 int sl = Utf8CharLen(s); 00716 s += sl; 00717 n += sl; 00718 } 00719 return n; 00720 } 00721 00722 int Utf8StrLen(const char *s) 00723 { 00724 if (cCharSetConv::SystemCharacterTable()) 00725 return strlen(s); 00726 int n = 0; 00727 while (*s) { 00728 s += Utf8CharLen(s); 00729 n++; 00730 } 00731 return n; 00732 } 00733 00734 char *Utf8Strn0Cpy(char *Dest, const char *Src, int n) 00735 { 00736 if (cCharSetConv::SystemCharacterTable()) 00737 return strn0cpy(Dest, Src, n); 00738 char *d = Dest; 00739 while (*Src) { 00740 int sl = Utf8CharLen(Src); 00741 n -= sl; 00742 if (n > 0) { 00743 while (sl--) 00744 *d++ = *Src++; 00745 } 00746 else 00747 break; 00748 } 00749 *d = 0; 00750 return Dest; 00751 } 00752 00753 int Utf8ToArray(const char *s, uint *a, int Size) 00754 { 00755 int n = 0; 00756 while (*s && --Size > 0) { 00757 if (cCharSetConv::SystemCharacterTable()) 00758 *a++ = (uchar)(*s++); 00759 else { 00760 int sl = Utf8CharLen(s); 00761 *a++ = Utf8CharGet(s, sl); 00762 s += sl; 00763 } 00764 n++; 00765 } 00766 if (Size > 0) 00767 *a = 0; 00768 return n; 00769 } 00770 00771 int Utf8FromArray(const uint *a, char *s, int Size, int Max) 00772 { 00773 int NumChars = 0; 00774 int NumSyms = 0; 00775 while (*a && NumChars < Size) { 00776 if (Max >= 0 && NumSyms++ >= Max) 00777 break; 00778 if (cCharSetConv::SystemCharacterTable()) { 00779 *s++ = *a++; 00780 NumChars++; 00781 } 00782 else { 00783 int sl = Utf8CharSet(*a); 00784 if (NumChars + sl <= Size) { 00785 Utf8CharSet(*a, s); 00786 a++; 00787 s += sl; 00788 NumChars += sl; 00789 } 00790 else 00791 break; 00792 } 00793 } 00794 if (NumChars < Size) 00795 *s = 0; 00796 return NumChars; 00797 } 00798 00799 // --- cCharSetConv ---------------------------------------------------------- 00800 00801 char *cCharSetConv::systemCharacterTable = NULL; 00802 00803 cCharSetConv::cCharSetConv(const char *FromCode, const char *ToCode) 00804 { 00805 if (!FromCode) 00806 FromCode = systemCharacterTable ? systemCharacterTable : "UTF-8"; 00807 if (!ToCode) 00808 ToCode = "UTF-8"; 00809 cd = iconv_open(ToCode, FromCode); 00810 result = NULL; 00811 length = 0; 00812 } 00813 00814 cCharSetConv::~cCharSetConv() 00815 { 00816 free(result); 00817 iconv_close(cd); 00818 } 00819 00820 void cCharSetConv::SetSystemCharacterTable(const char *CharacterTable) 00821 { 00822 free(systemCharacterTable); 00823 systemCharacterTable = NULL; 00824 if (!strcasestr(CharacterTable, "UTF-8")) { 00825 // Set up a map for the character values 128...255: 00826 char buf[129]; 00827 for (int i = 0; i < 128; i++) 00828 buf[i] = i + 128; 00829 buf[128] = 0; 00830 cCharSetConv csc(CharacterTable); 00831 const char *s = csc.Convert(buf); 00832 int i = 0; 00833 while (*s) { 00834 int sl = Utf8CharLen(s); 00835 SystemToUtf8[i] = Utf8CharGet(s, sl); 00836 s += sl; 00837 i++; 00838 } 00839 systemCharacterTable = strdup(CharacterTable); 00840 } 00841 } 00842 00843 const char *cCharSetConv::Convert(const char *From, char *To, size_t ToLength) 00844 { 00845 if (cd != (iconv_t)-1 && From && *From) { 00846 char *FromPtr = (char *)From; 00847 size_t FromLength = strlen(From); 00848 char *ToPtr = To; 00849 if (!ToPtr) { 00850 int NewLength = max(length, FromLength * 2); // some reserve to avoid later reallocations 00851 if (char *NewBuffer = (char *)realloc(result, NewLength)) { 00852 length = NewLength; 00853 result = NewBuffer; 00854 } 00855 else { 00856 esyslog("ERROR: out of memory"); 00857 return From; 00858 } 00859 ToPtr = result; 00860 ToLength = length; 00861 } 00862 else if (!ToLength) 00863 return From; // can't convert into a zero sized buffer 00864 ToLength--; // save space for terminating 0 00865 char *Converted = ToPtr; 00866 while (FromLength > 0) { 00867 if (iconv(cd, &FromPtr, &FromLength, &ToPtr, &ToLength) == size_t(-1)) { 00868 if (errno == E2BIG || errno == EILSEQ && ToLength < 1) { 00869 if (To) 00870 break; // caller provided a fixed size buffer, but it was too small 00871 // The result buffer is too small, so increase it: 00872 size_t d = ToPtr - result; 00873 size_t r = length / 2; 00874 int NewLength = length + r; 00875 if (char *NewBuffer = (char *)realloc(result, NewLength)) { 00876 length = NewLength; 00877 Converted = result = NewBuffer; 00878 } 00879 else { 00880 esyslog("ERROR: out of memory"); 00881 return From; 00882 } 00883 ToLength += r; 00884 ToPtr = result + d; 00885 } 00886 if (errno == EILSEQ) { 00887 // A character can't be converted, so mark it with '?' and proceed: 00888 FromPtr++; 00889 FromLength--; 00890 *ToPtr++ = '?'; 00891 ToLength--; 00892 } 00893 else if (errno != E2BIG) 00894 return From; // unknown error, return original string 00895 } 00896 } 00897 *ToPtr = 0; 00898 return Converted; 00899 } 00900 return From; 00901 } 00902 00903 // --- cString --------------------------------------------------------------- 00904 00905 cString::cString(const char *S, bool TakePointer) 00906 { 00907 s = TakePointer ? (char *)S : S ? strdup(S) : NULL; 00908 } 00909 00910 cString::cString(const cString &String) 00911 { 00912 s = String.s ? strdup(String.s) : NULL; 00913 } 00914 00915 cString::~cString() 00916 { 00917 free(s); 00918 } 00919 00920 cString &cString::operator=(const cString &String) 00921 { 00922 if (this == &String) 00923 return *this; 00924 free(s); 00925 s = String.s ? strdup(String.s) : NULL; 00926 return *this; 00927 } 00928 00929 cString &cString::operator=(const char *String) 00930 { 00931 if (s == String) 00932 return *this; 00933 free(s); 00934 s = String ? strdup(String) : NULL; 00935 return *this; 00936 } 00937 00938 cString &cString::Truncate(int Index) 00939 { 00940 int l = strlen(s); 00941 if (Index < 0) 00942 Index = l + Index; 00943 if (Index >= 0 && Index < l) 00944 s[Index] = 0; 00945 return *this; 00946 } 00947 00948 cString cString::sprintf(const char *fmt, ...) 00949 { 00950 va_list ap; 00951 va_start(ap, fmt); 00952 char *buffer; 00953 if (!fmt || vasprintf(&buffer, fmt, ap) < 0) { 00954 esyslog("error in vasprintf('%s', ...)", fmt); 00955 buffer = strdup("???"); 00956 } 00957 va_end(ap); 00958 return cString(buffer, true); 00959 } 00960 00961 cString cString::sprintf(const char *fmt, va_list &ap) 00962 { 00963 char *buffer; 00964 if (!fmt || vasprintf(&buffer, fmt, ap) < 0) { 00965 esyslog("error in vasprintf('%s', ...)", fmt); 00966 buffer = strdup("???"); 00967 } 00968 return cString(buffer, true); 00969 } 00970 00971 cString WeekDayName(int WeekDay) 00972 { 00973 char buffer[16]; 00974 WeekDay = WeekDay == 0 ? 6 : WeekDay - 1; // we start with Monday==0! 00975 if (0 <= WeekDay && WeekDay <= 6) { 00976 // TRANSLATORS: abbreviated weekdays, beginning with monday (must all be 3 letters!) 00977 const char *day = tr("MonTueWedThuFriSatSun"); 00978 day += Utf8SymChars(day, WeekDay * 3); 00979 strn0cpy(buffer, day, min(Utf8SymChars(day, 3) + 1, int(sizeof(buffer)))); 00980 return buffer; 00981 } 00982 else 00983 return "???"; 00984 } 00985 00986 cString WeekDayName(time_t t) 00987 { 00988 struct tm tm_r; 00989 return WeekDayName(localtime_r(&t, &tm_r)->tm_wday); 00990 } 00991 00992 cString WeekDayNameFull(int WeekDay) 00993 { 00994 WeekDay = WeekDay == 0 ? 6 : WeekDay - 1; // we start with Monday==0! 00995 switch (WeekDay) { 00996 case 0: return tr("Monday"); 00997 case 1: return tr("Tuesday"); 00998 case 2: return tr("Wednesday"); 00999 case 3: return tr("Thursday"); 01000 case 4: return tr("Friday"); 01001 case 5: return tr("Saturday"); 01002 case 6: return tr("Sunday"); 01003 default: return "???"; 01004 } 01005 } 01006 01007 cString WeekDayNameFull(time_t t) 01008 { 01009 struct tm tm_r; 01010 return WeekDayNameFull(localtime_r(&t, &tm_r)->tm_wday); 01011 } 01012 01013 cString DayDateTime(time_t t) 01014 { 01015 char buffer[32]; 01016 if (t == 0) 01017 time(&t); 01018 struct tm tm_r; 01019 tm *tm = localtime_r(&t, &tm_r); 01020 snprintf(buffer, sizeof(buffer), "%s %02d.%02d. %02d:%02d", *WeekDayName(tm->tm_wday), tm->tm_mday, tm->tm_mon + 1, tm->tm_hour, tm->tm_min); 01021 return buffer; 01022 } 01023 01024 cString TimeToString(time_t t) 01025 { 01026 char buffer[32]; 01027 if (ctime_r(&t, buffer)) { 01028 buffer[strlen(buffer) - 1] = 0; // strip trailing newline 01029 return buffer; 01030 } 01031 return "???"; 01032 } 01033 01034 cString DateString(time_t t) 01035 { 01036 char buf[32]; 01037 struct tm tm_r; 01038 tm *tm = localtime_r(&t, &tm_r); 01039 char *p = stpcpy(buf, WeekDayName(tm->tm_wday)); 01040 *p++ = ' '; 01041 strftime(p, sizeof(buf) - (p - buf), "%d.%m.%Y", tm); 01042 return buf; 01043 } 01044 01045 cString TimeString(time_t t) 01046 { 01047 char buf[25]; 01048 struct tm tm_r; 01049 strftime(buf, sizeof(buf), "%R", localtime_r(&t, &tm_r)); 01050 return buf; 01051 } 01052 01053 // --- RgbToJpeg ------------------------------------------------------------- 01054 01055 #define JPEGCOMPRESSMEM 500000 01056 01057 struct tJpegCompressData { 01058 int size; 01059 uchar *mem; 01060 }; 01061 01062 static void JpegCompressInitDestination(j_compress_ptr cinfo) 01063 { 01064 tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data; 01065 if (jcd) { 01066 cinfo->dest->free_in_buffer = jcd->size = JPEGCOMPRESSMEM; 01067 cinfo->dest->next_output_byte = jcd->mem = MALLOC(uchar, jcd->size); 01068 } 01069 } 01070 01071 static boolean JpegCompressEmptyOutputBuffer(j_compress_ptr cinfo) 01072 { 01073 tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data; 01074 if (jcd) { 01075 int Used = jcd->size; 01076 int NewSize = jcd->size + JPEGCOMPRESSMEM; 01077 if (uchar *NewBuffer = (uchar *)realloc(jcd->mem, NewSize)) { 01078 jcd->size = NewSize; 01079 jcd->mem = NewBuffer; 01080 } 01081 else { 01082 esyslog("ERROR: out of memory"); 01083 return false; 01084 } 01085 if (jcd->mem) { 01086 cinfo->dest->next_output_byte = jcd->mem + Used; 01087 cinfo->dest->free_in_buffer = jcd->size - Used; 01088 return true; 01089 } 01090 } 01091 return false; 01092 } 01093 01094 static void JpegCompressTermDestination(j_compress_ptr cinfo) 01095 { 01096 tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data; 01097 if (jcd) { 01098 int Used = cinfo->dest->next_output_byte - jcd->mem; 01099 if (Used < jcd->size) { 01100 if (uchar *NewBuffer = (uchar *)realloc(jcd->mem, Used)) { 01101 jcd->size = Used; 01102 jcd->mem = NewBuffer; 01103 } 01104 else 01105 esyslog("ERROR: out of memory"); 01106 } 01107 } 01108 } 01109 01110 uchar *RgbToJpeg(uchar *Mem, int Width, int Height, int &Size, int Quality) 01111 { 01112 if (Quality < 0) 01113 Quality = 0; 01114 else if (Quality > 100) 01115 Quality = 100; 01116 01117 jpeg_destination_mgr jdm; 01118 01119 jdm.init_destination = JpegCompressInitDestination; 01120 jdm.empty_output_buffer = JpegCompressEmptyOutputBuffer; 01121 jdm.term_destination = JpegCompressTermDestination; 01122 01123 struct jpeg_compress_struct cinfo; 01124 struct jpeg_error_mgr jerr; 01125 cinfo.err = jpeg_std_error(&jerr); 01126 jpeg_create_compress(&cinfo); 01127 cinfo.dest = &jdm; 01128 tJpegCompressData jcd; 01129 cinfo.client_data = &jcd; 01130 cinfo.image_width = Width; 01131 cinfo.image_height = Height; 01132 cinfo.input_components = 3; 01133 cinfo.in_color_space = JCS_RGB; 01134 01135 jpeg_set_defaults(&cinfo); 01136 jpeg_set_quality(&cinfo, Quality, true); 01137 jpeg_start_compress(&cinfo, true); 01138 01139 int rs = Width * 3; 01140 JSAMPROW rp[Height]; 01141 for (int k = 0; k < Height; k++) 01142 rp[k] = &Mem[rs * k]; 01143 jpeg_write_scanlines(&cinfo, rp, Height); 01144 jpeg_finish_compress(&cinfo); 01145 jpeg_destroy_compress(&cinfo); 01146 01147 Size = jcd.size; 01148 return jcd.mem; 01149 } 01150 01151 // --- cBase64Encoder -------------------------------------------------------- 01152 01153 const char *cBase64Encoder::b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 01154 01155 cBase64Encoder::cBase64Encoder(const uchar *Data, int Length, int MaxResult) 01156 { 01157 data = Data; 01158 length = Length; 01159 maxResult = MaxResult; 01160 i = 0; 01161 result = MALLOC(char, maxResult + 1); 01162 } 01163 01164 cBase64Encoder::~cBase64Encoder() 01165 { 01166 free(result); 01167 } 01168 01169 const char *cBase64Encoder::NextLine(void) 01170 { 01171 int r = 0; 01172 while (i < length && r < maxResult - 3) { 01173 result[r++] = b64[(data[i] >> 2) & 0x3F]; 01174 uchar c = (data[i] << 4) & 0x3F; 01175 if (++i < length) 01176 c |= (data[i] >> 4) & 0x0F; 01177 result[r++] = b64[c]; 01178 if (i < length) { 01179 c = (data[i] << 2) & 0x3F; 01180 if (++i < length) 01181 c |= (data[i] >> 6) & 0x03; 01182 result[r++] = b64[c]; 01183 } 01184 else { 01185 i++; 01186 result[r++] = '='; 01187 } 01188 if (i < length) { 01189 c = data[i] & 0x3F; 01190 result[r++] = b64[c]; 01191 } 01192 else 01193 result[r++] = '='; 01194 i++; 01195 } 01196 if (r > 0) { 01197 result[r] = 0; 01198 return result; 01199 } 01200 return NULL; 01201 } 01202 01203 // --- cBitStream ------------------------------------------------------------ 01204 01205 int cBitStream::GetBit(void) 01206 { 01207 if (index >= length) 01208 return 1; 01209 int r = (data[index >> 3] >> (7 - (index & 7))) & 1; 01210 ++index; 01211 return r; 01212 } 01213 01214 uint32_t cBitStream::GetBits(int n) 01215 { 01216 uint32_t r = 0; 01217 while (n--) 01218 r |= GetBit() << n; 01219 return r; 01220 } 01221 01222 void cBitStream::ByteAlign(void) 01223 { 01224 int n = index % 8; 01225 if (n > 0) 01226 SkipBits(8 - n); 01227 } 01228 01229 void cBitStream::WordAlign(void) 01230 { 01231 int n = index % 16; 01232 if (n > 0) 01233 SkipBits(16 - n); 01234 } 01235 01236 bool cBitStream::SetLength(int Length) 01237 { 01238 if (Length > length) 01239 return false; 01240 length = Length; 01241 return true; 01242 } 01243 01244 // --- cReadLine ------------------------------------------------------------- 01245 01246 cReadLine::cReadLine(void) 01247 { 01248 size = 0; 01249 buffer = NULL; 01250 } 01251 01252 cReadLine::~cReadLine() 01253 { 01254 free(buffer); 01255 } 01256 01257 char *cReadLine::Read(FILE *f) 01258 { 01259 int n = getline(&buffer, &size, f); 01260 if (n > 0) { 01261 n--; 01262 if (buffer[n] == '\n') { 01263 buffer[n] = 0; 01264 if (n > 0) { 01265 n--; 01266 if (buffer[n] == '\r') 01267 buffer[n] = 0; 01268 } 01269 } 01270 return buffer; 01271 } 01272 return NULL; 01273 } 01274 01275 // --- cPoller --------------------------------------------------------------- 01276 01277 cPoller::cPoller(int FileHandle, bool Out) 01278 { 01279 numFileHandles = 0; 01280 Add(FileHandle, Out); 01281 } 01282 01283 bool cPoller::Add(int FileHandle, bool Out) 01284 { 01285 if (FileHandle >= 0) { 01286 for (int i = 0; i < numFileHandles; i++) { 01287 if (pfd[i].fd == FileHandle && pfd[i].events == (Out ? POLLOUT : POLLIN)) 01288 return true; 01289 } 01290 if (numFileHandles < MaxPollFiles) { 01291 pfd[numFileHandles].fd = FileHandle; 01292 pfd[numFileHandles].events = Out ? POLLOUT : POLLIN; 01293 pfd[numFileHandles].revents = 0; 01294 numFileHandles++; 01295 return true; 01296 } 01297 esyslog("ERROR: too many file handles in cPoller"); 01298 } 01299 return false; 01300 } 01301 01302 bool cPoller::Poll(int TimeoutMs) 01303 { 01304 if (numFileHandles) { 01305 if (poll(pfd, numFileHandles, TimeoutMs) != 0) 01306 return true; // returns true even in case of an error, to let the caller 01307 // access the file and thus see the error code 01308 } 01309 return false; 01310 } 01311 01312 // --- cReadDir -------------------------------------------------------------- 01313 01314 cReadDir::cReadDir(const char *Directory) 01315 { 01316 directory = opendir(Directory); 01317 } 01318 01319 cReadDir::~cReadDir() 01320 { 01321 if (directory) 01322 closedir(directory); 01323 } 01324 01325 struct dirent *cReadDir::Next(void) 01326 { 01327 if (directory) { 01328 while (readdir_r(directory, &u.d, &result) == 0 && result) { 01329 if (strcmp(result->d_name, ".") && strcmp(result->d_name, "..")) 01330 return result; 01331 } 01332 } 01333 return NULL; 01334 } 01335 01336 // --- cStringList ----------------------------------------------------------- 01337 01338 cStringList::~cStringList() 01339 { 01340 Clear(); 01341 } 01342 01343 int cStringList::Find(const char *s) const 01344 { 01345 for (int i = 0; i < Size(); i++) { 01346 if (!strcmp(s, At(i))) 01347 return i; 01348 } 01349 return -1; 01350 } 01351 01352 void cStringList::Clear(void) 01353 { 01354 for (int i = 0; i < Size(); i++) 01355 free(At(i)); 01356 cVector<char *>::Clear(); 01357 } 01358 01359 // --- cFileNameList --------------------------------------------------------- 01360 01361 // TODO better GetFileNames(const char *Directory, cStringList *List)? 01362 cFileNameList::cFileNameList(const char *Directory, bool DirsOnly) 01363 { 01364 Load(Directory, DirsOnly); 01365 } 01366 01367 bool cFileNameList::Load(const char *Directory, bool DirsOnly) 01368 { 01369 Clear(); 01370 if (Directory) { 01371 cReadDir d(Directory); 01372 struct dirent *e; 01373 if (d.Ok()) { 01374 while ((e = d.Next()) != NULL) { 01375 if (DirsOnly) { 01376 struct stat ds; 01377 if (stat(AddDirectory(Directory, e->d_name), &ds) == 0) { 01378 if (!S_ISDIR(ds.st_mode)) 01379 continue; 01380 } 01381 } 01382 Append(strdup(e->d_name)); 01383 } 01384 Sort(); 01385 return true; 01386 } 01387 else 01388 LOG_ERROR_STR(Directory); 01389 } 01390 return false; 01391 } 01392 01393 // --- cFile ----------------------------------------------------------------- 01394 01395 bool cFile::files[FD_SETSIZE] = { false }; 01396 int cFile::maxFiles = 0; 01397 01398 cFile::cFile(void) 01399 { 01400 f = -1; 01401 } 01402 01403 cFile::~cFile() 01404 { 01405 Close(); 01406 } 01407 01408 bool cFile::Open(const char *FileName, int Flags, mode_t Mode) 01409 { 01410 if (!IsOpen()) 01411 return Open(open(FileName, Flags, Mode)); 01412 esyslog("ERROR: attempt to re-open %s", FileName); 01413 return false; 01414 } 01415 01416 bool cFile::Open(int FileDes) 01417 { 01418 if (FileDes >= 0) { 01419 if (!IsOpen()) { 01420 f = FileDes; 01421 if (f >= 0) { 01422 if (f < FD_SETSIZE) { 01423 if (f >= maxFiles) 01424 maxFiles = f + 1; 01425 if (!files[f]) 01426 files[f] = true; 01427 else 01428 esyslog("ERROR: file descriptor %d already in files[]", f); 01429 return true; 01430 } 01431 else 01432 esyslog("ERROR: file descriptor %d is larger than FD_SETSIZE (%d)", f, FD_SETSIZE); 01433 } 01434 } 01435 else 01436 esyslog("ERROR: attempt to re-open file descriptor %d", FileDes); 01437 } 01438 return false; 01439 } 01440 01441 void cFile::Close(void) 01442 { 01443 if (f >= 0) { 01444 close(f); 01445 files[f] = false; 01446 f = -1; 01447 } 01448 } 01449 01450 bool cFile::Ready(bool Wait) 01451 { 01452 return f >= 0 && AnyFileReady(f, Wait ? 1000 : 0); 01453 } 01454 01455 bool cFile::AnyFileReady(int FileDes, int TimeoutMs) 01456 { 01457 fd_set set; 01458 FD_ZERO(&set); 01459 for (int i = 0; i < maxFiles; i++) { 01460 if (files[i]) 01461 FD_SET(i, &set); 01462 } 01463 if (0 <= FileDes && FileDes < FD_SETSIZE && !files[FileDes]) 01464 FD_SET(FileDes, &set); // in case we come in with an arbitrary descriptor 01465 if (TimeoutMs == 0) 01466 TimeoutMs = 10; // load gets too heavy with 0 01467 struct timeval timeout; 01468 timeout.tv_sec = TimeoutMs / 1000; 01469 timeout.tv_usec = (TimeoutMs % 1000) * 1000; 01470 return select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0 && (FileDes < 0 || FD_ISSET(FileDes, &set)); 01471 } 01472 01473 bool cFile::FileReady(int FileDes, int TimeoutMs) 01474 { 01475 fd_set set; 01476 struct timeval timeout; 01477 FD_ZERO(&set); 01478 FD_SET(FileDes, &set); 01479 if (TimeoutMs >= 0) { 01480 if (TimeoutMs < 100) 01481 TimeoutMs = 100; 01482 timeout.tv_sec = TimeoutMs / 1000; 01483 timeout.tv_usec = (TimeoutMs % 1000) * 1000; 01484 } 01485 return select(FD_SETSIZE, &set, NULL, NULL, (TimeoutMs >= 0) ? &timeout : NULL) > 0 && FD_ISSET(FileDes, &set); 01486 } 01487 01488 bool cFile::FileReadyForWriting(int FileDes, int TimeoutMs) 01489 { 01490 fd_set set; 01491 struct timeval timeout; 01492 FD_ZERO(&set); 01493 FD_SET(FileDes, &set); 01494 if (TimeoutMs < 100) 01495 TimeoutMs = 100; 01496 timeout.tv_sec = 0; 01497 timeout.tv_usec = TimeoutMs * 1000; 01498 return select(FD_SETSIZE, NULL, &set, NULL, &timeout) > 0 && FD_ISSET(FileDes, &set); 01499 } 01500 01501 // --- cSafeFile ------------------------------------------------------------- 01502 01503 cSafeFile::cSafeFile(const char *FileName) 01504 { 01505 f = NULL; 01506 fileName = ReadLink(FileName); 01507 tempName = fileName ? MALLOC(char, strlen(fileName) + 5) : NULL; 01508 if (tempName) 01509 strcat(strcpy(tempName, fileName), ".$$$"); 01510 } 01511 01512 cSafeFile::~cSafeFile() 01513 { 01514 if (f) 01515 fclose(f); 01516 unlink(tempName); 01517 free(fileName); 01518 free(tempName); 01519 } 01520 01521 bool cSafeFile::Open(void) 01522 { 01523 if (!f && fileName && tempName) { 01524 f = fopen(tempName, "w"); 01525 if (!f) 01526 LOG_ERROR_STR(tempName); 01527 } 01528 return f != NULL; 01529 } 01530 01531 bool cSafeFile::Close(void) 01532 { 01533 bool result = true; 01534 if (f) { 01535 if (ferror(f) != 0) { 01536 LOG_ERROR_STR(tempName); 01537 result = false; 01538 } 01539 fflush(f); 01540 fsync(fileno(f)); 01541 if (fclose(f) < 0) { 01542 LOG_ERROR_STR(tempName); 01543 result = false; 01544 } 01545 f = NULL; 01546 if (result && rename(tempName, fileName) < 0) { 01547 LOG_ERROR_STR(fileName); 01548 result = false; 01549 } 01550 } 01551 else 01552 result = false; 01553 return result; 01554 } 01555 01556 // --- cUnbufferedFile ------------------------------------------------------- 01557 01558 #define USE_FADVISE 01559 01560 #define WRITE_BUFFER KILOBYTE(800) 01561 01562 cUnbufferedFile::cUnbufferedFile(void) 01563 { 01564 fd = -1; 01565 } 01566 01567 cUnbufferedFile::~cUnbufferedFile() 01568 { 01569 Close(); 01570 } 01571 01572 int cUnbufferedFile::Open(const char *FileName, int Flags, mode_t Mode) 01573 { 01574 Close(); 01575 fd = open(FileName, Flags, Mode); 01576 curpos = 0; 01577 #ifdef USE_FADVISE 01578 begin = lastpos = ahead = 0; 01579 cachedstart = 0; 01580 cachedend = 0; 01581 readahead = KILOBYTE(128); 01582 written = 0; 01583 totwritten = 0; 01584 if (fd >= 0) 01585 posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM); // we could use POSIX_FADV_SEQUENTIAL, but we do our own readahead, disabling the kernel one. 01586 #endif 01587 return fd; 01588 } 01589 01590 int cUnbufferedFile::Close(void) 01591 { 01592 if (fd >= 0) { 01593 #ifdef USE_FADVISE 01594 if (totwritten) // if we wrote anything make sure the data has hit the disk before 01595 fdatasync(fd); // calling fadvise, as this is our last chance to un-cache it. 01596 posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED); 01597 #endif 01598 int OldFd = fd; 01599 fd = -1; 01600 return close(OldFd); 01601 } 01602 errno = EBADF; 01603 return -1; 01604 } 01605 01606 // When replaying and going e.g. FF->PLAY the position jumps back 2..8M 01607 // hence we do not want to drop recently accessed data at once. 01608 // We try to handle the common cases such as PLAY->FF->PLAY, small 01609 // jumps, moving editing marks etc. 01610 01611 #define FADVGRAN KILOBYTE(4) // AKA fadvise-chunk-size; PAGE_SIZE or getpagesize(2) would also work. 01612 #define READCHUNK MEGABYTE(8) 01613 01614 void cUnbufferedFile::SetReadAhead(size_t ra) 01615 { 01616 readahead = ra; 01617 } 01618 01619 int cUnbufferedFile::FadviseDrop(off_t Offset, off_t Len) 01620 { 01621 // rounding up the window to make sure that not PAGE_SIZE-aligned data gets freed. 01622 return posix_fadvise(fd, Offset - (FADVGRAN - 1), Len + (FADVGRAN - 1) * 2, POSIX_FADV_DONTNEED); 01623 } 01624 01625 off_t cUnbufferedFile::Seek(off_t Offset, int Whence) 01626 { 01627 if (Whence == SEEK_SET && Offset == curpos) 01628 return curpos; 01629 curpos = lseek(fd, Offset, Whence); 01630 return curpos; 01631 } 01632 01633 ssize_t cUnbufferedFile::Read(void *Data, size_t Size) 01634 { 01635 if (fd >= 0) { 01636 #ifdef USE_FADVISE 01637 off_t jumped = curpos-lastpos; // nonzero means we're not at the last offset 01638 if ((cachedstart < cachedend) && (curpos < cachedstart || curpos > cachedend)) { 01639 // current position is outside the cached window -- invalidate it. 01640 FadviseDrop(cachedstart, cachedend-cachedstart); 01641 cachedstart = curpos; 01642 cachedend = curpos; 01643 } 01644 cachedstart = min(cachedstart, curpos); 01645 #endif 01646 ssize_t bytesRead = safe_read(fd, Data, Size); 01647 if (bytesRead > 0) { 01648 curpos += bytesRead; 01649 #ifdef USE_FADVISE 01650 cachedend = max(cachedend, curpos); 01651 01652 // Read ahead: 01653 // no jump? (allow small forward jump still inside readahead window). 01654 if (jumped >= 0 && jumped <= (off_t)readahead) { 01655 // Trigger the readahead IO, but only if we've used at least 01656 // 1/2 of the previously requested area. This avoids calling 01657 // fadvise() after every read() call. 01658 if (ahead - curpos < (off_t)(readahead / 2)) { 01659 posix_fadvise(fd, curpos, readahead, POSIX_FADV_WILLNEED); 01660 ahead = curpos + readahead; 01661 cachedend = max(cachedend, ahead); 01662 } 01663 if (readahead < Size * 32) { // automagically tune readahead size. 01664 readahead = Size * 32; 01665 } 01666 } 01667 else 01668 ahead = curpos; // jumped -> we really don't want any readahead, otherwise e.g. fast-rewind gets in trouble. 01669 #endif 01670 } 01671 #ifdef USE_FADVISE 01672 if (cachedstart < cachedend) { 01673 if (curpos - cachedstart > READCHUNK * 2) { 01674 // current position has moved forward enough, shrink tail window. 01675 FadviseDrop(cachedstart, curpos - READCHUNK - cachedstart); 01676 cachedstart = curpos - READCHUNK; 01677 } 01678 else if (cachedend > ahead && cachedend - curpos > READCHUNK * 2) { 01679 // current position has moved back enough, shrink head window. 01680 FadviseDrop(curpos + READCHUNK, cachedend - (curpos + READCHUNK)); 01681 cachedend = curpos + READCHUNK; 01682 } 01683 } 01684 lastpos = curpos; 01685 #endif 01686 return bytesRead; 01687 } 01688 return -1; 01689 } 01690 01691 ssize_t cUnbufferedFile::Write(const void *Data, size_t Size) 01692 { 01693 if (fd >=0) { 01694 ssize_t bytesWritten = safe_write(fd, Data, Size); 01695 #ifdef USE_FADVISE 01696 if (bytesWritten > 0) { 01697 begin = min(begin, curpos); 01698 curpos += bytesWritten; 01699 written += bytesWritten; 01700 lastpos = max(lastpos, curpos); 01701 if (written > WRITE_BUFFER) { 01702 if (lastpos > begin) { 01703 // Now do three things: 01704 // 1) Start writeback of begin..lastpos range 01705 // 2) Drop the already written range (by the previous fadvise call) 01706 // 3) Handle nonpagealigned data. 01707 // This is why we double the WRITE_BUFFER; the first time around the 01708 // last (partial) page might be skipped, writeback will start only after 01709 // second call; the third call will still include this page and finally 01710 // drop it from cache. 01711 off_t headdrop = min(begin, off_t(WRITE_BUFFER * 2)); 01712 posix_fadvise(fd, begin - headdrop, lastpos - begin + headdrop, POSIX_FADV_DONTNEED); 01713 } 01714 begin = lastpos = curpos; 01715 totwritten += written; 01716 written = 0; 01717 // The above fadvise() works when writing slowly (recording), but could 01718 // leave cached data around when writing at a high rate, e.g. when cutting, 01719 // because by the time we try to flush the cached pages (above) the data 01720 // can still be dirty - we are faster than the disk I/O. 01721 // So we do another round of flushing, just like above, but at larger 01722 // intervals -- this should catch any pages that couldn't be released 01723 // earlier. 01724 if (totwritten > MEGABYTE(32)) { 01725 // It seems in some setups, fadvise() does not trigger any I/O and 01726 // a fdatasync() call would be required do all the work (reiserfs with some 01727 // kind of write gathering enabled), but the syncs cause (io) load.. 01728 // Uncomment the next line if you think you need them. 01729 //fdatasync(fd); 01730 off_t headdrop = min(off_t(curpos - totwritten), off_t(totwritten * 2)); 01731 posix_fadvise(fd, curpos - totwritten - headdrop, totwritten + headdrop, POSIX_FADV_DONTNEED); 01732 totwritten = 0; 01733 } 01734 } 01735 } 01736 #endif 01737 return bytesWritten; 01738 } 01739 return -1; 01740 } 01741 01742 cUnbufferedFile *cUnbufferedFile::Create(const char *FileName, int Flags, mode_t Mode) 01743 { 01744 cUnbufferedFile *File = new cUnbufferedFile; 01745 if (File->Open(FileName, Flags, Mode) < 0) { 01746 delete File; 01747 File = NULL; 01748 } 01749 return File; 01750 } 01751 01752 // --- cLockFile ------------------------------------------------------------- 01753 01754 #define LOCKFILENAME ".lock-vdr" 01755 #define LOCKFILESTALETIME 600 // seconds before considering a lock file "stale" 01756 01757 cLockFile::cLockFile(const char *Directory) 01758 { 01759 fileName = NULL; 01760 f = -1; 01761 if (DirectoryOk(Directory)) 01762 fileName = strdup(AddDirectory(Directory, LOCKFILENAME)); 01763 } 01764 01765 cLockFile::~cLockFile() 01766 { 01767 Unlock(); 01768 free(fileName); 01769 } 01770 01771 bool cLockFile::Lock(int WaitSeconds) 01772 { 01773 if (f < 0 && fileName) { 01774 time_t Timeout = time(NULL) + WaitSeconds; 01775 do { 01776 f = open(fileName, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE); 01777 if (f < 0) { 01778 if (errno == EEXIST) { 01779 struct stat fs; 01780 if (stat(fileName, &fs) == 0) { 01781 if (abs(time(NULL) - fs.st_mtime) > LOCKFILESTALETIME) { 01782 esyslog("ERROR: removing stale lock file '%s'", fileName); 01783 if (remove(fileName) < 0) { 01784 LOG_ERROR_STR(fileName); 01785 break; 01786 } 01787 continue; 01788 } 01789 } 01790 else if (errno != ENOENT) { 01791 LOG_ERROR_STR(fileName); 01792 break; 01793 } 01794 } 01795 else { 01796 LOG_ERROR_STR(fileName); 01797 break; 01798 } 01799 if (WaitSeconds) 01800 cCondWait::SleepMs(1000); 01801 } 01802 } while (f < 0 && time(NULL) < Timeout); 01803 } 01804 return f >= 0; 01805 } 01806 01807 void cLockFile::Unlock(void) 01808 { 01809 if (f >= 0) { 01810 close(f); 01811 remove(fileName); 01812 f = -1; 01813 } 01814 } 01815 01816 // --- cListObject ----------------------------------------------------------- 01817 01818 cListObject::cListObject(void) 01819 { 01820 prev = next = NULL; 01821 } 01822 01823 cListObject::~cListObject() 01824 { 01825 } 01826 01827 void cListObject::Append(cListObject *Object) 01828 { 01829 next = Object; 01830 Object->prev = this; 01831 } 01832 01833 void cListObject::Insert(cListObject *Object) 01834 { 01835 prev = Object; 01836 Object->next = this; 01837 } 01838 01839 void cListObject::Unlink(void) 01840 { 01841 if (next) 01842 next->prev = prev; 01843 if (prev) 01844 prev->next = next; 01845 next = prev = NULL; 01846 } 01847 01848 int cListObject::Index(void) const 01849 { 01850 cListObject *p = prev; 01851 int i = 0; 01852 01853 while (p) { 01854 i++; 01855 p = p->prev; 01856 } 01857 return i; 01858 } 01859 01860 // --- cListBase ------------------------------------------------------------- 01861 01862 cListBase::cListBase(void) 01863 { 01864 objects = lastObject = NULL; 01865 count = 0; 01866 } 01867 01868 cListBase::~cListBase() 01869 { 01870 Clear(); 01871 } 01872 01873 void cListBase::Add(cListObject *Object, cListObject *After) 01874 { 01875 if (After && After != lastObject) { 01876 After->Next()->Insert(Object); 01877 After->Append(Object); 01878 } 01879 else { 01880 if (lastObject) 01881 lastObject->Append(Object); 01882 else 01883 objects = Object; 01884 lastObject = Object; 01885 } 01886 count++; 01887 } 01888 01889 void cListBase::Ins(cListObject *Object, cListObject *Before) 01890 { 01891 if (Before && Before != objects) { 01892 Before->Prev()->Append(Object); 01893 Before->Insert(Object); 01894 } 01895 else { 01896 if (objects) 01897 objects->Insert(Object); 01898 else 01899 lastObject = Object; 01900 objects = Object; 01901 } 01902 count++; 01903 } 01904 01905 void cListBase::Del(cListObject *Object, bool DeleteObject) 01906 { 01907 if (Object == objects) 01908 objects = Object->Next(); 01909 if (Object == lastObject) 01910 lastObject = Object->Prev(); 01911 Object->Unlink(); 01912 if (DeleteObject) 01913 delete Object; 01914 count--; 01915 } 01916 01917 void cListBase::Move(int From, int To) 01918 { 01919 Move(Get(From), Get(To)); 01920 } 01921 01922 void cListBase::Move(cListObject *From, cListObject *To) 01923 { 01924 if (From && To && From != To) { 01925 if (From->Index() < To->Index()) 01926 To = To->Next(); 01927 if (From == objects) 01928 objects = From->Next(); 01929 if (From == lastObject) 01930 lastObject = From->Prev(); 01931 From->Unlink(); 01932 if (To) { 01933 if (To->Prev()) 01934 To->Prev()->Append(From); 01935 From->Append(To); 01936 } 01937 else { 01938 lastObject->Append(From); 01939 lastObject = From; 01940 } 01941 if (!From->Prev()) 01942 objects = From; 01943 } 01944 } 01945 01946 void cListBase::Clear(void) 01947 { 01948 while (objects) { 01949 cListObject *object = objects->Next(); 01950 delete objects; 01951 objects = object; 01952 } 01953 objects = lastObject = NULL; 01954 count = 0; 01955 } 01956 01957 cListObject *cListBase::Get(int Index) const 01958 { 01959 if (Index < 0) 01960 return NULL; 01961 cListObject *object = objects; 01962 while (object && Index-- > 0) 01963 object = object->Next(); 01964 return object; 01965 } 01966 01967 static int CompareListObjects(const void *a, const void *b) 01968 { 01969 const cListObject *la = *(const cListObject **)a; 01970 const cListObject *lb = *(const cListObject **)b; 01971 return la->Compare(*lb); 01972 } 01973 01974 void cListBase::Sort(void) 01975 { 01976 int n = Count(); 01977 cListObject *a[n]; 01978 cListObject *object = objects; 01979 int i = 0; 01980 while (object && i < n) { 01981 a[i++] = object; 01982 object = object->Next(); 01983 } 01984 qsort(a, n, sizeof(cListObject *), CompareListObjects); 01985 objects = lastObject = NULL; 01986 for (i = 0; i < n; i++) { 01987 a[i]->Unlink(); 01988 count--; 01989 Add(a[i]); 01990 } 01991 } 01992 01993 // --- cHashBase ------------------------------------------------------------- 01994 01995 cHashBase::cHashBase(int Size) 01996 { 01997 size = Size; 01998 hashTable = (cList<cHashObject>**)calloc(size, sizeof(cList<cHashObject>*)); 01999 } 02000 02001 cHashBase::~cHashBase(void) 02002 { 02003 Clear(); 02004 free(hashTable); 02005 } 02006 02007 void cHashBase::Add(cListObject *Object, unsigned int Id) 02008 { 02009 unsigned int hash = hashfn(Id); 02010 if (!hashTable[hash]) 02011 hashTable[hash] = new cList<cHashObject>; 02012 hashTable[hash]->Add(new cHashObject(Object, Id)); 02013 } 02014 02015 void cHashBase::Del(cListObject *Object, unsigned int Id) 02016 { 02017 cList<cHashObject> *list = hashTable[hashfn(Id)]; 02018 if (list) { 02019 for (cHashObject *hob = list->First(); hob; hob = list->Next(hob)) { 02020 if (hob->object == Object) { 02021 list->Del(hob); 02022 break; 02023 } 02024 } 02025 } 02026 } 02027 02028 void cHashBase::Clear(void) 02029 { 02030 for (int i = 0; i < size; i++) { 02031 delete hashTable[i]; 02032 hashTable[i] = NULL; 02033 } 02034 } 02035 02036 cListObject *cHashBase::Get(unsigned int Id) const 02037 { 02038 cList<cHashObject> *list = hashTable[hashfn(Id)]; 02039 if (list) { 02040 for (cHashObject *hob = list->First(); hob; hob = list->Next(hob)) { 02041 if (hob->id == Id) 02042 return hob->object; 02043 } 02044 } 02045 return NULL; 02046 } 02047 02048 cList<cHashObject> *cHashBase::GetList(unsigned int Id) const 02049 { 02050 return hashTable[hashfn(Id)]; 02051 }