static int prerotateSingleLog(const struct logInfo *log, unsigned logNum,
                              struct logState *state, struct logNames *rotNames)
{
    struct tm now;
    const char *compext = "";
    const char *fileext = "";
    int hasErrors = 0;
    char *glob_pattern;
    glob_t globResult;
    int rc;
    int rotateCount = log->rotateCount ? log->rotateCount : 1;
    int logStart = (log->logStart == -1) ? 1 : log->logStart;
#define DATEEXT_LEN 64
#define PATTERN_LEN (DATEEXT_LEN * 2)
    char dext_str[DATEEXT_LEN];
    char dformat[PATTERN_LEN] = "";
    char dext_pattern[PATTERN_LEN];

    if (!state->doRotate)
        return 0;

    /* Logs with rotateCounts of 0 are rotated once, then removed. This
       lets scripts run properly, and everything gets mailed properly. */

    message(MESS_DEBUG, "rotating log %s, log->rotateCount is %d\n",
            log->files[logNum], log->rotateCount);

    if (log->flags & LOG_FLAG_COMPRESS) {
        if (!log->compress_ext) {
            message(MESS_ERROR, "log %s: compression enabled, but compression "
                "extension is not set\n", log->files[logNum]);
            return 1;
        }

        compext = log->compress_ext;
    }

    localtime_r(&nowSecs, &now);
    state->lastRotated = now;

    {
        const char *ld;
        char *logpath = strdup(log->files[logNum]);
        if (logpath == NULL) {
            message_OOM();
            return 1;
        }
        ld = dirname(logpath);
        if (log->oldDir) {
            if (log->oldDir[0] != '/') {
                if (asprintf(&rotNames->dirName, "%s/%s", ld, log->oldDir) < 0) {
                    rotNames->dirName = NULL;
                }
            } else
                rotNames->dirName = strdup(log->oldDir);
        } else
            rotNames->dirName = strdup(ld);
        free(logpath);

        if (rotNames->dirName == NULL) {
            message_OOM();
            return 1;
        }
    }

    {
        char *filename = strdup(log->files[logNum]);
        if (filename == NULL) {
            message_OOM();
            return 1;
        }

        rotNames->baseName = strdup(basename(filename));
        if (rotNames->baseName == NULL) {
            message_OOM();
            free(filename);
            return 1;
        }

        free(filename);
    }

    if (log->addextension) {
        const size_t baseLen = strlen(rotNames->baseName);
        const size_t extLen = strlen(log->addextension);
        if (baseLen >= extLen &&
                strncmp(&(rotNames->baseName[baseLen - extLen]),
                    log->addextension, extLen) == 0) {

            char *tempstr = strndup(rotNames->baseName, baseLen - extLen);
            if (tempstr == NULL) {
                message_OOM();
                return 1;
            }

            free(rotNames->baseName);
            rotNames->baseName = tempstr;
        }
        fileext = log->addextension;
    }

    if (log->extension) {
        const size_t baseLen = strlen(rotNames->baseName);
        const size_t extLen = strlen(log->extension);

        if (baseLen >= extLen &&
                strncmp(&(rotNames->baseName[baseLen - extLen]),
                    log->extension, extLen) == 0) {
            char *tempstr;

            fileext = log->extension;
            tempstr = strndup(rotNames->baseName, baseLen - extLen);
            if (tempstr == NULL) {
                message_OOM();
                return 1;
            }
            free(rotNames->baseName);
            rotNames->baseName = tempstr;
        }
    }

    /* Adjust "now" if we want yesterday's date */
    if (log->flags & LOG_FLAG_DATEYESTERDAY) {
        now.tm_hour = 12; /* set hour to noon to work around DST issues */
        now.tm_mday = now.tm_mday - 1;
        mktime(&now);
    }

    if (log->flags & LOG_FLAG_DATEHOURAGO) {
        now.tm_hour -= 1;
        mktime(&now);
    }

