Skip to content

Commit

Permalink
Allow rotation of sparse files with copytruncate
Browse files Browse the repository at this point in the history
  • Loading branch information
jkaluza committed Apr 2, 2015
1 parent 2c583ab commit f1dc0d9
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 15 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
VERSION = $(shell awk '/Version:/ { print $$2 }' logrotate.spec)
OS_NAME = $(shell uname -s)
LFS = $(shell echo `getconf LFS_CFLAGS 2>/dev/null`)
CFLAGS = -Wall -D_GNU_SOURCE -D$(OS_NAME) -DVERSION=\"$(VERSION)\" -DHAVE_STRPTIME=1 -DHAVE_QSORT $(RPM_OPT_FLAGS) $(LFS)
CFLAGS = -Wall -D_GNU_SOURCE -D$(OS_NAME) -DVERSION=\"$(VERSION)\" -DHAVE_STRPTIME=1 -DHAVE_QSORT -DHAVE_STRUCT_STAT_ST_BLOCKS -DHAVE_STRUCT_STAT_ST_BLKSIZE $(RPM_OPT_FLAGS) $(LFS)
PROG = logrotate
MAN = logrotate.8
MAN5 = logrotate.conf.5
Expand Down
2 changes: 2 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ AM_EXTRA_RECURSIVE_TARGETS([test])

AC_PROG_CC
AC_PROG_CC_STDC
AC_STRUCT_ST_BLKSIZE
AC_STRUCT_ST_BLOCKS

AC_CHECK_LIB([popt],[poptParseArgvString],,
AC_MSG_ERROR([libpopt required but not found]))
Expand Down
146 changes: 132 additions & 14 deletions logrotate.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <locale.h>
#include <sys/types.h>
#include <utime.h>
#include <stdint.h>

#if defined(SunOS)
#include <limits.h>
Expand Down Expand Up @@ -738,12 +739,140 @@ static int mailLogWrapper(char *mailFilename, char *mailCommand,
return 0;
}

/* Use a heuristic to determine whether stat buffer SB comes from a file
with sparse blocks. If the file has fewer blocks than would normally
be needed for a file of its size, then at least one of the blocks in
the file is a hole. In that case, return true. */
static int is_probably_sparse(struct stat const *sb)
{
#if defined(HAVE_STRUCT_STAT_ST_BLOCKS) && defined(HAVE_STRUCT_STAT_ST_BLKSIZE)
return (S_ISREG (sb->st_mode)
&& sb->st_blocks < sb->st_size / sb->st_blksize);
#else
return 0;
#endif
}

#define MIN(a,b) ((a) < (b) ? (a) : (b))

/* Return whether the buffer consists entirely of NULs.
Note the word after the buffer must be non NUL. */

static inline int is_nul (void const *buf, size_t bufsize)
{
void const *vp;
char const *cbuf = buf;
unsigned int const *wp = buf;

/* Find first nonzero *word*, or the word with the sentinel. */
while (*wp++ == 0)
continue;

/* Find the first nonzero *byte*, or the sentinel. */
vp = wp - 1;
char const *cp = vp;
while (*cp++ == 0)
continue;

return cbuf + bufsize < cp;
}

static size_t full_write(int fd, const void *buf, size_t count)
{
size_t total = 0;
const char *ptr = (const char *) buf;

while (count > 0)
{
size_t n_rw;
for (;;)
{
n_rw = write (fd, buf, count);
if (errno == EINTR)
continue;
else
break;
}
if (n_rw == (size_t) -1)
break;
if (n_rw == 0)
break;
total += n_rw;
ptr += n_rw;
count -= n_rw;
}

return total;
}

static int sparse_copy(int src_fd, int dest_fd, struct stat *sb,
const char *saveLog, const char *currLog)
{
int make_holes = is_probably_sparse(sb);
size_t max_n_read = SIZE_MAX;
int last_write_made_hole = 0;
off_t total_n_read = 0;
char buf[BUFSIZ];

while (max_n_read) {
int make_hole = 0;

ssize_t n_read = read (src_fd, buf, MIN (max_n_read, BUFSIZ));
if (n_read < 0) {
if (errno == EINTR) {
continue;
}
message(MESS_ERROR, "error reading %s: %s\n",
currLog, strerror(errno));
return 0;
}

if (n_read == 0)
break;

max_n_read -= n_read;
total_n_read += n_read;

if (make_holes) {
/* Sentinel required by is_nul(). */
buf[n_read] = '\1';

if ((make_hole = is_nul(buf, n_read))) {
if (lseek (dest_fd, n_read, SEEK_CUR) < 0) {
message(MESS_ERROR, "error seeking %s: %s\n",
saveLog, strerror(errno));
return 0;
}
}
}

if (!make_hole) {
size_t n = n_read;
if (full_write (dest_fd, buf, n) != n) {
message(MESS_ERROR, "error writing to %s: %s\n",
saveLog, strerror(errno));
return 0;
}
}

last_write_made_hole = make_hole;
}

if (last_write_made_hole) {
if (ftruncate(dest_fd, total_n_read) < 0) {
message(MESS_ERROR, "error ftruncate %s: %s\n",
saveLog, strerror(errno));
return 0;
}
}

return 1;
}

