mirror of
https://github.com/anope/anope.git
synced 2026-06-25 19:56:39 +02:00
23f5e9de79
git-svn-id: http://anope.svn.sourceforge.net/svnroot/anope/trunk@2115 5417fbe8-f217-4b02-8779-1006273d7864
760 lines
17 KiB
C
760 lines
17 KiB
C
/* Database file handling routines.
|
|
*
|
|
* (C) 2003-2009 Anope Team
|
|
* Contact us at team@anope.org
|
|
*
|
|
* Please read COPYING and README for further details.
|
|
*
|
|
* Based on the original code of Epona by Lara.
|
|
* Based on the original code of Services by Andy Church.
|
|
*
|
|
* $Id$
|
|
*
|
|
*/
|
|
|
|
#include "services.h"
|
|
#include "datafiles.h"
|
|
#include "modules.h"
|
|
#include <fcntl.h>
|
|
|
|
static int curday = 0;
|
|
static time_t lastwarn = 0;
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Return the version number on the file. Return 0 if there is no version
|
|
* number or the number doesn't make sense (i.e. less than 1 or greater
|
|
* than FILE_VERSION).
|
|
* @param f dbFile Struct Member
|
|
* @return int 0 if failure, 1 > is the version number
|
|
*/
|
|
int get_file_version(dbFILE * f)
|
|
{
|
|
FILE *fp = f->fp;
|
|
int version =
|
|
fgetc(fp) << 24 | fgetc(fp) << 16 | fgetc(fp) << 8 | fgetc(fp);
|
|
if (ferror(fp)) {
|
|
log_perror("Error reading version number on %s", f->filename);
|
|
return 0;
|
|
} else if (feof(fp)) {
|
|
alog("Error reading version number on %s: End of file detected",
|
|
f->filename);
|
|
return 0;
|
|
} else if (version < 1) {
|
|
alog("Invalid version number (%d) on %s", version, f->filename);
|
|
return 0;
|
|
}
|
|
return version;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Write the current version number to the file.
|
|
* @param f dbFile Struct Member
|
|
* @return 0 on error, 1 on success.
|
|
*/
|
|
int write_file_version(dbFILE * f, uint32 version)
|
|
{
|
|
FILE *fp = f->fp;
|
|
if (fputc(version >> 24 & 0xFF, fp) < 0 ||
|
|
fputc(version >> 16 & 0xFF, fp) < 0 ||
|
|
fputc(version >> 8 & 0xFF, fp) < 0 ||
|
|
fputc(version & 0xFF, fp) < 0) {
|
|
log_perror("Error writing version number on %s", f->filename);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Open the database for reading
|
|
* @param service If error whom to return the error as
|
|
* @param filename File to open as the database
|
|
* @return dbFile struct
|
|
*/
|
|
static dbFILE *open_db_read(const char *service, const char *filename)
|
|
{
|
|
dbFILE *f;
|
|
FILE *fp;
|
|
|
|
f = new dbFILE;
|
|
if (!f) {
|
|
log_perror("Can't read %s database %s", service, filename);
|
|
if (time(NULL) - lastwarn > WarningTimeout) {
|
|
ircdproto->SendGlobops(NULL,
|
|
"Write error on %s: Memory allocation failed",
|
|
filename);
|
|
lastwarn = time(NULL);
|
|
}
|
|
return NULL;
|
|
}
|
|
strscpy(f->filename, filename, sizeof(f->filename));
|
|
f->mode = 'r';
|
|
fp = fopen(f->filename, "rb");
|
|
if (!fp) {
|
|
int errno_save = errno;
|
|
if (errno != ENOENT)
|
|
log_perror("Can not read %s database %s", service,
|
|
f->filename);
|
|
if (time(NULL) - lastwarn > WarningTimeout) {
|
|
ircdproto->SendGlobops(NULL, "Write error on %s: %s", f->filename,
|
|
strerror(errno));
|
|
lastwarn = time(NULL);
|
|
}
|
|
delete f;
|
|
errno = errno_save;
|
|
return NULL;
|
|
}
|
|
f->fp = fp;
|
|
f->backupfp = NULL;
|
|
return f;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Open the database for writting
|
|
* @param service If error whom to return the error as
|
|
* @param filename File to open as the database
|
|
* @param version Database Version
|
|
* @return dbFile struct
|
|
*/
|
|
static dbFILE *open_db_write(const char *service, const char *filename,
|
|
uint32 version)
|
|
{
|
|
dbFILE *f;
|
|
int fd;
|
|
#ifdef _WIN32
|
|
char buffer[_MAX_PATH];
|
|
char win32filename[MAXPATHLEN];
|
|
|
|
/* Get the current working directory: */
|
|
if (_getcwd(buffer, _MAX_PATH) == NULL) {
|
|
alog("Warning: Unable to set Current working directory");
|
|
}
|
|
#endif
|
|
|
|
f = new dbFILE;
|
|
if (!f) {
|
|
log_perror("Can not read %s database %s", service, filename);
|
|
return NULL;
|
|
}
|
|
strscpy(f->filename, filename, sizeof(f->filename));
|
|
#ifndef _WIN32
|
|
filename = f->filename;
|
|
#else
|
|
snprintf(win32filename, sizeof(win32filename), "%s\\%s", buffer,
|
|
f->filename);
|
|
filename = win32filename;
|
|
#endif
|
|
f->mode = 'w';
|
|
|
|
*f->backupname = 0;
|
|
snprintf(f->backupname, sizeof(f->backupname), "%s.save", filename);
|
|
if (!*f->backupname || strcmp(f->backupname, filename) == 0) {
|
|
int errno_save = errno;
|
|
alog("Opening %s database %s for write: Filename too long",
|
|
service, filename);
|
|
delete f;
|
|
errno = errno_save;
|
|
return NULL;
|
|
}
|
|
#ifndef _WIN32
|
|
unlink(filename);
|
|
#else
|
|
DeleteFile(filename);
|
|
#endif
|
|
f->backupfp = fopen(filename, "rb");
|
|
#ifdef _WIN32
|
|
if (!MoveFileExA(filename, f->backupname, MOVEFILE_COPY_ALLOWED)
|
|
&& GetLastError() != ENOENT) {
|
|
int errno_save = GetLastError();
|
|
#else
|
|
if (rename(filename, f->backupname) < 0 && errno != ENOENT) {
|
|
int errno_save = errno;
|
|
#endif
|
|
|
|
static int walloped = 0;
|
|
if (!walloped) {
|
|
walloped++;
|
|
ircdproto->SendGlobops(NULL, "Can not back up %s database %s",
|
|
service, filename);
|
|
}
|
|
#ifdef _WIN32
|
|
if (debug) {
|
|
if (errno == ENOENT) {
|
|
alog("debug: Error %d (ENOENT) : the file or directory does not exist", errno);
|
|
} else if (errno == EACCES) {
|
|
alog("debug: Error %d (EACCES) : error while attempting to access file", errno);
|
|
} else {
|
|
alog("debug: Error %d", errno);
|
|
}
|
|
}
|
|
#else
|
|
if (debug) {
|
|
alog("debug: Error %d", errno);
|
|
}
|
|
#endif
|
|
errno = errno_save;
|
|
log_perror("Can not back up %s database %s", service, filename);
|
|
if (!NoBackupOkay) {
|
|
if (f->backupfp)
|
|
fclose(f->backupfp);
|
|
delete f;
|
|
errno = errno_save;
|
|
return NULL;
|
|
}
|
|
*f->backupname = 0;
|
|
}
|
|
#ifndef _WIN32
|
|
unlink(filename);
|
|
#else
|
|
DeleteFile(filename);
|
|
#endif
|
|
/* Use open() to avoid people sneaking a new file in under us */
|
|
#ifndef _WIN32
|
|
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
|
|
#else
|
|
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL | _O_BINARY, 0666);
|
|
#endif
|
|
f->fp = fdopen(fd, "wb"); /* will fail and return NULL if fd < 0 */
|
|
if (!f->fp || !write_file_version(f, version)) {
|
|
int errno_save = errno;
|
|
static int walloped = 0;
|
|
if (!walloped) {
|
|
walloped++;
|
|
ircdproto->SendGlobops(NULL, "Can't write to %s database %s",
|
|
service, filename);
|
|
}
|
|
errno = errno_save;
|
|
log_perror("Can't write to %s database %s", service, filename);
|
|
if (f->fp) {
|
|
fclose(f->fp);
|
|
#ifndef _WIN32
|
|
unlink(filename);
|
|
#else
|
|
DeleteFile(filename);
|
|
#endif
|
|
}
|
|
if (*f->backupname && rename(f->backupname, filename) < 0)
|
|
log_perror("Cannot restore backup copy of %s", filename);
|
|
|
|
/* Then the Lord said unto Moses, thou shalt free what thou hast malloced
|
|
* -- codemastr */
|
|
delete f;
|
|
errno = errno_save;
|
|
return NULL;
|
|
}
|
|
return f;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Open a database file for reading (*mode == 'r') or writing (*mode == 'w').
|
|
* Return the stream pointer, or NULL on error. When opening for write, it
|
|
* is an error for rename() to return an error (when backing up the original
|
|
* file) other than ENOENT, if NO_BACKUP_OKAY is not defined; it is an error
|
|
* if the version number cannot be written to the file; and it is a fatal
|
|
* error if opening the file for write fails and the backup was successfully
|
|
* made but cannot be restored.
|
|
* @param service If error whom to return the error as
|
|
* @param filename File to open as the database
|
|
* @param mode Mode for writting or reading
|
|
* @param version Database Version
|
|
* @return dbFile struct
|
|
*/
|
|
dbFILE *open_db(const char *service, const char *filename,
|
|
const char *mode, uint32 version)
|
|
{
|
|
if (*mode == 'r') {
|
|
return open_db_read(service, filename);
|
|
} else if (*mode == 'w') {
|
|
return open_db_write(service, filename, version);
|
|
} else {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Restore the database file to its condition before open_db(). This is
|
|
* identical to close_db() for files open for reading; however, for files
|
|
* open for writing, we first attempt to restore any backup file before
|
|
* closing files.
|
|
* @param dbFile struct
|
|
* @return void
|
|
*/
|
|
void restore_db(dbFILE * f)
|
|
{
|
|
int errno_save = errno;
|
|
|
|
if (f->mode == 'w') {
|
|
int ok = 0; /* Did we manage to restore the old file? */
|
|
errno = errno_save = 0;
|
|
if (*f->backupname && strcmp(f->backupname, f->filename) != 0) {
|
|
if (rename(f->backupname, f->filename) == 0)
|
|
ok = 1;
|
|
}
|
|
if (!ok && f->backupfp) {
|
|
char buf[1024];
|
|
unsigned int i;
|
|
ok = 1;
|
|
if (fseek(f->fp, 0, SEEK_SET) < 0)
|
|
ok = 0;
|
|
while (ok && (i = fread(buf, 1, sizeof(buf), f->backupfp)) > 0) {
|
|
if (fwrite(buf, 1, i, f->fp) != i)
|
|
ok = 0;
|
|
}
|
|
if (ok) {
|
|
fflush(f->fp);
|
|
ftruncate(fileno(f->fp), ftell(f->fp));
|
|
}
|
|
}
|
|
if (!ok && errno > 0)
|
|
log_perror("Unable to restore backup of %s", f->filename);
|
|
errno_save = errno;
|
|
if (f->backupfp)
|
|
fclose(f->backupfp);
|
|
if (*f->backupname)
|
|
#ifndef _WIN32
|
|
unlink(f->backupname);
|
|
#else
|
|
DeleteFile(f->backupname);
|
|
#endif
|
|
}
|
|
fclose(f->fp);
|
|
if (!errno_save)
|
|
errno_save = errno;
|
|
delete f;
|
|
errno = errno_save;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Close a database file. If the file was opened for write, remove the
|
|
* backup we (may have) created earlier.
|
|
* @param dbFile struct
|
|
* @return void
|
|
*/
|
|
void close_db(dbFILE * f)
|
|
{
|
|
if (f->mode == 'w' && *f->backupname
|
|
&& strcmp(f->backupname, f->filename) != 0) {
|
|
if (f->backupfp)
|
|
fclose(f->backupfp);
|
|
#ifndef _WIN32
|
|
unlink(f->backupname);
|
|
#else
|
|
DeleteFile(f->backupname);
|
|
#endif
|
|
}
|
|
fclose(f->fp);
|
|
delete f;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Read and write 2- and 4-byte quantities, pointers, and strings. All
|
|
* multibyte values are stored in big-endian order (most significant byte
|
|
* first). A pointer is stored as a byte, either 0 if NULL or 1 if not,
|
|
* and read pointers are returned as either (void *)0 or (void *)1. A
|
|
* string is stored with a 2-byte unsigned length (including the trailing
|
|
* \0) first; a length of 0 indicates that the string pointer is NULL.
|
|
* Written strings are truncated silently at 65534 bytes, and are always
|
|
* null-terminated.
|
|
*
|
|
* @param ret 16bit integer to write
|
|
* @param dbFile struct
|
|
* @return -1 on error, 0 otherwise.
|
|
*/
|
|
int read_int16(uint16 * ret, dbFILE * f)
|
|
{
|
|
int c1, c2;
|
|
|
|
c1 = fgetc(f->fp);
|
|
c2 = fgetc(f->fp);
|
|
if (c1 == EOF || c2 == EOF)
|
|
return -1;
|
|
*ret = c1 << 8 | c2;
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Write a 16bit integer
|
|
*
|
|
* @param ret 16bit integer to write
|
|
* @param dbFile struct
|
|
* @return -1 on error, 0 otherwise.
|
|
*/
|
|
int write_int16(uint16 val, dbFILE * f)
|
|
{
|
|
if (fputc((val >> 8) & 0xFF, f->fp) == EOF
|
|
|| fputc(val & 0xFF, f->fp) == EOF) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Read a unsigned 32bit integer
|
|
*
|
|
* @param ret unsigned 32bit integer to read
|
|
* @param dbFile struct
|
|
* @return -1 on error, 0 otherwise.
|
|
*/
|
|
int read_int32(uint32 * ret, dbFILE * f)
|
|
{
|
|
int c1, c2, c3, c4;
|
|
|
|
c1 = fgetc(f->fp);
|
|
c2 = fgetc(f->fp);
|
|
c3 = fgetc(f->fp);
|
|
c4 = fgetc(f->fp);
|
|
if (c1 == EOF || c2 == EOF || c3 == EOF || c4 == EOF)
|
|
return -1;
|
|
*ret = c1 << 24 | c2 << 16 | c3 << 8 | c4;
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Write a unsigned 32bit integer
|
|
*
|
|
* @param ret unsigned 32bit integer to write
|
|
* @param dbFile struct
|
|
* @return -1 on error, 0 otherwise.
|
|
*/
|
|
int write_int32(uint32 val, dbFILE * f)
|
|
{
|
|
if (fputc((val >> 24) & 0xFF, f->fp) == EOF)
|
|
return -1;
|
|
if (fputc((val >> 16) & 0xFF, f->fp) == EOF)
|
|
return -1;
|
|
if (fputc((val >> 8) & 0xFF, f->fp) == EOF)
|
|
return -1;
|
|
if (fputc((val) & 0xFF, f->fp) == EOF)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Read Pointer
|
|
*
|
|
* @param ret pointer to read
|
|
* @param dbFile struct
|
|
* @return -1 on error, 0 otherwise.
|
|
*/
|
|
int read_ptr(void **ret, dbFILE * f)
|
|
{
|
|
int c;
|
|
|
|
c = fgetc(f->fp);
|
|
if (c == EOF)
|
|
return -1;
|
|
*ret = (c ? reinterpret_cast<void *>(1) : reinterpret_cast<void *>(0));
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Write Pointer
|
|
*
|
|
* @param ret pointer to write
|
|
* @param dbFile struct
|
|
* @return -1 on error, 0 otherwise.
|
|
*/
|
|
int write_ptr(const void *ptr, dbFILE * f)
|
|
{
|
|
if (fputc(ptr ? 1 : 0, f->fp) == EOF)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Read String
|
|
*
|
|
* @param ret string
|
|
* @param dbFile struct
|
|
* @return -1 on error, 0 otherwise.
|
|
*/
|
|
int read_string(char **ret, dbFILE * f)
|
|
{
|
|
char *s;
|
|
uint16 len;
|
|
|
|
if (read_int16(&len, f) < 0)
|
|
return -1;
|
|
if (len == 0) {
|
|
*ret = NULL;
|
|
return 0;
|
|
}
|
|
s = new char[len];
|
|
if (len != fread(s, 1, len, f->fp)) {
|
|
delete [] s;
|
|
return -1;
|
|
}
|
|
*ret = s;
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Write String
|
|
*
|
|
* @param ret string
|
|
* @param dbFile struct
|
|
* @return -1 on error, 0 otherwise.
|
|
*/
|
|
int write_string(const char *s, dbFILE * f)
|
|
{
|
|
uint32 len;
|
|
|
|
if (!s)
|
|
return write_int16(0, f);
|
|
len = strlen(s);
|
|
if (len > 65534)
|
|
len = 65534;
|
|
if (write_int16(static_cast<uint16>(len + 1), f) < 0)
|
|
return -1;
|
|
if (len > 0 && fwrite(s, 1, len, f->fp) != len)
|
|
return -1;
|
|
if (fputc(0, f->fp) == EOF)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Renames a database
|
|
*
|
|
* @param name Database to name
|
|
* @param ext Extention
|
|
* @return void
|
|
*/
|
|
static void rename_database(const char *name, char *ext)
|
|
{
|
|
|
|
char destpath[PATH_MAX];
|
|
|
|
snprintf(destpath, sizeof(destpath), "backups/%s.%s", name, ext);
|
|
if (rename(name, destpath) != 0) {
|
|
alog("Backup of %s failed.", name);
|
|
ircdproto->SendGlobops(s_OperServ, "WARNING! Backup of %s failed.",
|
|
name);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Removes old databases
|
|
*
|
|
* @return void
|
|
*/
|
|
static void remove_backups()
|
|
{
|
|
|
|
char ext[9];
|
|
char path[PATH_MAX];
|
|
|
|
time_t t;
|
|
struct tm tm;
|
|
|
|
time(&t);
|
|
t -= (60 * 60 * 24 * KeepBackups);
|
|
tm = *localtime(&t);
|
|
strftime(ext, sizeof(ext), "%Y%m%d", &tm);
|
|
|
|
snprintf(path, sizeof(path), "backups/%s.%s", NickDBName, ext);
|
|
#ifndef _WIN32
|
|
unlink(path);
|
|
#else
|
|
DeleteFile(path);
|
|
#endif
|
|
snprintf(path, sizeof(path), "backups/%s.%s", ChanDBName, ext);
|
|
#ifndef _WIN32
|
|
unlink(path);
|
|
#else
|
|
DeleteFile(path);
|
|
#endif
|
|
snprintf(path, sizeof(path), "backups/%s.%s", OperDBName, ext);
|
|
#ifndef _WIN32
|
|
unlink(path);
|
|
#else
|
|
DeleteFile(path);
|
|
#endif
|
|
snprintf(path, sizeof(path), "backups/%s.%s", NewsDBName, ext);
|
|
#ifndef _WIN32
|
|
unlink(path);
|
|
#else
|
|
DeleteFile(path);
|
|
#endif
|
|
snprintf(path, sizeof(path), "backups/%s.%s", ExceptionDBName, ext);
|
|
#ifndef _WIN32
|
|
unlink(path);
|
|
#else
|
|
DeleteFile(path);
|
|
#endif
|
|
|
|
if (s_BotServ) {
|
|
snprintf(path, sizeof(path), "backups/%s.%s", BotDBName, ext);
|
|
#ifndef _WIN32
|
|
unlink(path);
|
|
#else
|
|
DeleteFile(path);
|
|
#endif
|
|
}
|
|
if (s_HostServ) {
|
|
snprintf(path, sizeof(path), "backups/%s.%s", HostDBName, ext);
|
|
#ifndef _WIN32
|
|
unlink(path);
|
|
#else
|
|
DeleteFile(path);
|
|
#endif
|
|
}
|
|
if (NSEmailReg) {
|
|
snprintf(path, sizeof(path), "backups/%s.%s", PreNickDBName, ext);
|
|
#ifndef _WIN32
|
|
unlink(path);
|
|
#else
|
|
DeleteFile(path);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* Handles database backups.
|
|
*
|
|
* @return void
|
|
*/
|
|
void backup_databases()
|
|
{
|
|
|
|
time_t t;
|
|
struct tm tm;
|
|
|
|
if (!KeepBackups) {
|
|
return;
|
|
}
|
|
|
|
time(&t);
|
|
tm = *localtime(&t);
|
|
|
|
if (!curday) {
|
|
curday = tm.tm_yday;
|
|
return;
|
|
}
|
|
|
|
if (curday != tm.tm_yday) {
|
|
|
|
char ext[9];
|
|
|
|
|
|
alog("Backing up databases");
|
|
FOREACH_MOD(I_OnBackupDatabase, OnBackupDatabase())
|
|
|
|
remove_backups();
|
|
|
|
curday = tm.tm_yday;
|
|
strftime(ext, sizeof(ext), "%Y%m%d", &tm);
|
|
|
|
rename_database(NickDBName, ext);
|
|
if (s_BotServ) {
|
|
rename_database(BotDBName, ext);
|
|
}
|
|
rename_database(ChanDBName, ext);
|
|
if (s_HostServ) {
|
|
rename_database(HostDBName, ext);
|
|
}
|
|
if (NSEmailReg) {
|
|
rename_database(PreNickDBName, ext);
|
|
}
|
|
|
|
rename_database(OperDBName, ext);
|
|
rename_database(NewsDBName, ext);
|
|
rename_database(ExceptionDBName, ext);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ModuleDatabaseBackup(const char *dbname)
|
|
{
|
|
|
|
time_t t;
|
|
struct tm tm;
|
|
|
|
if (!KeepBackups) {
|
|
return;
|
|
}
|
|
|
|
time(&t);
|
|
tm = *localtime(&t);
|
|
|
|
if (!curday) {
|
|
curday = tm.tm_yday;
|
|
return;
|
|
}
|
|
|
|
if (curday != tm.tm_yday) {
|
|
|
|
char ext[9];
|
|
|
|
if (debug) {
|
|
alog("Module Database Backing up %s", dbname);
|
|
}
|
|
ModuleRemoveBackups(dbname);
|
|
curday = tm.tm_yday;
|
|
strftime(ext, sizeof(ext), "%Y%m%d", &tm);
|
|
rename_database(dbname, ext);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ModuleRemoveBackups(const char *dbname)
|
|
{
|
|
char ext[9];
|
|
char path[PATH_MAX];
|
|
|
|
time_t t;
|
|
struct tm tm;
|
|
|
|
time(&t);
|
|
t -= (60 * 60 * 24 * KeepBackups);
|
|
tm = *localtime(&t);
|
|
strftime(ext, sizeof(ext), "%Y%m%d", &tm);
|
|
|
|
snprintf(path, sizeof(path), "backups/%s.%s", dbname, ext);
|
|
#ifndef _WIN32
|
|
unlink(path);
|
|
#else
|
|
DeleteFile(path);
|
|
#endif
|
|
}
|
|
|
|
/*************************************************************************/
|