diff --git a/engine/Socket.cpp b/engine/Socket.cpp index f3fe5b93..0f066a96 100644 --- a/engine/Socket.cpp +++ b/engine/Socket.cpp @@ -52,6 +52,8 @@ #include #include +#include +#include #endif #ifndef SHUT_RD @@ -73,6 +75,36 @@ using namespace TelEngine; static Mutex s_mutex; + +#ifdef _WINDOWS + +// The number of seconds from January 1, 1601 (Windows FILETIME) +// to EPOCH January 1, 1970 +#define FILETIME_EPOCH_SEC 11644473600 + +// Convert from FILETIME (100 nsec units since January 1, 1601) +// to time_t (seconds since January 1, 1970) +static inline unsigned int ftToEpoch(FILETIME& ft) +{ + // FILETIME in seconds + u_int64_t rval = ((ULARGE_INTEGER*)&ft)->QuadPart / 10000000; + // EPOCH time in seconds + rval -= FILETIME_EPOCH_SEC; + return (unsigned int)rval; +} + +// Convert from time_t (seconds since January 1, 1970) +// to FILETIME (100 nsec units since January 1, 1601) +static void epochToFt(unsigned int secEpoch, FILETIME& ft) +{ + u_int64_t time = (secEpoch + FILETIME_EPOCH_SEC) * 10000000; + ft.dwLowDateTime = (DWORD)time; + ft.dwHighDateTime = (DWORD)(time >> 32); +} + +#endif + + SocketAddr::SocketAddr(const struct sockaddr* addr, socklen_t len) : m_address(0), m_length(0) { @@ -536,8 +568,14 @@ bool File::openPath(const char* name, bool canWrite, bool canRead, copyError(); return false; } - if (append) - SetFilePointer(h,0,NULL,FILE_END); + // Move file pointer if append. Result might be the same as the error code + if (append && + ::SetFilePointer(h,0,NULL,FILE_END) == INVALID_SET_FILE_POINTER && + ::GetLastError() != NO_ERROR) { + copyError(); + ::CloseHandle(h); + return false; + } #else int flags = 0; if (canWrite) @@ -561,26 +599,55 @@ bool File::openPath(const char* name, bool canWrite, bool canRead, return true; } -unsigned int File::length() +int64_t File::length() { if (!valid()) return 0; #ifdef _WINDOWS - DWORD sz = GetFileSize(m_handle,NULL); - if (sz == (DWORD)-1) { + LARGE_INTEGER li; + li.LowPart = ::GetFileSize(m_handle,(LPDWORD)(&li.HighPart)); + if (li.LowPart == INVALID_FILE_SIZE && ::GetLastError() != NO_ERROR) { copyError(); - return 0; + return -1; } - return sz; + return li.QuadPart; #else - off_t pos = ::lseek(m_handle,0,SEEK_CUR); - if (pos == (off_t)-1) { + int64_t pos = seek(SeekCurrent); + if (pos < 0) { copyError(); return 0; } - off_t len = ::lseek(m_handle,0,SEEK_END); - ::lseek(m_handle,pos,SEEK_SET); - return (len == (off_t)-1) ? 0 : len; + int64_t len = seek(SeekEnd); + seek(SeekBegin,pos); + return len; +#endif +} + +// Set the file read/write pointer +int64_t File::seek(SeekPos pos, int64_t offset) +{ + if (!valid()) + return -1; +#ifdef _WINDOWS + int whence = (pos == SeekBegin) ? FILE_BEGIN : ((pos == SeekEnd) ? FILE_END : FILE_CURRENT); + LARGE_INTEGER li; + li.QuadPart = offset; + li.LowPart = ::SetFilePointer(m_handle,li.LowPart,&li.HighPart,whence); + // Check low 32bit value and the last error + // It might have the same as the error code + if (li.LowPart == INVALID_SET_FILE_POINTER && ::GetLastError() != NO_ERROR) { + copyError(); + return -1; + } + return li.QuadPart; +#else + int whence = (pos == SeekBegin) ? SEEK_SET : ((pos == SeekEnd) ? SEEK_END : SEEK_CUR); + off_t p = ::lseek(m_handle,(off_t)offset,whence); + if (p == (off_t)-1) { + copyError(); + return -1; + } + return (int64_t)p; #endif } @@ -652,11 +719,212 @@ bool File::createPipe(File& reader, File& writer) return false; } -bool File::remove(const char* name) +// Retrive the file's modification time (the file must be already opened) +bool File::getFileTime(unsigned int& secEpoch) { - if (null(name)) +#ifdef _WINDOWS + FILETIME ftWrite; + if (::GetFileTime(handle(),NULL,NULL,&ftWrite)) { + clearError(); + secEpoch = ftToEpoch(ftWrite); + return true; + } +#else + struct stat st; + if (0 == ::fstat(handle(),&st)) { + clearError(); + secEpoch = st.st_mtime; + return true; + } +#endif + copyError(); + return false; +} + +// Build the MD5 hex digest of an opened file. +bool File::md5(String& buffer) +{ + if (-1 == seek()) return false; - return !::unlink(name); + MD5 md5; + unsigned char buf[65536]; + bool ok = false; + unsigned int retry = 3; + while (retry) { + int n = readData(buf,sizeof(buf)); + if (n < 0) { + if (canRetry()) + retry--; + else + retry = 0; + continue; + } + if (n == 0) { + ok = true; + break; + } + DataBlock tmp(buf,n,false); + md5 << tmp; + tmp.clear(false); + } + if (ok) + buffer = md5.hexDigest(); + else + buffer = ""; + return ok; +} + + +// Set last error and return false +static inline bool getLastError(int* error) +{ + if (error) + *error = Thread::lastError(); + return false; +} + +// Check if a file name is non null +// Set error and return false if it is +static inline bool fileNameOk(const char* name, int* error) +{ + if (!null(name)) + return true; + if (error) +#ifdef _WINDOWS + *error = ERROR_INVALID_PARAMETER; +#else + *error = EINVAL; +#endif + return false; +} + +// Set a file's modification time +bool File::setFileTime(const char* name, unsigned int secEpoch, int* error) +{ + if (!fileNameOk(name,error)) + return false; +#ifdef _WINDOWS + File f; + if (f.openPath(name,true)) { + FILETIME ftWrite; + epochToFt(secEpoch,ftWrite); + bool ok = (0 != ::SetFileTime(f.handle(),NULL,NULL,&ftWrite)); + if (!ok && error) + *error = ::GetLastError(); + f.terminate(); + return ok; + } +#else + struct stat st; + if (0 == ::stat(name,&st)) { + struct utimbuf tb; + tb.actime = st.st_atime; + tb.modtime = secEpoch; + if (0 == ::utime(name,&tb)) + return true; + } +#endif + return getLastError(error); +} + +// Retrieve a file's modification time +bool File::getFileTime(const char* name, unsigned int& secEpoch, int* error) +{ + if (!fileNameOk(name,error)) + return false; +#ifdef _WINDOWS + WIN32_FILE_ATTRIBUTE_DATA fa; + if (::GetFileAttributesExA(name,GetFileExInfoStandard,&fa)) { + secEpoch = ftToEpoch(fa.ftLastWriteTime); + return true; + } +#else + struct stat st; + if (0 == ::stat(name,&st)) { + secEpoch = st.st_mtime; + return true; + } +#endif + return getLastError(error); +} + +// Check if a file exists +bool File::exists(const char* name, int* error) +{ + if (!fileNameOk(name,error)) + return false; +#ifdef _WINDOWS + WIN32_FIND_DATA d; + HANDLE h = ::FindFirstFile(name,&d); + if (h != invalidHandle()) { + ::FindClose(h); + return true; + } +#else + if (0 == ::access(name,F_OK)) + return true; +#endif + return getLastError(error); +} + +// Rename (move) a file (or directory) entry from the filesystem +bool File::rename(const char* oldFile, const char* newFile, int* error) +{ + if (!(fileNameOk(oldFile,error) && fileNameOk(newFile,error))) + return false; +#ifdef _WINDOWS + DWORD flags = MOVEFILE_COPY_ALLOWED | // Allow moving file on another volume + MOVEFILE_REPLACE_EXISTING | // Replace existing + MOVEFILE_WRITE_THROUGH; // Don't return until copy/delete is performed + if (::MoveFileExA(oldFile,newFile,flags)) + return true; +#else + if (0 == ::rename(oldFile,newFile)) + return true; +#endif + return getLastError(error); +} + +bool File::remove(const char* name, int* error) +{ + if (!fileNameOk(name,error)) + return false; +#ifdef _WINDOWS + if (::DeleteFileA(name)) + return true; +#else + if (0 == ::unlink(name)) + return true; +#endif + return getLastError(error); +} + +// Build the MD5 hex digest of a file. +bool File::md5(const char* name, String& buffer, int* error) +{ + File f; + bool ok = false; + if (f.openPath(name,false,true) && f.md5(buffer)) + ok = true; + else if (error) + *error = f.error(); + f.terminate(); + return ok; +} + +// Create a folder (directory) +bool File::mkDir(const char* path, int* error) +{ + if (!fileNameOk(path,error)) + return false; +#ifdef _WINDOWS + if (::CreateDirectoryA(path,NULL)) + return true; +#else + if (0 == ::mkdir(path,(mode_t)-1)) + return true; +#endif + return getLastError(error); } diff --git a/engine/TelEngine.cpp b/engine/TelEngine.cpp index 1022aa06..da5580fe 100644 --- a/engine/TelEngine.cpp +++ b/engine/TelEngine.cpp @@ -520,6 +520,83 @@ void Time::toTimeval(struct timeval* tv, u_int64_t usec) } } +// Build EPOCH time from date/time components +unsigned int Time::toEpoch(int year, unsigned int month, unsigned int day, + unsigned int hour, unsigned int minute, unsigned int sec, int offset) +{ + Debug(DebugAll,"Time::toEpoch(%d,%u,%u,%u,%u,%u,%d)", + year,month,day,hour,minute,sec,offset); + if (year < 1970) + return (unsigned int)-1; + if (month < 1 || month > 12 || !day) + return (unsigned int)-1; + if (hour == 24 && (minute || sec)) + return (unsigned int)-1; + else if (hour > 23 || minute > 59 || sec > 59) + return (unsigned int)-1; + // Check if month and day are correct in the given year + month--; + unsigned int m[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; + if (isLeap(year)) + m[1] = 29; + if (day > m[month]) + return (unsigned int)-1; + // Count the number of days since EPOCH + int64_t days = (year - 1970) * 365; + // Add a day for each leap year from 1970 to 'year' (not including) + for (int y = 1970; y < year; y += 4) + if (isLeap(y)) + days++; + // Add days ellapsed in given year + for (unsigned int i = 0; i < month; i++) + days += m[i]; + days += day - 1; + int64_t ret = (days * 24 + hour) * 3600 + minute * 60 + sec + offset; + + // Check for incorrect time or overflow + if (ret < 0 || ret > (unsigned int)-1) + return (unsigned int)-1; + return (unsigned int)ret; +} + +// Split a given EPOCH time into its date/time components +bool Time::toDateTime(unsigned int epochTimeSec, int& year, unsigned int& month, + unsigned int& day, unsigned int& hour, unsigned int& minute, unsigned int& sec) +{ +#ifdef _WINDOWS + FILETIME ft; + SYSTEMTIME st; + // 11644473600: the number of seconds from 1601, January 1st (FILETIME) + // to EPOCH (1970, January 1st) + // Remember: FILETIME keeps the number of 100 nsec units + u_int64_t time = (11644473600 + epochTimeSec) * 10000000; + ft.dwLowDateTime = (DWORD)time; + ft.dwHighDateTime = (DWORD)(time >> 32); + if (!FileTimeToSystemTime(&ft,&st)) + return false; + year = st.wYear; + month = st.wMonth; + day = st.wDay; + hour = st.wHour; + minute = st.wMinute; + sec = st.wSecond; +#else + struct tm t; + time_t time = (time_t)epochTimeSec; + if (!gmtime_r(&time,&t)) + return false; + year = 1900 + t.tm_year; + month = t.tm_mon + 1; + day = t.tm_mday; + hour = t.tm_hour; + minute = t.tm_min; + sec = t.tm_sec; +#endif + Debug(DebugAll,"Time::toDateTime(%u,%d,%u,%u,%u,%u,%u)", + epochTimeSec,year,month,day,hour,minute,sec); + return true; +} + bool GenObject::alive() const { diff --git a/engine/Thread.cpp b/engine/Thread.cpp index 998c0735..819d5026 100644 --- a/engine/Thread.cpp +++ b/engine/Thread.cpp @@ -640,4 +640,36 @@ void Thread::preExec() #endif } +// Get the last thread error +int Thread::lastError() +{ +#ifdef _WINDOWS + return ::GetLastError(); +#else + return errno; +#endif +} + +// Get an error string from system. +bool Thread::errorString(String& buffer, int code) +{ +#ifdef _WINDOWS + LPTSTR buf = 0; + DWORD res = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL,code,0,(LPTSTR)&buf,0,0); + if (buf) { + if (res > 0) + buffer.assign(buf,res); + ::LocalFree(buf); + } +#else + buffer = ::strerror(code); +#endif + if (buffer) + return true; + buffer << "Unknown error (code=" << code << ")"; + return false; +} + /* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/yateclass.h b/yateclass.h index 020ee6e1..af649f84 100644 --- a/yateclass.h +++ b/yateclass.h @@ -2549,6 +2549,44 @@ public: */ static u_int32_t secNow(); + /** + * Build EPOCH time from date/time components + * @param year The year component of the date. Must be greater then 1969 + * @param month The month component of the date (1 to 12) + * @param day The day component of the date (1 to 31) + * @param hour The hour component of the time (0 to 23). The hour can be 24 + * if minute and sec are 0 + * @param minute The minute component of the time (0 to 59) + * @param sec The seconds component of the time (0 to 59) + * @param offset Optional number of seconds to be added/substracted + * to/from result. It can't exceed the number of seconds in a day + * @return EPOCH time in seconds, -1 on failure + */ + static unsigned int toEpoch(int year, unsigned int month, unsigned int day, + unsigned int hour, unsigned int minute, unsigned int sec, int offset = 0); + + /** + * Split a given EPOCH time into its date/time components + * @param epochTimeSec EPOCH time in seconds + * @param year The year component of the date + * @param month The month component of the date (1 to 12) + * @param day The day component of the date (1 to 31) + * @param hour The hour component of the time (0 to 23) + * @param minute The minute component of the time (0 to 59) + * @param sec The seconds component of the time (0 to 59) + * @return True on succes, false if conversion failed + */ + static bool toDateTime(unsigned int epochTimeSec, int& year, unsigned int& month, + unsigned int& day, unsigned int& hour, unsigned int& minute, unsigned int& sec); + + /** + * Check if an year is a leap one + * @param year The year to check + * @return True if the given year is a leap one + */ + static inline bool isLeap(unsigned int year) + { return (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)); } + private: u_int64_t m_time; }; @@ -3771,6 +3809,34 @@ public: */ static void preExec(); + /** + * Get the last thread error + * @return The value returned by GetLastError() (on Windows) or + * the value of C library 'errno' variable otherwise + */ + static int lastError(); + + /** + * Get the last thread error's string from system. + * @param buffer The destination string + * @return True if an error string was retrieved. If false is returned, the buffer + * is filled with a generic string indicating an unknown error and its code + */ + static inline bool errorString(String& buffer) + { return errorString(buffer,lastError()); } + + /** + * Get an error string from system. + * On Windows the code parameter must be a code returned by GetLastError(). + * Otherwise, the error code should be a valid value for the C library 'errno' + * variable + * @param buffer The destination string + * @param code The error code + * @return True if an error string was retrieved. If false is returned, the buffer + * is filled with a generic string indicating an unknown error and its code + */ + static bool errorString(String& buffer, int code); + protected: /** * Creates and starts a new thread @@ -4160,6 +4226,15 @@ protected: class YATE_API File : public Stream { public: + /** + * Enumerate seek start position + */ + enum SeekPos { + SeekBegin, // Seek from file begine + SeekEnd, // Seek from file end + SeekCurrent // Seek from current position + }; + /** * Default constructor, creates a closed file */ @@ -4243,7 +4318,23 @@ public: * Find the length of the file if it has one * @return Length of the file or zero if length is not defined */ - virtual unsigned int length(); + virtual int64_t length(); + + /** + * Set the file read/write pointer + * @param pos The seek start as enumeration + * @param offset The number of bytes to move the pointer from starting position + * @return The new position of the file read/write pointer. Negative on failure + */ + virtual int64_t seek(SeekPos pos, int64_t offset = 0); + + /** + * Set the file read/write pointer from begin of file + * @param offset The position in file to move the pointer + * @return The new position of the file read/write pointer. Negative on failure + */ + inline int64_t seek(int64_t offset = 0) + { return seek(SeekBegin,offset); } /** * Write data to an open file @@ -4261,12 +4352,80 @@ public: */ virtual int readData(void* buffer, int length); + /** + * Retrive the file's modification time (the file must be already opened) + * @param secEpoch File creation time (seconds since Epoch) + * @return True on success + */ + bool getFileTime(unsigned int& secEpoch); + + /** + * Build the MD5 hex digest of a file. The file must be opened for read access. + * This method will move the file pointer + * @param buffer Destination buffer + * @return True on success + */ + virtual bool md5(String& buffer); + + /** + * Set a file's modification time. + * @param name Path and name of the file + * @param secEpoch File modification time (seconds since Epoch) + * @param error Optional pointer to error code to be filled on failure + * @return True on success + */ + static bool setFileTime(const char* name, unsigned int secEpoch, int* error = 0); + + /** + * Retrieve a file's modification time + * @param name Path and name of the file + * @param secEpoch File modification time (seconds since Epoch) + * @param error Optional pointer to error code to be filled on failure + * @return True on success + */ + static bool getFileTime(const char* name, unsigned int& secEpoch, int* error = 0); + + /** + * Check if a file exists + * @param name The file to check + * @param error Optional pointer to error code to be filled on failure + * @return True if the file exists + */ + static bool exists(const char* name, int* error = 0); + + /** + * Rename (move) a file (or directory) entry from the filesystem + * @param oldFile Path and name of the file to rename + * @param newFile The new path and name of the file + * @param error Optional pointer to error code to be filled on failure + * @return True if the file was successfully renamed (moved) + */ + static bool rename(const char* oldFile, const char* newFile, int* error = 0); + /** * Deletes a file entry from the filesystem * @param name Absolute path and name of the file to delete + * @param error Optional pointer to error code to be filled on failure * @return True if the file was successfully deleted */ - static bool remove(const char* name); + static bool remove(const char* name, int* error = 0); + + /** + * Build the MD5 hex digest of a file. + * @param name The file to build MD5 from + * @param buffer Destination buffer + * @param error Optional pointer to error code to be filled on failure + * @return True on success + */ + static bool md5(const char* name, String& buffer, int* error = 0); + + /** + * Create a folder (directory). It only creates the last directory in the path + * @param path The folder path + * @param error Optional pointer to error code to be filled on failure + * @return True on success + */ + static bool mkDir(const char* path, int* error = 0); /** * Create a pair of unidirectionally pipe connected streams