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