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
38extern uint64_t asl_file_cursor(asl_file_t *s);
39extern uint32_t asl_file_match_start(asl_file_t *s, uint64_t start_id, int32_t direction);
40extern uint32_t asl_file_match_next(asl_file_t *s, asl_msg_list_t *qlist, asl_msg_t **msg, uint64_t *last_id, int32_t direction, int32_t ruid, int32_t rgid);
41extern int asl_file_create(const char *path, uid_t uid, gid_t gid, mode_t mode);
42
43#define SECONDS_PER_DAY 86400
44
45/*
46 * The ASL Store is organized as a set of files in a common directory.
47 * Files are prefixed by the date (YYYY.MM.DD) of their contents.
48 *
49 * Messages with no access controls are saved in YYYY.MM.DD.asl
50 * Messages with access limited to UID uuu are saved in YYYY.MM.DD.Uuuu.asl
51 * Messages with access limited to GID ggg are saved in YYYY.MM.DD.Gggg.asl
52 * Messages with access limited to UID uuu and GID ggg are saved in YYYY.MM.DD.Uuuu.Gggg.asl
53 *
54 * Messages that have a value for ASLExpireTime are saved in BB.YYYY.MM.DD.asl
55 * where the timestamp is the "Best Before" date of the file.  Access controls
56 * are implemented as above with Uuuu and Gggg in the file name.  Note that the
57 * Best Before files are for the last day of the month, so a single file contains
58 * messages that expire in that month.
59 *
60 * An external tool runs daily and deletes "old" files.
61 */
62
63static time_t
64_asl_start_today()
65{
66	time_t now;
67	struct tm ctm;
68
69	memset(&ctm, 0, sizeof(struct tm));
70	now = time(NULL);
71
72	if (localtime_r((const time_t *)&now, &ctm) == NULL) return 0;
73
74	ctm.tm_sec = 0;
75	ctm.tm_min = 0;
76	ctm.tm_hour = 0;
77
78	return mktime(&ctm);
79}
80
81/*
82 * The base directory contains a data file which stores
83 * the last record ID.
84 *
85 * | MAX_ID (uint64_t) |
86 *
87 */
88ASL_STATUS
89asl_store_open_write(const char *basedir, asl_store_t **s)
90{
91	asl_store_t *out;
92	struct stat sb;
93	uint32_t i, flags;
94	char *path;
95	FILE *sd;
96	uint64_t last_id;
97	time_t start;
98
99	if (s == NULL) return ASL_STATUS_INVALID_ARG;
100
101	start = _asl_start_today();
102	if (start == 0) return ASL_STATUS_FAILED;
103
104	if (basedir == NULL) basedir = PATH_ASL_STORE;
105
106	memset(&sb, 0, sizeof(struct stat));
107	if (stat(basedir, &sb) != 0) return ASL_STATUS_INVALID_STORE;
108	if (!S_ISDIR(sb.st_mode)) return ASL_STATUS_INVALID_STORE;
109
110	path = NULL;
111	asprintf(&path, "%s/%s", basedir, FILE_ASL_STORE_DATA);
112	if (path == NULL) return ASL_STATUS_NO_MEMORY;
113
114	sd = NULL;
115
116	memset(&sb, 0, sizeof(struct stat));
117	if (stat(path, &sb) != 0)
118	{
119		if (errno != ENOENT)
120		{
121			free(path);
122			return ASL_STATUS_FAILED;
123		}
124
125		sd = fopen(path, "w+");
126		free(path);
127
128		if (sd == NULL) return ASL_STATUS_FAILED;
129
130		last_id = 0;
131
132		/* Create new StoreData file (8 bytes ID + 4 bytes flags) */
133
134		if (fwrite(&last_id, sizeof(uint64_t), 1, sd) != 1)
135		{
136			fclose(sd);
137			return ASL_STATUS_WRITE_FAILED;
138		}
139
140		flags = 0;
141		if (fwrite(&flags, sizeof(uint32_t), 1, sd) != 1)
142		{
143			fclose(sd);
144			return ASL_STATUS_WRITE_FAILED;
145		}
146
147		/* flush data */
148		fflush(sd);
149	}
150	else
151	{
152		sd = fopen(path, "r+");
153		free(path);
154
155		if (sd == NULL) return ASL_STATUS_FAILED;
156		if (fread(&last_id, sizeof(uint64_t), 1, sd) != 1)
157		{
158			fclose(sd);
159			return ASL_STATUS_READ_FAILED;
160		}
161
162		last_id = asl_core_ntohq(last_id);
163	}
164
165	out = (asl_store_t *)calloc(1, sizeof(asl_store_t));
166	if (out == NULL)
167	{
168		fclose(sd);
169		return ASL_STATUS_NO_MEMORY;
170	}
171
172	out->asl_type = ASL_TYPE_STORE;
173	out->refcount = 1;
174
175	if (basedir == NULL) out->base_dir = strdup(PATH_ASL_STORE);
176	else out->base_dir = strdup(basedir);
177
178	if (out->base_dir == NULL)
179	{
180		fclose(sd);
181		free(out);
182		return ASL_STATUS_NO_MEMORY;
183	}
184
185	out->start_today = start;
186	out->start_tomorrow = out->start_today + SECONDS_PER_DAY;
187	out->storedata = sd;
188	out->next_id = last_id + 1;
189
190	for (i = 0; i < FILE_CACHE_SIZE; i++)
191	{
192		memset(&out->file_cache[i], 0, sizeof(asl_cached_file_t));
193		out->file_cache[i].u = -1;
194		out->file_cache[i].g = -1;
195	}
196
197	*s = out;
198	return ASL_STATUS_OK;
199}
200
201uint32_t
202asl_store_set_flags(asl_store_t *s, uint32_t flags)
203{
204	if (s == NULL) return 0;
205	uint32_t oldflags = s->flags;
206	s->flags = flags;
207	return oldflags;
208}
209
210ASL_STATUS
211asl_store_statistics(asl_store_t *s, asl_msg_t **msg)
212{
213	asl_msg_t *out;
214
215	if (s == NULL) return ASL_STATUS_INVALID_STORE;
216	if (msg == NULL) return ASL_STATUS_INVALID_ARG;
217
218	out = asl_msg_new(ASL_TYPE_MSG);
219	if (out == NULL) return ASL_STATUS_NO_MEMORY;
220
221	/* does nothing for now */
222
223	*msg = out;
224	return ASL_STATUS_OK;
225}
226
227uint32_t
228asl_store_open_read(const char *basedir, asl_store_t **s)
229{
230	asl_store_t *out;
231	struct stat sb;
232
233	if (s == NULL) return ASL_STATUS_INVALID_ARG;
234
235	if (basedir == NULL) basedir = PATH_ASL_STORE;
236
237	memset(&sb, 0, sizeof(struct stat));
238	if (stat(basedir, &sb) != 0) return ASL_STATUS_INVALID_STORE;
239	if (!S_ISDIR(sb.st_mode)) return ASL_STATUS_INVALID_STORE;
240
241	out = (asl_store_t *)calloc(1, sizeof(asl_store_t));
242	if (out == NULL) return ASL_STATUS_NO_MEMORY;
243
244	out->asl_type = ASL_TYPE_STORE;
245	out->refcount = 1;
246
247	if (basedir == NULL) out->base_dir = strdup(PATH_ASL_STORE);
248	else out->base_dir = strdup(basedir);
249
250	if (out->base_dir == NULL)
251	{
252		free(out);
253		return ASL_STATUS_NO_MEMORY;
254	}
255
256	*s = out;
257	return ASL_STATUS_OK;
258}
259
260uint32_t
261asl_store_max_file_size(asl_store_t *s, size_t max)
262{
263	if (s == NULL) return ASL_STATUS_INVALID_STORE;
264
265	s->max_file_size = max;
266	return ASL_STATUS_OK;
267}
268
269__private_extern__ void
270asl_store_file_closeall(asl_store_t *s)
271{
272	uint32_t i;
273
274	if (s == NULL) return;
275
276	for (i = 0; i < FILE_CACHE_SIZE; i++)
277	{
278		if (s->file_cache[i].f != NULL) asl_file_close(s->file_cache[i].f);
279		s->file_cache[i].f = NULL;
280		if (s->file_cache[i].path != NULL) free(s->file_cache[i].path);
281		s->file_cache[i].path = NULL;
282		s->file_cache[i].u = -1;
283		s->file_cache[i].g = -1;
284		s->file_cache[i].bb = 0;
285		s->file_cache[i].ts = 0;
286	}
287}
288
289asl_store_t *
290asl_store_retain(asl_store_t *s)
291{
292	if (s == NULL) return NULL;
293	asl_retain((asl_object_t)s);
294	return s;
295}
296
297void
298asl_store_release(asl_store_t *s)
299{
300	if (s == NULL) return;
301	asl_release((asl_object_t)s);
302}
303
304ASL_STATUS
305asl_store_close(asl_store_t *s)
306{
307	if (s == NULL) return ASL_STATUS_OK;
308	asl_release((asl_object_t)s);
309	return ASL_STATUS_OK;
310}
311
312static void
313_asl_store_free_internal(asl_store_t *s)
314{
315	if (s == NULL) return;
316
317	if (s->base_dir != NULL) free(s->base_dir);
318	s->base_dir = NULL;
319	asl_store_file_closeall(s);
320	if (s->storedata != NULL) fclose(s->storedata);
321
322	free(s);
323}
324
325/*
326 * Sweep the file cache.
327 * Close any files that have not been used in the last FILE_CACHE_TTL seconds.
328 * Returns least recently used or unused cache slot.
329 */
330static uint32_t
331asl_store_file_cache_lru(asl_store_t *s, time_t now, uint32_t ignorex)
332{
333	time_t min;
334	uint32_t i, x;
335
336	if (s == NULL) return 0;
337
338	x = 0;
339	min = now - FILE_CACHE_TTL;
340
341	for (i = 0; i < FILE_CACHE_SIZE; i++)
342	{
343		if ((i != ignorex) && (s->file_cache[i].ts < min))
344		{
345			asl_file_close(s->file_cache[i].f);
346			s->file_cache[i].f = NULL;
347			if (s->file_cache[i].path != NULL) free(s->file_cache[i].path);
348			s->file_cache[i].path = NULL;
349			s->file_cache[i].u = -1;
350			s->file_cache[i].g = -1;
351			s->file_cache[i].bb = 0;
352			s->file_cache[i].ts = 0;
353		}
354
355		if (s->file_cache[i].ts < s->file_cache[x].ts) x = i;
356	}
357
358	return x;
359}
360
361ASL_STATUS
362asl_store_sweep_file_cache(asl_store_t *s)
363{
364	if (s == NULL) return ASL_STATUS_INVALID_STORE;
365
366	asl_store_file_cache_lru(s, time(NULL), FILE_CACHE_SIZE);
367	return ASL_STATUS_OK;
368}
369
370static char *
371asl_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)
372{
373	char *path  = NULL;
374
375	*u = 0;
376	*g = 0;
377	*m = 0644;
378
379	if (ruid == -1)
380	{
381		if (rgid == -1)
382		{
383			if (ext == NULL) asprintf(&path, "%s/%s", dir, base);
384			else asprintf(&path, "%s/%s.%s", dir, base, ext);
385		}
386		else
387		{
388			*g = rgid;
389			*m = 0600;
390			if (ext == NULL) asprintf(&path, "%s/%s.G%d", dir, base, *g);
391			else asprintf(&path, "%s/%s.G%d.%s", dir, base, *g, ext);
392		}
393	}
394	else
395	{
396		*u = ruid;
397		if (rgid == -1)
398		{
399			*m = 0600;
400			if (ext == NULL) asprintf(&path, "%s/%s.U%d", dir, base, *u);
401			else asprintf(&path, "%s/%s.U%d.%s", dir, base, *u, ext);
402		}
403		else
404		{
405			*g = rgid;
406			*m = 0600;
407			if (ext == NULL) asprintf(&path, "%s/%s.U%d.G%d", dir, base, *u, *g);
408			else asprintf(&path, "%s/%s.U%d.G%u.%s", dir, base, *u, *g, ext);
409		}
410	}
411
412	return path;
413}
414
415static ASL_STATUS
416asl_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)
417{
418	char *path;
419	mode_t m;
420	int32_t i, x;
421	uid_t u;
422	gid_t g;
423	uint32_t status;
424	asl_file_t *out;
425
426	if (s == NULL) return ASL_STATUS_INVALID_STORE;
427
428	/* see if the file is already open and in the cache */
429	for (i = 0; i < FILE_CACHE_SIZE; i++)
430	{
431		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))
432		{
433			s->file_cache[i].ts = now;
434			*f = s->file_cache[i].f;
435			if (check_cache == 1) asl_store_file_cache_lru(s, now, i);
436			return ASL_STATUS_OK;
437		}
438	}
439
440	u = 0;
441	g = 0;
442	m = 0644;
443	path = asl_store_make_ug_path(s->base_dir, tstring, "asl", (uid_t)ruid, (gid_t)rgid, &u, &g, &m);
444	if (path == NULL) return ASL_STATUS_NO_MEMORY;
445
446	out = NULL;
447	status = asl_file_open_write(path, m, u, g, &out);
448	if (status != ASL_STATUS_OK)
449	{
450		free(path);
451		return status;
452	}
453
454	x = asl_store_file_cache_lru(s, now, FILE_CACHE_SIZE);
455	if (s->file_cache[x].f != NULL) asl_file_close(s->file_cache[x].f);
456	if (s->file_cache[x].path != NULL) free(s->file_cache[x].path);
457
458	s->file_cache[x].f = out;
459	s->file_cache[x].path = path;
460	s->file_cache[x].u = ruid;
461	s->file_cache[x].g = rgid;
462	s->file_cache[x].bb = bb;
463	s->file_cache[x].ts = time(NULL);
464
465	*f = out;
466
467	return ASL_STATUS_OK;
468}
469
470__private_extern__ char *
471asl_store_file_path(asl_store_t *s, asl_file_t *f)
472{
473	uint32_t i;
474
475	if (s == NULL) return NULL;
476
477	for (i = 0; i < FILE_CACHE_SIZE; i++)
478	{
479		if (s->file_cache[i].f == f)
480		{
481			if (s->file_cache[i].path == NULL) return NULL;
482			return strdup(s->file_cache[i].path);
483		}
484	}
485
486	return NULL;
487}
488
489__private_extern__ void
490asl_store_file_close(asl_store_t *s, asl_file_t *f)
491{
492	uint32_t i;
493
494	if (s == NULL) return;
495	if (f == NULL) return;
496
497	for (i = 0; i < FILE_CACHE_SIZE; i++)
498	{
499		if (s->file_cache[i].f == f)
500		{
501			asl_file_close(s->file_cache[i].f);
502			s->file_cache[i].f = NULL;
503			if (s->file_cache[i].path != NULL) free(s->file_cache[i].path);
504			s->file_cache[i].path = NULL;
505			s->file_cache[i].u = -1;
506			s->file_cache[i].g = -1;
507			s->file_cache[i].bb = 0;
508			s->file_cache[i].ts = 0;
509			return;
510		}
511	}
512}
513
514ASL_STATUS
515asl_store_save(asl_store_t *s, asl_msg_t *msg)
516{
517	struct tm ctm;
518	time_t msg_time, now, bb;
519	char *path, *tmp_path, *tstring, *scratch;
520	const char *val;
521	uid_t ruid;
522	gid_t rgid;
523	asl_file_t *f;
524	uint32_t status, check_cache, trigger_aslmanager, len;
525	uint64_t xid, ftime;
526	size_t fsize;
527
528	if (s == NULL) return ASL_STATUS_INVALID_STORE;
529	if (msg == NULL) return ASL_STATUS_INVALID_ARG;
530
531	now = time(NULL);
532
533	check_cache = 0;
534	if ((s->last_write + FILE_CACHE_TTL) <= now) check_cache = 1;
535
536	trigger_aslmanager = 0;
537
538	msg_time = 0;
539	val = NULL;
540
541	if (asl_msg_lookup(msg, ASL_KEY_TIME, &val, NULL) != 0) msg_time = now;
542	else if (val == NULL) msg_time = now;
543	else msg_time = asl_core_parse_time(val, NULL);
544
545	if (msg_time >= s->start_tomorrow)
546	{
547		if (now >= s->start_tomorrow)
548		{
549			/* new day begins */
550			check_cache = 0;
551			asl_store_file_closeall(s);
552
553			/*
554			 * _asl_start_today should never fail, but if it does,
555			 * just push forward one day.  That will probably be correct, and if
556			 * it isn't, the next message that gets saved will push it ahead again
557			 * until we get to the right date.
558			 */
559			s->start_today = _asl_start_today();
560			if (s->start_today == 0) s->start_today = s->start_tomorrow;
561
562			s->start_tomorrow = s->start_today + SECONDS_PER_DAY;
563		}
564	}
565
566	ruid = -1;
567	rgid = -1;
568	if ((s->flags & ASL_STORE_FLAG_NO_ACLS) == 0)
569	{
570		val = NULL;
571		if ((asl_msg_lookup(msg, ASL_KEY_READ_UID, &val, NULL) == 0) && (val != NULL)) ruid = atoi(val);
572
573		val = NULL;
574		if ((asl_msg_lookup(msg, ASL_KEY_READ_GID, &val, NULL) == 0) && (val != NULL)) rgid = atoi(val);
575	}
576
577	bb = 0;
578	if ((s->flags & ASL_STORE_FLAG_NO_TTL) == 0)
579	{
580		val = NULL;
581		if ((asl_msg_lookup(msg, ASL_KEY_EXPIRE_TIME, &val, NULL) == 0) && (val != NULL))
582		{
583			bb = 1;
584			msg_time = asl_core_parse_time(val, NULL);
585		}
586	}
587
588	if (fseeko(s->storedata, 0, SEEK_SET) != 0) return ASL_STATUS_WRITE_FAILED;
589
590	xid = asl_core_htonq(s->next_id);
591	if (fwrite(&xid, sizeof(uint64_t), 1, s->storedata) != 1) return ASL_STATUS_WRITE_FAILED;
592
593	/* flush data */
594	fflush(s->storedata);
595
596	xid = s->next_id;
597	s->next_id++;
598
599	s->last_write = now;
600
601	if (localtime_r((const time_t *)&msg_time, &ctm) == NULL) return ASL_STATUS_FAILED;
602
603	tstring = NULL;
604	if (bb == 1)
605	{
606		/*
607		 * This supports 12 monthly "Best Before" buckets.
608		 * We advance the actual expiry time to day zero of the following month.
609		 * mktime() is clever enough to know that you actually mean the last day
610		 * of the previous month.  What we get back from localtime is the last
611		 * day of the month in which the message expires, which we use in the name.
612		 */
613		ctm.tm_sec = 0;
614		ctm.tm_min = 0;
615		ctm.tm_hour = 0;
616		ctm.tm_mday = 0;
617		ctm.tm_mon += 1;
618
619		bb = mktime(&ctm);
620
621		if (localtime_r((const time_t *)&bb, &ctm) == NULL) return ASL_STATUS_FAILED;
622		asprintf(&tstring, "BB.%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
623	}
624	else
625	{
626		asprintf(&tstring, "%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
627	}
628
629	if (tstring == NULL) return ASL_STATUS_NO_MEMORY;
630
631	status = asl_store_file_open_write(s, tstring, ruid, rgid, bb, &f, now, check_cache);
632	free(tstring);
633	tstring = NULL;
634
635	if (status != ASL_STATUS_OK) return status;
636
637	status = asl_file_save(f, msg, &xid);
638	if (status != ASL_STATUS_OK) return status;
639
640	fsize = asl_file_size(f);
641	ftime = asl_file_ctime(f);
642
643	/* if file is larger than max_file_size, rename it and trigger aslmanager */
644	if ((s->max_file_size != 0) && (fsize > s->max_file_size))
645	{
646		trigger_aslmanager = 1;
647		status = ASL_STATUS_OK;
648
649		path = asl_store_file_path(s, f);
650
651		asl_store_file_close(s, f);
652
653		if (path != NULL)
654		{
655			tmp_path = NULL;
656
657			len = strlen(path);
658			if ((len >= 4) && (!strcmp(path + len - 4, ".asl")))
659			{
660				/* rename xxxxxxx.asl to xxxxxxx.timestamp.asl */
661				scratch = strdup(path);
662				if (scratch != NULL)
663				{
664					scratch[len - 4] = '\0';
665					asprintf(&tmp_path, "%s.%llu.asl", scratch, ftime);
666					free(scratch);
667
668				}
669			}
670			else
671			{
672				/* append timestamp */
673				asprintf(&tmp_path, "%s.%llu", path, ftime);
674			}
675
676			if (tmp_path == NULL)
677			{
678				status = ASL_STATUS_NO_MEMORY;
679			}
680			else
681			{
682				if (rename(path, tmp_path) != 0) status = ASL_STATUS_FAILED;
683				free(tmp_path);
684			}
685
686			free(path);
687		}
688	}
689
690	if (trigger_aslmanager != 0) asl_trigger_aslmanager();
691
692	return status;
693}
694
695static ASL_STATUS
696asl_store_mkdir(asl_store_t *s, const char *dir, mode_t m)
697{
698	char *tstring = NULL;
699	int status;
700	struct stat sb;
701
702	asprintf(&tstring, "%s/%s", s->base_dir, dir);
703	if (tstring == NULL) return ASL_STATUS_NO_MEMORY;
704
705	memset(&sb, 0, sizeof(struct stat));
706	status = stat(tstring, &sb);
707
708	if (status == 0)
709	{
710		/* must be a directory */
711		if (!S_ISDIR(sb.st_mode))
712		{
713			free(tstring);
714			return ASL_STATUS_INVALID_STORE;
715		}
716	}
717	else
718	{
719		if (errno == ENOENT)
720		{
721			/* doesn't exist - create it */
722			if (mkdir(tstring, m) != 0)
723			{
724				free(tstring);
725				return ASL_STATUS_WRITE_FAILED;
726			}
727		}
728		else
729		{
730			/* stat failed for some other reason */
731			free(tstring);
732			return ASL_STATUS_FAILED;
733		}
734	}
735
736	free(tstring);
737	return ASL_STATUS_OK;
738}
739
740ASL_STATUS
741asl_store_open_aux(asl_store_t *s, asl_msg_t *msg, int *out_fd, char **url)
742{
743	struct tm ctm;
744	time_t msg_time, bb;
745	char *path, *dir, *tstring;
746	const char *val;
747	uid_t ruid, u;
748	gid_t rgid, g;
749	mode_t m;
750	uint32_t status;
751	uint64_t fid;
752	int fd;
753
754	if (s == NULL) return ASL_STATUS_INVALID_STORE;
755	if (msg == NULL) return ASL_STATUS_INVALID_ARG;
756	if (out_fd == NULL) return ASL_STATUS_INVALID_ARG;
757	if (url == NULL) return ASL_STATUS_INVALID_ARG;
758
759	msg_time = time(NULL);
760
761	ruid = -1;
762	rgid = -1;
763	if ((s->flags & ASL_STORE_FLAG_NO_ACLS) == 0)
764	{
765		val = NULL;
766		if ((asl_msg_lookup(msg, ASL_KEY_READ_UID, &val, NULL) == 0) && (val != NULL)) ruid = atoi(val);
767
768		val = NULL;
769		if ((asl_msg_lookup(msg, ASL_KEY_READ_GID, &val, NULL) == 0) && (val != NULL)) rgid = atoi(val);
770	}
771
772	bb = 0;
773	if ((s->flags & ASL_STORE_FLAG_NO_TTL) == 0)
774	{
775		val = NULL;
776		if ((asl_msg_lookup(msg, ASL_KEY_EXPIRE_TIME, &val, NULL) == 0) && (val != NULL))
777		{
778			bb = 1;
779			msg_time = asl_core_parse_time(val, NULL);
780		}
781	}
782
783	if (localtime_r((const time_t *)&msg_time, &ctm) == NULL) return ASL_STATUS_FAILED;
784
785	dir = NULL;
786	if (bb == 1)
787	{
788		/*
789		 * This supports 12 monthly "Best Before" buckets.
790		 * We advance the actual expiry time to day zero of the following month.
791		 * mktime() is clever enough to know that you actually mean the last day
792		 * of the previous month.  What we get back from localtime is the last
793		 * day of the month in which the message expires, which we use in the name.
794		 */
795		ctm.tm_sec = 0;
796		ctm.tm_min = 0;
797		ctm.tm_hour = 0;
798		ctm.tm_mday = 0;
799		ctm.tm_mon += 1;
800
801		bb = mktime(&ctm);
802
803		if (localtime_r((const time_t *)&bb, &ctm) == NULL) return ASL_STATUS_FAILED;
804		asprintf(&dir, "BB.AUX.%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
805	}
806	else
807	{
808		asprintf(&dir, "AUX.%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
809	}
810
811	if (dir == NULL) return ASL_STATUS_NO_MEMORY;
812
813	status = asl_store_mkdir(s, dir, 0755);
814	if (status != ASL_STATUS_OK)
815	{
816		free(dir);
817		return status;
818	}
819
820	fid = s->next_id;
821	s->next_id++;
822	tstring = NULL;
823
824	asprintf(&tstring, "%s/%llu", dir, fid);
825	free(dir);
826	if (tstring == NULL) return ASL_STATUS_NO_MEMORY;
827
828	u = 0;
829	g = 0;
830	m = 0644;
831	path = asl_store_make_ug_path(s->base_dir, tstring, NULL, ruid, rgid, &u, &g, &m);
832	free(tstring);
833	if (path == NULL) return ASL_STATUS_NO_MEMORY;
834
835	fd = asl_file_create(path, u, g, m);
836	if (fd < 0)
837	{
838		free(path);
839		*out_fd = -1;
840		return ASL_STATUS_WRITE_FAILED;
841	}
842
843	/* URL is file://<path> */
844	*url = NULL;
845	asprintf(url, "file://%s", path);
846	free(path);
847
848	*out_fd = fd;
849
850	return status;
851}
852
853asl_msg_list_t *
854asl_store_match(asl_store_t *s, asl_msg_list_t *qlist, uint64_t *last_id, uint64_t start_id, uint32_t count, uint32_t duration, int32_t direction)
855{
856	DIR *dp;
857	struct dirent *dent;
858	uint32_t status;
859	asl_file_t *f;
860	char *path;
861	asl_file_list_t *files;
862	asl_msg_list_t *res;
863
864	if (s == NULL) return NULL;
865
866	files = NULL;
867
868	/*
869	 * Open all readable files
870	 */
871	dp = opendir(s->base_dir);
872	if (dp == NULL) return NULL;
873
874	while ((dent = readdir(dp)) != NULL)
875	{
876		if (dent->d_name[0] == '.') continue;
877
878		path = NULL;
879		asprintf(&path, "%s/%s", s->base_dir, dent->d_name);
880
881		/* 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 */
882		status = asl_file_open_read(path, &f);
883		if (path != NULL) free(path);
884		if ((status != ASL_STATUS_OK) || (f == NULL)) continue;
885
886		files = asl_file_list_add(files, f);
887	}
888
889	closedir(dp);
890
891	res = asl_file_list_match(files, qlist, last_id, start_id, count, duration, direction);
892	asl_file_list_close(files);
893	return res;
894}
895
896/*
897 * PRIVATE FOR DEV TOOLS SUPPORT
898 * DO NOT USE THIS INTERFACE OTHERWISE
899 *
900 * This is only called by a client that compiled with a 10.9 SDK, but is running
901 * with an new 10.10 libasl.
902 *
903 * Only searches the ASL database, so the store (first parameter) is ignored.
904 *
905 * The query and result are old-style message lists.
906 *
907 */
908typedef struct
909{
910	uint32_t count;
911	uint32_t curr;
912	void **msg;
913} asl_msg_list_v1_t;
914
915ASL_STATUS
916asl_store_match_timeout(void *ignored, void *query_v1, void **result_v1, uint64_t *last_id, uint64_t start_id, uint32_t count, int32_t direction, uint32_t usec)
917{
918	asl_store_t *asldb = NULL;
919	asl_msg_list_v1_t *listv1;
920	asl_msg_list_t *qlist = NULL;
921	uint32_t status, n;
922
923	if (result_v1 == NULL) return ASL_STATUS_FAILED;
924	*result_v1 = NULL;
925
926	status = asl_store_open_read(NULL, &asldb);
927	if (status != ASL_STATUS_OK) return status;
928
929	/* convert query_v1 into an asl_msg_list_t */
930	listv1 = (asl_msg_list_v1_t *)query_v1;
931	if (listv1 != NULL)
932	{
933		if (listv1->count > 0) qlist = (asl_msg_list_t *)asl_new(ASL_TYPE_LIST);
934
935		for (listv1->curr = 0; listv1->curr < listv1->count; listv1->curr++)
936		{
937			asl_append((asl_object_t)qlist, (asl_object_t)listv1->msg[listv1->curr]);
938		}
939	}
940
941	asl_msg_list_t *result = asl_store_match(asldb, qlist, last_id, start_id, count, usec, direction);
942	asl_release((asl_object_t)asldb);
943	asl_release((asl_object_t)qlist);
944
945	if (result == NULL) return ASL_STATUS_OK;
946
947	n = asl_count((asl_object_t)result);
948	if (n == 0)
949	{
950		asl_release((asl_object_t)result);
951		return ASL_STATUS_OK;
952	}
953
954	listv1 = (asl_msg_list_v1_t *)calloc(1, sizeof(asl_msg_list_v1_t));
955	if (listv1 == NULL)
956	{
957		asl_release((asl_object_t)result);
958		return ASL_STATUS_NO_MEMORY;
959	}
960
961	listv1->count = n;
962	listv1->msg = (void **)calloc(listv1->count, sizeof(void *));
963	if (listv1 == NULL)
964	{
965		free(listv1);
966		asl_release((asl_object_t)result);
967		return ASL_STATUS_NO_MEMORY;
968	}
969
970	for (listv1->curr = 0; listv1->curr < listv1->count; listv1->curr++)
971	{
972		listv1->msg[listv1->curr] = asl_retain(asl_next((asl_object_t)result));
973	}
974
975	listv1->curr = 0;
976	*result_v1 = listv1;
977
978	asl_release((asl_object_t)result);
979	return ASL_STATUS_OK;
980}
981
982ASL_STATUS
983asl_store_match_start(asl_store_t *s, uint64_t start_id, int32_t direction)
984{
985	DIR *dp;
986	struct dirent *dent;
987	uint32_t status;
988	asl_file_t *f;
989	char *path;
990	asl_file_list_t *files;
991
992	if (s == NULL) return ASL_STATUS_INVALID_STORE;
993
994	if (s->work != NULL) asl_file_list_match_end(s->work);
995	s->work = NULL;
996
997	files = NULL;
998
999	/*
1000	 * Open all readable files
1001	 */
1002	dp = opendir(s->base_dir);
1003	if (dp == NULL) return ASL_STATUS_READ_FAILED;
1004
1005	while ((dent = readdir(dp)) != NULL)
1006	{
1007		if (dent->d_name[0] == '.') continue;
1008
1009		path = NULL;
1010		asprintf(&path, "%s/%s", s->base_dir, dent->d_name);
1011
1012		/*
1013		 * NB asl_file_open_read will fail if path is NULL,
1014		 * if it is not an ASL store file, or if it isn't readable.
1015		 * We expect that.
1016		 */
1017		status = asl_file_open_read(path, &f);
1018		if (path != NULL) free(path);
1019		if ((status != ASL_STATUS_OK) || (f == NULL)) continue;
1020
1021		files = asl_file_list_add(files, f);
1022	}
1023
1024	closedir(dp);
1025
1026	s->work = asl_file_list_match_start(files, start_id, direction);
1027	if (s->work == NULL) return ASL_STATUS_FAILED;
1028
1029	return ASL_STATUS_OK;
1030}
1031
1032ASL_STATUS
1033asl_store_match_next(asl_store_t *s, asl_msg_list_t *qlist, asl_msg_list_t **res, uint32_t count)
1034{
1035	if (s == NULL) return ASL_STATUS_INVALID_STORE;
1036	if (s->work == NULL) return ASL_STATUS_OK;
1037
1038	return asl_file_list_match_next(s->work, qlist, res, count);
1039}
1040
1041#pragma mark -
1042#pragma mark asl_object support
1043
1044static void
1045_jump_dealloc(asl_object_private_t *obj)
1046{
1047	_asl_store_free_internal((asl_store_t *)obj);
1048}
1049
1050static asl_object_private_t *
1051_jump_next(asl_object_private_t *obj)
1052{
1053	asl_store_t *s = (asl_store_t *)obj;
1054	asl_msg_list_t *list;
1055	asl_msg_t *out = NULL;
1056	uint64_t last = 0;
1057
1058	if (s == NULL) return NULL;
1059	if (s->curr == SIZE_MAX) return NULL;
1060
1061	s->curr++;
1062	list = asl_store_match(s, NULL, &last, s->curr, 1, 0, 1);
1063	if (list == NULL)
1064	{
1065		s->curr = SIZE_MAX;
1066		return NULL;
1067	}
1068
1069	s->curr = last;
1070	out = asl_msg_list_get_index(list, 0);
1071	asl_msg_list_release(list);
1072
1073	return (asl_object_private_t *)out;
1074}
1075
1076static asl_object_private_t *
1077_jump_prev(asl_object_private_t *obj)
1078{
1079	asl_store_t *s = (asl_store_t *)obj;
1080	asl_msg_list_t *list;
1081	asl_msg_t *out = NULL;
1082	uint64_t last = 0;
1083
1084	if (s == NULL) return NULL;
1085	if (s->curr == 0) return NULL;
1086
1087	s->curr--;
1088	if (s->curr == 0) return NULL;
1089
1090	list = asl_store_match(s, NULL, &last, s->curr, 1, 0, -1);
1091	if (list == NULL)
1092	{
1093		s->curr = 0;
1094		return NULL;
1095	}
1096
1097	s->curr = last;
1098	out = asl_msg_list_get_index(list, 0);
1099	asl_msg_list_release(list);
1100
1101	return (asl_object_private_t *)out;
1102}
1103
1104static void
1105_jump_set_iteration_index(asl_object_private_t *obj, size_t n)
1106{
1107	asl_store_t *s = (asl_store_t *)obj;
1108	if (s == NULL) return;
1109
1110	s->curr = n;
1111}
1112
1113static void
1114_jump_append(asl_object_private_t *obj, asl_object_private_t *newobj)
1115{
1116	asl_store_t *s = (asl_store_t *)obj;
1117	int type = asl_get_type((asl_object_t)newobj);
1118	if (s == NULL) return;
1119	if (s->flags & ASL_FILE_FLAG_READ) return;
1120
1121	if (type == ASL_TYPE_LIST)
1122	{
1123		asl_msg_t *msg;
1124		asl_msg_list_reset_iteration((asl_msg_list_t *)newobj, 0);
1125		while (NULL != (msg = asl_msg_list_next((asl_msg_list_t *)newobj)))
1126		{
1127			if (asl_store_save(s, msg) != ASL_STATUS_OK) return;
1128		}
1129	}
1130	else if ((type == ASL_TYPE_MSG) || (type == ASL_TYPE_QUERY))
1131	{
1132		asl_store_save(s, (asl_msg_t *)newobj);
1133	}
1134}
1135
1136static asl_object_private_t *
1137_jump_search(asl_object_private_t *obj, asl_object_private_t *query)
1138{
1139	asl_store_t *s = (asl_store_t *)obj;
1140	int type = asl_get_type((asl_object_t)query);
1141	asl_msg_list_t *out = NULL;
1142	asl_msg_list_t *ql = NULL;
1143	uint64_t last;
1144	uint32_t status = ASL_STATUS_FAILED;
1145
1146	if (query == NULL)
1147	{
1148		out = asl_store_match(s, NULL, &last, 0, 0, 0, 1);
1149	}
1150	else if (type == ASL_TYPE_LIST)
1151	{
1152		out = asl_store_match(s, (asl_msg_list_t *)query, &last, 0, 0, 0, 1);
1153	}
1154	else if ((type == ASL_TYPE_MSG) || (type == ASL_TYPE_QUERY))
1155	{
1156		ql = asl_msg_list_new();
1157		asl_msg_list_append(ql, query);
1158
1159		out = asl_store_match(s, ql, &last, 0, 0, 0, 1);
1160		asl_msg_list_release(ql);
1161	}
1162
1163	if (status != ASL_STATUS_OK) return NULL;
1164	return (asl_object_private_t *)out;
1165}
1166
1167static asl_object_private_t *
1168_jump_match(asl_object_private_t *obj, asl_object_private_t *qlist, size_t *last, size_t start, size_t count, uint32_t duration, int32_t dir)
1169{
1170	uint64_t x;
1171	asl_msg_list_t *out;
1172
1173	out = asl_store_match((asl_store_t *)obj, (asl_msg_list_t *)qlist, &x, start, count, duration, dir);
1174	*last = x;
1175	return (asl_object_private_t *)out;
1176}
1177
1178__private_extern__ const asl_jump_table_t *
1179asl_store_jump_table()
1180{
1181	static const asl_jump_table_t jump =
1182	{
1183		.alloc = NULL,
1184		.dealloc = &_jump_dealloc,
1185		.set_key_val_op = NULL,
1186		.unset_key = NULL,
1187		.get_val_op_for_key = NULL,
1188		.get_key_val_op_at_index = NULL,
1189		.count = NULL,
1190		.next = &_jump_next,
1191		.prev = &_jump_prev,
1192		.get_object_at_index = NULL,
1193		.set_iteration_index = &_jump_set_iteration_index,
1194		.remove_object_at_index = NULL,
1195		.append = &_jump_append,
1196		.prepend = NULL,
1197		.search = &_jump_search,
1198		.match = &_jump_match
1199	};
1200
1201	return &jump;
1202}
1203