static int copyTruncate(char *currLog, char *saveLog, struct stat *sb,
int flags)
{
char buf[BUFSIZ];
int fdcurr = -1, fdsave = -1;
ssize_t cnt;

message(MESS_DEBUG, "copying %s to %s\n", currLog, saveLog);

Expand Down Expand Up @@ -820,21 +949,10 @@ static int copyTruncate(char *currLog, char *saveLog, struct stat *sb,
return 1;
}

while ((cnt = read(fdcurr, buf, sizeof(buf))) > 0) {
if (write(fdsave, buf, cnt) != cnt) {
message(MESS_ERROR, "error writing to %s: %s\n",
saveLog, strerror(errno));
if (sparse_copy(fdcurr, fdsave, sb, saveLog, currLog) != 1) {
close(fdcurr);
close(fdsave);
return 1;
}
}
if (cnt != 0) {
message(MESS_ERROR, "error reading %s: %s\n",
currLog, strerror(errno));
close(fdcurr);
close(fdsave);
return 1;
}
}

Expand Down
71 changes: 71 additions & 0 deletions test/test
Original file line number Diff line number Diff line change
Expand Up @@ -1539,4 +1539,75 @@ test.log 0
test.log.$DATESTRING 0 zero
EOF

# ------------------------------- Test 62 ------------------------------------
# Rotate sparse file
preptest test.log 24 1 0

echo -n zero > test.log
truncate -s 10M test.log
echo x >> test.log

cp test.log test.example

SIZE_SPARSE_OLD=$(du test.log|awk '{print $1}')
SIZE_OLD=$(du --apparent-size test.log|awk '{print $1}')
$RLR test-config.24 --force
SIZE_NEW=$(du --apparent-size test.log.1|awk '{print $1}')
SIZE_SPARSE_NEW=$(du test.log.1|awk '{print $1}')

if [ $SIZE_OLD != $SIZE_NEW ]; then
echo "Bad apparent size of sparse logs"
echo "test.log: $SIZE_OLD"
echo "test.log.1: $SIZE_NEW"
exit 3
fi

if [ $SIZE_SPARSE_OLD -gt 100 ] || [ $SIZE_SPARSE_NEW -gt 100 ]; then
echo "Bad size of sparse logs"
echo "test.log: $SIZE_SPARSE_OLD"
echo "test.log.1: $SIZE_SPARSE_NEW"
exit 3
fi

checkoutput <<EOF
test.log 0
test.log.1 0 zerox
EOF

cleanup 63

# ------------------------------- Test 63 ------------------------------------
# Rotate sparse file, no data should be lost when hole is in the end of file
preptest test.log 24 1 0

echo -n zero > test.log
truncate -s 10M test.log

cp test.log test.example

SIZE_SPARSE_OLD=$(du test.log|awk '{print $1}')
SIZE_OLD=$(du --apparent-size test.log|awk '{print $1}')
$RLR test-config.24 --force
SIZE_NEW=$(du --apparent-size test.log.1|awk '{print $1}')
SIZE_SPARSE_NEW=$(du test.log.1|awk '{print $1}')

if [ $SIZE_OLD != $SIZE_NEW ]; then
echo "Bad apparent size of sparse logs"
echo "test.log: $SIZE_OLD"
echo "test.log.1: $SIZE_NEW"
exit 3
fi

if [ $SIZE_SPARSE_OLD -gt 100 ] || [ $SIZE_SPARSE_NEW -gt 100 ]; then
echo "Bad size of sparse logs"
echo "test.log: $SIZE_SPARSE_OLD"
echo "test.log.1: $SIZE_SPARSE_NEW"
exit 3
fi

checkoutput <<EOF
test.log 0
test.log.1 0 zero
EOF

cleanup

0 comments on commit f1dc0d9

Please sign in to comment.