1 Star 0 Fork 0

薛建强/logrotate

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
logrotate.c 104.64 KB
一键复制 编辑 原始数据 按行查看 历史
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334
#include "queue.h"
#include <limits.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <popt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <glob.h>
#include <locale.h>
#include <sys/types.h>
#include <utime.h>
#include <stdint.h>
#include <libgen.h>
#include <signal.h>
#if !defined(PATH_MAX) && defined(__FreeBSD__)
#include <sys/param.h>
#endif
#include "log.h"
#include "logrotate.h"
static char *prev_context;
#ifdef WITH_SELINUX
#include <selinux/selinux.h>
static int selinux_enabled = 0;
static int selinux_enforce = 0;
#endif
#ifdef WITH_ACL
#include "sys/acl.h"
#define acl_type acl_t
#else
#define acl_type void *
#endif
static acl_type prev_acl = NULL;
#if !defined(GLOB_ABORTED) && defined(GLOB_ABEND)
#define GLOB_ABORTED GLOB_ABEND
#endif
#ifdef PATH_MAX
#define STATEFILE_BUFFER_SIZE ( 2 * PATH_MAX + 16 )
#else
#define STATEFILE_BUFFER_SIZE 4096
#endif
#ifdef __hpux
extern int asprintf(char **str, const char *fmt, ...);
#endif
/* Number of seconds in a day */
#define DAY_SECONDS 86400
struct logState {
char *fn;
struct tm lastRotated; /* only tm_hour, tm_mday, tm_mon, tm_year are good! */
struct stat sb;
int doRotate;
int isUsed; /* True if there is real log file in system for this state. */
LIST_ENTRY(logState) list;
};
struct logNames {
char *firstRotated;
char *disposeName;
char *finalName;
char *dirName;
char *baseName;
};
struct compData {
size_t prefix_len;
const char *dformat;
};
static struct logStateList {
LIST_HEAD(stateSet, logState) head;
} **states;
int numLogs = 0;
int debug = 0;
static unsigned int hashSize;
static const char *mailCommand = DEFAULT_MAIL_COMMAND;
static time_t nowSecs = 0;
static uid_t save_euid;
static gid_t save_egid;
static int globerr(const char *pathname, int theerr)
{
message(MESS_ERROR, "error accessing %s: %s\n", pathname,
strerror(theerr));
/* We want the glob operation to abort on error, so return 1 */
return 1;
}
/* We could switch to qsort_r to get rid of this global variable,
* but qsort_r is not portable enough (Linux vs. *BSD vs ...)... */
static struct compData _compData;
static int compGlobResult(const void *result1, const void *result2) {
struct tm time_tmp;
time_t t1, t2;
const char *r1 = *(char * const*)(result1);
const char *r2 = *(char * const*)(result2);
memset(&time_tmp, 0, sizeof(struct tm));
strptime(r1 + _compData.prefix_len, _compData.dformat, &time_tmp);
t1 = mktime(&time_tmp);
memset(&time_tmp, 0, sizeof(struct tm));
strptime(r2 + _compData.prefix_len, _compData.dformat, &time_tmp);
t2 = mktime(&time_tmp);
if (t1 < t2) return -1;
if (t1 > t2) return 1;
return 0;
}
static void sortGlobResult(glob_t *result, size_t prefix_len, const char *dformat) {
if (!dformat || *dformat == '\0') {
return;
}
_compData.prefix_len = prefix_len;
_compData.dformat = dformat;
qsort(result->gl_pathv, result->gl_pathc, sizeof(char *), compGlobResult);
}
int switch_user(uid_t user, gid_t group) {
save_egid = getegid();
save_euid = geteuid();
if (save_euid == user && save_egid == group)
return 0;
message(MESS_DEBUG, "switching euid from %u to %u and egid from %u to %u (pid %d)\n",
(unsigned) save_euid, (unsigned) user, (unsigned) save_egid, (unsigned) group, getpid());
if (setegid(group) || seteuid(user)) {
message(MESS_ERROR, "error switching euid from %u to %u and egid from %u to %u (pid %d): %s\n",
(unsigned) save_euid, (unsigned) user, (unsigned) save_egid, (unsigned) group, getpid(),
strerror(errno));
return 1;
}
return 0;
}
static int switch_user_permanently(const struct logInfo *log) {
const gid_t group = getegid();
const uid_t user = geteuid();
if (!(log->flags & LOG_FLAG_SU)) {
return 0;
}
if (user != log->suUid) {
message(MESS_ERROR, "current euid (%u) does not match uid of log configuration (%u) (pid %d)\n",
(unsigned) user, (unsigned) log->suUid, getpid());
return 1;
}
if (group != log->suGid) {
message(MESS_ERROR, "current egid (%u) does not match gid of log configuration (%u) (pid %d)\n",
(unsigned) group, (unsigned) log->suGid, getpid());
return 1;
}
/* we are already the final configuration specified user/group */
if (getuid() == user && getgid() == group) {
return 0;
}
/* switch to full root first */
if (setgid(getgid()) || setuid(getuid())) {
message(MESS_ERROR, "error getting rid of euid != uid (pid %d): %s\n",
getpid(), strerror(errno));
return 1;
}
message(MESS_DEBUG, "switching uid to %u and gid to %u permanently (pid %d)\n",
(unsigned) user, (unsigned) group, getpid());
if (setgid(group) || setuid(user)) {
message(MESS_ERROR, "error switching uid to %u and gid to %u (pid %d): %s\n",
(unsigned) user, (unsigned) group, getpid(), strerror(errno));
return 1;
}
if (user != ROOT_UID && (setuid(ROOT_UID) != -1 || seteuid(ROOT_UID) != -1)) {
message(MESS_ERROR, "failed to switch user permanently, able to switch back (pid %d)\n",
getpid());
return 1;
}
if (chdir("/") != 0) {
message(MESS_ERROR, "failed to change current directory to root path: %s\n",
strerror(errno));
return -1;
}
return 0;
}
int switch_user_back(void) {
return switch_user(save_euid, save_egid);
}
static int switch_user_back_permanently(void) {
gid_t tmp_egid = save_egid;
uid_t tmp_euid = save_euid;
int ret = switch_user(save_euid, save_egid);
save_euid = tmp_euid;
save_egid = tmp_egid;
return ret;
}
static int open_logfile(const char *path, const struct logInfo *log, int write_access) {
int fd, flags;
struct stat sb;
fd = open(path, O_NOFOLLOW | O_NOCTTY | O_NONBLOCK | (write_access ? O_RDWR : O_RDONLY));
if (fd < 0)
return fd;
if (fstat(fd, &sb) != 0) {
close(fd);
return -1;
}
if (! S_ISREG(sb.st_mode)) {
close(fd);
errno = ENOTSUP;
return -1;
}
if (sb.st_nlink != 1 && !(log->flags & LOG_FLAG_ALLOWHARDLINK)) {
close(fd);
errno = ENOTSUP;
return -1;
}
/*
* Unset O_NONBLOCK for portability, since O_NONBLOCK is unspecified for
* regular files by POSIX.
*/
if ((flags = fcntl(fd, F_GETFL)) == -1) {
close(fd);
return -1;
}
if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) == -1) {
close(fd);
return -1;
}
return fd;
}
static void unescape(char *arg)
{
char *p = arg;
char *next;
char escaped;
while ((next = strchr(p, '\\')) != NULL) {
p = next;
switch (p[1]) {
case 'n':
escaped = '\n';
break;
case '\\':
escaped = '\\';
break;
default:
++p;
continue;
}
/* Overwrite the backslash with the intended character,
* and shift everything down one */
*p++ = escaped;
memmove(p, p+1, 1 + strlen(p+1));
}
}
#define HASH_SIZE_MIN 64
#define HASH_SIZE_MAX 8192
static int allocateHash(unsigned long hs)
{
unsigned int i;
/* Enforce some reasonable minimum hash size */
if (hs < HASH_SIZE_MIN)
hs = HASH_SIZE_MIN;
/* Enforce some reasonable maximum hash size */
if (hs > HASH_SIZE_MAX)
hs = HASH_SIZE_MAX;
message(MESS_DEBUG, "Allocating hash table for state file, size %lu entries\n",
hs);
states = calloc(hs, sizeof(struct logStateList *));
if (states == NULL) {
message_OOM();
return 1;
}
for (i = 0; i < hs; i++) {
states[i] = malloc(sizeof *states[0]);
if (states[i] == NULL) {
message_OOM();
return 1;
}
LIST_INIT(&(states[i]->head));
}
hashSize = (unsigned)hs;
return 0;
}
#define HASH_CONST 13
#if defined(__clang__) && defined(__clang_major__) && (__clang_major__ >= 4)
__attribute__((no_sanitize("unsigned-integer-overflow")))
#endif
static int hashIndex(const char *fn)
{
unsigned hash = 0;
if (!hashSize)
/* hash table not yet allocated */
return -1;
while (*fn) {
hash *= HASH_CONST;
hash += (unsigned char)*fn++;
}
return (int)(hash % hashSize);
}
/* safe implementation of dup2(oldfd, nefd) followed by close(oldfd) */
static int movefd(int oldfd, int newfd)
{
int rc;
if (oldfd == newfd)
/* avoid accidental close of newfd in case it is equal to oldfd */
return 0;
rc = dup2(oldfd, newfd);
if (rc == 0)
close(oldfd);
return rc;
}
static int setSecCtx(int fdSrc, const char *src, char **pPrevCtx)
{
#ifdef WITH_SELINUX
char *srcCtx;
*pPrevCtx = NULL;
if (!selinux_enabled)
/* pretend success */
return 0;
/* read security context of fdSrc */
if (fgetfilecon_raw(fdSrc, &srcCtx) < 0) {
if (errno == ENOTSUP)
/* pretend success */
return 0;
message(MESS_ERROR, "getting file context %s: %s\n", src,
strerror(errno));
return selinux_enforce;
}
/* save default security context for restoreSecCtx() */
if (getfscreatecon_raw(pPrevCtx) < 0) {
message(MESS_ERROR, "getting default context: %s\n", strerror(errno));
freecon(srcCtx);
return selinux_enforce;
}
/* set default security context to match fdSrc */
if (setfscreatecon_raw(srcCtx) < 0) {
message(MESS_ERROR, "setting default context to %s: %s\n", srcCtx,
strerror(errno));
freecon(*pPrevCtx);
*pPrevCtx = NULL;
freecon(srcCtx);
return selinux_enforce;
}
message(MESS_DEBUG, "set default create context to %s\n", srcCtx);
freecon(srcCtx);
#else
(void) fdSrc;
(void) src;
(void) pPrevCtx;
#endif
return 0;
}
static int setSecCtxByName(const char *src, const struct logInfo *log, char **pPrevCtx)
{
int hasErrors = 0;
#ifdef WITH_SELINUX
int fd;
if (!selinux_enabled)
/* pretend success */
return 0;
fd = open_logfile(src, log, 0);
if (fd < 0) {
message(MESS_ERROR, "error opening %s: %s\n", src, strerror(errno));
return 1;
}
hasErrors = setSecCtx(fd, src, pPrevCtx);
close(fd);
#else
(void) src;
(void) log;
(void) pPrevCtx;
#endif
return hasErrors;
}
static void restoreSecCtx(char **pPrevCtx)
{
#ifdef WITH_SELINUX
if (!*pPrevCtx)
/* no security context saved for restoration */
return;
/* set default security context to the previously stored one */
if (selinux_enabled && setfscreatecon_raw(*pPrevCtx) < 0)
message(MESS_ERROR, "setting default context to %s: %s\n", *pPrevCtx,
strerror(errno));
/* free the memory allocated to save the security context */
freecon(*pPrevCtx);
*pPrevCtx = NULL;
#else
(void) pPrevCtx;
#endif
}
static struct logState *newState(const char *fn)
{
struct tm now;
struct logState *new;
time_t lr_time;
message(MESS_DEBUG, "Creating new state\n");
localtime_r(&nowSecs, &now);
new = malloc(sizeof(*new));
if (new == NULL) {
message_OOM();
return NULL;
}
new->fn = strdup(fn);
if (new->fn == NULL) {
message_OOM();
free(new);
return NULL;
}
new->doRotate = 0;
new->isUsed = 0;
memset(&new->lastRotated, 0, sizeof(new->lastRotated));
new->lastRotated.tm_hour = now.tm_hour;
new->lastRotated.tm_mday = now.tm_mday;
new->lastRotated.tm_mon = now.tm_mon;
new->lastRotated.tm_year = now.tm_year;
new->lastRotated.tm_isdst = now.tm_isdst;
/* fill in the rest of the new->lastRotated fields */
lr_time = mktime(&new->lastRotated);
localtime_r(&lr_time, &new->lastRotated);
return new;
}
static struct logState *findState(const char *fn)
{
const int i = hashIndex(fn);
struct logState *p;
if (i < 0)
/* hash table not yet allocated */
return NULL;
for (p = states[i]->head.lh_first; p != NULL; p = p->list.le_next)
if (!strcmp(fn, p->fn))
break;
/* new state */
if (p == NULL) {
if ((p = newState(fn)) == NULL)
return NULL;
LIST_INSERT_HEAD(&(states[i]->head), p, list);
}
return p;
}
static int waitpid_checked(pid_t pid, const char **errmsg)
{
int status;
if (waitpid(pid, &status, 0) < 0) {
*errmsg = strerror(errno);
return -1;
}
if (!WIFEXITED(status)) {
*errmsg = "child did not terminate normally";
return -2;
}
if (WEXITSTATUS(status) != 0) {
*errmsg = "child did not exit with status 0";
return -3;
}
*errmsg = NULL;
return 0;
}
static int runScript(const struct logInfo *log, const char *logfn,
const char *logrotfn, const char *script, const char **errmsg)
{
pid_t pid;
if (debug) {
message(MESS_DEBUG, "running script with args %s %s: \"%s\"\n",
logfn, logrotfn ? logrotfn : "", script);
return 0;
}
pid = fork();
if (pid == -1) {
message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
return -1;
}
if (pid == 0) {
if (log->flags & LOG_FLAG_SU) {
if (switch_user_back_permanently() != 0) {
exit(1);
}
}
execl("/bin/sh", "sh", "-c", (char *) script, "logrotate_script", (char *) logfn, (char *) logrotfn, (char *) NULL);
message(MESS_ERROR, "cannot execute sub-shell: %s\n", strerror(errno));
exit(1);
}
return waitpid_checked(pid, errmsg);
}
#ifdef WITH_ACL
static int is_acl_well_supported(int err)
{
switch (err) {
case ENOTSUP: /* no file system support */
case EINVAL: /* acl does not point to a valid ACL */
case ENOSYS: /* compatibility - acl_(g|s)et_fd(3) should never return this */
case EBUSY: /* compatibility - acl_(g|s)et_fd(3) should never return this */
return 0;
default:
return 1;
}
}
#endif /* WITH_ACL */
static int createOutputFile(const char *fileName, int flags, const struct stat *sb,
acl_type acl, int force_mode)
{
int fd = -1;
struct stat sb_create;
int acl_set = 0;
int i;
for (i = 0; i < 2; ++i) {
struct tm now;
size_t fileName_size, buf_size;
char *backupName, *ptr;
fd = open(fileName, (flags | O_CREAT | O_EXCL | O_NOFOLLOW),
(S_IRUSR | S_IWUSR) & sb->st_mode);
if ((fd >= 0) || (errno != EEXIST))
break;
/* the destination file already exists, while it should not */
localtime_r(&nowSecs, &now);
fileName_size = strlen(fileName);
buf_size = fileName_size + sizeof("-YYYYMMDDHH.backup");
backupName = malloc(buf_size);
if (!backupName) {
message_OOM();
return -1;
}
ptr = backupName;
/* construct backupName starting with fileName */
strcpy(ptr, fileName);
ptr += fileName_size;
buf_size -= fileName_size;
/* append the -YYYYMMDDHH time stamp and the .backup suffix */
ptr += strftime(ptr, buf_size, "-%Y%m%d%H", &now);
strcpy(ptr, ".backup");
message(MESS_ERROR, "destination %s already exists, renaming to %s\n",
fileName, backupName);
if (rename(fileName, backupName) != 0) {
message(MESS_ERROR, "error renaming already existing output file"
" %s to %s: %s\n", fileName, backupName, strerror(errno));
free(backupName);
return -1;
}
free(backupName);
/* existing file renamed, try it once again */
}
if (fd < 0) {
message(MESS_ERROR, "error creating output file %s: %s\n",
fileName, strerror(errno));
return -1;
}
if (fchmod(fd, (S_IRUSR | S_IWUSR) & sb->st_mode)) {
message(MESS_ERROR, "error setting mode of %s: %s\n",
fileName, strerror(errno));
close(fd);
return -1;
}
if (fstat(fd, &sb_create)) {
message(MESS_ERROR, "fstat of %s failed: %s\n", fileName,
strerror(errno));
close(fd);
return -1;
}
/* Only attempt to set user/group if running as root */
if (
ROOT_UID == geteuid() &&
(sb_create.st_uid != sb->st_uid || sb_create.st_gid != sb->st_gid) &&
fchown(fd, sb->st_uid, sb->st_gid)
) {
message(MESS_ERROR, "error setting owner of %s to uid %u and gid %u: %s\n",
fileName, (unsigned) sb->st_uid, (unsigned) sb->st_gid, strerror(errno));
close(fd);
return -1;
}
#ifdef WITH_ACL
if (!force_mode && acl) {
if (acl_set_fd(fd, acl) == -1) {
if (is_acl_well_supported(errno)) {
message(MESS_ERROR, "setting ACL for %s: %s\n",
fileName, strerror(errno));
close(fd);
return -1;
}
acl_set = 0;
}
else {
acl_set = 1;
}
}
#else
(void) acl;
#endif
if (!acl_set || force_mode) {
if (fchmod(fd, sb->st_mode)) {
message(MESS_ERROR, "error setting mode of %s: %s\n",
fileName, strerror(errno));
close(fd);
return -1;
}
}
return fd;
}
/* unlink, but try to call shred from GNU coreutils if LOG_FLAG_SHRED
* is enabled (in that case fd needs to be a valid file descriptor) */
static int shred_file(int fd, const char *filename, const struct logInfo *log)
{
char count[12]; /* 11 digits - that's a lot of shredding :) */
const char *fullCommand[6];
const char *errmsg = NULL;
int id = 0;
pid_t pid;
if (log->preremove) {
message(MESS_DEBUG, "running preremove script\n");
if (runScript(log, filename, NULL, log->preremove, &errmsg) < 0) {
message(MESS_ERROR,
"error running preremove script "
"for %s of '%s' (%s). Not removing this file.\n",
filename, log->pattern, errmsg);
/* What ever was supposed to happen did not happen,
* therefore do not unlink the file yet. */
return 1;
}
}
if (!(log->flags & LOG_FLAG_SHRED)) {
goto unlink_file;
}
if (!(log->flags & LOG_FLAG_ALLOWHARDLINK)) {
struct stat sb;
if (fstat(fd, &sb) != 0) {
message(MESS_ERROR, "cannot stat %s: %s\n", filename, strerror(errno));
return 1;
}
if (sb.st_nlink != 1) {
message(MESS_ERROR, "failed to shred \"%s\", because shredding files with"
" multiple hard links is disabled for %s.\n",
filename, log->pattern);
return 1;
}
}
message(MESS_DEBUG, "Using shred to remove the file %s\n", filename);
fullCommand[id++] = "shred";
fullCommand[id++] = "-u";
if (log->shred_cycles != 0) {
fullCommand[id++] = "-n";
snprintf(count, sizeof(count), "%d", log->shred_cycles);
fullCommand[id++] = count;
}
fullCommand[id++] = "-";
fullCommand[id++] = NULL;
pid = fork();
if (pid == -1) {
message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
return 1;
}
if (pid == 0) {
movefd(fd, STDOUT_FILENO);
if (switch_user_permanently(log) != 0) {
exit(1);
}
execvp(fullCommand[0], (void *) fullCommand);
message(MESS_ERROR, "cannot execute shred command: %s\n", strerror(errno));
exit(1);
}
if (waitpid_checked(pid, &errmsg) < 0) {
message(MESS_ERROR, "Failed to shred %s (%s), trying unlink\n", filename, errmsg);
return unlink(filename);
}
/* We have to unlink it after shred anyway,
* because it doesn't remove the file itself */
unlink_file:
if (unlink(filename) == 0)
return 0;
if (errno != ENOENT)
return 1;
/* unlink of log file that no longer exists is not a fatal error */
message(MESS_ERROR, "error unlinking log file %s: %s\n", filename,
strerror(errno));
return 0;
}
static int removeLogFile(const char *name, const struct logInfo *log)
{
int fd = -1;
int result = 0;
message(MESS_DEBUG, "removing old log %s\n", name);
if (log->flags & LOG_FLAG_SHRED) {
fd = open_logfile(name, log, 1);
if (fd < 0) {
message(MESS_ERROR, "error opening %s: %s\n",
name, strerror(errno));
return 1;
}
}
if (!debug && shred_file(fd, name, log)) {
message(MESS_ERROR, "Failed to remove old log %s: %s\n",
name, strerror(errno));
result = 1;
}
if (fd != -1)
close(fd);
return result;
}
static void setAtimeMtime(int fd, const char *filename, const struct stat *sb)
{
/* If we can't change atime/mtime, it's not a disaster. It might
possibly fail under SELinux. But do try to preserve the
fractional part if we have utimensat(). */
#if defined HAVE_FUTIMENS && defined HAVE_STRUCT_STAT_ST_ATIM && defined HAVE_STRUCT_STAT_ST_MTIM
struct timespec ts[2];
ts[0] = sb->st_atim;
ts[1] = sb->st_mtim;
futimens(fd, ts);
(void)filename;
#elif defined HAVE_UTIMENSAT && defined HAVE_STRUCT_STAT_ST_ATIM && defined HAVE_STRUCT_STAT_ST_MTIM
struct timespec ts[2];
ts[0] = sb->st_atim;
ts[1] = sb->st_mtim;
utimensat(AT_FDCWD, filename, ts, AT_SYMLINK_NOFOLLOW);
(void)fd;
#else
struct utimbuf utim;
utim.actime = sb->st_atime;
utim.modtime = sb->st_mtime;
utime(filename, &utim);
(void)fd;
#endif
}
static int compressLogFile(const char *name, const struct logInfo *log, const struct stat *sb)
{
char *compressedName;
const char *errmsg = NULL;
int inFile;
int outFile;
int compressPipe[2];
char *prevCtx;
pid_t pid;
message(MESS_DEBUG, "compressing log with: %s\n", log->compress_prog);
if (debug)
return 0;
if ((inFile = open_logfile(name, log, log->flags & LOG_FLAG_SHRED)) < 0) {
message(MESS_ERROR, "unable to open %s (%s) for compression: %s\n",
name, (log->flags & LOG_FLAG_SHRED) ? "read-write" : "read-only", strerror(errno));
return 1;
}
if (setSecCtx(inFile, name, &prevCtx) != 0) {
/* error msg already printed */
close(inFile);
return 1;
}
#ifdef WITH_ACL
if ((prev_acl = acl_get_fd(inFile)) == NULL) {
if (is_acl_well_supported(errno)) {
message(MESS_ERROR, "getting file ACL %s: %s\n",
name, strerror(errno));
restoreSecCtx(&prevCtx);
close(inFile);
return 1;
}
}
#endif
if (asprintf(&compressedName, "%s%s", name, log->compress_ext) < 0) {
message_OOM();
close(inFile);
return 1;
}
outFile =
createOutputFile(compressedName, O_RDWR, sb, prev_acl, 0);
restoreSecCtx(&prevCtx);
#ifdef WITH_ACL
if (prev_acl) {
acl_free(prev_acl);
prev_acl = NULL;
}
#endif
if (outFile < 0) {
close(inFile);
free(compressedName);
return 1;
}
/* pipe used to capture stderr of the compress process */
if (pipe(compressPipe) < 0) {
message(MESS_ERROR, "error opening pipe for compress: %s\n",
strerror(errno));
close(inFile);
close(outFile);
free(compressedName);
return 1;
}
pid = fork();
if (pid == -1) {
message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
close(inFile);
close(outFile);
close(compressPipe[1]);
close(compressPipe[0]);
free(compressedName);
return 1;
}
if (pid == 0) {
const char **fullCommand;
int i;
/* close read end of pipe in the child process */
close(compressPipe[0]);
free(compressedName);
movefd(inFile, STDIN_FILENO);
movefd(outFile, STDOUT_FILENO);
if (switch_user_permanently(log) != 0) {
exit(1);
}
movefd(compressPipe[1], STDERR_FILENO);
/* export name of file to compress for custom compress scripts */
{
char *envInFilename;
if (asprintf(&envInFilename, "LOGROTATE_COMPRESSED_FILENAME=%s", name) < 0) {
message_OOM();
exit(1);
}
putenv(envInFilename);
/* do not free envInFilename, cause putenv(3) might not create a copy */
}
fullCommand = malloc(sizeof(*fullCommand) * ((size_t)log->compress_options_count + 2));
if (!fullCommand) {
message_OOM();
exit(1);
}
fullCommand[0] = log->compress_prog;
for (i = 0; i < log->compress_options_count; i++)
fullCommand[i + 1] = log->compress_options_list[i];
fullCommand[log->compress_options_count + 1] = NULL;
execvp(fullCommand[0], (void *) fullCommand);
message(MESS_ERROR, "cannot execute compress command '%s': %s\n", fullCommand[0], strerror(errno));
exit(1);
}
/* close write end of pipe in the parent process */
close(compressPipe[1]);
{
int error_printed = 0;
for (;;) {
char buff[4096];
ssize_t n_read = read(compressPipe[0], buff, sizeof(buff) - 1);
if (n_read < 0) {
if (errno == EINTR)
continue;
else
break;
}
if (n_read == 0)
break;
if (!error_printed) {
error_printed = 1;
message(MESS_ERROR, "Compressing program wrote following message "
"to stderr when compressing log %s:\n", name);
}
buff[n_read] = '\0';
fprintf(stderr, "%s", buff);
}
}
close(compressPipe[0]);
if (waitpid_checked(pid, &errmsg) < 0) {
message(MESS_ERROR, "failed to compress log %s: %s\n", name, errmsg);
fsync(outFile);
close(inFile);
close(outFile);
unlink(compressedName);
free(compressedName);
return 1;
}
fsync(outFile);
setAtimeMtime(outFile, compressedName, sb);
close(outFile);
free(compressedName);
if (shred_file(inFile, name, log)) {
close(inFile);
return 1;
}
close(inFile);
return 0;
}
static int mailLog(const struct logInfo *log, const char *logFile, const char *mailComm,
const char *uncompressCommand, const char *address, const char *subject)
{
int mailInput;
pid_t mailChild, uncompressChild = 0;
const char *errmsg = NULL;
int uncompressPipe[2];
char * const mailArgv[] = { (char *) mailComm, (char *) "-s", (char *) subject, (char *) address, NULL };
int rc = 0;
if ((mailInput = open_logfile(logFile, log, 0)) < 0) {
message(MESS_ERROR, "failed to open %s for mailing: %s\n", logFile,
strerror(errno));
return 1;
}
if (uncompressCommand) {
/* pipe used to capture output of the uncompress process */
if (pipe(uncompressPipe) < 0) {
message(MESS_ERROR, "error opening pipe for uncompress: %s\n",
strerror(errno));
close(mailInput);
return 1;
}
uncompressChild = fork();
if (uncompressChild == -1) {
message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
close(mailInput);
close(uncompressPipe[1]);
close(uncompressPipe[0]);
return 1;
}
if (uncompressChild == 0) {
/* uncompress child */
/* close read end of pipe in the child process */
close(uncompressPipe[0]);
movefd(mailInput, STDIN_FILENO);
movefd(uncompressPipe[1], STDOUT_FILENO);
if (switch_user_permanently(log) != 0) {
exit(1);
}
execlp(uncompressCommand, uncompressCommand, (char *) NULL);
message(MESS_ERROR, "cannot execute uncompress command: %s\n", strerror(errno));
exit(1);
}
close(mailInput);
mailInput = uncompressPipe[0];
close(uncompressPipe[1]);
}
mailChild = fork();
if (mailChild == -1) {
message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
close(mailInput);
return 1;
}
if (mailChild == 0) {
movefd(mailInput, STDIN_FILENO);
close(STDOUT_FILENO);
/* mail command runs as root */
if (log->flags & LOG_FLAG_SU) {
if (switch_user_back_permanently() != 0) {
exit(1);
}
}
execvp(mailArgv[0], mailArgv);
message(MESS_ERROR, "cannot execute mail command: %s\n", strerror(errno));
exit(1);
}
close(mailInput);
if (waitpid_checked(mailChild, &errmsg) < 0) {
message(MESS_ERROR, "mail command failed for %s: %s\n", logFile, errmsg);
rc = 1;
}
if (uncompressCommand) {
if (waitpid_checked(uncompressChild, &errmsg) < 0) {
message(MESS_ERROR, "uncompress command failed mailing %s: %s\n",
logFile, errmsg);
rc = 1;
}
}
return rc;
}
static int mailLogWrapper(const char *mailFilename, const char *mailComm,
unsigned logNum, const struct logInfo *log)
{
/* uncompress already compressed log files before mailing them */
const char *uncompress_prog = (log->flags & LOG_FLAG_COMPRESS)
? log->uncompress_prog
: NULL;
const char *subject = mailFilename;
if (log->flags & LOG_FLAG_MAILFIRST) {
if (log->flags & LOG_FLAG_DELAYCOMPRESS)
/* the log we are mailing has not been compressed yet */
uncompress_prog = NULL;
if (uncompress_prog)
/* use correct subject when mailfirst is enabled */
subject = log->files[logNum];
}
return mailLog(log, mailFilename, mailComm, uncompress_prog,
log->logAddress, subject);
}
/* 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_blksize != 0
&& 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 int is_nul (void const *buf, size_t bufsize)
{
char const *cbuf = buf;
char const *cp = buf;
/* Find the first nonzero *byte*, or the sentinel. */
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 unsigned char *ptr = (const unsigned char *) buf;
while (count > 0)
{
size_t n_rw;
for (;;)
{
n_rw = (size_t)write (fd, ptr, count);
if (n_rw == (size_t) -1 && errno == EINTR)
continue;
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, const struct stat *sb,
const char *saveLog, const char *currLog)
{
const 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 + 1];
while (max_n_read) {
int make_hole = 0;
size_t bytes_read;
const 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;
bytes_read = (size_t)n_read;
max_n_read -= bytes_read;
total_n_read += n_read;
if (make_holes) {
/* Sentinel required by is_nul(). */
buf[bytes_read] = '\1';
if ((make_hole = is_nul(buf, bytes_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) {
if (full_write (dest_fd, buf, bytes_read) != bytes_read) {
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(const char *currLog, const char *saveLog, const struct stat *sb,
const struct logInfo *log, int skip_copy)
{
int rc = 1;
int fdcurr = -1, fdsave = -1;
message(MESS_DEBUG, "%scopying %s to %s\n", skip_copy ? "skip " : "", currLog, saveLog);
if (!debug) {
/* read access is sufficient for 'copy' but not for 'copytruncate' */
const int read_only = (log->flags & LOG_FLAG_COPY)
&& !(log->flags & LOG_FLAG_COPYTRUNCATE);
if ((fdcurr = open_logfile(currLog, log, !read_only)) < 0) {
message(MESS_ERROR, "error opening %s: %s\n", currLog,
strerror(errno));
goto fail;
}
if (!skip_copy) {
char *prevCtx;
if (setSecCtx(fdcurr, currLog, &prevCtx) != 0) {
/* error msg already printed */
goto fail;
}
#ifdef WITH_ACL
if ((prev_acl = acl_get_fd(fdcurr)) == NULL) {
if (is_acl_well_supported(errno)) {
message(MESS_ERROR, "getting file ACL %s: %s\n",
currLog, strerror(errno));
restoreSecCtx(&prevCtx);
goto fail;
}
}
#endif /* WITH_ACL */
fdsave = createOutputFile(saveLog, O_WRONLY, sb, prev_acl, 0);
restoreSecCtx(&prevCtx);
#ifdef WITH_ACL
if (prev_acl) {
acl_free(prev_acl);
prev_acl = NULL;
}
#endif
if (fdsave < 0)
goto fail;
if (sparse_copy(fdcurr, fdsave, sb, saveLog, currLog) != 1) {
message(MESS_ERROR, "error copying %s to %s: %s\n", currLog,
saveLog, strerror(errno));
unlink(saveLog);
goto fail;
}
}
}
if (log->flags & LOG_FLAG_COPYTRUNCATE) {
message(MESS_DEBUG, "truncating %s\n", currLog);
if (!debug) {
if (fdsave >= 0)
fsync(fdsave);
if (ftruncate(fdcurr, 0)) {
message(MESS_ERROR, "error truncating %s: %s\n", currLog,
strerror(errno));
goto fail;
}
}
} else
message(MESS_DEBUG, "Not truncating %s\n", currLog);
rc = 0;
fail:
if (fdcurr >= 0) {
close(fdcurr);
}
if (fdsave >= 0) {
close(fdsave);
}
return rc;
}
/* return value similar to mktime() but the exact time is ignored */
static time_t mktimeFromDateOnly(const struct tm *src)
{
/* explicit struct copy to retain C89 compatibility */
struct tm tmp;
memcpy(&tmp, src, sizeof tmp);
/* abstract out (nullify) fields expressing the exact time */
tmp.tm_hour = 0;
tmp.tm_min = 0;
tmp.tm_sec = 0;
return mktime(&tmp);
}
/* return by how many days the date was advanced but ignore exact time */
static long daysElapsed(const struct tm *now, const struct tm *last)
{
const double diff = difftime(mktimeFromDateOnly(now), mktimeFromDateOnly(last));
return (long) ((intmax_t)diff / DAY_SECONDS);
}
static int findNeedRotating(const struct logInfo *log, unsigned logNum, int force)
{
struct stat sb;
struct logState *state;
struct tm now;
message(MESS_DEBUG, "considering log %s\n", log->files[logNum]);
localtime_r(&nowSecs, &now);
/* Check if parent directory of this log has safe permissions */
if ((log->flags & LOG_FLAG_SU) == 0 && getuid() == ROOT_UID) {
char *ld;
char *logpath = strdup(log->files[logNum]);
if (logpath == NULL) {
message_OOM();
return 1;
}
ld = dirname(logpath);
if (stat(ld, &sb)) {
/* If parent directory doesn't exist, it's not real error
(unless nomissingok is specified)
and rotation is not needed */
if (errno != ENOENT || (log->flags & LOG_FLAG_MISSINGOK) == 0) {
message(MESS_ERROR, "stat of %s failed: %s\n", ld,
strerror(errno));
free(logpath);
return 1;
}
free(logpath);
return 0;
}
/* Don't rotate in directories writable by others or group which is not "root" */
if ((sb.st_gid != 0 && (sb.st_mode & S_IWGRP)) || (sb.st_mode & S_IWOTH)) {
message(MESS_ERROR, "skipping \"%s\" because parent directory has insecure permissions"
" (It's world writable or writable by group which is not \"root\")"
" Set \"su\" directive in config file to tell logrotate which user/group"
" should be used for rotation.\n"
,log->files[logNum]);
free(logpath);
return 1;
}
free(logpath);
}
if (lstat(log->files[logNum], &sb)) {
if ((log->flags & LOG_FLAG_MISSINGOK) && (errno == ENOENT)) {
message(MESS_DEBUG, " log %s does not exist -- skipping\n",
log->files[logNum]);
return 0;
}
message(MESS_ERROR, "stat of %s failed: %s\n", log->files[logNum],
strerror(errno));
return 1;
}
state = findState(log->files[logNum]);
if (!state)
return 1;
state->doRotate = 0;
state->sb = sb;
state->isUsed = 1;
if ((sb.st_mode & S_IFMT) == S_IFLNK) {
message(MESS_DEBUG, " log %s is symbolic link. Rotation of symbolic"
" links is not allowed to avoid security issues -- skipping.\n",
log->files[logNum]);
return 0;
}
if (!(log->flags & LOG_FLAG_ALLOWHARDLINK) && sb.st_nlink != 1) {
message(MESS_DEBUG, " log %s has multiple (%lu) hard links. Rotation of files"
" with multiple hard links is not allowed for %s -- skipping.\n",
log->files[logNum], (unsigned long)sb.st_nlink, log->pattern);
return 0;
}
message(MESS_DEBUG, " Now: %d-%02d-%02d %02d:%02d\n", 1900 + now.tm_year,
1 + now.tm_mon, now.tm_mday,
now.tm_hour, now.tm_min);
message(MESS_DEBUG, " Last rotated at %d-%02d-%02d %02d:%02d\n", 1900 + state->lastRotated.tm_year,
1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
state->lastRotated.tm_hour, state->lastRotated.tm_min);
if (force) {
/* user forced rotation of logs from command line */
state->doRotate = 1;
}
else if (log->maxsize && sb.st_size > log->maxsize) {
state->doRotate = 1;
}
else if (log->criterium == ROT_SIZE) {
state->doRotate = (sb.st_size >= log->threshold);
if (!state->doRotate) {
message(MESS_DEBUG, " log does not need rotating "
"(log size is below the 'size' threshold)\n");
}
} else if (difftime(mktime(&state->lastRotated), mktime(&now)) > (25 * 3600)) {
/* 25 hours allows for DST changes as well as geographical moves */
message(MESS_ERROR,
"log %s last rotated in the future -- rotation forced\n",
log->files[logNum]);
state->doRotate = 1;
} else if (state->lastRotated.tm_year != now.tm_year ||
state->lastRotated.tm_mon != now.tm_mon ||
state->lastRotated.tm_mday != now.tm_mday ||
state->lastRotated.tm_hour != now.tm_hour) {
long days;
switch (log->criterium) {
case ROT_WEEKLY:
days = daysElapsed(&now, &state->lastRotated);
/* rotate if date is advanced by 7+ days (exact time is ignored) */
state->doRotate = (days >= 7)
/* ... or if we have not yet rotated today */
|| (days >= 1
/* ... and the selected weekday is today */
&& (unsigned)now.tm_wday == log->weekday);
if (!state->doRotate) {
message(MESS_DEBUG, " log does not need rotating "
"(log has been rotated at %d-%02d-%02d %02d:%02d, "
"which is less than a week ago)\n", 1900 + state->lastRotated.tm_year,
1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
state->lastRotated.tm_hour, state->lastRotated.tm_min);
}
break;
case ROT_HOURLY:
state->doRotate = ((now.tm_hour != state->lastRotated.tm_hour) ||
(now.tm_mday != state->lastRotated.tm_mday) ||
(now.tm_mon != state->lastRotated.tm_mon) ||
(now.tm_year != state->lastRotated.tm_year));
if (!state->doRotate) {
message(MESS_DEBUG, " log does not need rotating "
"(log has been rotated at %d-%02d-%02d %02d:%02d, "
"which is less than an hour ago)\n", 1900 + state->lastRotated.tm_year,
1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
state->lastRotated.tm_hour, state->lastRotated.tm_min);
}
break;
case ROT_DAYS:
state->doRotate = ((now.tm_mday != state->lastRotated.tm_mday) ||
(now.tm_mon != state->lastRotated.tm_mon) ||
(now.tm_year != state->lastRotated.tm_year));
if (!state->doRotate) {
message(MESS_DEBUG, " log does not need rotating "
"(log has been rotated at %d-%02d-%02d %02d:%02d, "
"which is less than a day ago)\n", 1900 + state->lastRotated.tm_year,
1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
state->lastRotated.tm_hour, state->lastRotated.tm_min);
}
break;
case ROT_MONTHLY:
/* rotate if the logs haven't been rotated this month or
this year */
state->doRotate = ((now.tm_mon != state->lastRotated.tm_mon) ||
(now.tm_year != state->lastRotated.tm_year));
if (!state->doRotate) {
message(MESS_DEBUG, " log does not need rotating "
"(log has been rotated at %d-%02d-%02d %02d:%02d, "
"which is less than a month ago)\n", 1900 + state->lastRotated.tm_year,
1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
state->lastRotated.tm_hour, state->lastRotated.tm_min);
}
break;
case ROT_YEARLY:
/* rotate if the logs haven't been rotated this year */
state->doRotate = (now.tm_year != state->lastRotated.tm_year);
if (!state->doRotate) {
message(MESS_DEBUG, " log does not need rotating "
"(log has been rotated at %d-%02d-%02d %02d:%02d, "
"which is less than a year ago)\n", 1900 + state->lastRotated.tm_year,
1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
state->lastRotated.tm_hour, state->lastRotated.tm_min);
}
break;
case ROT_SIZE:
default:
/* ack! */
state->doRotate = 0;
break;
}
if (log->minsize && sb.st_size < log->minsize) {
state->doRotate = 0;
message(MESS_DEBUG, " log does not need rotating "
"('minsize' directive is used and the log "
"size is smaller than the minsize value)\n");
}
if (log->rotateMinAge && log->rotateMinAge * DAY_SECONDS >= difftime(nowSecs, sb.st_mtime)) {
state->doRotate = 0;
message(MESS_DEBUG, " log does not need rotating "
"('minage' directive is used and the log "
"age is smaller than the minage days)\n");
}
}
else if (!state->doRotate) {
message(MESS_DEBUG, " log does not need rotating "
"(log has already been rotated)\n");
}
/* The notifempty flag overrides the normal criteria */
if (state->doRotate && !(log->flags & LOG_FLAG_IFEMPTY) && !sb.st_size) {
state->doRotate = 0;
message(MESS_DEBUG, " log does not need rotating "
"(log is empty)\n");
}
if (state->doRotate) {
message(MESS_DEBUG, " log needs rotating\n");
}
return 0;
}
/* find the rotated file with the highest index */
static int findLastRotated(const struct logNames *rotNames,
const char *fileext, const char *compext)
{
char *pattern;
int glob_rc;
glob_t globResult;
size_t i;
int last = 0;
size_t prefixLen, suffixLen;
if (asprintf(&pattern, "%s/%s.*%s%s", rotNames->dirName,
rotNames->baseName, fileext, compext) < 0)
/* out of memory */
return -1;
glob_rc = glob(pattern, 0, globerr, &globResult);
free(pattern);
switch (glob_rc) {
case 0:
/* glob() succeeded */
break;
case GLOB_NOMATCH:
/* found nothing -> assume first rotation */
return 0;
default:
/* glob() failed */
return -1;
}
prefixLen = strlen(rotNames->dirName) + /* '/' */1
+ strlen(rotNames->baseName) + /* '.' */ 1;
suffixLen = strlen(fileext) + strlen(compext);
for (i = 0; i < globResult.gl_pathc; ++i) {
char *fileName = globResult.gl_pathv[i];
const size_t fileNameLen = strlen(fileName);
int num;
char c;
if (fileNameLen <= prefixLen + suffixLen)
/* not enough room for index in this file name */
continue;
/* cut off prefix/suffix */
fileName[fileNameLen - suffixLen] = '\0';
fileName += prefixLen;
if (sscanf(fileName, "%d%c", &num, &c) != 1)
/* index not matched in this file name */
continue;
/* update last index */
if (last < num)
last = num;
}
globfree(&globResult);
return last;
}
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;
#define DATEEXT_LEN 64
#define PATTERN_LEN (DATEEXT_LEN * 2)
char dext_str[DATEEXT_LEN];
char dformat[PATTERN_LEN] = "";
char dext_pattern[PATTERN_LEN];
const char *final_dformat;
size_t ret;
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) {
const char *dext = log->dateformat;
size_t i = 0, j = 0;
memset(dext_pattern, 0, sizeof(dext_pattern));
while (*dext == ' ')
dext++;
while (*dext != '\0') {
/* Will there be a space for a char and '\0'? */
if (j >= (sizeof(dext_pattern) - 1) ||
i >= (sizeof(dformat) - 2)) {
message(MESS_ERROR, "Date format %s is too long\n",
log->dateformat);
return 1;
}
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);
return 1;
}
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);
return 1;
}
dformat[i++] = *(dext++);
dformat[i] = *dext;
break;
case 'z':
strncat(dext_pattern, "[-+][0-9][0-9][0-9][0-9]",
sizeof(dext_pattern) - strlen(dext_pattern) - 1);
j += 24;
if (j >= (sizeof(dext_pattern) - 1)) {
message(MESS_ERROR, "Date format %s is too long\n",
log->dateformat);
return 1;
}
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);
final_dformat = dformat;
} else {
if (log->criterium == ROT_HOURLY) {
/* hourly adds another two digits */
final_dformat = "-%Y%m%d%H";
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 */
final_dformat = "-%Y%m%d";
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';
}
ret = strftime(dext_str, sizeof(dext_str), final_dformat, &now);
if (ret == 0) {
message(MESS_ERROR, "failed to apply date format '%s'\n", final_dformat);
return 1;
}
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, log->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)
&&
(((intmax_t)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, log->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, log->logStart, fileext,
(log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext) < 0) {
message_OOM();
free(oldName);
rotNames->firstRotated = NULL;
return 1;
}
for (i = rotateCount + log->logStart - 1; (i >= log->logStart) && !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 (((intmax_t)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, log->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, log->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;
}
static int rotateSingleLog(const struct logInfo *log, unsigned logNum,
struct logState *state, struct logNames *rotNames)
{
int hasErrors = 0;
struct stat sb;
char *savedContext = NULL;
if (!state->doRotate)
return 0;
if (!hasErrors) {
if (!(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) {
if (setSecCtxByName(log->files[logNum], log, &savedContext) != 0) {
/* error msg already printed */
return 1;
}
#ifdef WITH_ACL
if ((prev_acl = acl_get_file(log->files[logNum], ACL_TYPE_ACCESS)) == NULL) {
if (is_acl_well_supported(errno)) {
message(MESS_ERROR, "getting file ACL %s: %s\n",
log->files[logNum], strerror(errno));
hasErrors = 1;
}
}
#endif /* WITH_ACL */
if (log->flags & LOG_FLAG_TMPFILENAME) {
char *tmpFilename;
if (asprintf(&tmpFilename, "%s%s", log->files[logNum], ".tmp") < 0) {
message_OOM();
restoreSecCtx(&savedContext);
return 1;
}
message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum],
tmpFilename);
if (!debug && !hasErrors && rename(log->files[logNum], tmpFilename)) {
message(MESS_ERROR, "failed to rename %s to %s: %s\n",
log->files[logNum], tmpFilename,
strerror(errno));
hasErrors = 1;
}
free(tmpFilename);
}
else {
message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum],
rotNames->finalName);
if (!debug && !hasErrors &&
rename(log->files[logNum], rotNames->finalName)) {
message(MESS_ERROR, "failed to rename %s to %s: %s\n",
log->files[logNum], rotNames->finalName,
strerror(errno));
hasErrors = 1;
}
}
if (!log->rotateCount) {
const char *ext = "";
if (log->compress_ext
&& (log->flags & LOG_FLAG_COMPRESS)
&& !(log->flags & LOG_FLAG_DELAYCOMPRESS))
ext = log->compress_ext;
free(rotNames->disposeName);
if (asprintf(&rotNames->disposeName, "%s%s", rotNames->finalName, ext) < 0) {
message_OOM();
rotNames->disposeName = NULL;
return 1;
}
message(MESS_DEBUG, "disposeName will be %s\n", rotNames->disposeName);
}
}
if (!hasErrors && (log->flags & LOG_FLAG_CREATE) &&
!(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) {
int have_create_mode = 0;
if (log->createUid == NO_UID)
sb.st_uid = state->sb.st_uid;
else
sb.st_uid = log->createUid;
if (log->createGid == NO_GID)
sb.st_gid = state->sb.st_gid;
else
sb.st_gid = log->createGid;
if (log->createMode == NO_MODE)
sb.st_mode = state->sb.st_mode & 0777;
else {
sb.st_mode = log->createMode;
have_create_mode = 1;
}
message(MESS_DEBUG, "creating new %s mode = 0%o uid = %d "
"gid = %d\n", log->files[logNum], (unsigned int) sb.st_mode,
(int) sb.st_uid, (int) sb.st_gid);
if (!debug) {
if (!hasErrors) {
int fd = createOutputFile(log->files[logNum], O_RDWR,
&sb, prev_acl, have_create_mode);
#ifdef WITH_ACL
if (prev_acl) {
acl_free(prev_acl);
prev_acl = NULL;
}
#endif
if (fd < 0)
hasErrors = 1;
else {
close(fd);
}
}
}
}
restoreSecCtx(&savedContext);
if (!hasErrors
&& (log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))
&& !(log->flags & LOG_FLAG_TMPFILENAME)) {
hasErrors = copyTruncate(log->files[logNum], rotNames->finalName,
&state->sb, log,
!log->rotateCount && !log->logAddress);
}
#ifdef WITH_ACL
if (prev_acl) {
acl_free(prev_acl);
prev_acl = NULL;
}
#endif /* WITH_ACL */
}
return hasErrors;
}
static int postrotateSingleLog(const struct logInfo *log, unsigned logNum,
const struct logState *state,
const struct logNames *rotNames)
{
int hasErrors = 0;
if (!state->doRotate) {
return 0;
}
if (!hasErrors && (log->flags & LOG_FLAG_TMPFILENAME)) {
char *tmpFilename;
if (asprintf(&tmpFilename, "%s%s", log->files[logNum], ".tmp") < 0) {
message_OOM();
return 1;
}
hasErrors = copyTruncate(tmpFilename, rotNames->finalName,
&state->sb, log, /* skip_copy */ 0);
message(MESS_DEBUG, "removing tmp log %s\n", tmpFilename);
if (!debug && !hasErrors) {
unlink(tmpFilename);
}
free(tmpFilename);
}
if (!hasErrors && (log->flags & LOG_FLAG_COMPRESS) &&
!(log->flags & LOG_FLAG_DELAYCOMPRESS)) {
/* whether copying was skipped in rotateSingleLog() -> copyTruncate() */
int skipped_copy = (log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY)) &&
!(log->flags & LOG_FLAG_TMPFILENAME) &&
!log->rotateCount &&
!log->logAddress;
if (!skipped_copy)
hasErrors = compressLogFile(rotNames->finalName, log, &state->sb);
}
if (!hasErrors && log->logAddress) {
const char *mailFilename;
if (log->flags & LOG_FLAG_MAILFIRST)
mailFilename = rotNames->firstRotated;
else
mailFilename = rotNames->disposeName;
if (mailFilename)
hasErrors = mailLogWrapper(mailFilename, mailCommand, logNum, log);
}
if (!hasErrors && rotNames->disposeName)
hasErrors = removeLogFile(rotNames->disposeName, log);
restoreSecCtx(&prev_context);
return hasErrors;
}
static int rotateLogSet(const struct logInfo *log, int force)
{
unsigned i, j;
int hasErrors = 0;
int *logHasErrors;
int numRotated = 0;
struct logState **state;
struct logNames **rotNames;
const char *errmsg = NULL;
message(MESS_DEBUG, "\nrotating pattern: %s ", log->pattern);
if (force) {
message(MESS_DEBUG, "forced from command line ");
}
else {
switch (log->criterium) {
case ROT_HOURLY:
message(MESS_DEBUG, "hourly ");
break;
case ROT_DAYS:
message(MESS_DEBUG, "after %jd days ", (intmax_t)log->threshold);
break;
case ROT_WEEKLY:
message(MESS_DEBUG, "weekly ");
break;
case ROT_MONTHLY:
message(MESS_DEBUG, "monthly ");
break;
case ROT_YEARLY:
message(MESS_DEBUG, "yearly ");
break;
case ROT_SIZE:
message(MESS_DEBUG, "%jd bytes ", (intmax_t)log->threshold);
break;
default:
message(MESS_FATAL, "rotateLogSet() does not have case for: %u ",
(unsigned) log->criterium);
}
}
if (log->oldDir)
message(MESS_DEBUG, "olddir is %s, ", log->oldDir);
if (log->flags & LOG_FLAG_IFEMPTY)
message(MESS_DEBUG, "empty log files are rotated, ");
else
message(MESS_DEBUG, "empty log files are not rotated, ");
if (log->minsize)
message(MESS_DEBUG, "only log files >= %jd bytes are rotated, ", (intmax_t)log->minsize);
if (log->maxsize)
message(MESS_DEBUG, "log files >= %jd are rotated earlier, ", (intmax_t)log->maxsize);
if (log->rotateMinAge)
message(MESS_DEBUG, "only log files older than %d days are rotated, ", log->rotateMinAge);
if ((log->rotateCount == -1) && (log->rotateAge == 0))
message(MESS_DEBUG, "old logs are kept forever\n");
else {
if (log->logAddress)
message(MESS_DEBUG, "old logs mailed to %s, ", log->logAddress);
if (log->rotateCount == 0)
message(MESS_DEBUG, "no old logs will be kept\n");
else {
if (log->rotateCount == -1)
message(MESS_DEBUG, "(unlimited rotations), ");
else
message(MESS_DEBUG, "(%d rotations), ", log->rotateCount);
message(MESS_DEBUG, "old logs are removed");
if (log->rotateAge > 0)
message(MESS_DEBUG, " after %d days", log->rotateAge);
message(MESS_DEBUG, "\n");
}
}
if (log->numFiles == 0) {
message(MESS_DEBUG, "No logs found. Rotation not needed.\n");
return 0;
}
logHasErrors = calloc(log->numFiles, sizeof(int));
if (!logHasErrors) {
message_OOM();
return 1;
}
if (log->flags & LOG_FLAG_SU) {
if (switch_user(log->suUid, log->suGid) != 0) {
free(logHasErrors);
return 1;
}
}
for (i = 0; i < log->numFiles; i++) {
const struct logState *logState;
logHasErrors[i] = findNeedRotating(log, i, force);
hasErrors |= logHasErrors[i];
/* sure is a lot of findStating going on .. */
if (((logState = findState(log->files[i]))) && logState->doRotate)
numRotated++;
}
if (log->first) {
if (!numRotated) {
message(MESS_DEBUG, "not running first action script, "
"since no logs will be rotated\n");
} else {
message(MESS_DEBUG, "running first action script\n");
if (runScript(log, log->pattern, NULL, log->first, &errmsg) < 0) {
message(MESS_ERROR, "error running first action script "
"for %s: %s\n", log->pattern, errmsg);
hasErrors = 1;
if (log->flags & LOG_FLAG_SU) {
if (switch_user_back() != 0) {
free(logHasErrors);
return 1;
}
}
/* finish early, firstaction failed, affects all logs in set */
free(logHasErrors);
return hasErrors;
}
}
}
state = malloc(log->numFiles * sizeof(struct logState *));
rotNames = malloc(log->numFiles * sizeof(struct logNames *));
if (state == NULL || rotNames == NULL) {
message_OOM();
if (log->flags & LOG_FLAG_SU) {
switch_user_back();
}
free(rotNames);
free(state);
free(logHasErrors);
return 1;
}
for (j = 0;
(!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < log->numFiles)
|| ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < 1); j++) {
for (i = j;
((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
|| (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
state[i] = findState(log->files[i]);
if (!state[i])
logHasErrors[i] = 1;
rotNames[i] = malloc(sizeof(struct logNames));
if (rotNames[i] == NULL) {
message_OOM();
if (log->flags & LOG_FLAG_SU) {
switch_user_back();
}
free(rotNames);
free(state);
free(logHasErrors);
return 1;
}
memset(rotNames[i], 0, sizeof(struct logNames));
}
if (log->pre
&& (!(
(!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && (logHasErrors[j] || !state[j]->doRotate))
|| (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS))
))
) {
if (!numRotated) {
message(MESS_DEBUG, "not running prerotate script, "
"since no logs will be rotated\n");
} else {
message(MESS_DEBUG, "running prerotate script\n");
if (runScript(log, (log->flags & LOG_FLAG_SHAREDSCRIPTS) ? log->pattern : log->files[j],
NULL, log->pre, &errmsg) < 0) {
if (log->flags & LOG_FLAG_SHAREDSCRIPTS)
message(MESS_ERROR,
"error running shared prerotate script "
"for '%s': %s\n", log->pattern, errmsg);
else {
message(MESS_ERROR,
"error running non-shared prerotate script "
"for %s of '%s': %s\n", log->files[j], log->pattern, errmsg);
}
logHasErrors[j] = 1;
hasErrors = 1;
}
}
}
for (i = j;
((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
|| (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
|| (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) {
logHasErrors[i] |= prerotateSingleLog(log, i, state[i], rotNames[i]);
hasErrors |= logHasErrors[i];
}
}
for (i = j;
((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
|| (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
|| (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) {
logHasErrors[i] |=
rotateSingleLog(log, i, state[i], rotNames[i]);
hasErrors |= logHasErrors[i];
}
}
if (log->post
&& (!(
(!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && (logHasErrors[j] || !state[j]->doRotate))
|| (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS))
))
) {
if (!numRotated) {
message(MESS_DEBUG, "not running postrotate script, "
"since no logs were rotated\n");
} else {
const char *logfn = (log->flags & LOG_FLAG_SHAREDSCRIPTS) ? log->pattern : log->files[j];
/* It only makes sense to pass in a final rotated filename if scripts are not shared */
const char *logrotfn = (log->flags & LOG_FLAG_SHAREDSCRIPTS) ? NULL : rotNames[j]->finalName;
message(MESS_DEBUG, "running postrotate script\n");
if (runScript(log, logfn, logrotfn, log->post, &errmsg) < 0) {
if (log->flags & LOG_FLAG_SHAREDSCRIPTS)
message(MESS_ERROR,
"error running shared postrotate script "
"for '%s': %s\n", log->pattern, errmsg);
else {
message(MESS_ERROR,
"error running non-shared postrotate script "
"for %s of '%s': %s\n", log->files[j], log->pattern, errmsg);
}
logHasErrors[j] = 1;
hasErrors = 1;
}
}
}
for (i = j;
((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
|| (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
|| (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) {
logHasErrors[i] |=
postrotateSingleLog(log, i, state[i], rotNames[i]);
hasErrors |= logHasErrors[i];
}
}
}
for (i = 0; i < log->numFiles; i++) {
free(rotNames[i]->firstRotated);
free(rotNames[i]->disposeName);
free(rotNames[i]->finalName);
free(rotNames[i]->dirName);
free(rotNames[i]->baseName);
free(rotNames[i]);
}
free(rotNames);
free(state);
if (log->last) {
if (!numRotated) {
message(MESS_DEBUG, "not running last action script, "
"since no logs will be rotated\n");
} else {
message(MESS_DEBUG, "running last action script\n");
if (runScript(log, log->pattern, NULL, log->last, &errmsg) < 0) {
message(MESS_ERROR, "error running last action script "
"for %s: %s\n", log->pattern, errmsg);
hasErrors = 1;
}
}
}
if (log->flags & LOG_FLAG_SU) {
if (switch_user_back() != 0) {
free(logHasErrors);
return 1;
}
}
free(logHasErrors);
return hasErrors;
}
static int writeState(const char *stateFilename)
{
struct logState *p;
FILE *f;
char *chptr;
unsigned int i = 0;
int error = 0;
int bytes = 0;
int fdcurr;
int fdsave;
struct stat sb;
char *tmpFilename = NULL;
time_t last_time;
char *prevCtx;
int force_mode = 0;
if (!strcmp(stateFilename, "/dev/null"))
/* explicitly asked not to write the state file */
return 0;
fdcurr = open(stateFilename, O_RDONLY);
if (fdcurr == -1) {
/* the statefile should exist, lockState() already created an empty
* state file in case it did not exist initially */
message(MESS_ERROR, "error opening state file %s: %s\n",
stateFilename, strerror(errno));
return 1;
}
if (fstat(fdcurr, &sb) == -1) {
message(MESS_ERROR, "error stating %s: %s\n", stateFilename, strerror(errno));
close(fdcurr);
return 1;
}
if (!S_ISREG(sb.st_mode)) {
message(MESS_ERROR, "not writing state to %s because it is not a regular file\n", stateFilename);
close(fdcurr);
return 1;
}
tmpFilename = malloc(strlen(stateFilename) + 5 );
if (tmpFilename == NULL) {
message_OOM();
close(fdcurr);
return 1;
}
strcpy(tmpFilename, stateFilename);
strcat(tmpFilename, ".tmp");
/* Remove possible tmp state file from previous run */
error = unlink(tmpFilename);
if (error == -1 && errno != ENOENT) {
message(MESS_ERROR, "error removing old temporary state file %s: %s\n",
tmpFilename, strerror(errno));
free(tmpFilename);
close(fdcurr);
return 1;
}
error = 0;
/* get attributes, to assign them to the new state file */
if (setSecCtx(fdcurr, stateFilename, &prevCtx) != 0) {
/* error msg already printed */
free(tmpFilename);
close(fdcurr);
return 1;
}
#ifdef WITH_ACL
if ((prev_acl = acl_get_fd(fdcurr)) == NULL) {
if (is_acl_well_supported(errno)) {
message(MESS_ERROR, "getting file ACL %s: %s\n",
stateFilename, strerror(errno));
restoreSecCtx(&prevCtx);
free(tmpFilename);
close(fdcurr);
return 1;
}
}
#endif
close(fdcurr);
if (sb.st_mode & (mode_t)S_IROTH) {
/* drop world-readable flag to prevent others from locking */
sb.st_mode &= ~(mode_t)S_IROTH;
force_mode = 1;
}
fdsave = createOutputFile(tmpFilename, O_RDWR, &sb, prev_acl, force_mode);
#ifdef WITH_ACL
if (prev_acl) {
acl_free(prev_acl);
prev_acl = NULL;
}
#endif
restoreSecCtx(&prevCtx);
if (fdsave < 0) {
free(tmpFilename);
return 1;
}
f = fdopen(fdsave, "w");
if (!f) {
message(MESS_ERROR, "error creating temp state file %s: %s\n",
tmpFilename, strerror(errno));
free(tmpFilename);
return 1;
}
bytes = fprintf(f, "logrotate state -- version 2\n");
if (bytes < 0)
error = bytes;
/*
* Time in seconds it takes earth to go around sun. The value is
* astronomical measurement (solar year) rather than something derived from
* a convention (calendar year).
*/
#define SECONDS_IN_YEAR 31556926
for (i = 0; i < hashSize && error == 0; i++) {
for (p = states[i]->head.lh_first; p != NULL && error == 0;
p = p->list.le_next) {
/* Skip states which are not used for more than a year. */
last_time = mktime(&p->lastRotated);
if (!p->isUsed && difftime(nowSecs, last_time) > SECONDS_IN_YEAR) {
message(MESS_DEBUG, "Removing %s from state file, "
"because it does not exist and has not been rotated for one year\n",
p->fn);
continue;
}
error = fputc('"', f) == EOF;
for (chptr = p->fn; *chptr && error == 0; chptr++) {
switch (*chptr) {
case '"':
case '\\':
error = fputc('\\', f) == EOF;
break;
case '\n':
error = fputc('\\', f) == EOF;
if (error == 0) {
error = fputc('n', f) == EOF;
}
continue;
default:
break;
}
if (error == 0 && fputc(*chptr, f) == EOF) {
error = 1;
}
}
if (error == 0 && fputc('"', f) == EOF)
error = 1;
if (error == 0) {
bytes = fprintf(f, " %d-%d-%d-%d:%d:%d\n",
p->lastRotated.tm_year + 1900,
p->lastRotated.tm_mon + 1,
p->lastRotated.tm_mday,
p->lastRotated.tm_hour,
p->lastRotated.tm_min,
p->lastRotated.tm_sec);
if (bytes < 0)
error = bytes;
}
}
}
if (error == 0)
error = fflush(f);
if (error == 0)
error = fsync(fdsave);
if (error == 0)
error = fclose(f);
else {
int saved_errno;
saved_errno = errno;
fclose(f);
errno = saved_errno;
}
if (error == 0) {
if (rename(tmpFilename, stateFilename)) {
message(MESS_ERROR, "error renaming temp state file %s to %s: %s\n",
tmpFilename, stateFilename, strerror(errno));
unlink(tmpFilename);
error = 1;
}
}
else {
if (errno)
message(MESS_ERROR, "error creating temp state file %s: %s\n",
tmpFilename, strerror(errno));
else
message(MESS_ERROR, "error creating temp state file %s%s\n",
tmpFilename, error == ENOMEM ?
": Insufficient storage space is available." : "" );
unlink(tmpFilename);
}
free(tmpFilename);
return error;
}
static int readState(const char *stateFilename)
{
FILE *f;
char buf[STATEFILE_BUFFER_SIZE];
int line = 0;
int fd;
struct stat f_stat;
int rc = 0;
message(MESS_DEBUG, "Reading state from file: %s\n", stateFilename);
fd = open(stateFilename, O_RDONLY);
if (fd == -1) {
/* treat non-openable file as an empty file for allocateHash() */
f_stat.st_size = 0;
/* Do not return until the hash table is allocated.
* In debug mode the state file might not exist,
* cause lockState() is not called */
if (!debug) {
message(MESS_ERROR, "error opening state file %s: %s\n",
stateFilename, strerror(errno));
rc = 1;
} else if (errno == ENOENT) {
message(MESS_DEBUG, "state file %s does not exist\n",
stateFilename);
} else {
message(MESS_ERROR, "error opening state file %s; assuming empty state: %s\n",
stateFilename, strerror(errno));
}
} else {
if (fstat(fd, &f_stat) == -1) {
/* treat non-statable file as an empty file for allocateHash() */
f_stat.st_size = 0;
message(MESS_ERROR, "error stat()ing state file %s: %s\n",
stateFilename, strerror(errno));
/* do not return until the hash table is allocated */
rc = 1;
}
}
/* Try to estimate how many state entries we have in the state file.
* We expect single entry to have around 80 characters (Of course this is
* just an estimation). During the testing I've found out that 200 entries
* per single hash entry gives good mem/performance ratio. */
if (allocateHash((size_t)f_stat.st_size / 80 / 200))
rc = 1;
if (rc || (f_stat.st_size == 0)) {
/* error already occurred, or we have no state file to read from */
if (fd != -1)
close(fd);
return rc;
}
f = fdopen(fd, "r");
if (!f) {
message(MESS_ERROR, "error opening state file %s: %s\n",
stateFilename, strerror(errno));
close(fd);
return 1;
}
if (!fgets(buf, sizeof(buf) - 1, f)) {
message(MESS_ERROR, "error reading top line of %s\n",
stateFilename);
fclose(f);
return 1;
}
if (strcmp(buf, "logrotate state -- version 1\n") != 0 &&
strcmp(buf, "logrotate state -- version 2\n") != 0) {
fclose(f);
message(MESS_ERROR, "bad top line in state file %s\n",
stateFilename);
return 1;
}
line++;
while (fgets(buf, sizeof(buf) - 1, f)) {
const size_t i = strlen(buf);
char *filename;
int argc;
const char **argv = NULL;
int year, month, day, hour, minute, second;
struct logState *st;
time_t lr_time;
line++;
if (i == 0) {
message(MESS_ERROR, "line %d not parsable in state file %s\n",
line, stateFilename);
fclose(f);
return 1;
}
if (buf[i - 1] != '\n') {
message(MESS_ERROR, "line %d too long in state file %s\n",
line, stateFilename);
fclose(f);
return 1;
}
buf[i - 1] = '\0';
if (i == 1)
continue;
year = month = day = hour = minute = second = 0;
if (poptParseArgvString(buf, &argc, &argv) || (argc != 2) ||
(sscanf(argv[1], "%d-%d-%d-%d:%d:%d", &year, &month, &day, &hour, &minute, &second) < 3)) {
message(MESS_ERROR, "bad line %d in state file %s\n",
line, stateFilename);
free(argv);
fclose(f);
return 1;
}
/* Hack to hide earlier bug */
if ((year != 1900) && (year < 1970 || year > 2100)) {
message(MESS_ERROR,
"bad year %d for file %s in state file %s\n", year,
argv[0], stateFilename);
free(argv);
fclose(f);
return 1;
}
if (month < 1 || month > 12) {
message(MESS_ERROR,
"bad month %d for file %s in state file %s\n", month,
argv[0], stateFilename);
free(argv);
fclose(f);
return 1;
}
/* 0 to hide earlier bug */
if (day < 0 || day > 31) {
message(MESS_ERROR,
"bad day %d for file %s in state file %s\n", day,
argv[0], stateFilename);
free(argv);
fclose(f);
return 1;
}
if (hour < 0 || hour > 23) {
message(MESS_ERROR,
"bad hour %d for file %s in state file %s\n", hour,
argv[0], stateFilename);
free(argv);
fclose(f);
return 1;
}
if (minute < 0 || minute > 59) {
message(MESS_ERROR,
"bad minute %d for file %s in state file %s\n", minute,
argv[0], stateFilename);
free(argv);
fclose(f);
return 1;
}
if (second < 0 || second > 59) {
message(MESS_ERROR,
"bad second %d for file %s in state file %s\n", second,
argv[0], stateFilename);
free(argv);
fclose(f);
return 1;
}
year -= 1900;
month -= 1;
filename = strdup(argv[0]);
if (filename == NULL) {
message_OOM();
free(argv);
fclose(f);
return 1;
}
unescape(filename);
if ((st = findState(filename)) == NULL) {
free(argv);
free(filename);
fclose(f);
return 1;
}
memset(&st->lastRotated, 0, sizeof(st->lastRotated));
st->lastRotated.tm_year = year;
st->lastRotated.tm_mon = month;
st->lastRotated.tm_mday = day;
st->lastRotated.tm_hour = hour;
st->lastRotated.tm_min = minute;
st->lastRotated.tm_sec = second;
st->lastRotated.tm_isdst = -1;
/* fill in the rest of the st->lastRotated fields */
lr_time = mktime(&st->lastRotated);
localtime_r(&lr_time, &st->lastRotated);
free(argv);
free(filename);
}
fclose(f);
return 0;
}
static int lockState(const char *stateFilename, int skip_state_lock, int wait_for_state_lock)
{
int lockFd;
int lockFlags;
struct stat sb;
if (!strcmp(stateFilename, "/dev/null")) {
return 0;
}
lockFd = open(stateFilename, O_RDWR | O_CLOEXEC);
if (lockFd == -1) {
if (errno == ENOENT) {
message(MESS_DEBUG, "Creating stub state file: %s\n",
stateFilename);
/* create a stub state file with mode 0640 */
lockFd = open(stateFilename, O_CREAT | O_EXCL | O_WRONLY,
S_IWUSR | S_IRUSR | S_IRGRP);
if (lockFd == -1) {
message(MESS_ERROR, "error creating stub state file %s: %s\n",
stateFilename, strerror(errno));
return 1;
}
} else {
message(MESS_ERROR, "error opening state file %s: %s\n",
stateFilename, strerror(errno));
return 1;
}
}
if (skip_state_lock) {
message(MESS_DEBUG, "Skip locking state file %s\n",
stateFilename);
close(lockFd);
return 0;
}
if (fstat(lockFd, &sb) == -1) {
message(MESS_ERROR, "error stat()ing state file %s: %s\n",
stateFilename, strerror(errno));
close(lockFd);
return 1;
}
if (sb.st_mode & S_IROTH) {
message(MESS_WARN, "state file %s is world-readable"
" and thus can be locked from other unprivileged users."
" Skipping lock acquisition...\n",
stateFilename);
close(lockFd);
return 0;
}
lockFlags = LOCK_EX;
if (wait_for_state_lock)
message(MESS_DEBUG, "waiting for lock on state file %s\n", stateFilename);
else
lockFlags |= LOCK_NB;
if (flock(lockFd, lockFlags) == -1) {
if (errno == EWOULDBLOCK) {
message(MESS_ERROR, "state file %s is already locked\n"
"logrotate does not support parallel execution on the"
" same set of logfiles.\n", stateFilename);
} else {
message(MESS_ERROR, "error acquiring lock on state file %s: %s\n",
stateFilename, strerror(errno));
}
close(lockFd);
return 1;
}
message(MESS_DEBUG, "acquired lock on state file %s\n", stateFilename);
/* keep lockFd open till we terminate */
return 0;
}
int main(int argc, const char **argv)
{
int force = 0;
int skip_state_lock = 0;
int wait_for_state_lock = 0;
const char *stateFile = STATEFILE;
const char *logFile = NULL;
FILE *logFd = NULL;
int rc = 0;
int arg;
const char **files;
poptContext optCon;
const struct logInfo *log;
const struct poptOption options[] = {
{"debug", 'd', 0, NULL, 'd',
"Don't do anything, just test and print debug messages", NULL},
{"force", 'f', 0, &force, 0, "Force file rotation", NULL},
{"mail", 'm', POPT_ARG_STRING, &mailCommand, 0,
"Command to send mail (instead of `" DEFAULT_MAIL_COMMAND "')",
"command"},
{"state", 's', POPT_ARG_STRING, &stateFile, 0,
"Path of state file",
"statefile"},
{"skip-state-lock", '\0', POPT_ARG_NONE, &skip_state_lock, 0, "Do not lock the state file", NULL},
{"wait-for-state-lock", '\0', POPT_ARG_NONE, &wait_for_state_lock, 0, "Wait for lock on the state file", NULL},
{"verbose", 'v', 0, NULL, 'v', "Display messages during rotation", NULL},
{"log", 'l', POPT_ARG_STRING, &logFile, 'l', "Log file or 'syslog' to log to syslog",
"logfile"},
{"version", '\0', POPT_ARG_NONE, NULL, 'V', "Display version information", NULL},
POPT_AUTOHELP { NULL, 0, 0, NULL, 0, NULL, NULL }
};
logSetLevel(MESS_WARN);
setlocale (LC_ALL, "");
optCon = poptGetContext("logrotate", argc, argv, options, 0);
poptReadDefaultConfig(optCon, 1);
poptSetOtherOptionHelp(optCon, "[OPTION...] <configfile>");
while ((arg = poptGetNextOpt(optCon)) >= 0) {
switch (arg) {
case 'd':
debug = 1;
message(MESS_WARN, "logrotate in debug mode does nothing"
" except printing debug messages! Consider using verbose"
" mode (-v) instead if this is not what you want.\n\n");
/* fallthrough */
case 'v':
logSetLevel(MESS_DEBUG);
break;
case 'l':
if (strcmp(logFile, "syslog") == 0) {
logToSyslog(1);
}
else {
logFd = fopen(logFile, "w");
if (!logFd) {
message(MESS_ERROR, "error opening log file %s: %s\n",
logFile, strerror(errno));
break;
}
logSetMessageFile(logFd);
}
break;
case 'V':
printf("logrotate %s\n", VERSION);
printf("\n");
printf(" Default mail command: %s\n", DEFAULT_MAIL_COMMAND);
printf(" Default compress command: %s\n", COMPRESS_COMMAND);
printf(" Default uncompress command: %s\n", UNCOMPRESS_COMMAND);
printf(" Default compress extension: %s\n", COMPRESS_EXT);
printf(" Default state file path: %s\n", STATEFILE);
#ifdef WITH_ACL
printf(" ACL support: yes\n");
#else
printf(" ACL support: no\n");
#endif
#ifdef WITH_SELINUX
printf(" SELinux support: yes\n");
#else
printf(" SELinux support: no\n");
#endif
poptFreeContext(optCon);
exit(0);
default:
break;
}
}
if (arg < -1) {
fprintf(stderr, "logrotate: bad argument %s: %s\n",
poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
poptStrerror(rc));
poptFreeContext(optCon);
return 2;
}
files = poptGetArgs(optCon);
if (!files) {
fprintf(stderr, "logrotate " VERSION
" - Copyright (C) 1995-2001 Red Hat, Inc.\n");
fprintf(stderr,
"This may be freely redistributed under the terms of "
"the GNU General Public License\n\n");
poptPrintUsage(optCon, stderr, 0);
poptFreeContext(optCon);
exit(1);
}
if (skip_state_lock && wait_for_state_lock) {
fprintf(stderr, "logrotate: options --skip-state-lock and"
" --wait-for-state-lock are mutually exclusive\n");
poptFreeContext(optCon);
exit(1);
}
#ifdef WITH_SELINUX
selinux_enabled = (is_selinux_enabled() > 0);
selinux_enforce = security_getenforce();
#endif
TAILQ_INIT(&logs);
if (readAllConfigPaths(files))
rc = 1;
poptFreeContext(optCon);
nowSecs = time(NULL);
/* localtime_r(3) is not required to call tzset(3) */
tzset();
if (!debug && lockState(stateFile, skip_state_lock, wait_for_state_lock)) {
exit(3);
}
if (readState(stateFile))
rc = 1;
message(MESS_DEBUG, "\nHandling %d logs\n", numLogs);
/* Restore SIGCHLD handler in case our parent process had it ignored.
* Not doing this may cause our forked children to exit prematurely before our
* waitpid() calls, making it impossible to known the child proper exit code */
if (signal(SIGCHLD, SIG_DFL) == SIG_ERR)
message(MESS_WARN, "failed to reset SIGCHLD handler: %s\n", strerror(errno));
for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next)
rc |= rotateLogSet(log, force);
if (!debug)
rc |= writeState(stateFile);
return (rc != 0);
}
/* vim: set et sw=4 ts=4: */
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/jianqiangxue/logrotate.git
git@gitee.com:jianqiangxue/logrotate.git
jianqiangxue
logrotate
logrotate
main

搜索帮助