]> git.saurik.com Git - apple/syslog.git/blob - syslogd.tproj/bb_convert.c
syslog-217.1.4.tar.gz
[apple/syslog.git] / syslogd.tproj / bb_convert.c
1 /*
2 * Copyright (c) 2009-2010 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include <stdio.h>
25 #include <dirent.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <stdint.h>
30 #include <errno.h>
31 #include <time.h>
32 #include <sys/time.h>
33 #include <sys/stat.h>
34 #include <asl.h>
35 #include <asl_private.h>
36 #include <asl_core.h>
37 #include <asl_file.h>
38 #include <asl_store.h>
39
40 extern time_t asl_parse_time(const char *);
41
42 #define TEMP_NAME "_TMP_.asl"
43 #define STORE_DATA_FLAGS 0x00000000
44
45 #if TARGET_IPHONE_SIMULATOR
46 const char *store_path;
47 #else
48 static const char *store_path = PATH_ASL_STORE;
49 #endif
50
51 /*
52 * Cache the output file for BB writes.
53 * we write messages in the order in which they were generated,
54 * so we are almost guaranteed to use the cache in most cases.
55 */
56 static asl_file_t *cache_file = NULL;
57 static uid_t cache_uid = -1;
58 static uid_t cache_gid = -1;
59 static time_t cache_bb = 0;
60
61 typedef struct name_list_s
62 {
63 char *name;
64 struct name_list_s *next;
65 } name_list_t;
66
67 static name_list_t *
68 add_to_list(name_list_t *l, const char *name)
69 {
70 name_list_t *e, *x;
71
72 if (name == NULL) return l;
73
74 e = (name_list_t *)calloc(1, sizeof(name_list_t));
75 if (e == NULL) return NULL;
76
77 e->name = strdup(name);
78 if (e->name == NULL)
79 {
80 free(e);
81 return NULL;
82 }
83
84 /* list is sorted by name (i.e. primarily by timestamp) */
85 if (l == NULL) return e;
86
87 if (strcmp(e->name, l->name) <= 0)
88 {
89 e->next = l;
90 return e;
91 }
92
93 for (x = l; (x->next != NULL) && (strcmp(e->name, x->next->name) > 0) ; x = x->next);
94
95 e->next = x->next;
96 x->next = e;
97 return l;
98 }
99
100 static void
101 free_list(name_list_t *l)
102 {
103 name_list_t *e;
104
105 while (l != NULL)
106 {
107 e = l;
108 l = l->next;
109 free(e->name);
110 free(e);
111 }
112
113 free(l);
114 }
115
116 /* find all messages that have an ASLExpireTime key */
117 static uint32_t
118 do_ASLExpireTime_search(asl_store_t *s, asl_search_result_t **out)
119 {
120 asl_search_result_t q, *query, *res;
121 asl_msg_t *qm[1];
122 uint32_t status;
123 uint64_t mid;
124
125 qm[0] = asl_msg_new(ASL_TYPE_QUERY);
126 if (qm[0] == NULL) return ASL_STATUS_NO_MEMORY;
127
128 q.count = 1;
129 q.curr = 0;
130 q.msg = qm;
131 query = &q;
132
133 if (asl_msg_set_key_val_op(qm[0], ASL_KEY_EXPIRE_TIME, NULL, ASL_QUERY_OP_TRUE) != 0)
134 {
135 asl_msg_release(qm[0]);
136 return ASL_STATUS_NO_MEMORY;
137 }
138
139 res = NULL;
140 mid = 0;
141 status = asl_store_match(s, query, out, &mid, 0, 0, 1);
142
143 asl_msg_release(qm[0]);
144 return status;
145 }
146
147 /* remove all messages that have an ASLExpireTime key */
148 static uint32_t
149 do_ASLExpireTime_filter(const char *name)
150 {
151 aslmsg msg;
152 asl_file_t *in, *out;
153 uint32_t status;
154 uint64_t mid;
155 char *inpath, *outpath;
156 struct stat sb;
157
158 if (name == NULL) return ASL_STATUS_INVALID_ARG;
159
160 in = NULL;
161 inpath = NULL;
162 asprintf(&inpath, "%s/%s", store_path, name);
163 if (inpath == NULL) return ASL_STATUS_NO_MEMORY;
164
165 memset(&sb, 0, sizeof(struct stat));
166 if (stat(inpath, &sb) < 0)
167 {
168 free(inpath);
169 return ASL_STATUS_INVALID_STORE;
170 }
171
172 status = asl_file_open_read(inpath, &in);
173 if (status != ASL_STATUS_OK)
174 {
175 free(inpath);
176 return ASL_STATUS_OK;
177 }
178
179 out = NULL;
180 outpath = NULL;
181 asprintf(&outpath, "%s/%s", store_path, TEMP_NAME);
182 if (outpath == NULL)
183 {
184 asl_file_close(in);
185 free(inpath);
186 return ASL_STATUS_NO_MEMORY;
187 }
188
189 status = asl_file_open_write(outpath, sb.st_mode, sb.st_uid, sb.st_gid, &out);
190 if (status != ASL_STATUS_OK)
191 {
192 asl_file_close(in);
193 free(inpath);
194 free(outpath);
195 return status;
196 }
197
198 out->flags = ASL_FILE_FLAG_PRESERVE_MSG_ID;
199
200 msg = NULL;
201 while (asl_file_fetch_next(in, &msg) == ASL_STATUS_OK)
202 {
203 if (msg == NULL) break;
204
205 mid = 0;
206
207 if (asl_get(msg, ASL_KEY_EXPIRE_TIME) == NULL) status = asl_file_save(out, msg, &mid);
208
209 asl_free(msg);
210 msg = NULL;
211
212 if (status != ASL_STATUS_OK) break;
213 }
214
215 asl_file_close(in);
216 asl_file_close(out);
217
218 unlink(inpath);
219 rename(outpath, inpath);
220
221 free(inpath);
222 free(outpath);
223
224 return status;
225 }
226
227 /* qsort compare function for sorting by message ID */
228 static int
229 sort_compare(const void *a, const void *b)
230 {
231 const char *va, *vb;
232 uint64_t na, nb;
233
234 va = asl_get(*(aslmsg *)a, ASL_KEY_MSG_ID);
235 vb = asl_get(*(aslmsg *)b, ASL_KEY_MSG_ID);
236
237 if (va == NULL) return -1;
238 if (vb == NULL) return 1;
239
240 na = atoll(va);
241 nb = atoll(vb);
242
243 if (na < nb) return -1;
244 if (na > nb) return 1;
245 return 0;
246 }
247
248 /* save a message to an appropriately named BB file */
249 static uint32_t
250 save_bb_msg(aslmsg msg)
251 {
252 const char *val;
253 uid_t u, ruid;
254 gid_t g, rgid;
255 struct tm ctm;
256 time_t msg_time, bb;
257 char *path, *tstring;
258 asl_file_t *out;
259 uint64_t mid;
260 mode_t m;
261 uint32_t status;
262
263 if (msg == NULL) return ASL_STATUS_OK;
264
265 val = asl_get(msg, ASL_KEY_EXPIRE_TIME);
266 if (val == NULL) return ASL_STATUS_INVALID_ARG;
267 msg_time = asl_parse_time(val);
268
269 val = asl_get(msg, ASL_KEY_READ_UID);
270 ruid = -1;
271 if (val != NULL) ruid = atoi(val);
272
273 val = asl_get(msg, ASL_KEY_READ_GID);
274 rgid = -1;
275 if (val != NULL) rgid = atoi(val);
276
277 if (localtime_r((const time_t *)&msg_time, &ctm) == NULL) return ASL_STATUS_FAILED;
278
279 /*
280 * This supports 12 monthy "Best Before" buckets.
281 * We advance the actual expiry time to day zero of the following month.
282 * mktime() is clever enough to know that you actually mean the last day
283 * of the previous month. What we get back from localtime is the last
284 * day of the month in which the message expires, which we use in the name.
285 */
286 ctm.tm_sec = 0;
287 ctm.tm_min = 0;
288 ctm.tm_hour = 0;
289 ctm.tm_mday = 0;
290 ctm.tm_mon += 1;
291
292 bb = mktime(&ctm);
293
294 u = 0;
295 g = 0;
296 if (ruid != -1) u = ruid;
297 if (rgid != -1) g = rgid;
298
299 out = NULL;
300
301 if (cache_file != NULL)
302 {
303 if ((cache_uid == u) && (cache_gid == g) && (cache_bb == bb))
304 {
305 out = cache_file;
306 }
307 else
308 {
309 asl_file_close(cache_file);
310 cache_file = NULL;
311 cache_uid = -1;
312 cache_gid = -1;
313 cache_bb = 0;
314 }
315 }
316
317 if (out == NULL)
318 {
319 if (localtime_r((const time_t *)&bb, &ctm) == NULL) return ASL_STATUS_FAILED;
320
321 tstring = NULL;
322 asprintf(&tstring, "%s/BB.%d.%02d.%02d", store_path, ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
323 if (tstring == NULL) return ASL_STATUS_NO_MEMORY;
324
325 path = NULL;
326 m = 0644;
327
328 if (ruid == -1)
329 {
330 if (rgid == -1)
331 {
332 asprintf(&path, "%s.asl", tstring);
333 }
334 else
335 {
336 m = 0640;
337 asprintf(&path, "%s.G%d.asl", tstring, g);
338 }
339 }
340 else
341 {
342 if (rgid == -1)
343 {
344 m = 0600;
345 asprintf(&path, "%s.U%d.asl", tstring, u);
346 }
347 else
348 {
349 m = 0640;
350 asprintf(&path, "%s.U%d.G%u.asl", tstring, u, g);
351 }
352 }
353
354 if (path == NULL) return ASL_STATUS_NO_MEMORY;
355
356 status = asl_file_open_write(path, m, u, g, &out);
357 free(path);
358 if (status != ASL_STATUS_OK) return status;
359 if (out == NULL) return ASL_STATUS_FAILED;
360
361 out->flags = ASL_FILE_FLAG_PRESERVE_MSG_ID;
362
363 cache_file = out;
364 cache_uid = u;
365 cache_gid = g;
366 cache_bb = bb;
367 }
368
369 status = asl_file_save(out, msg, &mid);
370
371 return status;
372 }
373
374 static uint32_t
375 finish_conversion()
376 {
377 FILE *sd;
378 uint32_t store_flags;
379 int status;
380 char *path;
381
382 path = NULL;
383 asprintf(&path, "%s/%s", store_path, FILE_ASL_STORE_DATA);
384
385 sd = fopen(path, "a");
386 free(path);
387 if (sd == NULL) return ASL_STATUS_WRITE_FAILED;
388
389 store_flags = STORE_DATA_FLAGS;
390 status = fwrite(&store_flags, sizeof(uint32_t), 1, sd);
391 fclose(sd);
392
393 if (status != 1) return ASL_STATUS_WRITE_FAILED;
394
395 return ASL_STATUS_OK;
396 }
397
398 /*
399 * Utility to convert a data store with LongTTL files into
400 * a store with Best Before files.
401 *
402 * Returns quickly if the data store has already been converted.
403 *
404 * Older versions of the data store included messages with non-standard time-to-live
405 * records in the daily data files (yyyy.mm.dd.asl). When the files expired, aslmanager
406 * first copied messages with ASLExpireTime keys to a LongTTL file, then deleted the
407 * original data file.
408 *
409 * We now write ASLExpireTime messages to a Best Before file (BB.yyyy.mm.dd.asl)
410 * and aslmanager just deletes these files after the Best Before date has passed.
411 *
412 * If StoreData is bigger than 8 bytes, the store has been converted. Do nothing.
413 *
414 * Convert the store:
415 * Search the store for messages that have an ASLExpireTime.
416 * Sort by ASLMessageID
417 * Remove all BB.* files and all LongTTL.* files
418 * Write the ASLExpireTime messages into a new set of BB files
419 * Re-write each YMD file without messages that have an ASLExpireTime
420 * Add a new 4-byte flags field to StoreData
421 */
422
423 uint32_t
424 bb_convert(const char *name)
425 {
426 struct stat sb;
427 asl_store_t *store;
428 uint32_t status;
429 asl_search_result_t *expire_time_records;
430 DIR *dp;
431 struct dirent *dent;
432 int i;
433 name_list_t *list, *e;
434 char *path;
435
436 if (name != NULL) store_path = name;
437
438 /* StoreData must exist */
439 path = NULL;
440 asprintf(&path, "%s/%s", store_path, FILE_ASL_STORE_DATA);
441 if (path == NULL) return ASL_STATUS_NO_MEMORY;
442
443 memset(&sb, 0, sizeof(struct stat));
444 i = stat(path, &sb);
445 free(path);
446 if (i != 0) return ASL_STATUS_INVALID_STORE;
447
448 /* must be a regular file */
449 if (!S_ISREG(sb.st_mode)) return ASL_STATUS_INVALID_STORE;
450
451 /* check is the store has already been converted */
452 if (sb.st_size > sizeof(uint64_t)) return ASL_STATUS_OK;
453
454 /* find ASLExpireTime messages */
455 status = asl_store_open_read(store_path, &store);
456 if (status != ASL_STATUS_OK) return status;
457
458 expire_time_records = NULL;
459 status = do_ASLExpireTime_search(store, &expire_time_records);
460
461 asl_store_close(store);
462 if (status != ASL_STATUS_OK) return status;
463
464 /* unlink BB.* and LongTTL.* */
465 dp = opendir(store_path);
466 if (dp == NULL) return ASL_STATUS_READ_FAILED;
467
468 while ((dent = readdir(dp)) != NULL)
469 {
470 if ((!strncmp(dent->d_name, "BB.", 3)) || (!strncmp(dent->d_name, "LongTTL.", 8)))
471 {
472 path = NULL;
473 asprintf(&path, "%s/%s", store_path, dent->d_name);
474 if (path == NULL)
475 {
476 closedir(dp);
477 return ASL_STATUS_NO_MEMORY;
478 }
479
480 unlink(path);
481 free(path);
482 }
483 }
484
485 closedir(dp);
486
487 if ((expire_time_records == NULL) || (expire_time_records->count == 0)) return finish_conversion();
488
489 /* sort by ASLMessageID */
490 qsort(expire_time_records->msg, expire_time_records->count, sizeof(aslmsg), sort_compare);
491
492 /* save the ASLExpireTime messages into a new set of BB files */
493 for (i = 0; i < expire_time_records->count; i++)
494 {
495 status = save_bb_msg((aslmsg)expire_time_records->msg[i]);
496 if (status != ASL_STATUS_OK)
497 {
498 if (cache_file != NULL) asl_file_close(cache_file);
499 return status;
500 }
501 }
502
503 if (cache_file != NULL) asl_file_close(cache_file);
504
505 aslresponse_free(expire_time_records);
506
507 /* Re-write each YMD file without messages that have an ASLExpireTime */
508 dp = opendir(store_path);
509 if (dp == NULL) return ASL_STATUS_READ_FAILED;
510
511 list = NULL;
512
513 while ((dent = readdir(dp)) != NULL)
514 {
515 if ((dent->d_name[0] < '0') || (dent->d_name[0] > '9')) continue;
516 list = add_to_list(list, dent->d_name);
517 }
518
519 closedir(dp);
520
521 for (e = list; e != NULL; e = e->next)
522 {
523 status = do_ASLExpireTime_filter(e->name);
524 if (status != ASL_STATUS_OK)
525 {
526 free_list(list);
527 return status;
528 }
529 }
530
531 free_list(list);
532
533 return finish_conversion();
534 }