Added File manipulation methods. Added Time conversion methods. Thread last error string can now be obtained from the system.

git-svn-id: http://yate.null.ro/svn/yate/trunk@2416 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
marian 2009-01-09 14:30:05 +00:00
parent 4e51840e9e
commit 31350b4fbe
4 changed files with 553 additions and 17 deletions

View File

@ -52,6 +52,8 @@
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <utime.h>
#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);
}

View File

@ -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
{

View File

@ -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: */

View File

@ -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