    /* Construct the glob pattern corresponding to the date format */
    dext_str[0] = '\0';
    if (log->dateformat) {
        char *dext;
        size_t i = 0, j = 0;
        memset(dext_pattern, 0, sizeof(dext_pattern));
        dext = log->dateformat;
        while (*dext == ' ')
            dext++;
        while ((*dext != '\0') && (!hasErrors)) {
            /* Will there be a space for a char and '\0'? */
            if (j >= (sizeof(dext_pattern) - 1)) {
                message(MESS_ERROR, "Date format %s is too long\n",
                        log->dateformat);
                hasErrors = 1;
                break;
            }
            if (*dext == '%') {
                switch (*(dext + 1)) {
                    case 'Y':
                        strncat(dext_pattern, "[0-9][0-9]",
                                sizeof(dext_pattern) - strlen(dext_pattern) - 1);
                        j += 10; /* strlen("[0-9][0-9]") */
                        /* FALLTHRU */
                    case 'm':
                    case 'd':
                    case 'H':
                    case 'M':
                    case 'S':
                    case 'V':
                        strncat(dext_pattern, "[0-9][0-9]",
                                sizeof(dext_pattern) - strlen(dext_pattern) - 1);
                        j += 10;
                        if (j >= (sizeof(dext_pattern) - 1)) {
                            message(MESS_ERROR, "Date format %s is too long\n",
                                    log->dateformat);
                            hasErrors = 1;
                            break;
                        }
                        dformat[i++] = *(dext++);
                        dformat[i] = *dext;
                        break;
                    case 's':
                        /* End of year 2293 this pattern does not work. */
                        strncat(dext_pattern,
                                "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",
                                sizeof(dext_pattern) - strlen(dext_pattern) - 1);
                        j += 50;
                        if (j >= (sizeof(dext_pattern) - 1)) {
                            message(MESS_ERROR, "Date format %s is too long\n",
                                    log->dateformat);
                            hasErrors = 1;
                            break;
                        }
                        dformat[i++] = *(dext++);
                        dformat[i] = *dext;
                        break;
                    default:
                        dformat[i++] = *dext;
                        dformat[i] = '%';
                        dext_pattern[j++] = *dext;
                        break;
                }
            } else {
                dformat[i] = *dext;
                dext_pattern[j++] = *dext;
            }
            ++i;
            ++dext;
        }
        dformat[i] = '\0';
        message(MESS_DEBUG, "Converted '%s' -> '%s'\n", log->dateformat, dformat);
        strftime(dext_str, sizeof(dext_str), dformat, &now);
    } else {
        if (log->criterium == ROT_HOURLY) {
            /* hourly adds another two digits */
            strftime(dext_str, sizeof(dext_str), "-%Y%m%d%H", &now);
            strncpy(dext_pattern, "-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",
                    sizeof(dext_pattern));
        } else {
            /* The default dateformat and glob pattern */
            strftime(dext_str, sizeof(dext_str), "-%Y%m%d", &now);
            strncpy(dext_pattern, "-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",
                    sizeof(dext_pattern));
        }
        dext_pattern[PATTERN_LEN - 1] = '\0';
    }
    message(MESS_DEBUG, "dateext suffix '%s'\n", dext_str);
    message(MESS_DEBUG, "glob pattern '%s'\n", dext_pattern);

    if (setSecCtxByName(log->files[logNum], log, &prev_context) != 0) {
        /* error msg already printed */
        return 1;
    }

    /* First compress the previous log when necessary */
    if ((log->flags & LOG_FLAG_COMPRESS) &&
            (log->flags & LOG_FLAG_DELAYCOMPRESS)) {
        if (log->flags & LOG_FLAG_DATEEXT) {
            /* glob for uncompressed files with our pattern */
            if (asprintf(&glob_pattern, "%s/%s%s%s", rotNames->dirName,
                         rotNames->baseName, dext_pattern, fileext) < 0) {
                message_OOM();
                return 1;
            }
            rc = glob(glob_pattern, 0, globerr, &globResult);
            if (!rc && globResult.gl_pathc > 0) {
                size_t glob_count;
                sortGlobResult(&globResult, strlen(rotNames->dirName) + 1 + strlen(rotNames->baseName), dformat);
                for (glob_count = 0; glob_count < globResult.gl_pathc && !hasErrors; glob_count++) {
                    struct stat sbprev;
                    const char *oldName = globResult.gl_pathv[glob_count];

                    if (stat(oldName, &sbprev)) {
                        if (errno == ENOENT)
                            message(MESS_DEBUG, "previous log %s does not exist\n", oldName);
                        else
                            message(MESS_ERROR, "cannot stat %s: %s\n", oldName, strerror(errno));
                    } else {
                        hasErrors = compressLogFile(oldName, log, &sbprev);
                    }
                }
            } else {
                message(MESS_DEBUG,
                        "glob finding logs to compress failed\n");
            }
            globfree(&globResult);
            free(glob_pattern);
        } else {
            struct stat sbprev;
            char *oldName;
            if (asprintf(&oldName, "%s/%s.%d%s", rotNames->dirName,
                         rotNames->baseName, logStart, fileext) < 0) {
                message_OOM();
                return 1;
            }
            if (stat(oldName, &sbprev)) {
                if (errno == ENOENT)
                    message(MESS_DEBUG, "previous log %s does not exist\n", oldName);
                else
                    message(MESS_ERROR, "cannot stat %s: %s\n", oldName, strerror(errno));
            } else {
                hasErrors = compressLogFile(oldName, log, &sbprev);
            }
            free(oldName);
        }
    }

