1 /* Copyright (C) 2012 Monty Program Ab
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; version 2 of the License.
6 
7    This program is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10    GNU General Public License for more details.
11 
12    You should have received a copy of the GNU General Public License
13    along with this program; if not, write to the Free Software
14    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA */
15 
16 #include <my_global.h>
17 #include <mysql/plugin.h>
18 #include <my_sys.h>
19 #include <string.h>
20 #include <my_thread_local.h>
21 
22 #include "logger.h"
23 #include "audit_log.h"
24 
25 extern char *mysql_data_home;
26 
27 #ifndef FLOGGER_NO_PSI
28   #define flogger_mutex_init(A,B,C) \
29             if ((B)->thread_safe) \
30               mysql_mutex_init(A,&((B)->lock),C)
31 
32   #define flogger_mutex_destroy(A) \
33             if ((A)->thread_safe) \
34               mysql_mutex_destroy(&((A)->lock))
35 
36   #define flogger_mutex_lock(A) \
37             if ((A)->thread_safe) \
38               mysql_mutex_lock(&((A)->lock))
39 
40   #define flogger_mutex_unlock(A) \
41             if ((A)->thread_safe) \
42               mysql_mutex_unlock(&((A)->lock))
43 #else
44   #define flogger_mutex_init(A,B,C) \
45             if ((B)->thread_safe) \
46               pthread_mutex_init(&((B)->lock.m_mutex), C)
47 
48   #define flogger_mutex_destroy(A) \
49             if ((A)->thread_safe) \
50               pthread_mutex_destroy(&((A)->lock.m_mutex))
51 
52   #define flogger_mutex_lock(A) \
53             if ((A)->thread_safe) \
54               pthread_mutex_lock(&((A)->lock.m_mutex))
55 
56   #define flogger_mutex_unlock(A) \
57             if ((A)->thread_safe) \
58               pthread_mutex_unlock(&((A)->lock.m_mutex))
59 #endif /*!FLOGGER_NO_PSI*/
60 
61 #if defined(HAVE_PSI_INTERFACE) && !defined(FLOGGER_NO_PSI)
62 /* These belong to the service initialization */
63 static PSI_mutex_key key_LOCK_logger_service;
64 static PSI_mutex_info mutex_list[]=
65 {{ &key_LOCK_logger_service, "file_logger::lock", PSI_FLAG_GLOBAL}};
66 #else
67 #define key_LOCK_logger_service 0
68 #endif /*HAVE_PSI_INTERFACE && !FLOGGER_NO_PSI*/
69 
70 typedef struct logger_handle_st {
71   File file;
72   char path[FN_REFLEN];
73   unsigned long long size_limit;
74   unsigned int rotations;
75   size_t path_len;
76   mysql_mutex_t lock;
77   int thread_safe;
78 } LSFS;
79 
80 
81 #define LOG_FLAGS (O_APPEND | O_CREAT | O_WRONLY)
82 
n_dig(unsigned int i)83 static unsigned int n_dig(unsigned int i)
84 {
85   return (i == 0) ? 0 : ((i < 10) ? 1 : ((i < 100) ? 2 : 3));
86 }
87 
logger_open(const char * path,unsigned long long size_limit,unsigned int rotations,int thread_safe,logger_prolog_func_t header)88 LOGGER_HANDLE *logger_open(const char *path,
89                            unsigned long long size_limit,
90                            unsigned int rotations,
91                            int thread_safe,
92                            logger_prolog_func_t header)
93 {
94   LOGGER_HANDLE new_log, *l_perm;
95   MY_STAT stat_arg;
96   char buf[128];
97   size_t len;
98 
99   /*
100     I don't think we ever need more rotations,
101     but if it's so, the rotation procedure should be adapted to it.
102   */
103   if (rotations > 999)
104     return 0;
105 
106   new_log.rotations= rotations;
107   new_log.size_limit= size_limit;
108   new_log.path_len= strlen(fn_format(new_log.path, path,
109         mysql_data_home, "", MY_UNPACK_FILENAME));
110   new_log.thread_safe= thread_safe;
111 
112   if (new_log.path_len+n_dig(rotations)+1 > FN_REFLEN)
113   {
114     errno= ENAMETOOLONG;
115     /* File path too long */
116     return 0;
117   }
118 
119   if ((new_log.file= my_open(new_log.path, LOG_FLAGS, 0666)) < 0)
120   {
121     errno= my_errno();
122     /* Check errno for the cause */
123     return 0;
124   }
125 
126   if (my_fstat(new_log.file, &stat_arg, MYF(0)))
127   {
128     errno= my_errno();
129     my_close(new_log.file, MYF(0));
130     new_log.file= -1;
131     return 0;
132   }
133 
134   if (!(l_perm= (LOGGER_HANDLE *) my_malloc(key_memory_audit_log_logger_handle,
135                                             sizeof(LOGGER_HANDLE), MYF(0))))
136   {
137     my_close(new_log.file, MYF(0));
138     new_log.file= -1;
139     return 0; /* End of memory */
140   }
141   *l_perm= new_log;
142 
143   flogger_mutex_init(key_LOCK_logger_service, l_perm,
144                      MY_MUTEX_INIT_FAST);
145 
146   len= header(&stat_arg, buf, sizeof(buf));
147   my_write(l_perm->file, (uchar *)buf, len, MYF(0));
148 
149   return l_perm;
150 }
151 
logger_close(LOGGER_HANDLE * log,logger_epilog_func_t footer)152 int logger_close(LOGGER_HANDLE *log, logger_epilog_func_t footer)
153 {
154   int result;
155   File file= log->file;
156   char buf[128];
157   size_t len;
158 
159   len= footer(buf, sizeof(buf));
160   my_write(file, (uchar *)buf, len, MYF(0));
161 
162   flogger_mutex_destroy(log);
163   my_free(log);
164   if ((result= my_close(file, MYF(0))))
165     errno= my_errno();
166   return result;
167 }
168 
169 
logger_reopen(LOGGER_HANDLE * log,logger_prolog_func_t header,logger_epilog_func_t footer)170 int logger_reopen(LOGGER_HANDLE *log, logger_prolog_func_t header,
171                   logger_epilog_func_t footer)
172 {
173   int result= 0;
174   MY_STAT stat_arg;
175   char buf[128];
176   size_t len;
177 
178   flogger_mutex_lock(log);
179 
180   len= footer(buf, sizeof(buf));
181   my_write(log->file, (uchar *)buf, len, MYF(0));
182 
183   if ((result= my_close(log->file, MYF(0))))
184   {
185     errno= my_errno();
186     goto error;
187   }
188 
189   if ((log->file= my_open(log->path, LOG_FLAGS, MYF(0))) < 0)
190   {
191     errno= my_errno();
192     result= 1;
193     goto error;
194   }
195 
196   if ((result= my_fstat(log->file, &stat_arg, MYF(0))))
197   {
198     errno= my_errno();
199     goto error;
200   }
201 
202   len= header(&stat_arg, buf, sizeof(buf));
203   my_write(log->file, (uchar *)buf, len, MYF(0));
204 
205 error:
206   flogger_mutex_unlock(log);
207 
208   return result;
209 }
210 
211 
logname(LOGGER_HANDLE * log,char * buf,size_t buf_len,unsigned int n_log)212 static char *logname(LOGGER_HANDLE *log, char *buf, size_t buf_len,
213                      unsigned int n_log)
214 {
215   snprintf(buf+log->path_len, buf_len, ".%0*u", n_dig(log->rotations),
216            n_log);
217   return buf;
218 }
219 
220 
do_rotate(LOGGER_HANDLE * log)221 static int do_rotate(LOGGER_HANDLE *log)
222 {
223   char namebuf[FN_REFLEN];
224   int result;
225   unsigned int i;
226   char *buf_old, *buf_new, *tmp;
227 
228   if (log->rotations == 0)
229     return 0;
230 
231   memcpy(namebuf, log->path, log->path_len);
232 
233   buf_new= logname(log, namebuf, sizeof(namebuf), log->rotations);
234   buf_old= log->path;
235   for (i=log->rotations-1; i>0; i--)
236   {
237     logname(log, buf_old, FN_REFLEN, i);
238     if (!access(buf_old, F_OK) &&
239         (result= my_rename(buf_old, buf_new, MYF(0))))
240       goto exit;
241     tmp= buf_old;
242     buf_old= buf_new;
243     buf_new= tmp;
244   }
245   if ((result= my_close(log->file, MYF(0))))
246     goto exit;
247   namebuf[log->path_len]= 0;
248   result= my_rename(namebuf, logname(log, log->path, FN_REFLEN, 1), MYF(0));
249   log->file= my_open(namebuf, LOG_FLAGS, MYF(0));
250 exit:
251   errno= my_errno();
252   return log->file < 0 || result;
253 }
254 
255 
logger_write(LOGGER_HANDLE * log,const char * buffer,size_t size,log_record_state_t state)256 int logger_write(LOGGER_HANDLE *log, const char *buffer, size_t size,
257                  log_record_state_t state)
258 {
259   int result;
260   my_off_t filesize;
261 
262   flogger_mutex_lock(log);
263 
264   result= my_write(log->file, (uchar *) buffer, size, MYF(0));
265 
266   if (state == LOG_RECORD_COMPLETE && log->rotations > 0)
267   {
268     if ((filesize= my_tell(log->file, MYF(0))) == (my_off_t) -1 ||
269         ((unsigned long long)filesize >= log->size_limit &&
270          do_rotate(log)))
271     {
272       result= -1;
273       errno= my_errno();
274       goto exit; /* Log rotation needed but failed */
275     }
276   }
277 
278 exit:
279   flogger_mutex_unlock(log);
280   return result;
281 }
282 
283 
logger_init_mutexes()284 void logger_init_mutexes()
285 {
286 #if defined(HAVE_PSI_INTERFACE) && !defined(FLOGGER_NO_PSI) && !defined(FLOGGER_NO_THREADSAFE)
287   mysql_mutex_register(AUDIT_LOG_PSI_CATEGORY,
288                        mutex_list, array_elements(mutex_list));
289 #endif /*HAVE_PSI_INTERFACE && !FLOGGER_NO_PSI*/
290 }
291 
logger_sync(LOGGER_HANDLE * log)292 int logger_sync(LOGGER_HANDLE *log)
293 {
294   return my_sync(log->file, MYF(0));
295 }
296 
logger_set_size_limit(LOGGER_HANDLE * log,unsigned long long size_limit)297 void logger_set_size_limit(LOGGER_HANDLE *log, unsigned long long size_limit)
298 {
299   log->size_limit= size_limit;
300 }
301 
logger_set_rotations(LOGGER_HANDLE * log,unsigned int rotations)302 void logger_set_rotations(LOGGER_HANDLE *log, unsigned int rotations)
303 {
304   log->rotations= rotations;
305 }
306