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 <my_pthread.h>
20 #include <string.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(sizeof(LOGGER_HANDLE), MYF(0))))
135   {
136     my_close(new_log.file, MYF(0));
137     new_log.file= -1;
138     return 0; /* End of memory */
139   }
140   *l_perm= new_log;
141 
142   flogger_mutex_init(key_LOCK_logger_service, l_perm,
143                      MY_MUTEX_INIT_FAST);
144 
145   len= header(&stat_arg, buf, sizeof(buf));
146   my_write(l_perm->file, (uchar *)buf, len, MYF(0));
147 
148   return l_perm;
149 }
150 
logger_close(LOGGER_HANDLE * log,logger_epilog_func_t footer)151 int logger_close(LOGGER_HANDLE *log, logger_epilog_func_t footer)
152 {
153   int result;
154   File file= log->file;
155   char buf[128];
156   size_t len;
157 
158   len= footer(buf, sizeof(buf));
159   my_write(file, (uchar *)buf, len, MYF(0));
160 
161   flogger_mutex_destroy(log);
162   my_free(log);
163   if ((result= my_close(file, MYF(0))))
164     errno= my_errno;
165   return result;
166 }
167 
168 
logger_reopen(LOGGER_HANDLE * log,logger_prolog_func_t header,logger_epilog_func_t footer)169 int logger_reopen(LOGGER_HANDLE *log, logger_prolog_func_t header,
170                   logger_epilog_func_t footer)
171 {
172   int result= 0;
173   MY_STAT stat_arg;
174   char buf[128];
175   size_t len;
176 
177   flogger_mutex_lock(log);
178 
179   len= footer(buf, sizeof(buf));
180   my_write(log->file, (uchar *)buf, len, MYF(0));
181 
182   if ((result= my_close(log->file, MYF(0))))
183   {
184     errno= my_errno;
185     goto error;
186   }
187 
188   if ((log->file= my_open(log->path, LOG_FLAGS, MYF(0))) < 0)
189   {
190     errno= my_errno;
191     result= 1;
192     goto error;
193   }
194 
195   if ((result= my_fstat(log->file, &stat_arg, MYF(0))))
196   {
197     errno= my_errno;
198     goto error;
199   }
200 
201   len= header(&stat_arg, buf, sizeof(buf));
202   my_write(log->file, (uchar *)buf, len, MYF(0));
203 
204 error:
205   flogger_mutex_unlock(log);
206 
207   return result;
208 }
209 
210 
logname(LOGGER_HANDLE * log,char * buf,unsigned int n_log)211 static char *logname(LOGGER_HANDLE *log, char *buf, unsigned int n_log)
212 {
213   sprintf(buf+log->path_len, ".%0*u", n_dig(log->rotations), n_log);
214   return buf;
215 }
216 
217 
do_rotate(LOGGER_HANDLE * log)218 static int do_rotate(LOGGER_HANDLE *log)
219 {
220   char namebuf[FN_REFLEN];
221   int result;
222   unsigned int i;
223   char *buf_old, *buf_new, *tmp;
224 
225   if (log->rotations == 0)
226     return 0;
227 
228   memcpy(namebuf, log->path, log->path_len);
229 
230   buf_new= logname(log, namebuf, log->rotations);
231   buf_old= log->path;
232   for (i=log->rotations-1; i>0; i--)
233   {
234     logname(log, buf_old, i);
235     if (!access(buf_old, F_OK) &&
236         (result= my_rename(buf_old, buf_new, MYF(0))))
237       goto exit;
238     tmp= buf_old;
239     buf_old= buf_new;
240     buf_new= tmp;
241   }
242   if ((result= my_close(log->file, MYF(0))))
243     goto exit;
244   namebuf[log->path_len]= 0;
245   result= my_rename(namebuf, logname(log, log->path, 1), MYF(0));
246   log->file= my_open(namebuf, LOG_FLAGS, MYF(0));
247 exit:
248   errno= my_errno;
249   return log->file < 0 || result;
250 }
251 
252 
logger_write(LOGGER_HANDLE * log,const char * buffer,size_t size,log_record_state_t state)253 int logger_write(LOGGER_HANDLE *log, const char *buffer, size_t size,
254                  log_record_state_t state)
255 {
256   int result;
257   my_off_t filesize;
258 
259   flogger_mutex_lock(log);
260 
261   result= my_write(log->file, (uchar *) buffer, size, MYF(0));
262 
263   if (state == LOG_RECORD_COMPLETE && log->rotations > 0)
264   {
265     if ((filesize= my_tell(log->file, MYF(0))) == (my_off_t) -1 ||
266         ((unsigned long long)filesize >= log->size_limit &&
267          do_rotate(log)))
268     {
269       result= -1;
270       errno= my_errno;
271       goto exit; /* Log rotation needed but failed */
272     }
273   }
274 
275 exit:
276   flogger_mutex_unlock(log);
277   return result;
278 }
279 
280 
logger_init_mutexes()281 void logger_init_mutexes()
282 {
283 #if defined(HAVE_PSI_INTERFACE) && !defined(FLOGGER_NO_PSI) && !defined(FLOGGER_NO_THREADSAFE)
284   mysql_mutex_register(AUDIT_LOG_PSI_CATEGORY,
285                        mutex_list, array_elements(mutex_list));
286 #endif /*HAVE_PSI_INTERFACE && !FLOGGER_NO_PSI*/
287 }
288 
logger_sync(LOGGER_HANDLE * log)289 int logger_sync(LOGGER_HANDLE *log)
290 {
291   return my_sync(log->file, MYF(0));
292 }
293 
logger_set_size_limit(LOGGER_HANDLE * log,unsigned long long size_limit)294 void logger_set_size_limit(LOGGER_HANDLE *log, unsigned long long size_limit)
295 {
296   log->size_limit= size_limit;
297 }
298 
logger_set_rotations(LOGGER_HANDLE * log,unsigned int rotations)299 void logger_set_rotations(LOGGER_HANDLE *log, unsigned int rotations)
300 {
301   log->rotations= rotations;
302 }
303