2 * Copyright (c) 2004-2011 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #include <TargetConditionals.h>
26 #if TARGET_IPHONE_SIMULATOR
30 #include <sys/types.h>
32 #include <sys/socket.h>
47 #define MY_ID "bsd_out"
49 #define _PATH_WALL "/usr/bin/wall"
50 #define ASL_KEY_FACILITY "Facility"
51 #define FACILITY_KERNEL "kern"
52 #define _PATH_CONSOLE "/dev/console"
54 #define DST_TYPE_NONE 0
55 #define DST_TYPE_FILE 1
56 #define DST_TYPE_CONS 2
57 #define DST_TYPE_SOCK 3
58 #define DST_TYPE_WALL 4
59 #define DST_TYPE_NOTE 5
61 #define CLOSE_ON_IDLE_SEC 300
63 static dispatch_queue_t bsd_out_queue
;
64 static dispatch_source_t bsd_idle_timer
;
72 struct sockaddr
*addr
;
74 uint32_t *fac_prefix_len
;
79 dispatch_source_t dup_timer
;
81 TAILQ_ENTRY(config_rule
) entries
;
84 static TAILQ_HEAD(cr
, config_rule
) bsd_out_rule
;
86 extern uint32_t asl_core_string_hash(const char *s
, uint32_t inlen
);
89 _level_for_name(const char *name
)
91 if (name
== NULL
) return -1;
93 if (!strcasecmp(name
, "emerg")) return ASL_LEVEL_EMERG
;
94 if (!strcasecmp(name
, "panic")) return ASL_LEVEL_EMERG
;
95 if (!strcasecmp(name
, "alert")) return ASL_LEVEL_ALERT
;
96 if (!strcasecmp(name
, "crit")) return ASL_LEVEL_CRIT
;
97 if (!strcasecmp(name
, "err")) return ASL_LEVEL_ERR
;
98 if (!strcasecmp(name
, "error")) return ASL_LEVEL_ERR
;
99 if (!strcasecmp(name
, "warn")) return ASL_LEVEL_WARNING
;
100 if (!strcasecmp(name
, "warning")) return ASL_LEVEL_WARNING
;
101 if (!strcasecmp(name
, "notice")) return ASL_LEVEL_NOTICE
;
102 if (!strcasecmp(name
, "info")) return ASL_LEVEL_INFO
;
103 if (!strcasecmp(name
, "debug")) return ASL_LEVEL_DEBUG
;
104 if (!strcmp(name
, "*")) return ASL_LEVEL_DEBUG
;
107 if (!strcasecmp(name
, "none")) return -2;
113 _syslog_dst_open(struct config_rule
*r
)
117 struct addrinfo hints
, *gai
, *ai
;
119 if (r
== NULL
) return -1;
120 if (r
->fd
!= -1) return 0;
122 if (r
->dst
[0] == '/')
124 r
->fd
= open(r
->dst
, O_WRONLY
| O_APPEND
| O_CREAT
| O_NOCTTY
, 0644);
127 asldebug("%s: open failed for file: %s (%s)\n", MY_ID
, r
->dst
, strerror(errno
));
131 r
->type
= DST_TYPE_FILE
;
132 if (!strcmp(r
->dst
, _PATH_CONSOLE
)) r
->type
= DST_TYPE_CONS
;
137 if (r
->dst
[0] == '!')
139 r
->type
= DST_TYPE_NOTE
;
144 if (r
->dst
[0] == '@')
146 node
= strdup(r
->dst
+ 1);
147 if (node
== NULL
) return -1;
150 serv
= strrchr(node
, ':');
151 if (serv
!= NULL
) *serv
++ = '\0';
152 else serv
= "syslog";
154 memset(&hints
, 0, sizeof(hints
));
155 hints
.ai_family
= PF_UNSPEC
;
156 hints
.ai_socktype
= SOCK_DGRAM
;
157 i
= getaddrinfo(node
, serv
, &hints
, &gai
);
161 asldebug("%s: getaddrinfo failed for node %s service %s: (%s)\n", MY_ID
, node
, serv
, gai_strerror(i
));
165 for (ai
= gai
; ai
!= NULL
; ai
= ai
->ai_next
)
167 r
->fd
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
168 if (r
->fd
< 0) continue;
170 r
->addr
= (struct sockaddr
*)malloc(ai
->ai_addrlen
);
171 if (r
->addr
== NULL
) return -1;
173 memcpy(r
->addr
, ai
->ai_addr
, ai
->ai_addrlen
);
182 asldebug("%s: connection failed for %s\n", MY_ID
, (r
->dst
) + 1);
188 if (fcntl(r
->fd
, F_SETFL
, O_NONBLOCK
) < 0)
192 asldebug("%s: couldn't set O_NONBLOCK for fd %d: %s\n", MY_ID
, r
->fd
, strerror(errno
));
198 r
->type
= DST_TYPE_SOCK
;
203 if (strcmp(r
->dst
, "*") == 0)
205 r
->type
= DST_TYPE_WALL
;
210 /* Can't deal with dst! */
211 asldebug("%s: unsupported / unknown output name: %s\n", MY_ID
, r
->dst
);
216 _syslog_dst_close(struct config_rule
*r
)
218 if (r
== NULL
) return;
231 if (r
->fd
>= 0) close(r
->fd
);
238 if (r
->fd
>= 0) close(r
->fd
);
255 _clean_facility_name(char *s
)
260 if (s
== NULL
) return NULL
;
262 if (len
== 0) return NULL
;
266 if ((*s
== '\'') || (*s
== '"'))
270 if (p
[len
- 1] == *s
) len
--;
273 out
= calloc(1, len
+ 1);
274 if (out
== NULL
) return NULL
;
283 char **semi
, **comma
, *star
;
284 int i
, j
, n
, lasts
, lastc
, pri
;
285 struct config_rule
*out
;
287 if (s
== NULL
) return -1;
288 while ((*s
== ' ') || (*s
== '\t')) s
++;
289 if (*s
== '#') return -1;
291 semi
= explode(s
, "; \t");
293 if (semi
== NULL
) return -1;
294 out
= (struct config_rule
*)calloc(1, sizeof(struct config_rule
));
295 if (out
== NULL
) return -1;
300 for (i
= 0; semi
[i
] != NULL
; i
++)
302 if (semi
[i
][0] == '\0') continue;
307 out
->dst
= strdup(semi
[lasts
]);
308 if (out
->dst
== NULL
) return -1;
310 for (i
= 0; i
< lasts
; i
++)
312 if (semi
[i
][0] == '\0') continue;
313 comma
= explode(semi
[i
], ",.");
315 for (j
= 0; comma
[j
] != NULL
; j
++)
317 if (comma
[j
][0] == '\0') continue;
321 for (j
= 0; j
< lastc
; j
++)
323 if (comma
[j
][0] == '\0') continue;
324 pri
= _level_for_name(comma
[lastc
]);
325 if (pri
== -1) continue;
329 out
->facility
= (char **)calloc(1, sizeof(char *));
330 out
->fac_prefix_len
= (uint32_t *)calloc(1, sizeof(uint32_t));
331 out
->pri
= (int *)calloc(1, sizeof(int));
335 out
->facility
= (char **)reallocf(out
->facility
, (out
->count
+ 1) * sizeof(char *));
336 out
->fac_prefix_len
= (uint32_t *)reallocf(out
->fac_prefix_len
, (out
->count
+ 1) * sizeof(uint32_t));
337 out
->pri
= (int *)reallocf(out
->pri
, (out
->count
+ 1) * sizeof(int));
340 if (out
->facility
== NULL
) return -1;
341 if (out
->fac_prefix_len
== NULL
) return -1;
342 if (out
->pri
== NULL
) return -1;
344 out
->facility
[out
->count
] = _clean_facility_name(comma
[j
]);
345 if (out
->facility
[out
->count
] == NULL
) return -1;
347 out
->fac_prefix_len
[out
->count
] = 0;
348 star
= strchr(out
->facility
[out
->count
], '*');
349 if (star
!= NULL
) out
->fac_prefix_len
[out
->count
] = (uint32_t)(star
- out
->facility
[out
->count
]);
351 out
->pri
[out
->count
] = pri
;
355 free_string_list(comma
);
358 free_string_list(semi
);
360 TAILQ_INSERT_TAIL(&bsd_out_rule
, out
, entries
);
366 _bsd_send_repeat_msg(struct config_rule
*r
)
372 if (r
== NULL
) return -1;
373 if (r
->type
!= DST_TYPE_FILE
) return 0;
374 if (r
->last_count
== 0) return 0;
377 dispatch_suspend(r
->dup_timer
);
380 memset(vt
, 0, sizeof(vt
));
385 asprintf(&msg
, "%s: --- last message repeated %u time%s ---\n", vt
+ 4, r
->last_count
, (r
->last_count
== 1) ? "" : "s");
387 if (msg
== NULL
) return -1;
390 status
= write(r
->fd
, msg
, len
);
391 if ((status
< 0) || (status
< len
))
393 asldebug("%s: error writing repeat message (%s): %s\n", MY_ID
, r
->dst
, strerror(errno
));
395 /* Try re-opening the file (once) and write again */
397 r
->fd
= open(r
->dst
, O_WRONLY
| O_APPEND
| O_CREAT
| O_NOCTTY
, 0644);
400 asldebug("%s: re-open failed for file: %s (%s)\n", MY_ID
, r
->dst
, strerror(errno
));
405 status
= write(r
->fd
, msg
, len
);
406 if ((status
< 0) || (status
< len
))
408 asldebug("%s: error re-writing message (%s): %s\n", MY_ID
, r
->dst
, strerror(errno
));
419 _bsd_send(asl_msg_t
*msg
, struct config_rule
*r
, char **out
, char **fwd
, time_t now
)
422 const char *vlevel
, *vfacility
;
424 int pf
, fc
, status
, is_dup
, do_write
;
425 uint32_t msg_hash
, n
;
427 if (out
== NULL
) return -1;
428 if (fwd
== NULL
) return -1;
429 if (r
== NULL
) return -1;
433 if (r
->type
== DST_TYPE_NOTE
)
435 notify_post(r
->dst
+1);
442 /* Build output string if it hasn't been built by a previous rule-match */
445 *out
= asl_format_message((asl_msg_t
*)msg
, ASL_MSG_FMT_BSD
, ASL_TIME_FMT_LCL
, ASL_ENCODE_SAFE
, &n
);
446 if (*out
== NULL
) return -1;
449 /* check if message is a duplicate of the last message, and inside the dup time window */
451 if ((global
.bsd_max_dup_time
> 0) && (*out
!= NULL
) && (r
->last_msg
!= NULL
))
453 msg_hash
= asl_core_string_hash(*out
+ 16, strlen(*out
+ 16));
454 if ((r
->last_hash
== msg_hash
) && (!strcmp(r
->last_msg
, *out
+ 16)))
456 if ((now
- r
->last_time
) < global
.bsd_max_dup_time
) is_dup
= 1;
460 if ((*fwd
== NULL
) && (r
->type
== DST_TYPE_SOCK
))
463 vlevel
= asl_msg_get_val_for_key(msg
, ASL_KEY_LEVEL
);
464 if (vlevel
!= NULL
) pf
= atoi(vlevel
);
466 fc
= asl_syslog_faciliy_name_to_num(asl_msg_get_val_for_key(msg
, ASL_KEY_FACILITY
));
467 if (fc
> 0) pf
|= fc
;
470 asprintf(&sf
, "<%d>%s", pf
, *out
);
471 if (sf
== NULL
) return -1;
476 if (r
->type
== DST_TYPE_SOCK
) outlen
= strlen(*fwd
);
477 else outlen
= strlen(*out
);
479 if ((r
->type
== DST_TYPE_FILE
) || (r
->type
== DST_TYPE_CONS
))
482 * If current message is NOT a duplicate and r->last_count > 0
483 * we need to write a "last message was repeated N times" log entry
485 if ((r
->type
== DST_TYPE_FILE
) && (is_dup
== 0) && (r
->last_count
> 0)) _bsd_send_repeat_msg(r
);
490 * Special case for kernel messages.
491 * Don't write kernel messages to /dev/console.
492 * The kernel printf routine already sends them to /dev/console
493 * so writing them here would cause duplicates.
495 vfacility
= asl_msg_get_val_for_key(msg
, ASL_KEY_FACILITY
);
496 if ((vfacility
!= NULL
) && (!strcmp(vfacility
, FACILITY_KERNEL
)) && (r
->type
== DST_TYPE_CONS
)) do_write
= 0;
497 if ((do_write
== 1) && (r
->type
== DST_TYPE_FILE
) && (is_dup
== 1))
501 if (r
->dup_timer
== NULL
)
503 /* create a timer to flush dups on this file */
504 r
->dup_timer
= dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER
, 0, 0, bsd_out_queue
);
505 dispatch_source_set_event_handler(r
->dup_timer
, ^{ _bsd_send_repeat_msg(r
); });
508 if (r
->last_count
== 0)
510 /* start the timer */
511 dispatch_source_set_timer(r
->dup_timer
, dispatch_time(DISPATCH_TIME_NOW
, NSEC_PER_SEC
* global
.bsd_max_dup_time
), DISPATCH_TIME_FOREVER
, 0);
512 dispatch_resume(r
->dup_timer
);
516 if (do_write
== 0) status
= outlen
;
517 else status
= write(r
->fd
, *out
, outlen
);
519 if ((status
< 0) || (status
< outlen
))
521 asldebug("%s: error writing message (%s): %s\n", MY_ID
, r
->dst
, strerror(errno
));
523 /* Try re-opening the file (once) and write again */
525 r
->fd
= open(r
->dst
, O_WRONLY
| O_APPEND
| O_CREAT
| O_NOCTTY
, 0644);
528 asldebug("%s: re-open failed for file: %s (%s)\n", MY_ID
, r
->dst
, strerror(errno
));
532 status
= write(r
->fd
, *out
, outlen
);
533 if ((status
< 0) || (status
< outlen
))
535 asldebug("%s: error re-writing message (%s): %s\n", MY_ID
, r
->dst
, strerror(errno
));
539 else if ((r
->type
== DST_TYPE_SOCK
) && (r
->addr
!= NULL
))
541 status
= sendto(r
->fd
, *fwd
, outlen
, 0, r
->addr
, r
->addr
->sa_len
);
542 if (status
< 0) asldebug("%s: error sending message (%s): %s\n", MY_ID
, r
->dst
, strerror(errno
));
544 else if (r
->type
== DST_TYPE_WALL
)
546 #if !TARGET_OS_EMBEDDED
547 FILE *pw
= popen(_PATH_WALL
, "w");
550 asldebug("%s: error sending wall message: %s\n", MY_ID
, strerror(errno
));
554 fprintf(pw
, "%s", *out
);
568 if (*out
!= NULL
) r
->last_msg
= strdup(*out
+ 16);
570 r
->last_hash
= msg_hash
;
579 _bsd_rule_match(asl_msg_t
*msg
, struct config_rule
*r
)
585 if (msg
== NULL
) return 0;
586 if (r
== NULL
) return 0;
587 if (r
->count
== 0) return 0;
591 for (i
= 0; i
< r
->count
; i
++)
593 if (r
->pri
[i
] == -1) continue;
595 if ((test
== 1) && (r
->pri
[i
] >= 0)) continue;
596 if ((test
== 0) && (r
->pri
[i
] == -2)) continue;
599 val
= asl_msg_get_val_for_key(msg
, ASL_KEY_FACILITY
);
601 if (strcmp(r
->facility
[i
], "*") == 0)
605 else if ((r
->fac_prefix_len
[i
] > 0) && (strncasecmp(r
->facility
[i
], val
, r
->fac_prefix_len
[i
]) == 0))
609 else if ((val
!= NULL
) && (strcasecmp(r
->facility
[i
], val
) == 0))
614 if (f
== 0) continue;
616 /* Turn off matching facility with priority "none" */
623 val
= asl_msg_get_val_for_key(msg
, ASL_KEY_LEVEL
);
624 if (val
== NULL
) continue;
627 if (pri
< 0) continue;
629 if (pri
<= r
->pri
[i
]) test
= 1;
636 _bsd_match_and_send(asl_msg_t
*msg
)
638 struct config_rule
*r
;
642 if (msg
== NULL
) return -1;
649 for (r
= bsd_out_rule
.tqh_first
; r
!= NULL
; r
= r
->entries
.tqe_next
)
651 if (_bsd_rule_match(msg
, r
) == 1) _bsd_send(msg
, r
, &out
, &fwd
, now
);
661 bsd_out_message(asl_msg_t
*msg
, int64_t msize
)
663 if (msg
== NULL
) return;
665 OSAtomicIncrement32(&global
.bsd_queue_count
);
666 asl_msg_retain((asl_msg_t
*)msg
);
668 dispatch_async(bsd_out_queue
, ^{
669 _bsd_match_and_send(msg
);
670 asl_msg_release((asl_msg_t
*)msg
);
672 /* end of the output module chain (after asl) - decrement global memory stats */
673 OSAtomicAdd64(-1ll * msize
, &global
.memory_size
);
675 OSAtomicDecrement32(&global
.bsd_queue_count
);
680 _bsd_close_idle_files()
683 struct config_rule
*r
;
688 for (r
= bsd_out_rule
.tqh_first
; r
!= NULL
; r
= r
->entries
.tqe_next
)
690 /* only applies to files */
691 if (r
->type
!= DST_TYPE_FILE
) continue;
694 * If the last message repeat count is non-zero, a _bsd_flush_duplicates()
695 * call will occur within 30 seconds. Don't bother closing the file.
697 if (r
->last_count
> 0) continue;
699 delta
= now
- r
->last_time
;
700 if (delta
> CLOSE_ON_IDLE_SEC
) _syslog_dst_close(r
);
705 _parse_config_file(const char *confname
)
710 cf
= fopen(confname
, "r");
711 if (cf
== NULL
) return 1;
713 while (NULL
!= (line
= get_line_from_file(cf
)))
727 static dispatch_once_t once
;
729 asldebug("%s: init\n", MY_ID
);
731 TAILQ_INIT(&bsd_out_rule
);
732 _parse_config_file(_PATH_SYSLOG_CONF
);
734 dispatch_once(&once
, ^{
735 bsd_out_queue
= dispatch_queue_create("BSD Out Queue", NULL
);
737 /* start a timer to close idle files */
738 bsd_idle_timer
= dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER
, 0, 0, bsd_out_queue
);
739 dispatch_source_set_event_handler(bsd_idle_timer
, ^{ _bsd_close_idle_files(); });
740 dispatch_source_set_timer(bsd_idle_timer
, dispatch_time(DISPATCH_TIME_NOW
, NSEC_PER_SEC
* CLOSE_ON_IDLE_SEC
), NSEC_PER_SEC
* CLOSE_ON_IDLE_SEC
, 0);
741 dispatch_resume(bsd_idle_timer
);
748 _bsd_out_close_internal(void)
750 struct config_rule
*r
, *n
;
754 for (r
= bsd_out_rule
.tqh_first
; r
!= NULL
; r
= n
)
756 n
= r
->entries
.tqe_next
;
758 if (r
->dup_timer
!= NULL
)
760 if (r
->last_count
> 0) _bsd_send_repeat_msg(r
);
761 dispatch_source_cancel(r
->dup_timer
);
762 dispatch_resume(r
->dup_timer
);
763 dispatch_release(r
->dup_timer
);
769 free(r
->fac_prefix_len
);
772 if (r
->fd
>= 0) close(r
->fd
);
774 if (r
->facility
!= NULL
)
776 for (i
= 0; i
< r
->count
; i
++)
777 free(r
->facility
[i
]);
783 TAILQ_REMOVE(&bsd_out_rule
, r
, entries
);
793 dispatch_async(bsd_out_queue
, ^{
794 _bsd_out_close_internal();
803 dispatch_async(bsd_out_queue
, ^{
804 _bsd_out_close_internal();
811 #endif /* !TARGET_IPHONE_SIMULATOR */