1 /*
2 **  OSSP l2 - Flexible Logging
3 **  Copyright (c) 2001-2005 Cable & Wireless <http://www.cw.com/>
4 **  Copyright (c) 2001-2005 The OSSP Project <http://www.ossp.org/>
5 **  Copyright (c) 2001-2005 Ralf S. Engelschall <rse@engelschall.com>
6 **
7 **  This file is part of OSSP l2, a flexible logging library which
8 **  can be found at http://www.ossp.org/pkg/lib/l2/.
9 **
10 **  Permission to use, copy, modify, and distribute this software for
11 **  any purpose with or without fee is hereby granted, provided that
12 **  the above copyright notice and this permission notice appear in all
13 **  copies.
14 **
15 **  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
16 **  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 **  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 **  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
19 **  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 **  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 **  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
22 **  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 **  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 **  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25 **  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 **  SUCH DAMAGE.
27 **
28 **  l2_ch_file.c: file channel implementation
29 */
30 
31 #include "l2.h"
32 
33 #include <fcntl.h>
34 #include <unistd.h>
35 #include <sys/stat.h>
36 #include <sys/time.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 
40 /* declare private channel configuration */
41 typedef struct {
42     int   fd;
43     char *path;
44     int   append;
45     int   trunc;
46     int   perm;
47     int   jitter;
48     int   jittercount;
49     int   monitor;
50     long  monitortime;
51     dev_t monitordev;
52     ino_t monitorino;
53 } l2_ch_file_t;
54 
55 /* open channel file */
openchfile(l2_context_t * ctx,l2_channel_t * ch,int mode)56 static void openchfile(l2_context_t *ctx, l2_channel_t *ch, int mode)
57 {
58     l2_ch_file_t *cfg = (l2_ch_file_t *)ctx->vp;
59     mode_t mask;
60     struct timeval tv;
61     struct stat st;
62 
63     /* open channel file */
64     mask = umask(0);
65     cfg->fd = open(cfg->path, mode, cfg->perm);
66     umask(mask);
67 
68     /* prepare jittering counter */
69     cfg->jittercount = 0;
70 
71     /* prepare monitoring time and stat */
72     if (cfg->monitor >= 1) {
73         if (gettimeofday(&tv, NULL) != -1)
74             cfg->monitortime = tv.tv_sec;
75         else
76             cfg->monitortime = 0;
77         if (   (cfg->fd != -1)
78             && (fstat(cfg->fd, &st) != -1)) {
79             cfg->monitordev = st.st_dev;
80             cfg->monitorino = st.st_ino;
81         }
82         else {
83             cfg->monitordev = 0;
84             cfg->monitorino = 0;
85         }
86     }
87 }
88 
89 /* create channel */
hook_create(l2_context_t * ctx,l2_channel_t * ch)90 static l2_result_t hook_create(l2_context_t *ctx, l2_channel_t *ch)
91 {
92     l2_ch_file_t *cfg;
93 
94     /* allocate private channel configuration */
95     if ((cfg = (l2_ch_file_t *)malloc(sizeof(l2_ch_file_t))) == NULL)
96         return L2_ERR_ARG;
97 
98     /* initialize configuration with reasonable defaults */
99     cfg->fd          = -1;
100     cfg->path        = NULL;
101     cfg->append      = -1;
102     cfg->trunc       = -1;
103     cfg->perm        = (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
104     cfg->jitter      = 0;
105     cfg->jittercount = 0;
106     cfg->monitor     = 0;
107     cfg->monitortime = 0;
108     cfg->monitordev  = 0;
109     cfg->monitorino  = 0;
110 
111     /* link private channel configuration into channel context */
112     ctx->vp = cfg;
113 
114     return L2_OK;
115 }
116 
117 /* configure channel */
hook_configure(l2_context_t * ctx,l2_channel_t * ch,const char * fmt,va_list * ap)118 static l2_result_t hook_configure(l2_context_t *ctx, l2_channel_t *ch, const char *fmt, va_list *ap)
119 {
120     l2_ch_file_t *cfg = (l2_ch_file_t *)ctx->vp;
121     l2_param_t pa[7];
122     l2_result_t rv;
123     l2_env_t *env;
124 
125     /* feed and call generic parameter parsing engine */
126     L2_PARAM_SET(pa[0], path,    STR, &cfg->path);
127     L2_PARAM_SET(pa[1], append,  INT, &cfg->append);
128     L2_PARAM_SET(pa[2], trunc,   INT, &cfg->trunc);
129     L2_PARAM_SET(pa[3], perm,    INT, &cfg->perm);
130     L2_PARAM_SET(pa[4], jitter,  INT, &cfg->jitter);
131     L2_PARAM_SET(pa[5], monitor, INT, &cfg->monitor);
132     L2_PARAM_END(pa[6]);
133     l2_channel_env(ch, &env);
134     rv = l2_util_setparams(env, pa, fmt, ap);
135 
136     return rv;
137 }
138 
139 /* open channel */
hook_open(l2_context_t * ctx,l2_channel_t * ch)140 static l2_result_t hook_open(l2_context_t *ctx, l2_channel_t *ch)
141 {
142     l2_ch_file_t *cfg = (l2_ch_file_t *)ctx->vp;
143 
144     /* "append" backward compatibility; only cfg->trunc is used in the code
145      * make sure append/trunc either both use defaults, both are set different, or only one is set
146      *
147      * truth table for user input,       append, trunc => resulting trunc
148      *                 -----------------+------+------+------------------
149      *                                       -1     -1      0 (default)
150      *                 trunc=0               -1      0      0
151      *                 trunc=1               -1      1      1
152      *                 append=0               0     -1      1
153      *                 append=0, trunc=0      0      0      ERROR
154      *                 append=0, trunc=1      0      1      1
155      *                 append=1               1     -1      0
156      *                 append=1, trunc=0      1      0      0
157      *                 append=1, trunc=1      1      1      ERROR
158      */
159     if (cfg->append >= 1)
160         cfg->append = 1; /* reduce to -1 (undef), 0 (no), 1 (yes) */
161     if (cfg->trunc >= 1)
162         cfg->trunc = 1; /* reduce to -1 (undef), 0 (no), 1 (yes) */
163     if (   cfg->append != -1
164         && cfg->trunc != -1
165         && cfg->append == cfg->trunc) /* collision */
166         return L2_ERR_USE;
167     if (   cfg->trunc == -1)
168         cfg->trunc = (1 - cfg->append) & 1;
169 
170     /* make sure jitter count is positive number */
171     if (cfg->jitter < 0)
172         return L2_ERR_USE;
173 
174     /* make sure monitor time is positive number */
175     if (cfg->monitor < 0)
176         return L2_ERR_USE;
177 
178     /* make sure a path was set */
179     if (cfg->path == NULL)
180         return L2_ERR_USE;
181 
182     /* open channel file */
183     if (cfg->trunc == 1)
184         openchfile(ctx, ch, O_WRONLY|O_CREAT|O_TRUNC);
185     else
186         openchfile(ctx, ch, O_WRONLY|O_CREAT|O_APPEND);
187 
188     if (cfg->fd == -1)
189         return L2_ERR_SYS;
190 
191     return L2_OK;
192 }
193 
194 /* write to channel */
hook_write(l2_context_t * ctx,l2_channel_t * ch,l2_level_t level,const char * buf,size_t buf_size)195 static l2_result_t hook_write(l2_context_t *ctx, l2_channel_t *ch,
196                               l2_level_t level, const char *buf, size_t buf_size)
197 {
198     l2_ch_file_t *cfg = (l2_ch_file_t *)ctx->vp;
199     l2_result_t rc = L2_OK;
200     int reopen = 0;
201     struct timeval tv;
202     struct stat st;
203 
204     /* if jittering, count writes and reopen file if jitter threshold is reached or exceeded */
205     if (cfg->jitter >= 1) {
206         cfg->jittercount++;
207         if (cfg->jittercount >= cfg->jitter) {
208             cfg->jittercount = 0;
209             reopen = 1;
210         }
211     }
212 
213     /* if monitoring, from time to time check for a renamed log and reopen file on detection */
214     if (cfg->monitor >= 1) {
215         int dostat = 0;
216         if (gettimeofday(&tv, NULL) != -1) {
217             if ((tv.tv_sec - cfg->monitortime) >= cfg->monitor) {
218                 cfg->monitortime = tv.tv_sec;
219                 dostat = 1;
220             }
221         }
222         else {
223             dostat = 1;
224         }
225         if (dostat == 1) {
226             if (stat(cfg->path, &st) == -1) {
227                 reopen = 1;
228             }
229             else {
230                 if (   (cfg->monitordev != st.st_dev)
231                     || (cfg->monitorino != st.st_ino)) {
232                     reopen = 1;
233                 }
234             }
235         }
236     }
237 
238     /* close for reopen if required */
239     if (reopen == 1 && cfg->fd != -1) {
240         close(cfg->fd);
241         cfg->fd = -1;
242     }
243 
244     /* open if required */
245     if (cfg->fd == -1) {
246         openchfile(ctx, ch, O_WRONLY|O_CREAT|O_APPEND);
247     }
248 
249     if (cfg->fd == -1)
250         return L2_ERR_SYS;
251 
252     /* write message to channel file */
253     if (write(cfg->fd, buf, buf_size) == -1)
254         rc = L2_ERR_SYS;
255 
256     return rc;
257 }
258 
259 /* close channel */
hook_close(l2_context_t * ctx,l2_channel_t * ch)260 static l2_result_t hook_close(l2_context_t *ctx, l2_channel_t *ch)
261 {
262     l2_ch_file_t *cfg = (l2_ch_file_t *)ctx->vp;
263 
264     /* close channel file */
265     close(cfg->fd);
266     cfg->fd = -1;
267 
268     return L2_OK;
269 }
270 
271 /* destroy channel */
hook_destroy(l2_context_t * ctx,l2_channel_t * ch)272 static l2_result_t hook_destroy(l2_context_t *ctx, l2_channel_t *ch)
273 {
274     l2_ch_file_t *cfg = (l2_ch_file_t *)ctx->vp;
275 
276     /* destroy channel configuration */
277     if (cfg->path != NULL)
278         free(cfg->path);
279     free(cfg);
280 
281     return L2_OK;
282 }
283 
284 /* exported channel handler structure */
285 l2_handler_t l2_handler_file = {
286     "file",
287     L2_CHANNEL_OUTPUT,
288     hook_create,
289     hook_configure,
290     hook_open,
291     hook_write,
292     NULL,
293     hook_close,
294     hook_destroy
295 };
296 
297