1/*
2 * Copyright (c) 2007-2009 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 <sys/param.h>
35#include <servers/bootstrap.h>
36#include <bootstrap_priv.h>
37#include <mach/mach.h>
38#include <copyfile.h>
39#include <fcntl.h>
40#include <zlib.h>
41#include <xpc/xpc.h>
42#include <xpc/private.h>
43#include <os/assumes.h>
44#include <vproc_priv.h>
45#include <asl.h>
46#include <asl_private.h>
47#include <asl_core.h>
48#include <asl_file.h>
49#include <asl_store.h>
50#include "asl_common.h"
51
52#define DEFAULT_MAX_SIZE 150000000
53#define IOBUFSIZE 4096
54
55#define DO_ASLDB	0x00000001
56#define DO_MODULE	0x00000002
57#define DO_CHECKPT	0x00000004
58
59#define DEBUG_FLAG_MASK  0xfffffff0
60#define DEBUG_LEVEL_MASK 0x0000000f
61#define DEBUG_STDERR     0x00000010
62#define DEBUG_ASL        0x00000020
63
64extern kern_return_t _asl_server_query
65(
66 mach_port_t server,
67 caddr_t request,
68 mach_msg_type_number_t requestCnt,
69 uint64_t startid,
70 int count,
71 int flags,
72 caddr_t *reply,
73 mach_msg_type_number_t *replyCnt,
74 uint64_t *lastid,
75 int *status,
76 security_token_t *token
77);
78
79/* global */
80static time_t module_ttl;
81static uint32_t debug;
82static int dryrun;
83static int asl_aux_fd = -1;
84static aslclient aslc;
85static mach_port_t asl_server_port;
86static xpc_connection_t listener;
87static dispatch_queue_t serverq;
88
89typedef struct name_list_s
90{
91	char *name;
92	size_t size;
93	struct name_list_s *next;
94} name_list_t;
95
96void
97set_debug(int flag, const char *str)
98{
99	int level, x;
100
101	if (str == NULL) x = ASL_LEVEL_ERR;
102	else if (((str[0] == 'L') || (str[0] == 'l')) && ((str[1] >= '0') && (str[1] <= '7')) && (str[2] == '\0')) x = atoi(str+1);
103	else if ((str[0] >= '0') && (str[0] <= '7') && (str[1] == '\0')) x = ASL_LEVEL_CRIT + atoi(str);
104	else x = ASL_LEVEL_ERR;
105
106	if (x <= 0) x = 0;
107	else if (x > 7) x = 7;
108
109	level = debug & DEBUG_LEVEL_MASK;
110	if (x > level) level = x;
111
112	debug = debug & DEBUG_FLAG_MASK;
113	debug |= flag;
114	debug |= level;
115}
116
117void
118debug_log(int level, const char *str, ...)
119{
120	va_list v;
121
122	if ((debug & DEBUG_STDERR) && (level <= (debug & DEBUG_LEVEL_MASK)))
123	{
124		va_start(v, str);
125		vfprintf(stderr, str, v);
126		va_end(v);
127	}
128
129	if (debug & DEBUG_ASL)
130	{
131		char *line = NULL;
132
133		if (aslc == NULL)
134		{
135			aslc = asl_open("aslmanager", "syslog", 0);
136			aslmsg msg = asl_new(ASL_TYPE_MSG);
137
138			asl_set(msg, ASL_KEY_MSG, "Status Report");
139			asl_set(msg, ASL_KEY_LEVEL, ASL_STRING_NOTICE);
140			asl_create_auxiliary_file(msg, "Status Report", "public.text", &asl_aux_fd);
141			asl_free(msg);
142		}
143
144		va_start(v, str);
145		vasprintf(&line, str, v);
146		va_end(v);
147
148		if (line != NULL) write(asl_aux_fd, line, strlen(line));
149		free(line);
150	}
151}
152
153__attribute__((noreturn)) static void
154xpc_server_exit(int status)
155{
156	xpc_connection_cancel(listener);
157	xpc_release(listener);
158	dispatch_release(serverq);
159	exit(status);
160}
161
162name_list_t *
163add_to_name_list(name_list_t *l, const char *name, size_t size)
164{
165	name_list_t *e, *x;
166
167	if (name == NULL) return l;
168
169	e = (name_list_t *)calloc(1, sizeof(name_list_t));
170	if (e == NULL) return NULL;
171
172	e->name = strdup(name);
173	if (e->name == NULL)
174	{
175		free(e);
176		return NULL;
177	}
178
179	e->size = size;
180
181	/* list is sorted by name (i.e. primarily by timestamp) */
182	if (l == NULL) return e;
183
184	if (strcmp(e->name, l->name) <= 0)
185	{
186		e->next = l;
187		return e;
188	}
189
190	for (x = l; (x->next != NULL) && (strcmp(e->name, x->next->name) > 0) ; x = x->next);
191
192	e->next = x->next;
193	x->next = e;
194	return l;
195}
196
197void
198free_name_list(name_list_t *l)
199{
200	name_list_t *e;
201
202	while (l != NULL)
203	{
204		e = l;
205		l = l->next;
206		free(e->name);
207		free(e);
208	}
209
210	free(l);
211}
212
213/*
214 * Copy ASL files by reading and writing each record.
215 * Setting ASL_FILE_FLAG_UNLIMITED_CACHE when copying optimizes tring uniquing.
216 */
217uint32_t
218copy_asl_file(const char *src, const char *dst, mode_t mode)
219{
220	asl_search_result_t *res;
221	asl_file_t *f;
222	uint32_t status, i;
223	uint64_t mid;
224
225	if (src == NULL) return ASL_STATUS_INVALID_ARG;
226	if (dst == NULL) return ASL_STATUS_INVALID_ARG;
227
228	f = NULL;
229	status = asl_file_open_read(src, &f);
230	if (status != ASL_STATUS_OK) return status;
231
232	res = NULL;
233	mid = 0;
234
235	status = asl_file_match(f, NULL, &res, &mid, 0, 0, 1);
236	asl_file_close(f);
237
238	if (status != ASL_STATUS_OK) return status;
239	if (res == NULL) return ASL_STATUS_OK;
240	if (res->count == 0)
241	{
242		aslresponse_free(res);
243		return ASL_STATUS_OK;
244	}
245
246	f = NULL;
247	status = asl_file_open_write(dst, mode, -1, -1, &f);
248	if (status != ASL_STATUS_OK) return status;
249	if (f == ASL_STATUS_OK) return ASL_STATUS_FAILED;
250
251	f->flags = ASL_FILE_FLAG_UNLIMITED_CACHE | ASL_FILE_FLAG_PRESERVE_MSG_ID;
252
253	for (i = 0; i < res->count; i++)
254	{
255		mid = 0;
256		status = asl_file_save(f, (aslmsg)(res->msg[i]), &mid);
257		if (status != ASL_STATUS_OK) break;
258	}
259
260	asl_file_close(f);
261	return status;
262}
263
264int
265copy_compress_file(asl_out_dst_data_t *asldst, const char *src, const char *dst)
266{
267	int in, out;
268	size_t n;
269	gzFile gz;
270	char buf[IOBUFSIZE];
271
272	in = open(src, O_RDONLY, 0);
273	if (in < 0) return -1;
274
275	out = open(dst, O_WRONLY | O_CREAT, asldst->mode);
276	if (out >= 0) out = asl_out_dst_set_access(out, asldst);
277	if (out < 0)
278	{
279		close(in);
280		return -1;
281	}
282
283	gz = gzdopen(out, "w");
284	if (gz == NULL)
285	{
286		close(in);
287		close(out);
288		return -1;
289	}
290
291	do {
292		n = read(in, buf, sizeof(buf));
293		if (n > 0) gzwrite(gz, buf, n);
294	} while (n == IOBUFSIZE);
295
296	gzclose(gz);
297	close(in);
298	close(out);
299
300	return 0;
301}
302
303int32_t
304filesystem_copy(asl_out_dst_data_t *asldst, const char *src, const char *dst, uint32_t flags)
305{
306	char *dot;
307
308	if ((src == NULL) || (dst == NULL)) return 0;
309
310	dot = strrchr(src, '.');
311	if ((dot != NULL) && (!strcmp(dot, ".gz"))) flags &= ~MODULE_FLAG_COMPRESS;
312
313	if (((flags & MODULE_FLAG_COMPRESS) == 0) && (!strcmp(src, dst))) return 0;
314
315	if (flags & MODULE_FLAG_TYPE_ASL) debug_log(ASL_LEVEL_NOTICE, "  copy asl %s ---> %s\n", src, dst);
316	else if (flags & MODULE_FLAG_COMPRESS) debug_log(ASL_LEVEL_NOTICE, "  copy compress %s ---> %s.gz\n", src, dst);
317	else debug_log(ASL_LEVEL_NOTICE, "  copy %s ---> %s\n", src, dst);
318
319	if (dryrun == 1) return 0;
320
321	if (flags & MODULE_FLAG_TYPE_ASL)
322	{
323		uint32_t status = copy_asl_file(src, dst, asldst->mode);
324		if (status != 0)
325		{
326			debug_log(ASL_LEVEL_ERR, "  FAILED status %u [%s] asl copy %s ---> %s\n", status, asl_core_error(status), src, dst);
327			return 0;
328		}
329	}
330	else if (flags & MODULE_FLAG_COMPRESS)
331	{
332		char gzdst[MAXPATHLEN];
333
334		snprintf(gzdst, sizeof(gzdst), "%s.gz", dst);
335
336		int status = copy_compress_file(asldst, src, gzdst);
337		if (status != 0)
338		{
339			debug_log(ASL_LEVEL_ERR, "  FAILED status %d errno %d [%s] copy & compress %s ---> %s\n", status, errno, strerror(errno), src, dst);
340			return 0;
341		}
342	}
343	else
344	{
345		int status = copyfile(src, dst, NULL, COPYFILE_ALL | COPYFILE_RECURSIVE);
346		if (status != 0)
347		{
348			debug_log(ASL_LEVEL_ERR, "  FAILED status %d errno %d [%s] copy %s ---> %s\n", status, errno, strerror(errno), src, dst);
349			return 0;
350		}
351	}
352
353	return 1;
354}
355
356void
357filesystem_rename(const char *src, const char *dst)
358{
359	int status = 0;
360
361	debug_log(ASL_LEVEL_NOTICE, "  rename %s ---> %s\n", src, dst);
362	if (dryrun == 1) return;
363
364	status = rename(src, dst);
365	if (status != 0) debug_log(ASL_LEVEL_ERR, "  FAILED status %d errno %d [%s] rename %s ---> %s\n", status, errno, strerror(errno), src, dst);
366}
367
368void
369filesystem_unlink(const char *path)
370{
371	int status = 0;
372
373	debug_log(ASL_LEVEL_NOTICE, "  remove %s\n", path);
374	if (dryrun == 1) return;
375
376	status = unlink(path);
377	if (status != 0) debug_log(ASL_LEVEL_ERR, "  FAILED status %d errno %d [%s] unlink %s\n", status, errno, strerror(errno), path);
378}
379
380void
381filesystem_rmdir(const char *path)
382{
383	int status = 0;
384
385	debug_log(ASL_LEVEL_NOTICE, "  remove directory %s\n", path);
386	if (dryrun == 1) return;
387
388	status = rmdir(path);
389	if (status != 0) debug_log(ASL_LEVEL_ERR, "  FAILED status %d errno %d [%s] rmdir %s\n", status, errno, strerror(errno), path);
390}
391
392int
393remove_directory(const char *path)
394{
395	DIR *dp;
396	struct dirent *dent;
397	char *str;
398
399	dp = opendir(path);
400	if (dp == NULL) return 0;
401
402	while ((dent = readdir(dp)) != NULL)
403	{
404		if ((!strcmp(dent->d_name, ".")) || (!strcmp(dent->d_name, ".."))) continue;
405		asprintf(&str, "%s/%s", path, dent->d_name);
406		if (str != NULL)
407		{
408			filesystem_unlink(str);
409			free(str);
410			str = NULL;
411		}
412	}
413
414	closedir(dp);
415	filesystem_rmdir(path);
416
417	return 0;
418}
419
420/*
421 * Used to set config parameters.
422 * Line format "= name value"
423 */
424static void
425_aslmanager_set_param(asl_out_dst_data_t *dst, char *s)
426{
427	char **l;
428	uint32_t count;
429
430	if (s == NULL) return;
431	if (s[0] == '\0') return;
432
433	/* skip '=' and whitespace */
434	if (*s == '=') s++;
435	while ((*s == ' ') || (*s == '\t')) s++;
436
437	l = explode(s, " \t");
438	if (l == NULL) return;
439
440	for (count = 0; l[count] != NULL; count++);
441
442	/* name is required */
443	if (count == 0)
444	{
445		free_string_list(l);
446		return;
447	}
448
449	/* value is required */
450	if (count == 1)
451	{
452		free_string_list(l);
453		return;
454	}
455
456	if (!strcasecmp(l[0], "aslmanager_debug"))
457	{
458		/* = debug level */
459		set_debug(DEBUG_ASL, l[1]);
460	}
461	else if (!strcasecmp(l[0], "store_ttl"))
462	{
463		/* = store_ttl days */
464		dst->ttl = (time_t)atoll(l[1]);
465	}
466	else if (!strcasecmp(l[0], "module_ttl"))
467	{
468		/* = module_ttl days */
469		module_ttl = (time_t)atoll(l[1]);
470	}
471	else if (!strcasecmp(l[0], "max_store_size"))
472	{
473		/* = max_file_size bytes */
474		dst->all_max = atoi(l[1]);
475	}
476	else if (!strcasecmp(l[0], "archive"))
477	{
478		free(dst->rotate_dir);
479		dst->rotate_dir = NULL;
480
481		/* = archive {0|1} path */
482		if (!strcmp(l[1], "1"))
483		{
484			if (l[2] == NULL) dst->rotate_dir = strdup(PATH_ASL_ARCHIVE);
485			else dst->rotate_dir = strdup(l[2]);
486		}
487	}
488	else if (!strcasecmp(l[0], "store_path"))
489	{
490		/* = archive path */
491		free(dst->path);
492		dst->path = strdup(l[1]);
493	}
494	else if (!strcasecmp(l[0], "archive_mode"))
495	{
496		dst->mode = strtol(l[1], NULL, 0);
497		if ((dst->mode == 0) && (errno == EINVAL)) dst->mode = 0400;
498	}
499
500	free_string_list(l);
501}
502
503size_t
504directory_size(const char *path)
505{
506	DIR *dp;
507	struct dirent *dent;
508	struct stat sb;
509	size_t size;
510	char *str;
511
512	dp = opendir(path);
513	if (dp == NULL) return 0;
514
515	size = 0;
516	while ((dent = readdir(dp)) != NULL)
517	{
518		if ((!strcmp(dent->d_name, ".")) || (!strcmp(dent->d_name, ".."))) continue;
519
520		memset(&sb, 0, sizeof(struct stat));
521		str = NULL;
522		asprintf(&str, "%s/%s", path, dent->d_name);
523
524		if ((str != NULL) && (stat(str, &sb) == 0) && S_ISREG(sb.st_mode))
525		{
526			size += sb.st_size;
527			free(str);
528		}
529	}
530
531	closedir(dp);
532	return size;
533}
534
535static int
536process_asl_data_store(asl_out_dst_data_t *dst)
537{
538	int32_t today_ymd_stringlen, expire_ymd_stringlen;
539	time_t now, ttl, ymd_expire;
540	struct tm ctm;
541	char today_ymd_string[32], expire_ymd_string[32], *str;
542	DIR *dp;
543	struct dirent *dent;
544	name_list_t *ymd_list, *bb_list, *aux_list, *bb_aux_list, *e;
545	size_t file_size, store_size;
546	struct stat sb;
547
548	ymd_list = NULL;
549	bb_list = NULL;
550	aux_list = NULL;
551	bb_aux_list = NULL;
552	store_size = 0;
553
554	if (dst == NULL) return 0;
555	if (dst->path == NULL) return 0;
556
557	debug_log(ASL_LEVEL_NOTICE, "----------------------------------------\n");
558	debug_log(ASL_LEVEL_NOTICE, "Processing data store %s\n", dst->path);
559
560	if (dst->rotate_dir != NULL)
561	{
562		/* check archive */
563		memset(&sb, 0, sizeof(struct stat));
564		if (stat(dst->rotate_dir, &sb) == 0)
565		{
566			/* must be a directory */
567			if (!S_ISDIR(sb.st_mode))
568			{
569				debug_log(ASL_LEVEL_ERR, "aslmanager error: archive %s is not a directory", dst->rotate_dir);
570				return -1;
571			}
572		}
573		else
574		{
575			if (errno == ENOENT)
576			{
577				/* archive doesn't exist - create it */
578				if (mkdir(dst->rotate_dir, 0755) != 0)
579				{
580					debug_log(ASL_LEVEL_ERR, "aslmanager error: can't create archive %s: %s\n", dst->rotate_dir, strerror(errno));
581					return -1;
582				}
583			}
584			else
585			{
586				/* stat failed for some other reason */
587				debug_log(ASL_LEVEL_ERR, "aslmanager error: can't stat archive %s: %s\n", dst->rotate_dir, strerror(errno));
588				return -1;
589			}
590		}
591	}
592
593	chdir(dst->path);
594
595	/* determine current time */
596	now = time(NULL);
597
598	/* ttl 0 means files never expire */
599	ymd_expire = 0;
600	ttl = dst->ttl * SECONDS_PER_DAY;
601
602	if ((ttl > 0) && (ttl <= now)) ymd_expire = now - ttl;
603
604	/* construct today's date as YYYY.MM.DD */
605	memset(&ctm, 0, sizeof(struct tm));
606	if (localtime_r((const time_t *)&now, &ctm) == NULL) return -1;
607
608	snprintf(today_ymd_string, sizeof(today_ymd_string), "%d.%02d.%02d.", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
609	today_ymd_stringlen = strlen(today_ymd_string);
610
611	/* construct regular file expiry date as YYYY.MM.DD */
612	memset(&ctm, 0, sizeof(struct tm));
613	if (localtime_r((const time_t *)&ymd_expire, &ctm) == NULL) return -1;
614
615	snprintf(expire_ymd_string, sizeof(expire_ymd_string), "%d.%02d.%02d.", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
616	expire_ymd_stringlen = strlen(expire_ymd_string);
617
618	debug_log(ASL_LEVEL_NOTICE, "Expiry Date %s\n", expire_ymd_string);
619
620	dp = opendir(dst->path);
621	if (dp == NULL) return -1;
622
623	/* gather a list of YMD files, AUX dirs, BB.AUX dirs, and BB files */
624	while ((dent = readdir(dp)) != NULL)
625	{
626		memset(&sb, 0, sizeof(struct stat));
627		file_size = 0;
628		if (stat(dent->d_name, &sb) == 0) file_size = sb.st_size;
629
630		if ((dent->d_name[0] >= '0') && (dent->d_name[0] <= '9'))
631		{
632			ymd_list = add_to_name_list(ymd_list, dent->d_name, file_size);
633			store_size += file_size;
634		}
635		else if (!strncmp(dent->d_name, "AUX.", 4) && (dent->d_name[4] >= '0') && (dent->d_name[4] <= '9') && S_ISDIR(sb.st_mode))
636		{
637			file_size = directory_size(dent->d_name);
638			aux_list = add_to_name_list(aux_list, dent->d_name, file_size);
639			store_size += file_size;
640		}
641		else if (!strncmp(dent->d_name, "BB.AUX.", 7) && (dent->d_name[7] >= '0') && (dent->d_name[7] <= '9') && S_ISDIR(sb.st_mode))
642		{
643			file_size = directory_size(dent->d_name);
644			bb_aux_list = add_to_name_list(bb_aux_list, dent->d_name, file_size);
645			store_size += file_size;
646		}
647		else if (!strncmp(dent->d_name, "BB.", 3) && (dent->d_name[3] >= '0') && (dent->d_name[3] <= '9'))
648		{
649			bb_list = add_to_name_list(bb_list, dent->d_name, file_size);
650			store_size += file_size;
651		}
652		else if ((!strcmp(dent->d_name, ".")) || (!strcmp(dent->d_name, "..")))
653		{}
654		else if ((!strcmp(dent->d_name, "StoreData")) || (!strcmp(dent->d_name, "SweepStore")))
655		{}
656		else
657		{
658			debug_log(ASL_LEVEL_ERR, "aslmanager: unexpected file %s in ASL data store\n", dent->d_name);
659		}
660	}
661
662	closedir(dp);
663
664	debug_log(ASL_LEVEL_NOTICE, "Data Store Size = %lu\n", store_size);
665	debug_log(ASL_LEVEL_NOTICE, "Data Store YMD Files\n");
666	for (e = ymd_list; e != NULL; e = e->next) debug_log(ASL_LEVEL_NOTICE, "	%s   %lu\n", e->name, e->size);
667	debug_log(ASL_LEVEL_NOTICE, "Data Store AUX Directories\n");
668	for (e = aux_list; e != NULL; e = e->next) debug_log(ASL_LEVEL_NOTICE, "	%s   %lu\n", e->name, e->size);
669	debug_log(ASL_LEVEL_NOTICE, "Data Store BB.AUX Directories\n");
670	for (e = bb_aux_list; e != NULL; e = e->next) debug_log(ASL_LEVEL_NOTICE, "	%s   %lu\n", e->name, e->size);
671	debug_log(ASL_LEVEL_NOTICE, "Data Store BB Files\n");
672	for (e = bb_list; e != NULL; e = e->next) debug_log(ASL_LEVEL_NOTICE, "	%s   %lu\n", e->name, e->size);
673
674	/* Delete/achive expired YMD files */
675	debug_log(ASL_LEVEL_NOTICE, "Start YMD File Scan\n");
676
677	e = ymd_list;
678	while (e != NULL)
679	{
680		/* stop when a file name/date is after the expire date */
681		if (strncmp(e->name, expire_ymd_string, expire_ymd_stringlen) > 0) break;
682
683		if (dst->rotate_dir != NULL)
684		{
685			str = NULL;
686			asprintf(&str, "%s/%s", dst->rotate_dir, e->name);
687			if (str == NULL) return -1;
688
689			filesystem_copy(dst, e->name, str, 0);
690			free(str);
691		}
692
693		filesystem_unlink(e->name);
694		store_size -= e->size;
695		e->size = 0;
696
697		e = e->next;
698	}
699
700	debug_log(ASL_LEVEL_NOTICE, "Finished YMD File Scan\n");
701
702	/* Delete/achive expired YMD AUX directories */
703	debug_log(ASL_LEVEL_NOTICE, "Start AUX Directory Scan\n");
704
705	e = aux_list;
706	while (e != NULL)
707	{
708		/* stop when a file name/date is after the expire date */
709		if (strncmp(e->name + 4, expire_ymd_string, expire_ymd_stringlen) > 0) break;
710
711		if (dst->rotate_dir != NULL)
712		{
713			str = NULL;
714			asprintf(&str, "%s/%s", dst->rotate_dir, e->name);
715			if (str == NULL) return -1;
716
717			filesystem_copy(dst, e->name, str, 0);
718			free(str);
719		}
720
721		remove_directory(e->name);
722		store_size -= e->size;
723		e->size = 0;
724
725		e = e->next;
726	}
727
728	debug_log(ASL_LEVEL_NOTICE, "Finished AUX Directory Scan\n");
729
730	/* Delete/achive expired BB.AUX directories */
731	debug_log(ASL_LEVEL_NOTICE, "Start BB.AUX Directory Scan\n");
732
733	e = bb_aux_list;
734	while (e != NULL)
735	{
736		/* stop when a file name/date is after the expire date */
737		if (strncmp(e->name + 7, today_ymd_string, today_ymd_stringlen) > 0) break;
738
739		if (dst->rotate_dir != NULL)
740		{
741			str = NULL;
742			asprintf(&str, "%s/%s", dst->rotate_dir, e->name);
743			if (str == NULL) return -1;
744
745			filesystem_copy(dst, e->name, str, 0);
746			free(str);
747		}
748
749		remove_directory(e->name);
750		store_size -= e->size;
751		e->size = 0;
752
753		e = e->next;
754	}
755
756	debug_log(ASL_LEVEL_NOTICE, "Finished BB.AUX Directory Scan\n");
757
758	/* Delete/achive expired BB files */
759	debug_log(ASL_LEVEL_NOTICE, "Start BB Scan\n");
760
761	e = bb_list;
762	while (e != NULL)
763	{
764		/* stop when a file name/date is after the expire date */
765		if (strncmp(e->name + 3, today_ymd_string, today_ymd_stringlen) > 0) break;
766
767		if (dst->rotate_dir != NULL)
768		{
769			str = NULL;
770			asprintf(&str, "%s/%s", dst->rotate_dir, e->name);
771			if (str == NULL) return -1;
772
773			/* syslog -x [str] -f [e->name] */
774			filesystem_copy(dst, e->name, str, 0);
775			free(str);
776		}
777
778		filesystem_unlink(e->name);
779		store_size -= e->size;
780		e->size = 0;
781
782		e = e->next;
783	}
784
785	debug_log(ASL_LEVEL_NOTICE, "Finished BB Scan\n");
786
787	if (dst->all_max > 0)
788	{
789		/* if data store is over max_size, delete/archive more YMD files */
790		if (store_size > dst->all_max) debug_log(ASL_LEVEL_NOTICE, "Additional YMD Scan\n");
791
792		e = ymd_list;
793		while ((e != NULL) && (store_size > dst->all_max))
794		{
795			if (e->size != 0)
796			{
797				/* stop when we get to today's files */
798				if (strncmp(e->name, today_ymd_string, today_ymd_stringlen) == 0) break;
799
800				if (dst->rotate_dir != NULL)
801				{
802					str = NULL;
803					asprintf(&str, "%s/%s", dst->rotate_dir, e->name);
804					if (str == NULL) return -1;
805
806					/* syslog -x [str] -f [e->name] */
807					filesystem_copy(dst, e->name, str, 0);
808					free(str);
809				}
810
811				filesystem_unlink(e->name);
812				store_size -= e->size;
813				e->size = 0;
814			}
815
816			e = e->next;
817		}
818
819		/* if data store is over dst->all_max, delete/archive more BB files */
820		if (store_size > dst->all_max) debug_log(ASL_LEVEL_NOTICE, "Additional BB Scan\n");
821
822		e = bb_list;
823		while ((e != NULL) && (store_size > dst->all_max))
824		{
825			if (e->size != 0)
826			{
827				if (dst->rotate_dir != NULL)
828				{
829					str = NULL;
830					asprintf(&str, "%s/%s", dst->rotate_dir, e->name);
831					if (str == NULL) return -1;
832
833					/* syslog -x [str] -f [e->name] */
834					filesystem_copy(dst, e->name, str, 0);
835					free(str);
836				}
837
838				filesystem_unlink(e->name);
839				store_size -= e->size;
840				e->size = 0;
841			}
842
843			e = e->next;
844		}
845	}
846
847	free_name_list(ymd_list);
848	free_name_list(bb_list);
849	free_name_list(aux_list);
850	free_name_list(bb_aux_list);
851
852	debug_log(ASL_LEVEL_NOTICE, "Data Store Size = %lu\n", store_size);
853
854	return 0;
855}
856
857/* move sequenced source files to dst dir, renaming as we go */
858static int
859module_copy_rename(asl_out_dst_data_t *dst)
860{
861	asl_out_file_list_t *src_list, *dst_list, *f, *dst_last;
862	char *base, *dst_dir;
863	char fpathsrc[MAXPATHLEN], fpathdst[MAXPATHLEN];
864	uint32_t src_count, dst_count;
865	int32_t x, moved;
866
867	if (dst == NULL) return -1;
868	if (dst->path == NULL) return -1;
869
870	base = strrchr(dst->path, '/');
871	if (base == NULL) return -1;
872
873	src_list = asl_list_src_files(dst);
874	if (src_list == 0)
875	{
876		debug_log(ASL_LEVEL_INFO, "    no src files\n");
877		return 0;
878	}
879
880	debug_log(ASL_LEVEL_INFO, "    src files\n");
881
882	src_count = 0;
883	for (f = src_list; f != NULL; f = f->next)
884	{
885		debug_log(ASL_LEVEL_INFO, "      %s\n", f->name);
886		src_count++;
887	}
888
889	dst_list = asl_list_dst_files(dst);
890
891	*base = '\0';
892	base++;
893
894	dst_dir = dst->rotate_dir;
895	if (dst_dir == NULL) dst_dir = dst->path;
896
897	dst_count = 0;
898	dst_last = dst_list;
899
900	if (dst_list == NULL) debug_log(ASL_LEVEL_INFO, "    no dst files\n");
901	else debug_log(ASL_LEVEL_INFO, "    dst files\n");
902
903	for (f = dst_list; f != NULL; f = f->next)
904	{
905		debug_log(ASL_LEVEL_INFO, "      %s\n", f->name);
906		dst_last = f;
907		dst_count++;
908	}
909
910	if (dst->flags & MODULE_FLAG_STYLE_SEQ)
911	{
912		for (f = dst_last; f != NULL; f = f->prev)
913		{
914			int is_gz = 0;
915			char *dot = strrchr(f->name, '.');
916			if ((dot != NULL) && (!strcmp(dot, ".gz"))) is_gz = 1;
917
918			snprintf(fpathsrc, sizeof(fpathsrc), "%s/%s", dst_dir, f->name);
919			snprintf(fpathdst, sizeof(fpathdst), "%s/%s.%d%s", dst_dir, base, f->seq+src_count, (is_gz == 1) ? ".gz" : "");
920			filesystem_rename(fpathsrc, fpathdst);
921		}
922
923		for (f = src_list, x = 0; f != NULL; f = f->next, x++)
924		{
925			snprintf(fpathsrc, sizeof(fpathsrc), "%s/%s", dst->path, f->name);
926			snprintf(fpathdst, sizeof(fpathdst), "%s/%s.%d", dst_dir, base, x);
927			moved = filesystem_copy(dst, fpathsrc, fpathdst, dst->flags);
928			if (moved != 0) filesystem_unlink(fpathsrc);
929		}
930	}
931	else
932	{
933		for (f = src_list; f != NULL; f = f->next)
934		{
935			/* final / active base stamped file looks like a checkpointed file - ignore it */
936			if ((dst->flags & MODULE_FLAG_BASESTAMP) && (f->next == NULL)) break;
937
938			snprintf(fpathsrc, sizeof(fpathsrc), "%s/%s", dst->path, f->name);
939
940			/* MODULE_FLAG_EXTERNAL files are not decorated with a timestamp */
941			if (dst->flags & MODULE_FLAG_EXTERNAL)
942			{
943				char tstamp[32];
944
945				asl_make_timestamp(f->ftime, dst->flags, tstamp, sizeof(tstamp));
946				snprintf(fpathdst, sizeof(fpathdst), "%s/%s.%s", dst_dir, base, tstamp);
947			}
948			else
949			{
950				snprintf(fpathdst, sizeof(fpathdst), "%s/%s", dst_dir, f->name);
951			}
952
953			moved = filesystem_copy(dst, fpathsrc, fpathdst, dst->flags);
954			if (moved != 0) filesystem_unlink(fpathsrc);
955		}
956	}
957
958	asl_out_file_list_free(src_list);
959	asl_out_file_list_free(dst_list);
960
961	if (base != NULL) *--base = '/';
962
963	return 0;
964}
965
966/* delete expired files */
967static int
968module_expire(asl_out_dst_data_t *dst)
969{
970	asl_out_file_list_t *dst_list, *f;
971	char *base, *dst_dir, fpath[MAXPATHLEN];
972	time_t now, ttl, cutoff;
973
974	if (dst == NULL) return -1;
975	if (dst->path == NULL) return -1;
976	if (dst->ttl == 0) return 0;
977
978	ttl = 0;
979	if (module_ttl > 0) ttl = module_ttl;
980	else ttl = dst->ttl;
981
982	ttl *= SECONDS_PER_DAY;
983
984	now = time(NULL);
985	if (ttl > now) return 0;
986
987	cutoff = now - ttl;
988
989	base = strrchr(dst->path, '/');
990	if (base == NULL) return -1;
991
992	dst_list = asl_list_dst_files(dst);
993
994	*base = '\0';
995
996	dst_dir = dst->rotate_dir;
997	if (dst_dir == NULL) dst_dir = dst->path;
998
999	if (dst_list == NULL)
1000	{
1001		debug_log(ASL_LEVEL_INFO, "    no dst files\n");
1002	}
1003	else
1004	{
1005		debug_log(ASL_LEVEL_INFO, "    dst files\n");
1006		for (f = dst_list; f != NULL; f = f->next) debug_log(ASL_LEVEL_INFO, "      %s\n", f->name);
1007	}
1008
1009	for (f = dst_list; f != NULL; f = f->next)
1010	{
1011		if (f->ftime <= cutoff)
1012		{
1013			snprintf(fpath, sizeof(fpath), "%s/%s", dst_dir, f->name);
1014			filesystem_unlink(fpath);
1015		}
1016	}
1017
1018	asl_out_file_list_free(dst_list);
1019
1020	if (base != NULL) *base = '/';
1021
1022	return 0;
1023}
1024
1025/* check all_max size and delete files (oldest first) to stay within size limit */
1026static int
1027module_check_size(asl_out_dst_data_t *dst)
1028{
1029	asl_out_file_list_t *dst_list, *f;
1030	char *base, *dst_dir, fpath[MAXPATHLEN];
1031	size_t total;
1032
1033	if (dst == NULL) return -1;
1034	if (dst->path == NULL) return -1;
1035
1036	if (dst->all_max == 0) return 0;
1037
1038	base = NULL;
1039	dst_dir = dst->rotate_dir;
1040	if (dst_dir == NULL)
1041	{
1042		dst_dir = dst->path;
1043		base = strrchr(dst->path, '/');
1044		if (base == NULL) return -1;
1045		*base = '\0';
1046	}
1047
1048	dst_list = asl_list_dst_files(dst);
1049
1050	if (dst_list == NULL)
1051	{
1052		debug_log(ASL_LEVEL_INFO, "    no dst files\n");
1053	}
1054	else
1055	{
1056		debug_log(ASL_LEVEL_INFO, "    dst files\n");
1057		for (f = dst_list; f != NULL; f = f->next) debug_log(ASL_LEVEL_INFO, "      %s size %lu\n", f->name, f->size);
1058	}
1059
1060	total = 0;
1061	for (f = dst_list; f != NULL; f = f->next) total += f->size;
1062
1063	for (f = dst_list; (total > dst->all_max) && (f != NULL); f = f->next)
1064	{
1065		snprintf(fpath, sizeof(fpath), "%s/%s", dst_dir, f->name);
1066		filesystem_unlink(fpath);
1067		total -= f->size;
1068	}
1069
1070	asl_out_file_list_free(dst_list);
1071
1072	if (base != NULL) *base = '/';
1073
1074	return 0;
1075}
1076
1077
1078static int
1079process_module(asl_out_module_t *mod)
1080{
1081	asl_out_rule_t *r;
1082
1083	if (mod == NULL) return -1;
1084
1085	debug_log(ASL_LEVEL_NOTICE, "----------------------------------------\n");
1086	debug_log(ASL_LEVEL_NOTICE, "Processing module %s\n", (mod->name == NULL) ? "asl.conf" : mod->name);
1087
1088	for (r = mod->ruleset; r != NULL; r = r->next)
1089	{
1090		if (r->action == ACTION_OUT_DEST)
1091		{
1092			if (r->dst == NULL)
1093			{
1094				debug_log(ASL_LEVEL_NOTICE, "NULL dst data for output rule - skipped\n");
1095			}
1096			else if (r->dst->flags & MODULE_FLAG_ROTATE)
1097			{
1098				debug_log(ASL_LEVEL_NOTICE, "Checking file %s\n", r->dst->path);
1099				debug_log(ASL_LEVEL_NOTICE, "- Rename, move to destination directory, and compress as required\n");
1100
1101				module_copy_rename(r->dst);
1102
1103				if (r->dst->ttl > 0)
1104				{
1105					debug_log(ASL_LEVEL_NOTICE, "- Check for expired files - TTL = %d days\n", r->dst->ttl);
1106					module_expire(r->dst);
1107				}
1108
1109				if (r->dst->all_max > 0)
1110				{
1111					debug_log(ASL_LEVEL_NOTICE, "- Check total storage used - MAX = %lu\n", r->dst->all_max);
1112					module_check_size(r->dst);
1113				}
1114			}
1115			else if ((r->dst->flags & MODULE_FLAG_TYPE_ASL_DIR) && (r->dst->ttl > 0))
1116			{
1117				process_asl_data_store(r->dst);
1118			}
1119		}
1120	}
1121
1122	debug_log(ASL_LEVEL_NOTICE, "Finished processing module %s\n", (mod->name == NULL) ? "asl.conf" : mod->name);
1123	return 0;
1124}
1125
1126aslresponse
1127control_query(aslmsg a)
1128{
1129	asl_search_result_t *out;
1130	char *qstr, *str, *res;
1131	uint32_t len, reslen, status;
1132	uint64_t cmax, qmin;
1133	kern_return_t kstatus;
1134	caddr_t vmstr;
1135	security_token_t sec;
1136
1137	if (asl_server_port == MACH_PORT_NULL)
1138	{
1139		bootstrap_look_up2(bootstrap_port, ASL_SERVICE_NAME, &asl_server_port, 0, BOOTSTRAP_PRIVILEGED_SERVER);
1140		if (asl_server_port == MACH_PORT_NULL) return NULL;
1141	}
1142
1143	qstr = asl_msg_to_string((asl_msg_t *)a, &len);
1144
1145	str = NULL;
1146	if (qstr == NULL)
1147	{
1148		asprintf(&str, "1\nQ [= ASLOption control]\n");
1149	}
1150	else
1151	{
1152		asprintf(&str, "1\n%s [= ASLOption control]\n", qstr);
1153		free(qstr);
1154	}
1155
1156	if (str == NULL) return NULL;
1157
1158	/* length includes trailing nul */
1159	len = strlen(str) + 1;
1160	out = NULL;
1161	qmin = 0;
1162	cmax = 0;
1163	sec.val[0] = -1;
1164	sec.val[1] = -1;
1165
1166	res = NULL;
1167	reslen = 0;
1168	status = ASL_STATUS_OK;
1169
1170	kstatus = vm_allocate(mach_task_self(), (vm_address_t *)&vmstr, len, TRUE);
1171	if (kstatus != KERN_SUCCESS) return NULL;
1172
1173	memmove(vmstr, str, len);
1174	free(str);
1175
1176	status = 0;
1177	kstatus = _asl_server_query(asl_server_port, vmstr, len, qmin, 1, 0, (caddr_t *)&res, &reslen, &cmax, (int *)&status, &sec);
1178	if (kstatus != KERN_SUCCESS) return NULL;
1179
1180	if (res == NULL) return NULL;
1181
1182	out = asl_list_from_string(res);
1183	vm_deallocate(mach_task_self(), (vm_address_t)res, reslen);
1184
1185	return out;
1186}
1187
1188int
1189checkpoint(const char *name)
1190{
1191	/* send checkpoint message to syslogd */
1192	debug_log(ASL_LEVEL_NOTICE, "Checkpoint module %s\n", (name == NULL) ? "*" : name);
1193	if (dryrun != 0) return 0;
1194
1195	aslmsg qmsg = asl_new(ASL_TYPE_QUERY);
1196	char *tmp = NULL;
1197	aslresponse res;
1198
1199	asprintf(&tmp, "%s checkpoint", (name == NULL) ? "*" : name);
1200	asl_set_query(qmsg, "action", tmp, ASL_QUERY_OP_EQUAL);
1201	free(tmp);
1202
1203	res = control_query(qmsg);
1204
1205	aslresponse_free(res);
1206	return 0;
1207}
1208
1209int
1210cli_main(int argc, char *argv[])
1211{
1212	int i, work;
1213	asl_out_module_t *mod, *m;
1214	asl_out_rule_t *r;
1215	asl_out_dst_data_t store;
1216	const char *mname = NULL;
1217
1218	if (geteuid() != 0)
1219	{
1220		if (argc == 0) debug = DEBUG_ASL;
1221		else debug = DEBUG_STDERR;
1222
1223		debug_log(ASL_LEVEL_ERR, "aslmanager must be run by root\n");
1224		exit(1);
1225	}
1226
1227	module_ttl = DEFAULT_TTL;
1228
1229	/* cobble up a dst_data for the main asl_store */
1230	memset(&store, 0, sizeof(store));
1231	store.ttl = DEFAULT_TTL;
1232	store.all_max = DEFAULT_MAX_SIZE;
1233
1234	/* get parameters from asl.conf */
1235	mod = asl_out_module_init();
1236
1237	if (mod != NULL)
1238	{
1239		for (r = mod->ruleset; r != NULL; r = r->next)
1240		{
1241			if (r->action == ACTION_SET_PARAM)
1242			{
1243				if (r->query == NULL) _aslmanager_set_param(&store, r->options);
1244			}
1245		}
1246	}
1247
1248	work = DO_ASLDB | DO_MODULE;
1249
1250	for (i = 1; i < argc; i++)
1251	{
1252		if (!strcmp(argv[i], "-a"))
1253		{
1254			if (((i + 1) < argc) && (argv[i + 1][0] != '-')) store.rotate_dir = strdup(argv[++i]);
1255			else store.rotate_dir = strdup(PATH_ASL_ARCHIVE);
1256			store.mode = 0400;
1257		}
1258		else if (!strcmp(argv[i], "-s"))
1259		{
1260			if (((i + 1) < argc) && (argv[i + 1][0] != '-')) store.path = strdup(argv[++i]);
1261		}
1262		else if (!strcmp(argv[i], "-store_ttl"))
1263		{
1264			if (((i + 1) < argc) && (argv[i + 1][0] != '-')) store.ttl = atoi(argv[++i]);
1265		}
1266		else if (!strcmp(argv[i], "-module_ttl"))
1267		{
1268			if (((i + 1) < argc) && (argv[i + 1][0] != '-')) module_ttl = atoi(argv[++i]);
1269		}
1270		else if (!strcmp(argv[i], "-ttl"))
1271		{
1272			if (((i + 1) < argc) && (argv[i + 1][0] != '-')) module_ttl = store.ttl = atoi(argv[++i]);
1273		}
1274		else if (!strcmp(argv[i], "-size"))
1275		{
1276			if (((i + 1) < argc) && (argv[i + 1][0] != '-')) store.all_max = asl_str_to_size(argv[++i]);
1277		}
1278		else if (!strcmp(argv[i], "-checkpoint"))
1279		{
1280			work |= DO_CHECKPT;
1281		}
1282		else if (!strcmp(argv[i], "-module"))
1283		{
1284			work &= ~DO_ASLDB;
1285
1286			/* optional name follows -module */
1287			if ((i +1) < argc)
1288			{
1289				if (argv[i + 1][0] != '-') mname = argv[++i];
1290			}
1291		}
1292		else if (!strcmp(argv[i], "-asldb"))
1293		{
1294			work = DO_ASLDB;
1295		}
1296		else if (!strcmp(argv[i], "-d"))
1297		{
1298			if (((i + i) < argc) && (argv[i+1][0] != '-')) set_debug(DEBUG_STDERR, argv[++i]);
1299			else set_debug(DEBUG_STDERR, NULL);
1300		}
1301		else if (!strcmp(argv[i], "-dd"))
1302		{
1303			dryrun = 1;
1304
1305			if (((i + i) < argc) && (argv[i+1][0] != '-')) set_debug(DEBUG_STDERR, argv[++i]);
1306			else set_debug(DEBUG_STDERR, NULL);
1307		}
1308	}
1309
1310	if (store.path == NULL) store.path = strdup(PATH_ASL_STORE);
1311
1312	debug_log(ASL_LEVEL_ERR, "aslmanager starting%s\n", (dryrun == 1) ? " dryrun" : "");
1313
1314	if (work & DO_ASLDB) process_asl_data_store(&store);
1315
1316	free(store.path);
1317	free(store.rotate_dir);
1318
1319	if (work & DO_MODULE)
1320	{
1321		if (work & DO_CHECKPT) checkpoint(mname);
1322
1323		if (mod != NULL)
1324		{
1325			for (m = mod; m != NULL; m = m->next)
1326			{
1327				if ((mname == NULL) || ((m->name != NULL) && (!strcmp(m->name, mname))))
1328				{
1329					process_module(m);
1330				}
1331			}
1332		}
1333	}
1334
1335	asl_out_module_free(mod);
1336
1337	debug_log(ASL_LEVEL_NOTICE, "----------------------------------------\n");
1338	debug_log(ASL_LEVEL_ERR, "aslmanager finished%s\n", (dryrun == 1) ? " dryrun" : "");
1339	if (asl_aux_fd >= 0) asl_close_auxiliary_file(asl_aux_fd);
1340
1341	return 0;
1342}
1343
1344static void
1345accept_connection(xpc_connection_t peer)
1346{
1347	xpc_connection_set_event_handler(peer, ^(xpc_object_t request) {
1348		if (xpc_get_type(request) == XPC_TYPE_DICTIONARY)
1349		{
1350			uid_t uid = xpc_connection_get_euid(peer);
1351
1352			/* send a reply immediately */
1353			xpc_object_t reply = xpc_dictionary_create_reply(request);
1354			xpc_connection_send_message(peer, reply);
1355			xpc_release(reply);
1356
1357			/*
1358			 * Some day, we may use the dictionary to pass parameters
1359			 * to aslmanager, but for now, we ignore the input.
1360			 */
1361			if (uid == 0) cli_main(0, NULL);
1362		}
1363		else if (xpc_get_type(request) == XPC_TYPE_ERROR)
1364		{
1365			/* disconnect */
1366		}
1367
1368		dispatch_async(serverq, ^__attribute__((noreturn)) { xpc_server_exit(0); });
1369	});
1370
1371	xpc_connection_resume(peer);
1372}
1373
1374int
1375main(int argc, char *argv[])
1376{
1377	int64_t is_managed = 0;
1378
1379	vproc_swap_integer(NULL, VPROC_GSK_IS_MANAGED, NULL, &is_managed);
1380
1381	if (is_managed == 0) return cli_main(argc, argv);
1382
1383	/* XPC server */
1384	serverq = dispatch_queue_create("aslmanager", NULL);
1385	xpc_track_activity();
1386
1387	/* Handle incoming messages. */
1388	listener = xpc_connection_create_mach_service("com.apple.aslmanager", serverq, XPC_CONNECTION_MACH_SERVICE_LISTENER);
1389	xpc_connection_set_event_handler(listener, ^(xpc_object_t peer) {
1390		if (xpc_get_type(peer) == XPC_TYPE_CONNECTION) accept_connection(peer);
1391	});
1392	xpc_connection_resume(listener);
1393
1394	dispatch_main();
1395}
1396