    if (log->flags & LOG_FLAG_DATEEXT) {
        /* glob for compressed files with our pattern
         * and compress ext */
        if (asprintf(&glob_pattern, "%s/%s%s%s%s", rotNames->dirName,
                     rotNames->baseName, dext_pattern, fileext, compext) < 0) {
            message_OOM();
            return 1;
        }
        rc = glob(glob_pattern, 0, globerr, &globResult);
        if (!rc) {
            /* search for files to drop, if we find one remember it,
             * if we find another one mail and remove the first and
             * remember the second and so on */
            struct stat fst_buf;
            size_t glob_count, mail_out = (size_t)-1;
            /* Remove the first (n - rotateCount) matches no real rotation
             * needed, since the files have the date in their name. Note that
             * (size_t)-1 == SIZE_T_MAX in rotateCount */
            sortGlobResult(&globResult, strlen(rotNames->dirName) + 1 + strlen(rotNames->baseName), dformat);
            for (glob_count = 0; glob_count < globResult.gl_pathc; glob_count++) {
                if (!stat((globResult.gl_pathv)[glob_count], &fst_buf)) {
                    if (((globResult.gl_pathc >= (size_t)rotateCount) && (glob_count <= (globResult.gl_pathc - (size_t)rotateCount)))
                            || ((log->rotateAge > 0)
                                &&
                                ((difftime(nowSecs, fst_buf.st_mtime) / DAY_SECONDS)
                                 > log->rotateAge))) {
                        if (mail_out != (size_t)-1) {
                            char *mailFilename =
                                (globResult.gl_pathv)[mail_out];
                            if (!hasErrors && log->logAddress)
                                hasErrors = mailLogWrapper(mailFilename, mailCommand,
                                                           logNum, log);
                            if (!hasErrors) {
                                message(MESS_DEBUG, "removing %s\n", mailFilename);
                                hasErrors = removeLogFile(mailFilename, log);
                            }
                        }
                        mail_out = glob_count;
                    }
                }
            }
            if (mail_out != (size_t)-1) {
                /* oldName is oldest Backup found (for unlink later) */
                const char *oldName = globResult.gl_pathv[mail_out];
                rotNames->disposeName = strdup(oldName);
                if (rotNames->disposeName == NULL) {
                    message_OOM();
                    globfree(&globResult);
                    free(glob_pattern);
                    return 1;
                }
            } else {
                free(rotNames->disposeName);
                rotNames->disposeName = NULL;
            }
        } else {
            message(MESS_DEBUG, "glob finding old rotated logs failed\n");
            free(rotNames->disposeName);
            rotNames->disposeName = NULL;
        }
        /* firstRotated is most recently created/compressed rotated log */
        if (asprintf(&rotNames->firstRotated, "%s/%s%s%s%s",
                rotNames->dirName, rotNames->baseName, dext_str, fileext,
                (log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext) < 0) {
            message_OOM();
            rotNames->firstRotated = NULL;
            globfree(&globResult);
            free(glob_pattern);
            return 1;
        }
        globfree(&globResult);
        free(glob_pattern);
    } else {
        int i;
        char *newName = NULL;
        char *oldName;

        if (rotateCount == -1) {
            rotateCount = findLastRotated(rotNames, fileext, compext);
            if (rotateCount < 0) {
                message(MESS_ERROR, "could not find last rotated file: %s/%s.*%s%s\n",
                        rotNames->dirName, rotNames->baseName, fileext, compext);
                return 1;
            }
        }

        if (asprintf(&oldName, "%s/%s.%d%s%s", rotNames->dirName,
                     rotNames->baseName, logStart + rotateCount, fileext,
                     compext) < 0) {
            message_OOM();
            return 1;
        }

        if (log->rotateCount != -1) {
            rotNames->disposeName = strdup(oldName);
            if (rotNames->disposeName == NULL) {
                message_OOM();
                free(oldName);
                return 1;
            }
        }

        if (asprintf(&rotNames->firstRotated, "%s/%s.%d%s%s", rotNames->dirName,
                rotNames->baseName, logStart, fileext,
                (log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext) < 0) {
            message_OOM();
            free(oldName);
            rotNames->firstRotated = NULL;
            return 1;
        }

        for (i = rotateCount + logStart - 1; (i >= 0) && !hasErrors; i--) {
            free(newName);
            newName = oldName;
            if (asprintf(&oldName, "%s/%s.%d%s%s", rotNames->dirName,
                         rotNames->baseName, i, fileext, compext) < 0) {
                message_OOM();
                oldName = NULL;
                break;
            }

            /* remove files hit by maxage */
            if (log->rotateAge) {
                struct stat fst_buf;

                if (stat(oldName, &fst_buf)) {
                    if (errno == ENOENT) {
                        message(MESS_DEBUG, "old log %s does not exist\n",
                                oldName);
                    } else {
                        message(MESS_ERROR, "cannot stat %s: %s\n", oldName,
                                strerror(errno));
                        hasErrors = 1;
                    }

                    continue;
                }

                if ((difftime(nowSecs, fst_buf.st_mtime) / DAY_SECONDS) > log->rotateAge) {
                    if (!hasErrors && log->logAddress)
                        hasErrors = mailLogWrapper(oldName, mailCommand,
                                                   logNum, log);
                    if (!hasErrors)
                        hasErrors = removeLogFile(oldName, log);

                    continue;
                }
            }

            message(MESS_DEBUG,
                    "renaming %s to %s (rotatecount %d, logstart %d, i %d), \n",
                    oldName, newName, rotateCount, logStart, i);

            if (!debug && rename(oldName, newName)) {
                if (errno == ENOENT) {
                    message(MESS_DEBUG, "old log %s does not exist\n",
                            oldName);
                } else {
                    message(MESS_ERROR, "error renaming %s to %s: %s\n",
                            oldName, newName, strerror(errno));
                    hasErrors = 1;
                }
            }
        }
        free(newName);
        free(oldName);
    } /* !LOG_FLAG_DATEEXT */

    if (log->flags & LOG_FLAG_DATEEXT) {
        char *destFile;
        struct stat fst_buf;

        if (asprintf(&(rotNames->finalName), "%s/%s%s%s", rotNames->dirName,
                     rotNames->baseName, dext_str, fileext) < 0) {
            message_OOM();
            rotNames->finalName = NULL;
            return 1;
        }
        if (asprintf(&destFile, "%s%s", rotNames->finalName, compext) < 0) {
            message_OOM();
            return 1;
        }
        if (!stat(destFile, &fst_buf)) {
            message(MESS_ERROR,
                    "destination %s already exists, skipping rotation\n",
                    rotNames->firstRotated);
            hasErrors = 1;
        }
        free(destFile);
    } else {
        /* note: the gzip extension is *not* used here! */
        if (asprintf(&(rotNames->finalName), "%s/%s.%d%s", rotNames->dirName,
                     rotNames->baseName, logStart, fileext) < 0) {
            message_OOM();
            rotNames->finalName = NULL;
        }
    }

    /* if the last rotation doesn't exist, that's okay */
    if (rotNames->disposeName && access(rotNames->disposeName, F_OK)) {
        message(MESS_DEBUG,
                "log %s doesn't exist -- won't try to dispose of it\n",
                rotNames->disposeName);
        free(rotNames->disposeName);
        rotNames->disposeName = NULL;
    }

    return hasErrors;
}