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 #ifdef O_LARGEFILE
64     /* Support for 1996-03-20 addition to Single UNIX Specification for
65      * systems that still require the Transitional Extensions to System
66      * Interfaces to support arbitrary file sizes (LFS >2GiB)
67      */
68     mode |= O_LARGEFILE;
69 #endif
70 
71     /* open channel file */
72     mask = umask(0);
73     cfg->fd = open(cfg->path, mode, cfg->perm);
74     umask(mask);
75 
76     /* prepare jittering counter */
77     cfg->jittercount = 0;
78 
79     /* prepare monitoring time and stat */
80     if (cfg->monitor >= 1) {
81         if (gettimeofday(&tv, NULL) != -1)
82             cfg->monitortime = tv.tv_sec;
83         else
84             cfg->monitortime = 0;
85         if (   (cfg->fd != -1)
86             && (fstat(cfg->fd, &st) != -1)) {
87             cfg->monitordev = st.st_dev;
88             cfg->monitorino = st.st_ino;
89         }
90         else {
91             cfg->monitordev = 0;
92             cfg->monitorino = 0;
93         }
94     }
95 }
96 
97 /* create channel */
hook_create(l2_context_t * ctx,l2_channel_t * ch)98 static l2_result_t hook_create(l2_context_t *ctx, l2_channel_t *ch)
99 {
100     l2_ch_file_t *cfg;
101 
102     /* allocate private channel configuration */
103     if ((cfg = (l2_ch_file_t *)malloc(sizeof(l2_ch_file_t))) == NULL)
104         return L2_ERR_ARG;
105 
106     /* initialize configuration with reasonable defaults */
107     cfg->fd          = -1;
108     cfg->path        = NULL;
109     cfg->append      = -1;
110     cfg->trunc       = -1;
111     cfg->perm        = (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
112     cfg->jitter      = 0;
113     cfg->jittercount = 0;
114     cfg->monitor     = 0;
115     cfg->monitortime = 0;
116     cfg->monitordev  = 0;
117     cfg->monitorino  = 0;
118 
119     /* link private channel configuration into channel context */
120     ctx->vp = cfg;
121 
122     return L2_OK;
123 }
124 
125 /* configure channel */
hook_configure(l2_context_t * ctx,l2_channel_t * ch,const char * fmt,va_list * ap)126 static l2_result_t hook_configure(l2_context_t *ctx, l2_channel_t *ch, const char *fmt, va_list *ap)
127 {
128     l2_ch_file_t *cfg = (l2_ch_file_t *)ctx->vp;
129     l2_param_t pa[7];
130     l2_result_t rv;
131     l2_env_t *env;
132 
133     /* feed and call generic parameter parsing engine */
134     L2_PARAM_SET(pa[0], path,    STR, &cfg->path);
135     L2_PARAM_SET(pa[1], append,  INT, &cfg->append);
136     L2_PARAM_SET(pa[2], trunc,   INT, &cfg->trunc);
137     L2_PARAM_SET(pa[3], perm,    INT, &cfg->perm);
138     L2_PARAM_SET(pa[4], jitter,  INT, &cfg->jitter);
139     L2_PARAM_SET(pa[5], monitor, INT, &cfg->monitor);
140     L2_PARAM_END(pa[6]);
141     l2_channel_env(ch, &env);
142     rv = l2_util_setparams(env, pa, fmt, ap);
143 
144     return rv;
145 }
146 
147 /* open channel */
hook_open(l2_context_t * ctx,l2_channel_t * ch)148 static l2_result_t hook_open(l2_context_t *ctx, l2_channel_t *ch)
149 {
150     l2_ch_file_t *cfg = (l2_ch_file_t *)ctx->vp;
151 
152     /* "append" backward compatibility; only cfg->trunc is used in the code
153      * make sure append/trunc either both use defaults, both are set different, or only one is set
154      *
155      * truth table for user input,       append, trunc => resulting trunc
156      *                 -----------------+------+------+------------------
157      *                                       -1     -1      0 (default)
158      *                 trunc=0               -1      0      0
159      *                 trunc=1               -1      1      1
160      *                 append=0               0     -1      1
161      *                 append=0, trunc=0      0      0      ERROR
162      *                 append=0, trunc=1      0      1      1
163      *                 append=1               1     -1      0
164      *                 append=1, trunc=0      1      0      0
165      *                 append=1, trunc=1      1      1      ERROR
166      */
167     if (cfg->append >= 1)
168         cfg->append = 1; /* reduce to -1 (undef), 0 (no), 1 (yes) */
169     if (cfg->trunc >= 1)
170         cfg->trunc = 1; /* reduce to -1 (undef), 0 (no), 1 (yes) */
171     if (   cfg->append != -1
172         && cfg->trunc != -1
173         && cfg->append == cfg->trunc) /* collision */
174         return L2_ERR_USE;
175     if (   cfg->trunc == -1)
176         cfg->trunc = (1 - cfg->append) & 1;
177 
178     /* make sure jitter count is positive number */
179     if (cfg->jitter < 0)
180         return L2_ERR_USE;
181 
182     /* make sure monitor time is positive number */
183     if (cfg->monitor < 0)
184         return L2_ERR_USE;
185 
186     /* make sure a path was set */
187     if (cfg->path == NULL)
188         return L2_ERR_USE;
189 
190     /* open channel file */
191     if (cfg->trunc == 1)
192         openchfile(ctx, ch, O_WRONLY|O_CREAT|O_TRUNC);
193     else
194         openchfile(ctx, ch, O_WRONLY|O_CREAT|O_APPEND);
195 
196     if (cfg->fd == -1)
197         return L2_ERR_SYS;
198 
199     return L2_OK;
200 }
201 
202 /* write to channel */
hook_write(l2_context_t * ctx,l2_channel_t * ch,l2_level_t level,const char * buf,size_t buf_size)203 static l2_result_t hook_write(l2_context_t *ctx, l2_channel_t *ch,
204                               l2_level_t level, const char *buf, size_t buf_size)
205 {
206     l2_ch_file_t *cfg = (l2_ch_file_t *)ctx->vp;
207     l2_result_t rc = L2_OK;
208     int reopen = 0;
209     struct timeval tv;
210     struct stat st;
211 
212     /* if jittering, count writes and reopen file if jitter threshold is reached or exceeded */
213     if (cfg->jitter >= 1) {
214         cfg->jittercount++;
215         if (cfg->jittercount >= cfg->jitter) {
216             cfg->jittercount = 0;
217             reopen = 1;
218         }
219     }
220 
221     /* if monitoring, from time to time check for a renamed log and reopen file on detection */
222     if (cfg->monitor >= 1) {
223         int dostat = 0;
224         if (gettimeofday(&tv, NULL) != -1) {
225             if ((tv.tv_sec - cfg->monitortime) >= cfg->monitor) {
226                 cfg->monitortime = tv.tv_sec;
227                 dostat = 1;
228             }
229         }
230         else {
231             dostat = 1;
232         }
233         if (dostat == 1) {
234             if (stat(cfg->path, &st) == -1) {
235                 reopen = 1;
236             }
237             else {
238                 if (   (cfg->monitordev != st.st_dev)
239                     || (cfg->monitorino != st.st_ino)) {
240                     reopen = 1;
241                 }
242             }
243         }
244     }
245 
246     /* close for reopen if required */
247     if (reopen == 1 && cfg->fd != -1) {
248         close(cfg->fd);
249         cfg->fd = -1;
250     }
251 
252     /* open if required */
253     if (cfg->fd == -1) {
254         openchfile(ctx, ch, O_WRONLY|O_CREAT|O_APPEND);
255     }
256 
257     if (cfg->fd == -1)
258         return L2_ERR_SYS;
259 
260     /* write message to channel file */
261     if (write(cfg->fd, buf, buf_size) == -1)
262         rc = L2_ERR_SYS;
263 
264     return rc;
265 }
266 
267 /* close channel */
hook_close(l2_context_t * ctx,l2_channel_t * ch)268 static l2_result_t hook_close(l2_context_t *ctx, l2_channel_t *ch)
269 {
270     l2_ch_file_t *cfg = (l2_ch_file_t *)ctx->vp;
271 
272     /* close channel file */
273     close(cfg->fd);
274     cfg->fd = -1;
275 
276     return L2_OK;
277 }
278 
279 /* destroy channel */
hook_destroy(l2_context_t * ctx,l2_channel_t * ch)280 static l2_result_t hook_destroy(l2_context_t *ctx, l2_channel_t *ch)
281 {
282     l2_ch_file_t *cfg = (l2_ch_file_t *)ctx->vp;
283 
284     /* destroy channel configuration */
285     if (cfg->path != NULL)
286         free(cfg->path);
287     free(cfg);
288 
289     return L2_OK;
290 }
291 
292 /* exported channel handler structure */
293 l2_handler_t l2_handler_file = {
294     "file",
295     L2_CHANNEL_OUTPUT,
296     hook_create,
297     hook_configure,
298     hook_open,
299     hook_write,
300     NULL,
301     hook_close,
302     hook_destroy
303 };
304 
305