1/*
2 * Copyright (c) 2007-2011 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 <stdlib.h>
25#include <unistd.h>
26#include <string.h>
27#include <errno.h>
28#include <dirent.h>
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <fcntl.h>
32#include <asl.h>
33#include <asl_private.h>
34#include <asl_core.h>
35#include <asl_store.h>
36#include <notify.h>
37
38#include <TargetConditionals.h>
39
40#if TARGET_IPHONE_SIMULATOR
41#include <dispatch/dispatch.h>
42#include <assert.h>
43#endif
44
45extern time_t asl_parse_time(const char *str);
46extern uint64_t asl_file_cursor(asl_file_t *s);
47extern uint32_t asl_file_match_start(asl_file_t *s, uint64_t start_id, int32_t direction);
48extern uint32_t asl_file_match_next(asl_file_t *s, aslresponse query, asl_msg_t **msg, uint64_t *last_id, int32_t direction, int32_t ruid, int32_t rgid);
49extern int asl_file_create(const char *path, uid_t uid, gid_t gid, mode_t mode);
50
51#define SECONDS_PER_DAY 86400
52
53/*
54 * The ASL Store is organized as a set of files in a common directory.
55 * Files are prefixed by the date (YYYY.MM.DD) of their contents.
56 *
57 * Messages with no access controls are saved in YYYY.MM.DD.asl
58 * Messages with access limited to UID uuu are saved in YYYY.MM.DD.Uuuu.asl
59 * Messages with access limited to GID ggg are saved in YYYY.MM.DD.Gggg.asl
60 * Messages with access limited to UID uuu and GID ggg are saved in YYYY.MM.DD.Uuuu.Gggg.asl
61 *
62 * Messages that have a value for ASLExpireTime are saved in BB.YYYY.MM.DD.asl
63 * where the timestamp is the "Best Before" date of the file.  Access controls
64 * are implemented as above with Uuuu and Gggg in the file name.  Note that the
65 * Best Before files are for the last day of the month, so a single file contains
66 * messages that expire in that month.
67 *
68 * An external tool runs daily and deletes "old" files.
69 */
70
71static time_t
72_asl_start_today()
73{
74	time_t now;
75	struct tm ctm;
76
77	memset(&ctm, 0, sizeof(struct tm));
78	now = time(NULL);
79
80	if (localtime_r((const time_t *)&now, &ctm) == NULL) return 0;
81
82	ctm.tm_sec = 0;
83	ctm.tm_min = 0;
84	ctm.tm_hour = 0;
85
86	return mktime(&ctm);
87}
88
89/*
90 * The base directory contains a data file which stores
91 * the last record ID.
92 *
93 * | MAX_ID (uint64_t) |
94 *
95 */
96uint32_t
97asl_store_open_write(const char *basedir, asl_store_t **s)
98{
99	asl_store_t *out;
100	struct stat sb;
101	uint32_t i, flags;
102	char *path;
103	FILE *sd;
104	uint64_t last_id;
105	time_t start;
106
107	if (s == NULL) return ASL_STATUS_INVALID_ARG;
108
109	start = _asl_start_today();
110	if (start == 0) return ASL_STATUS_FAILED;
111
112	if (basedir == NULL) basedir = PATH_ASL_STORE;
113
114	memset(&sb, 0, sizeof(struct stat));
115	if (stat(basedir, &sb) != 0) return ASL_STATUS_INVALID_STORE;
116	if (!S_ISDIR(sb.st_mode)) return ASL_STATUS_INVALID_STORE;
117
118	path = NULL;
119	asprintf(&path, "%s/%s", basedir, FILE_ASL_STORE_DATA);
120	if (path == NULL) return ASL_STATUS_NO_MEMORY;
121
122	sd = NULL;
123
124	memset(&sb, 0, sizeof(struct stat));
125	if (stat(path, &sb) != 0)
126	{
127		if (errno != ENOENT)
128		{
129			free(path);
130			return ASL_STATUS_FAILED;
131		}
132
133		sd = fopen(path, "w+");
134		free(path);
135
136		if (sd == NULL) return ASL_STATUS_FAILED;
137
138		last_id = 0;
139
140		/* Create new StoreData file (8 bytes ID + 4 bytes flags) */
141
142		if (fwrite(&last_id, sizeof(uint64_t), 1, sd) != 1)
143		{
144			fclose(sd);
145			return ASL_STATUS_WRITE_FAILED;
146		}
147
148		flags = 0;
149		if (fwrite(&flags, sizeof(uint32_t), 1, sd) != 1)
150		{
151			fclose(sd);
152			return ASL_STATUS_WRITE_FAILED;
153		}
154
155		/* flush data */
156		fflush(sd);
157	}
158	else
159	{
160		sd = fopen(path, "r+");
161		free(path);
162
163		if (sd == NULL) return ASL_STATUS_FAILED;
164		if (fread(&last_id, sizeof(uint64_t), 1, sd) != 1)
165		{
166			fclose(sd);
167			return ASL_STATUS_READ_FAILED;
168		}
169
170		last_id = asl_core_ntohq(last_id);
171	}
172
173	out = (asl_store_t *)calloc(1, sizeof(asl_store_t));
174	if (out == NULL)
175	{
176		fclose(sd);
177		return ASL_STATUS_NO_MEMORY;
178	}
179
180	if (basedir == NULL) out->base_dir = strdup(PATH_ASL_STORE);
181	else out->base_dir = strdup(basedir);
182
183	if (out->base_dir == NULL)
184	{
185		fclose(sd);
186		free(out);
187		return ASL_STATUS_NO_MEMORY;
188	}
189
190	out->start_today = start;
191	out->start_tomorrow = out->start_today + SECONDS_PER_DAY;
192	out->storedata = sd;
193	out->next_id = last_id + 1;
194
195	for (i = 0; i < FILE_CACHE_SIZE; i++)
196	{
197		memset(&out->file_cache[i], 0, sizeof(asl_cached_file_t));
198		out->file_cache[i].u = -1;
199		out->file_cache[i].g = -1;
200	}
201
202	*s = out;
203	return ASL_STATUS_OK;
204}
205
206uint32_t
207asl_store_statistics(asl_store_t *s, aslmsg *msg)
208{
209	aslmsg out;
210
211	if (s == NULL) return ASL_STATUS_INVALID_STORE;
212	if (msg == NULL) return ASL_STATUS_INVALID_ARG;
213
214	out = asl_new(ASL_TYPE_MSG);
215	if (out == NULL) return ASL_STATUS_NO_MEMORY;
216
217	/* does nothing for now */
218
219	*msg = out;
220	return ASL_STATUS_OK;
221}
222
223uint32_t
224asl_store_open_read(const char *basedir, asl_store_t **s)
225{
226	asl_store_t *out;
227	struct stat sb;
228
229	if (s == NULL) return ASL_STATUS_INVALID_ARG;
230
231	if (basedir == NULL) basedir = PATH_ASL_STORE;
232
233	memset(&sb, 0, sizeof(struct stat));
234	if (stat(basedir, &sb) != 0) return ASL_STATUS_INVALID_STORE;
235	if (!S_ISDIR(sb.st_mode)) return ASL_STATUS_INVALID_STORE;
236
237	out = (asl_store_t *)calloc(1, sizeof(asl_store_t));
238	if (out == NULL) return ASL_STATUS_NO_MEMORY;
239
240	if (basedir == NULL) out->base_dir = strdup(PATH_ASL_STORE);
241	else out->base_dir = strdup(basedir);
242
243	if (out->base_dir == NULL)
244	{
245		free(out);
246		return ASL_STATUS_NO_MEMORY;
247	}
248
249	*s = out;
250	return ASL_STATUS_OK;
251}
252
253uint32_t
254asl_store_max_file_size(asl_store_t *s, size_t max)
255{
256	if (s == NULL) return ASL_STATUS_INVALID_STORE;
257
258	s->max_file_size = max;
259	return ASL_STATUS_OK;
260}
261
262__private_extern__ void
263asl_store_file_closeall(asl_store_t *s)
264{
265	uint32_t i;
266
267	if (s == NULL) return;
268
269	for (i = 0; i < FILE_CACHE_SIZE; i++)
270	{
271		if (s->file_cache[i].f != NULL) asl_file_close(s->file_cache[i].f);
272		s->file_cache[i].f = NULL;
273		if (s->file_cache[i].path != NULL) free(s->file_cache[i].path);
274		s->file_cache[i].path = NULL;
275		s->file_cache[i].u = -1;
276		s->file_cache[i].g = -1;
277		s->file_cache[i].bb = 0;
278		s->file_cache[i].ts = 0;
279	}
280}
281
282uint32_t
283asl_store_close(asl_store_t *s)
284{
285	if (s == NULL) return ASL_STATUS_OK;
286
287	if (s->base_dir != NULL) free(s->base_dir);
288	s->base_dir = NULL;
289	asl_store_file_closeall(s);
290	if (s->storedata != NULL) fclose(s->storedata);
291
292	free(s);
293
294	return ASL_STATUS_OK;
295}
296
297/*
298 * Sweep the file cache.
299 * Close any files that have not been used in the last FILE_CACHE_TTL seconds.
300 * Returns least recently used or unused cache slot.
301 */
302static uint32_t
303asl_store_file_cache_lru(asl_store_t *s, time_t now, uint32_t ignorex)
304{
305	time_t min;
306	uint32_t i, x;
307
308	if (s == NULL) return 0;
309
310	x = 0;
311	min = now - FILE_CACHE_TTL;
312
313	for (i = 0; i < FILE_CACHE_SIZE; i++)
314	{
315		if ((i != ignorex) && (s->file_cache[i].ts < min))
316		{
317			asl_file_close(s->file_cache[i].f);
318			s->file_cache[i].f = NULL;
319			if (s->file_cache[i].path != NULL) free(s->file_cache[i].path);
320			s->file_cache[i].path = NULL;
321			s->file_cache[i].u = -1;
322			s->file_cache[i].g = -1;
323			s->file_cache[i].bb = 0;
324			s->file_cache[i].ts = 0;
325		}
326
327		if (s->file_cache[i].ts < s->file_cache[x].ts) x = i;
328	}
329
330	return x;
331}
332
333uint32_t
334asl_store_sweep_file_cache(asl_store_t *s)
335{
336	if (s == NULL) return ASL_STATUS_INVALID_STORE;
337
338	asl_store_file_cache_lru(s, time(NULL), FILE_CACHE_SIZE);
339	return ASL_STATUS_OK;
340}
341
342static char *
343asl_store_make_ug_path(const char *dir, const char *base, const char *ext, uid_t ruid, gid_t rgid, uid_t *u, gid_t *g, mode_t *m)
344{
345	char *path  = NULL;
346
347	*u = 0;
348	*g = 0;
349	*m = 0644;
350
351	if (ruid == -1)
352	{
353		if (rgid == -1)
354		{
355			if (ext == NULL) asprintf(&path, "%s/%s", dir, base);
356			else asprintf(&path, "%s/%s.%s", dir, base, ext);
357		}
358		else
359		{
360			*g = rgid;
361			*m = 0600;
362			if (ext == NULL) asprintf(&path, "%s/%s.G%d", dir, base, *g);
363			else asprintf(&path, "%s/%s.G%d.%s", dir, base, *g, ext);
364		}
365	}
366	else
367	{
368		*u = ruid;
369		if (rgid == -1)
370		{
371			*m = 0600;
372			if (ext == NULL) asprintf(&path, "%s/%s.U%d", dir, base, *u);
373			else asprintf(&path, "%s/%s.U%d.%s", dir, base, *u, ext);
374		}
375		else
376		{
377			*g = rgid;
378			*m = 0600;
379			if (ext == NULL) asprintf(&path, "%s/%s.U%d.G%d", dir, base, *u, *g);
380			else asprintf(&path, "%s/%s.U%d.G%u.%s", dir, base, *u, *g, ext);
381		}
382	}
383
384	return path;
385}
386
387static uint32_t
388asl_store_file_open_write(asl_store_t *s, char *tstring, int32_t ruid, int32_t rgid, time_t bb, asl_file_t **f, time_t now, uint32_t check_cache)
389{
390	char *path;
391	mode_t m;
392	int32_t i, x;
393	uid_t u;
394	gid_t g;
395	uint32_t status;
396	asl_file_t *out;
397
398	if (s == NULL) return ASL_STATUS_INVALID_STORE;
399
400	/* see if the file is already open and in the cache */
401	for (i = 0; i < FILE_CACHE_SIZE; i++)
402	{
403		if ((s->file_cache[i].u == ruid) && (s->file_cache[i].g == rgid) && (s->file_cache[i].bb == bb) && (s->file_cache[i].f != NULL))
404		{
405			s->file_cache[i].ts = now;
406			*f = s->file_cache[i].f;
407			if (check_cache == 1) asl_store_file_cache_lru(s, now, i);
408			return ASL_STATUS_OK;
409		}
410	}
411
412	u = 0;
413	g = 0;
414	m = 0644;
415	path = asl_store_make_ug_path(s->base_dir, tstring, "asl", (uid_t)ruid, (gid_t)rgid, &u, &g, &m);
416	if (path == NULL) return ASL_STATUS_NO_MEMORY;
417
418	out = NULL;
419	status = asl_file_open_write(path, m, u, g, &out);
420	if (status != ASL_STATUS_OK)
421	{
422		free(path);
423		return status;
424	}
425
426	x = asl_store_file_cache_lru(s, now, FILE_CACHE_SIZE);
427	if (s->file_cache[x].f != NULL) asl_file_close(s->file_cache[x].f);
428	if (s->file_cache[x].path != NULL) free(s->file_cache[x].path);
429
430	s->file_cache[x].f = out;
431	s->file_cache[x].path = path;
432	s->file_cache[x].u = ruid;
433	s->file_cache[x].g = rgid;
434	s->file_cache[x].bb = bb;
435	s->file_cache[x].ts = time(NULL);
436
437	*f = out;
438
439	return ASL_STATUS_OK;
440}
441
442__private_extern__ char *
443asl_store_file_path(asl_store_t *s, asl_file_t *f)
444{
445	uint32_t i;
446
447	if (s == NULL) return NULL;
448
449	for (i = 0; i < FILE_CACHE_SIZE; i++)
450	{
451		if (s->file_cache[i].f == f)
452		{
453			if (s->file_cache[i].path == NULL) return NULL;
454			return strdup(s->file_cache[i].path);
455		}
456	}
457
458	return NULL;
459}
460
461__private_extern__ void
462asl_store_file_close(asl_store_t *s, asl_file_t *f)
463{
464	uint32_t i;
465
466	if (s == NULL) return;
467	if (f == NULL) return;
468
469	for (i = 0; i < FILE_CACHE_SIZE; i++)
470	{
471		if (s->file_cache[i].f == f)
472		{
473			asl_file_close(s->file_cache[i].f);
474			s->file_cache[i].f = NULL;
475			if (s->file_cache[i].path != NULL) free(s->file_cache[i].path);
476			s->file_cache[i].path = NULL;
477			s->file_cache[i].u = -1;
478			s->file_cache[i].g = -1;
479			s->file_cache[i].bb = 0;
480			s->file_cache[i].ts = 0;
481			return;
482		}
483	}
484}
485
486uint32_t
487asl_store_save(asl_store_t *s, aslmsg msg)
488{
489	struct tm ctm;
490	time_t msg_time, now, bb;
491	char *path, *tmp_path, *tstring, *scratch;
492	const char *val;
493	uid_t ruid;
494	gid_t rgid;
495	asl_file_t *f;
496	uint32_t status, check_cache, trigger_aslmanager, len;
497	uint64_t xid, ftime;
498	size_t fsize;
499
500	if (s == NULL) return ASL_STATUS_INVALID_STORE;
501	if (msg == NULL) return ASL_STATUS_INVALID_ARG;
502
503	now = time(NULL);
504
505	check_cache = 0;
506	if ((s->last_write + FILE_CACHE_TTL) <= now) check_cache = 1;
507
508	trigger_aslmanager = 0;
509
510	msg_time = 0;
511	val = asl_get(msg, ASL_KEY_TIME);
512	if (val == NULL) msg_time = now;
513	else msg_time = asl_parse_time(val);
514
515	if (msg_time >= s->start_tomorrow)
516	{
517		if (now >= s->start_tomorrow)
518		{
519			/* new day begins */
520			check_cache = 0;
521			asl_store_file_closeall(s);
522
523			/*
524			 * _asl_start_today should never fail, but if it does,
525			 * just push forward one day.  That will probably be correct, and if
526			 * it isn't, the next message that gets saved will push it ahead again
527			 * until we get to the right date.
528			 */
529			s->start_today = _asl_start_today();
530			if (s->start_today == 0) s->start_today = s->start_tomorrow;
531
532			s->start_tomorrow = s->start_today + SECONDS_PER_DAY;
533		}
534	}
535
536	val = asl_get(msg, ASL_KEY_READ_UID);
537	ruid = -1;
538	if (val != NULL) ruid = atoi(val);
539
540	val = asl_get(msg, ASL_KEY_READ_GID);
541	rgid = -1;
542	if (val != NULL) rgid = atoi(val);
543
544	bb = 0;
545	val = asl_get(msg, ASL_KEY_EXPIRE_TIME);
546	if (val != NULL)
547	{
548		bb = 1;
549		msg_time = asl_parse_time(val);
550	}
551
552	if (fseeko(s->storedata, 0, SEEK_SET) != 0) return ASL_STATUS_WRITE_FAILED;
553
554	xid = asl_core_htonq(s->next_id);
555	if (fwrite(&xid, sizeof(uint64_t), 1, s->storedata) != 1) return ASL_STATUS_WRITE_FAILED;
556
557	/* flush data */
558	fflush(s->storedata);
559
560	xid = s->next_id;
561	s->next_id++;
562
563	s->last_write = now;
564
565	if (localtime_r((const time_t *)&msg_time, &ctm) == NULL) return ASL_STATUS_FAILED;
566
567	tstring = NULL;
568	if (bb == 1)
569	{
570		/*
571		 * This supports 12 monthly "Best Before" buckets.
572		 * We advance the actual expiry time to day zero of the following month.
573		 * mktime() is clever enough to know that you actually mean the last day
574		 * of the previous month.  What we get back from localtime is the last
575		 * day of the month in which the message expires, which we use in the name.
576		 */
577		ctm.tm_sec = 0;
578		ctm.tm_min = 0;
579		ctm.tm_hour = 0;
580		ctm.tm_mday = 0;
581		ctm.tm_mon += 1;
582
583		bb = mktime(&ctm);
584
585		if (localtime_r((const time_t *)&bb, &ctm) == NULL) return ASL_STATUS_FAILED;
586		asprintf(&tstring, "BB.%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
587	}
588	else
589	{
590		asprintf(&tstring, "%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
591	}
592
593	if (tstring == NULL) return ASL_STATUS_NO_MEMORY;
594
595	status = asl_store_file_open_write(s, tstring, ruid, rgid, bb, &f, now, check_cache);
596	free(tstring);
597	tstring = NULL;
598
599	if (status != ASL_STATUS_OK) return status;
600
601	status = asl_file_save(f, msg, &xid);
602	if (status != ASL_STATUS_OK) return status;
603
604	fsize = asl_file_size(f);
605	ftime = asl_file_ctime(f);
606
607	/* if file is larger than max_file_size, rename it and trigger aslmanager */
608	if ((s->max_file_size != 0) && (fsize > s->max_file_size))
609	{
610		trigger_aslmanager = 1;
611		status = ASL_STATUS_OK;
612
613		path = asl_store_file_path(s, f);
614
615		asl_store_file_close(s, f);
616
617		if (path != NULL)
618		{
619			tmp_path = NULL;
620
621			len = strlen(path);
622			if ((len >= 4) && (!strcmp(path + len - 4, ".asl")))
623			{
624				/* rename xxxxxxx.asl to xxxxxxx.timestamp.asl */
625				scratch = strdup(path);
626				if (scratch != NULL)
627				{
628					scratch[len - 4] = '\0';
629					asprintf(&tmp_path, "%s.%llu.asl", scratch, ftime);
630					free(scratch);
631
632				}
633			}
634			else
635			{
636				/* append timestamp */
637				asprintf(&tmp_path, "%s.%llu", path, ftime);
638			}
639
640			if (tmp_path == NULL)
641			{
642				status = ASL_STATUS_NO_MEMORY;
643			}
644			else
645			{
646				if (rename(path, tmp_path) != 0) status = ASL_STATUS_FAILED;
647				free(tmp_path);
648			}
649
650			free(path);
651		}
652	}
653
654	if (trigger_aslmanager != 0) asl_trigger_aslmanager();
655
656	return status;
657}
658
659static uint32_t
660asl_store_mkdir(asl_store_t *s, const char *dir, mode_t m)
661{
662	char *tstring = NULL;
663	int status;
664	struct stat sb;
665
666	asprintf(&tstring, "%s/%s", s->base_dir, dir);
667	if (tstring == NULL) return ASL_STATUS_NO_MEMORY;
668
669	memset(&sb, 0, sizeof(struct stat));
670	status = stat(tstring, &sb);
671
672	if (status == 0)
673	{
674		/* must be a directory */
675		if (!S_ISDIR(sb.st_mode))
676		{
677			free(tstring);
678			return ASL_STATUS_INVALID_STORE;
679		}
680	}
681	else
682	{
683		if (errno == ENOENT)
684		{
685			/* doesn't exist - create it */
686			if (mkdir(tstring, m) != 0)
687			{
688				free(tstring);
689				return ASL_STATUS_WRITE_FAILED;
690			}
691		}
692		else
693		{
694			/* stat failed for some other reason */
695			free(tstring);
696			return ASL_STATUS_FAILED;
697		}
698	}
699
700	free(tstring);
701	return ASL_STATUS_OK;
702}
703
704uint32_t
705asl_store_open_aux(asl_store_t *s, aslmsg msg, int *out_fd, char **url)
706{
707	struct tm ctm;
708	time_t msg_time, bb;
709	char *path, *dir, *tstring;
710	const char *val;
711	uid_t ruid, u;
712	gid_t rgid, g;
713	mode_t m;
714	uint32_t status;
715	uint64_t fid;
716	int fd;
717
718	if (s == NULL) return ASL_STATUS_INVALID_STORE;
719	if (msg == NULL) return ASL_STATUS_INVALID_ARG;
720	if (out_fd == NULL) return ASL_STATUS_INVALID_ARG;
721	if (url == NULL) return ASL_STATUS_INVALID_ARG;
722
723	msg_time = time(NULL);
724
725	val = asl_get(msg, ASL_KEY_READ_UID);
726	ruid = -1;
727	if (val != NULL) ruid = atoi(val);
728
729	val = asl_get(msg, ASL_KEY_READ_GID);
730	rgid = -1;
731	if (val != NULL) rgid = atoi(val);
732
733	bb = 0;
734	val = asl_get(msg, ASL_KEY_EXPIRE_TIME);
735	if (val != NULL)
736	{
737		bb = 1;
738		msg_time = asl_parse_time(val);
739	}
740
741	if (localtime_r((const time_t *)&msg_time, &ctm) == NULL) return ASL_STATUS_FAILED;
742
743	dir = NULL;
744	if (bb == 1)
745	{
746		/*
747		 * This supports 12 monthly "Best Before" buckets.
748		 * We advance the actual expiry time to day zero of the following month.
749		 * mktime() is clever enough to know that you actually mean the last day
750		 * of the previous month.  What we get back from localtime is the last
751		 * day of the month in which the message expires, which we use in the name.
752		 */
753		ctm.tm_sec = 0;
754		ctm.tm_min = 0;
755		ctm.tm_hour = 0;
756		ctm.tm_mday = 0;
757		ctm.tm_mon += 1;
758
759		bb = mktime(&ctm);
760
761		if (localtime_r((const time_t *)&bb, &ctm) == NULL) return ASL_STATUS_FAILED;
762		asprintf(&dir, "BB.AUX.%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
763	}
764	else
765	{
766		asprintf(&dir, "AUX.%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
767	}
768
769	if (dir == NULL) return ASL_STATUS_NO_MEMORY;
770
771	status = asl_store_mkdir(s, dir, 0755);
772	if (status != ASL_STATUS_OK)
773	{
774		free(dir);
775		return status;
776	}
777
778	fid = s->next_id;
779	s->next_id++;
780	tstring = NULL;
781
782	asprintf(&tstring, "%s/%llu", dir, fid);
783	free(dir);
784	if (tstring == NULL) return ASL_STATUS_NO_MEMORY;
785
786	u = 0;
787	g = 0;
788	m = 0644;
789	path = asl_store_make_ug_path(s->base_dir, tstring, NULL, ruid, rgid, &u, &g, &m);
790	free(tstring);
791	if (path == NULL) return ASL_STATUS_NO_MEMORY;
792
793	fd = asl_file_create(path, u, g, m);
794	if (fd < 0)
795	{
796		free(path);
797		*out_fd = -1;
798		return ASL_STATUS_WRITE_FAILED;
799	}
800
801	/* URL is file://<path> */
802	*url = NULL;
803	asprintf(url, "file://%s", path);
804	free(path);
805
806	*out_fd = fd;
807
808	return status;
809}
810
811uint32_t
812asl_store_match_timeout(asl_store_t *s, aslresponse query, aslresponse *res, uint64_t *last_id, uint64_t start_id, uint32_t count, int32_t direction, uint32_t usec)
813{
814	DIR *dp;
815	struct dirent *dent;
816	uint32_t status;
817	asl_file_t *f;
818	char *path;
819	asl_file_list_t *files;
820
821	if (s == NULL) return ASL_STATUS_INVALID_STORE;
822	if (res == NULL) return ASL_STATUS_INVALID_ARG;
823
824	files = NULL;
825
826	/*
827	 * Open all readable files
828	 */
829	dp = opendir(s->base_dir);
830	if (dp == NULL) return ASL_STATUS_READ_FAILED;
831
832	while ((dent = readdir(dp)) != NULL)
833	{
834		if (dent->d_name[0] == '.') continue;
835
836		path = NULL;
837		asprintf(&path, "%s/%s", s->base_dir, dent->d_name);
838
839		/* NB asl_file_open_read will fail if path is NULL, if the file is not an ASL store file, or if it isn't readable */
840		status = asl_file_open_read(path, &f);
841		if (path != NULL) free(path);
842		if ((status != ASL_STATUS_OK) || (f == NULL)) continue;
843
844		files = asl_file_list_add(files, f);
845	}
846
847	closedir(dp);
848
849	status = asl_file_list_match_timeout(files, query, res, last_id, start_id, count, direction, usec);
850	asl_file_list_close(files);
851	return status;
852}
853
854uint32_t
855asl_store_match(asl_store_t *s, aslresponse query, aslresponse *res, uint64_t *last_id, uint64_t start_id, uint32_t count, int32_t direction)
856{
857	return asl_store_match_timeout(s, query, res, last_id, start_id, count, direction, 0);
858}
859
860uint32_t
861asl_store_match_start(asl_store_t *s, uint64_t start_id, int32_t direction)
862{
863	DIR *dp;
864	struct dirent *dent;
865	uint32_t status;
866	asl_file_t *f;
867	char *path;
868	asl_file_list_t *files;
869
870	if (s == NULL) return ASL_STATUS_INVALID_STORE;
871
872	if (s->work != NULL) asl_file_list_match_end(s->work);
873	s->work = NULL;
874
875	files = NULL;
876
877	/*
878	 * Open all readable files
879	 */
880	dp = opendir(s->base_dir);
881	if (dp == NULL) return ASL_STATUS_READ_FAILED;
882
883	while ((dent = readdir(dp)) != NULL)
884	{
885		if (dent->d_name[0] == '.') continue;
886
887		path = NULL;
888		asprintf(&path, "%s/%s", s->base_dir, dent->d_name);
889
890		/* NB asl_file_open_read will fail if path is NULL, if the file is not an ASL store file, or if it isn't readable */
891		status = asl_file_open_read(path, &f);
892		if (path != NULL) free(path);
893		if ((status != ASL_STATUS_OK) || (f == NULL)) continue;
894
895		files = asl_file_list_add(files, f);
896	}
897
898	closedir(dp);
899
900	s->work = asl_file_list_match_start(files, start_id, direction);
901	if (s->work == NULL) return ASL_STATUS_FAILED;
902
903	return ASL_STATUS_OK;
904}
905
906uint32_t
907asl_store_match_next(asl_store_t *s, aslresponse query, aslresponse *res, uint32_t count)
908{
909	if (s == NULL) return ASL_STATUS_INVALID_STORE;
910	if (s->work == NULL) return ASL_STATUS_OK;
911
912	return asl_file_list_match_next(s->work, query, res, count);
913}
914
915#if TARGET_IPHONE_SIMULATOR
916const char *_path_asl_store(void) {
917	static char * path;
918	static dispatch_once_t once;
919
920	dispatch_once(&once, ^{
921		char *sim_log_dir = getenv("IPHONE_SIMULATOR_LOG_ROOT");
922		assert(sim_log_dir);
923
924		asprintf(&path, "%s/asl", sim_log_dir);
925		assert(path);
926	});
927
928	return path;
929}
930
931const char *_path_asl_archive(void) {
932	static char * path;
933	static dispatch_once_t once;
934
935	dispatch_once(&once, ^{
936		char *sim_log_dir = getenv("IPHONE_SIMULATOR_LOG_ROOT");
937		assert(sim_log_dir);
938
939		asprintf(&path, "%s/asl.archive", sim_log_dir);
940		assert(path);
941	});
942
943	return path;
944}
945#endif
946