diff --git a/config.c b/config.c
index e6b6a53..e9992e3 100644
--- a/config.c
+++ b/config.c
@@ -220,6 +220,61 @@ static char *readPath(const char *configFile, int lineNum, char *key,
return NULL;
}
+static int readModeUidGid(const char *configFile, int lineNum, char *key,
+ const char *directive, mode_t *mode, uid_t *uid,
+ gid_t *gid) {
+ char u[200], g[200];
+ int m;
+ char tmp;
+ int rc;
+ struct group *group;
+ struct passwd *pw = NULL;
+
+ rc = sscanf(key, "%o %199s %199s%c", &m, u, g, &tmp);
+ /* We support 'key <owner> <group> notation now */
+ if (rc == 0) {
+ rc = sscanf(key, "%199s %199s%c", u, g, &tmp);
+ /* Simulate that we have read mode and keep the default value. */
+ if (rc > 0) {
+ m = *mode;
+ rc += 1;
+ }
+ }
+
+ if (rc == 4) {
+ message(MESS_ERROR, "%s:%d extra arguments for "
+ "%s\n", configFile, lineNum, directive);
+ return -1;
+ }
+
+ if (rc > 0) {
+ *mode = m;
+ }
+
+ if (rc > 1) {
+ pw = getpwnam(u);
+ if (!pw) {
+ message(MESS_ERROR, "%s:%d unknown user '%s'\n",
+ configFile, lineNum, u);
+ return -1;
+ }
+ *uid = pw->pw_uid;
+ endpwent();
+ }
+ if (rc > 2) {
+ group = getgrnam(g);
+ if (!group) {
+ message(MESS_ERROR, "%s:%d unknown group '%s'\n",
+ configFile, lineNum, g);
+ return -1;
+ }
+ *gid = group->gr_gid;
+ endgrent();
+ }
+
+ return 0;
+}
+
static char *readAddress(const char *configFile, int lineNum, char *key,
char **startPtr, char **buf, size_t length)
{
@@ -249,6 +304,55 @@ static char *readAddress(const char *configFile, int lineNum, char *key,
return NULL;
}
+static int do_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+ struct stat sb;
+
+ if (stat(path, &sb) != 0) {
+ if (mkdir(path, mode) != 0 && errno != EEXIST) {
+ message(MESS_ERROR, "error creating %s: %s\n",
+ path, strerror(errno));
+ return -1;
+ }
+ if ((uid != sb.st_uid || gid != sb.st_gid) &&
+ chown(path, uid, gid)) {
+ message(MESS_ERROR, "error setting owner of %s to uid %d and gid %d: %s\n",
+ path, uid, gid, strerror(errno));
+ return -1;
+ }
+ }
+ else if (!S_ISDIR(sb.st_mode)) {
+ message(MESS_ERROR, "path %s already exists, but it is not a directory\n",
+ path);
+ errno = ENOTDIR;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int mkpath(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+ char *pp;
+ char *sp;
+ int rv;
+ char *copypath = strdup(path);
+
+ rv = 0;
+ pp = copypath;
+ while (rv == 0 && (sp = strchr(pp, '/')) != 0) {
+ if (sp != pp) {
+ *sp = '\0';
+ rv = do_mkdir(copypath, mode, uid, gid);
+ *sp = '/';
+ }
+ pp = sp + 1;
+ }
+ if (rv == 0) {
+ rv = do_mkdir(path, mode, uid, gid);
+ }
+ free(copypath);
+ return rv;
+}
+
static int checkFile(const char *fname)
{
int i;
@@ -330,6 +434,9 @@ static void copyLogInfo(struct logInfo *to, struct logInfo *from)
to->createGid = from->createGid;
to->suUid = from->suUid;
to->suGid = from->suGid;
+ to->olddirMode = from->olddirMode;
+ to->olddirUid = from->olddirUid;
+ to->olddirGid = from->olddirGid;
if (from->compress_options_count) {
poptDupArgv(from->compress_options_count, from->compress_options_list,
&to->compress_options_count, &to->compress_options_list);
@@ -539,6 +646,11 @@ int readAllConfigPaths(const char **paths)
.createMode = NO_MODE,
.createUid = NO_UID,
.createGid = NO_GID,
+ .olddirMode = NO_MODE,
+ .olddirUid = NO_UID,
+ .olddirGid = NO_GID,
+ .suUid = NO_UID,
+ .suGid = NO_GID,
.compress_options_list = NULL,
.compress_options_count = 0
};
@@ -579,13 +691,19 @@ static int globerr(const char *pathname, int theerr)
free(newlog->what); \
newlog->what = NULL; \
} while (0);
+#define RAISE_ERROR() \
+ if (newlog != defConfig) { \
+ state = STATE_ERROR; \
+ continue; \
+ } else { \
+ goto error; \
+ }
#define MAX_NESTING 16U
static int readConfigFile(const char *configFile, struct logInfo *defConfig)
{
int fd;
char *buf, *endtag, *key = NULL;
- char foo;
off_t length;
int lineNum = 1;
unsigned long long multiplier;
@@ -595,11 +713,8 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
struct logInfo *newlog = defConfig;
char *start, *chptr;
char *dirName;
- struct group *group;
struct passwd *pw = NULL;
int rc;
- char createOwner[200], createGroup[200];
- int createMode;
struct stat sb, sb2;
glob_t globResult;
const char **argv;
@@ -611,6 +726,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
static unsigned recursion_depth = 0U;
char *globerr_msg = NULL;
int in_config = 0;
+ int rv;
struct flock fd_lock = {
.l_start = 0,
.l_len = 0,
@@ -807,53 +923,22 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
} else if (!strcmp(key, "maillast")) {
newlog->flags &= ~LOG_FLAG_MAILFIRST;
} else if (!strcmp(key, "su")) {
+ mode_t tmp_mode = NO_MODE;
free(key);
key = isolateLine(&start, &buf, length);
if (key == NULL)
continue;
- rc = sscanf(key, "%199s %199s%c", createOwner,
- createGroup, &foo);
- if (rc == 3) {
- message(MESS_ERROR, "%s:%d extra arguments for "
- "su\n", configFile, lineNum);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ rv = readModeUidGid(configFile, lineNum, key, "su",
+ &tmp_mode, &newlog->suUid,
+ &newlog->suGid);
+ if (rv == -1) {
+ RAISE_ERROR();
}
-
- if (rc > 0) {
- pw = getpwnam(createOwner);
- if (!pw) {
- message(MESS_ERROR, "%s:%d unknown user '%s'\n",
- configFile, lineNum, createOwner);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
- }
- newlog->suUid = pw->pw_uid;
- endpwent();
- }
- if (rc > 1) {
- group = getgrnam(createGroup);
- if (!group) {
- message(MESS_ERROR, "%s:%d unknown group '%s'\n",
- configFile, lineNum, createGroup);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
- }
- newlog->suGid = group->gr_gid;
- endgrent();
+ else if (tmp_mode != NO_MODE) {
+ message(MESS_ERROR, "%s:%d extra arguments for "
+ "su\n", configFile, lineNum);
+ RAISE_ERROR();
}
newlog->flags |= LOG_FLAG_SU;
@@ -863,65 +948,30 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
if (key == NULL)
continue;
- rc = sscanf(key, "%o %199s %199s%c", &createMode,
- createOwner, createGroup, &foo);
- /* We support 'create <owner> <group> notation now */
- if (rc == 0) {
- rc = sscanf(key, "%199s %199s%c",
- createOwner, createGroup, &foo);
- /* Simulate that we have read createMode and se it
- * to NO_MODE. */
- if (rc > 0) {
- createMode = NO_MODE;
- rc += 1;
- }
- }
- if (rc == 4) {
- message(MESS_ERROR, "%s:%d extra arguments for "
- "create\n", configFile, lineNum);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ rv = readModeUidGid(configFile, lineNum, key, "create",
+ &newlog->createMode, &newlog->createUid,
+ &newlog->createGid);
+ if (rv == -1) {
+ RAISE_ERROR();
}
- if (rc > 0)
- newlog->createMode = createMode;
-
- if (rc > 1) {
- pw = getpwnam(createOwner);
- if (!pw) {
- message(MESS_ERROR, "%s:%d unknown user '%s'\n",
- configFile, lineNum, createOwner);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
- }
- newlog->createUid = pw->pw_uid;
- endpwent();
- }
- if (rc > 2) {
- group = getgrnam(createGroup);
- if (!group) {
- message(MESS_ERROR, "%s:%d unknown group '%s'\n",
- configFile, lineNum, createGroup);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
- }
- newlog->createGid = group->gr_gid;
- endgrent();
+ newlog->flags |= LOG_FLAG_CREATE;
+ } else if (!strcmp(key, "createolddir")) {
+ free(key);
+ key = isolateLine(&start, &buf, length);
+ if (key == NULL)
+ continue;
+
+ rv = readModeUidGid(configFile, lineNum, key, "createolddir",
+ &newlog->olddirMode, &newlog->olddirUid,
+ &newlog->olddirGid);
+ if (rv == -1) {
+ RAISE_ERROR();
}
- newlog->flags |= LOG_FLAG_CREATE;
+ newlog->flags |= LOG_FLAG_OLDDIRCREATE;
+ } else if (!strcmp(key, "nocreateolddir")) {
+ newlog->flags &= ~LOG_FLAG_OLDDIRCREATE;
} else if (!strcmp(key, "nocreate")) {
newlog->flags &= ~LOG_FLAG_CREATE;
} else if (!strcmp(key, "size") || !strcmp(key, "minsize") ||
@@ -945,12 +995,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
free(opt);
message(MESS_ERROR, "%s:%d unknown unit '%c'\n",
configFile, lineNum, key[l]);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
} else {
multiplier = 1;
}
@@ -960,12 +1005,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
message(MESS_ERROR, "%s:%d bad size '%s'\n",
configFile, lineNum, key);
free(opt);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
if (!strncmp(opt, "size", 4)) {
newlog->criterium = ROT_SIZE;
@@ -1015,12 +1055,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
message(MESS_ERROR,
"%s:%d bad rotation count '%s'\n",
configFile, lineNum, key);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
}
else continue;
@@ -1034,12 +1069,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
if (*chptr || newlog->logStart < 0) {
message(MESS_ERROR, "%s:%d bad start count '%s'\n",
configFile, lineNum, key);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
}
else continue;
@@ -1052,12 +1082,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
if (*chptr || newlog->rotateAge < 0) {
message(MESS_ERROR, "%s:%d bad maximum age '%s'\n",
configFile, lineNum, start);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
}
else continue;
@@ -1069,12 +1094,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
freeLogItem(logAddress);
if (!(newlog->logAddress = readAddress(configFile, lineNum,
"mail", &start, &buf, length))) {
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
else continue;
} else if (!strcmp(key, "nomail")) {
@@ -1177,31 +1197,8 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
if (!(newlog->oldDir = readPath(configFile, lineNum,
"olddir", &start, &buf, length))) {
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
- }
-
-#if 0
- if (stat(newlog->oldDir, &sb)) {
- message(MESS_ERROR, "%s:%d error verifying olddir "
- "path %s: %s\n", configFile, lineNum,
- newlog->oldDir, strerror(errno));
- free(newlog->oldDir);
- goto error;
+ RAISE_ERROR();
}
-
- if (!S_ISDIR(sb.st_mode)) {
- message(MESS_ERROR, "%s:%d olddir path %s is not a "
- "directory\n", configFile, lineNum,
- newlog->oldDir);
- free(newlog->oldDir);
- goto error;
- }
-#endif
message(MESS_DEBUG, "olddir is now %s\n", newlog->oldDir);
} else if (!strcmp(key, "extension")) {
if ((key = isolateValue
@@ -1222,24 +1219,14 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
if (!
(newlog->compress_prog =
readPath(configFile, lineNum, "compress", &start, &buf, length))) {
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
if (access(newlog->compress_prog, X_OK)) {
message(MESS_ERROR,
"%s:%d compression program %s is not an executable file\n",
configFile, lineNum, newlog->compress_prog);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
message(MESS_DEBUG, "compress_prog is now %s\n",
@@ -1252,24 +1239,14 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
(newlog->uncompress_prog =
readPath(configFile, lineNum, "uncompress",
&start, &buf, length))) {
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
if (access(newlog->uncompress_prog, X_OK)) {
message(MESS_ERROR,
"%s:%d uncompression program %s is not an executable file\n",
configFile, lineNum, newlog->uncompress_prog);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
message(MESS_DEBUG, "uncompress_prog is now %s\n",
@@ -1285,12 +1262,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
}
if (!(options = isolateLine(&start, &buf, length))) {
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
if (poptParseArgvString(options,
@@ -1300,12 +1272,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
"%s:%d invalid compression options\n",
configFile, lineNum);
free(options);
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
message(MESS_DEBUG, "compress_options is now %s\n",
@@ -1318,12 +1285,7 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
(newlog->compress_ext =
readPath(configFile, lineNum, "compress-ext",
&start, &buf, length))) {
- if (newlog != defConfig) {
- state = STATE_ERROR;
- continue;
- } else {
- goto error;
- }
+ RAISE_ERROR();
}
message(MESS_DEBUG, "compress_ext is now %s\n",
@@ -1484,45 +1446,57 @@ static int readConfigFile(const char *configFile, struct logInfo *defConfig)
globerr_msg = NULL;
if (!(newlog->flags & LOG_FLAG_MISSINGOK))
goto error;
- }
+ }
- if (newlog->oldDir) {
+ if (newlog->oldDir) {
for (i = 0; i < newlog->numFiles; i++) {
char *ld;
+ int rv;
dirName = ourDirName(newlog->files[i]);
if (stat(dirName, &sb2)) {
- message(MESS_ERROR,
- "%s:%d error verifying log file "
- "path %s: %s\n", configFile, lineNum,
- dirName, strerror(errno));
- free(dirName);
- goto error;
+ message(MESS_ERROR,
+ "%s:%d error verifying log file "
+ "path %s: %s\n", configFile, lineNum,
+ dirName, strerror(errno));
+ free(dirName);
+ goto error;
}
- ld = alloca(strlen(dirName) + strlen(newlog->oldDir) +
- 2);
+ ld = alloca(strlen(dirName) + strlen(newlog->oldDir) + 2);
sprintf(ld, "%s/%s", dirName, newlog->oldDir);
free(dirName);
- if (newlog->oldDir[0] != '/')
- dirName = ld;
- else
- dirName = newlog->oldDir;
- if (stat(dirName, &sb)) {
- message(MESS_ERROR, "%s:%d error verifying olddir "
- "path %s: %s\n", configFile, lineNum,
- dirName, strerror(errno));
- goto error;
+ if (newlog->oldDir[0] != '/') {
+ dirName = ld;
+ }
+ else {
+ dirName = newlog->oldDir;
+ }
+
+ rv = stat(dirName, &sb);
+ if (rv) {
+ if (errno == ENOENT && newlog->flags & LOG_FLAG_OLDDIRCREATE) {
+ if (mkpath(dirName, newlog->olddirMode,
+ newlog->olddirUid, newlog->olddirGid)) {
+ goto error;
+ }
+ }
+ else {
+ message(MESS_ERROR, "%s:%d error verifying olddir "
+ "path %s: %s\n", configFile, lineNum,
+ dirName, strerror(errno));
+ goto error;
+ }
}
if (sb.st_dev != sb2.st_dev) {
- message(MESS_ERROR,
- "%s:%d olddir %s and log file %s "
- "are on different devices\n", configFile,
- lineNum, newlog->oldDir, newlog->files[i]);
- goto error;
+ message(MESS_ERROR,
+ "%s:%d olddir %s and log file %s "
+ "are on different devices\n", configFile,
+ lineNum, newlog->oldDir, newlog->files[i]);
+ goto error;
}
}
- }
+ }
newlog = defConfig;
state = STATE_DEFINITION_END;
diff --git a/logrotate.8 b/logrotate.8
index 8cb9c2f..2cd2370 100644
--- a/logrotate.8
+++ b/logrotate.8
@@ -216,6 +216,16 @@ file for the omitted attributes. This option can be disabled using the
\fBnocreate\fR option.
.TP
+\fBcreateolddir \fImode\fR \fIowner\fR \fIgroup\fR
+If the directory specified by \fBolddir\fR directive does not exist, it is
+created. \fImode\fR specifies the mode for the \fBolddir\fR directory
+in octal (the same as \fBchmod\fR(2)), \fIowner\fR specifies the user name
+who will own the \fBolddir\fR directory, and \fIgroup\fR specifies the group
+the \fBolddir\fR directory will belong to. This option can be disabled using the
+\fBnocreateolddir\fR option.
+
+
+.TP
\fBdaily\fR
Log files are rotated every day.
@@ -350,6 +360,10 @@ Do not truncate the original log file in place after creating a copy
New log files are not created (this overrides the \fBcreate\fR option).
.TP
+\fBnocreateolddir\fR
+\fBolddir\fR directory is not created by logrotate when it does not exist.
+
+.TP
\fBnodelaycompress\fR
Do not postpone compression of the previous log file to the next rotation cycle
(this overrides the \fBdelaycompress\fR option).
diff --git a/logrotate.h b/logrotate.h
index 813418e..cf42703 100644
--- a/logrotate.h
+++ b/logrotate.h
@@ -20,6 +20,7 @@
#define LOG_FLAG_SHRED (1 << 10)
#define LOG_FLAG_SU (1 << 11)
#define LOG_FLAG_DATEYESTERDAY (1 << 12)
+#define LOG_FLAG_OLDDIRCREATE (1 << 13)
#define NO_MODE ((mode_t) -1)
#define NO_UID ((uid_t) -1)
@@ -55,6 +56,9 @@ struct logInfo {
gid_t createGid;
uid_t suUid; /* switch user to this uid and group to this gid */
gid_t suGid;
+ mode_t olddirMode;
+ uid_t olddirUid;
+ uid_t olddirGid;
/* these are at the end so they end up nil */
const char **compress_options_list;
int compress_options_count;
diff --git a/test/test b/test/test
index 25b76a6..e9ce46f 100755
--- a/test/test
+++ b/test/test
@@ -453,9 +453,15 @@ cleanup 13
# ------------------------------- Test 13 ------------------------------------
preptest test.log 13 1 0
rm -rf testdir
-mkdir testdir
$RLR test-config.13 --force
+ls -l|grep testdir|grep "drwx------." 2>/dev/null >/dev/null
+if [ $? != 0 ]; then
+ echo "testdir should have mode 2700, but it has:"
+ ls -l|grep testdir
+ exit 3
+fi
+
checkoutput <<EOF
test.log 0
testdir/test.log.1 0 zero
@@ -888,13 +894,15 @@ $RLR test-config.35 --force
getfacl test.log|grep "user:nobody:rwx" >/dev/null
if [ $? != 0 ]; then
- echo "test.log must not contain user:nobody:rwx"
+ echo "test.log must contain user:nobody:rwx"
+ getfacl test.log
exit 3
fi
getfacl test.log.1|grep "user:nobody:rwx" >/dev/null
if [ $? != 0 ]; then
echo "test.log.1 must contain user:nobody:rwx"
+ getfacl test.log.1
exit 3
fi
diff --git a/test/test-config.13.in b/test/test-config.13.in
index 31a29ef..dc2efd5 100644
--- a/test/test-config.13.in
+++ b/test/test-config.13.in
@@ -4,4 +4,5 @@ create
monthly
rotate 1
olddir &DIR&/testdir
+ createolddir 700 &USER& &GROUP&
}