]> git.saurik.com Git - apple/xnu.git/blobdiff - libkern/os/log_encode.h
xnu-3789.1.32.tar.gz
[apple/xnu.git] / libkern / os / log_encode.h
diff --git a/libkern/os/log_encode.h b/libkern/os/log_encode.h
new file mode 100644 (file)
index 0000000..88839fb
--- /dev/null
@@ -0,0 +1,500 @@
+/*
+ * Copyright (c) 2015-2016 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#ifndef log_encode_h
+#define log_encode_h
+
+#include "log_encode_types.h"
+#include <sys/param.h>
+
+#if KERNEL
+#define isdigit(ch) (((ch) >= '0') && ((ch) <= '9'))
+#endif
+
+static bool
+_encode_data(os_log_buffer_value_t content, const void *arg, uint16_t arg_len, os_log_buffer_context_t context)
+{
+    struct os_log_arginfo_s arginfo;
+    void *databuf;
+    
+    if (content->flags & OS_LOG_CONTENT_FLAG_PRIVATE) {
+        databuf = context->privdata + context->privdata_off;
+        arginfo.length = MIN(arg_len, (context->privdata_sz - context->privdata_off));
+        arginfo.offset = context->privdata_off;
+    } else {
+        databuf = context->pubdata + context->pubdata_off;
+        arginfo.length = MIN(arg_len, (context->pubdata_sz - context->pubdata_off));
+        arginfo.offset = context->pubdata_off;
+    }
+    
+    if (context->arg_content_sz > 0) {
+        arginfo.length = MIN(context->arg_content_sz, arginfo.length);
+    }
+    
+    memcpy(content->value, &arginfo, sizeof(arginfo));
+    content->size = sizeof(arginfo);
+    
+    if (arginfo.length) {
+        if (content->type == OS_LOG_BUFFER_VALUE_TYPE_STRING
+#ifndef KERNEL
+            || content->type == OS_LOG_BUFFER_VALUE_TYPE_OBJECT
+#endif
+            ) {
+            strlcpy(databuf, arg, arginfo.length);
+        } else {
+            memcpy(databuf, arg, arginfo.length);
+        }
+    }
+    
+    if (content->flags & OS_LOG_CONTENT_FLAG_PRIVATE) {
+        context->privdata_off += arginfo.length;
+    } else {
+        context->pubdata_off += arginfo.length;
+    }
+    
+    context->content_off += sizeof(*content) + content->size;
+    context->arg_content_sz = 0;
+    
+    return true;
+}
+
+#ifndef KERNEL
+static void
+_os_log_parse_annotated(char *annotated, const char **visibility, const char **library, const char **type)
+{
+    char *values[3] = { NULL };
+    int cnt = 0;
+    int idx = 0;
+    
+    for (; cnt < 3;) {
+        char *token = strsep(&annotated, ", {}");
+        if (token == NULL) {
+            break;
+        }
+        
+        if (*token == '\0') {
+            continue;
+        }
+        
+        values[cnt++] = token;
+    }
+    
+    if ((cnt > 0) && (!strcmp(values[0], "public") || !strcmp(values[0], "private"))) {
+        if (visibility != NULL) {
+            (*visibility) = values[0];
+        }
+        
+        idx++;
+    }
+    
+    if (idx < cnt && (library != NULL) && (type != NULL)) {
+        char *decoder = values[idx];
+        
+        for (cnt = 0; cnt < 3; ) {
+            char *token = strsep(&decoder, ": {}");
+            if (token == NULL) {
+                break;
+            }
+            
+            if (*token == '\0') {
+                continue;
+            }
+            
+            values[cnt++] = token;
+        }
+        
+        if (cnt == 2) {
+            (*library) = values[0];
+            (*type) = values[1];
+        }
+        
+        if (cnt == 1) {
+            (*library) = "builtin";
+            (*type) = values[0];
+        }
+    }
+}
+#endif /* !KERNEL */
+
+OS_ALWAYS_INLINE
+static inline bool
+_os_log_encode_arg(const void *arg, uint16_t arg_len, os_log_value_type_t ctype, bool is_private, os_log_buffer_context_t context)
+{
+    os_log_buffer_value_t content = (os_log_buffer_value_t) &context->buffer->content[context->content_off];
+    size_t content_sz = sizeof(*content) + arg_len;
+    char tempString[OS_LOG_BUFFER_MAX_SIZE] = {};
+#ifndef KERNEL
+    bool obj_private = true;
+#endif
+    
+    content->type = ctype;
+    content->flags = (is_private ? OS_LOG_CONTENT_FLAG_PRIVATE : 0);
+    
+#ifndef KERNEL
+    if (context->annotated != NULL) {
+        const char *visibility = NULL;
+        
+        _os_log_parse_annotated(context->annotated, &visibility, NULL, NULL);
+        if (visibility) {
+            if (!strcasecmp(visibility, "private")) {
+                content->flags |= OS_LOG_CONTENT_FLAG_PRIVATE;
+            } else if (!strcasecmp(visibility, "public")) {
+                content->flags &= ~OS_LOG_CONTENT_FLAG_PRIVATE;
+            }
+        }
+        
+        context->annotated = NULL;
+    }
+#endif /* !KERNEL */
+    
+    switch (ctype) {
+        case OS_LOG_BUFFER_VALUE_TYPE_COUNT:
+        case OS_LOG_BUFFER_VALUE_TYPE_SCALAR:
+            if (is_private) {
+                _encode_data(content, tempString, strlen(tempString) + 1, context);
+            } else {
+                if ((context->content_off + content_sz) > context->content_sz) {
+                    return false;
+                }
+                
+                memcpy(content->value, arg, arg_len);
+                content->size = arg_len;
+                context->content_off += content_sz;
+            }
+            break;
+            
+        case OS_LOG_BUFFER_VALUE_TYPE_STRING:
+            context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
+            if (_os_log_string_is_public(arg)) {
+                content->flags &= ~OS_LOG_CONTENT_FLAG_PRIVATE;
+            }
+            
+            _encode_data(content, arg, arg_len, context);
+            break;
+            
+#ifndef KERNEL
+        case OS_LOG_BUFFER_VALUE_TYPE_POINTER:
+            context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
+            _encode_data(content, arg, arg_len, context);
+            break;
+            
+        case OS_LOG_BUFFER_VALUE_TYPE_OBJECT:
+            context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
+            if (!_NSCF2data(arg, tempString, sizeof(tempString), &obj_private)) {
+                tempString[0] = '\0';
+            }
+            
+            if (!obj_private) {
+                content->flags &= ~OS_LOG_CONTENT_FLAG_PRIVATE;
+            }
+            
+            _encode_data(content, tempString, strlen(tempString) + 1, context);
+            break;
+#endif /* !KERNEL */
+    }
+    
+    if (content->flags & OS_LOG_CONTENT_FLAG_PRIVATE) {
+        context->buffer->flags |= OS_LOG_BUFFER_HAS_PRIVATE;
+    }
+    
+    context->arg_idx++;
+    
+    return true;
+}
+
+static bool
+_os_log_encode(const char *format, va_list args, int saved_errno, os_log_buffer_context_t context)
+{
+    const char *percent = strchr(format, '%');
+#ifndef KERNEL
+    char annotated[256];
+#endif
+    
+    while (percent != NULL) {
+        ++percent;
+        if (percent[0] != '%') {
+            struct os_log_format_value_s value;
+            int     type = OST_INT;
+#ifndef KERNEL
+            bool    long_double = false;
+#endif
+            int     prec = 0;
+            char    ch;
+            
+            for (bool done = false; !done; percent++) {
+                switch (ch = percent[0]) {
+                        /* type of types or other */
+                    case 'l': // longer
+                        type++;
+                        break;
+                        
+                    case 'h': // shorter
+                        type--;
+                        break;
+                        
+                    case 'z':
+                        type = OST_SIZE;
+                        break;
+                        
+                    case 'j':
+                        type = OST_INTMAX;
+                        break;
+                        
+                    case 't':
+                        type = OST_PTRDIFF;
+                        break;
+                        
+                    case '.': // precision
+                        if ((percent[1]) == '*') {
+                            prec = va_arg(args, int);
+                            _os_log_encode_arg(&prec, sizeof(prec), OS_LOG_BUFFER_VALUE_TYPE_COUNT, false, context);
+                            percent++;
+                            continue;
+                        } else {
+                            // we have to read the precision and do the right thing
+                            const char *fmt = percent + 1;
+                            prec = 0;
+                            while (isdigit(ch = *fmt++)) {
+                                prec = 10 * prec + (ch - '0');
+                            }
+                            
+                            if (prec > 1024) {
+                                prec = 1024;
+                            }
+                            
+                            _os_log_encode_arg(&prec, sizeof(prec), OS_LOG_BUFFER_VALUE_TYPE_COUNT, false, context);
+                        }
+                        break;
+                        
+                    case '-': // left-align
+                    case '+': // force sign
+                    case ' ': // prefix non-negative with space
+                    case '#': // alternate
+                    case '\'': // group by thousands
+                        break;
+                        
+                        /* fixed types */
+                    case 'd': // integer
+                    case 'i': // integer
+                    case 'o': // octal
+                    case 'u': // unsigned
+                    case 'x': // hex
+                    case 'X': // upper-hex
+                        switch (type) {
+                            case OST_CHAR:
+                                value.type.ch = va_arg(args, int);
+                                _os_log_encode_arg(&value.type.ch, sizeof(value.type.ch), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                                break;
+                                
+                            case OST_SHORT:
+                                value.type.s = va_arg(args, int);
+                                _os_log_encode_arg(&value.type.s, sizeof(value.type.s), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                                break;
+                                
+                            case OST_INT:
+                                value.type.i = va_arg(args, int);
+                                _os_log_encode_arg(&value.type.i, sizeof(value.type.i), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                                break;
+                                
+                            case OST_LONG:
+                                value.type.l = va_arg(args, long);
+                                _os_log_encode_arg(&value.type.l, sizeof(value.type.l), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                                break;
+                                
+                            case OST_LONGLONG:
+                                value.type.ll = va_arg(args, long long);
+                                _os_log_encode_arg(&value.type.ll, sizeof(value.type.ll), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                                break;
+                                
+                            case OST_SIZE:
+                                value.type.z = va_arg(args, size_t);
+                                _os_log_encode_arg(&value.type.z, sizeof(value.type.z), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                                break;
+                                
+                            case OST_INTMAX:
+                                value.type.im = va_arg(args, intmax_t);
+                                _os_log_encode_arg(&value.type.im, sizeof(value.type.im), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                                break;
+                                
+                            case OST_PTRDIFF:
+                                value.type.pd = va_arg(args, ptrdiff_t);
+                                _os_log_encode_arg(&value.type.pd, sizeof(value.type.pd), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                                break;
+                                
+                            default:
+                                return false;
+                        }
+                        done = true;
+                        break;
+                        
+#ifndef KERNEL
+                    case '{':
+                        // we do not support this for shimmed code
+                        if (context->shimmed) {
+                            return false;
+                        }
+                        
+                        for (const char *curr2 = percent + 1; (ch = (*curr2)) != NUL; curr2++) {
+                            if (ch == '}') {
+                                strlcpy(annotated, percent, MIN(curr2 - (percent + 1), sizeof(annotated)));
+                                context->annotated = annotated;
+                                percent = curr2;
+                                break;
+                            }
+                        }
+                        break;
+#endif /* !KERNEL */
+                        
+                    case 'p': // pointer
+                        value.type.p = va_arg(args, void *);
+                        _os_log_encode_arg(&value.type.p, sizeof(value.type.p), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                        done = true;
+                        break;
+                        
+#ifndef KERNEL
+                    case 'P': // pointer data
+                        if (context->shimmed) { // we do not support this for shimmed code
+                            return false;
+                        }
+                        
+                        context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
+                        value.type.p = va_arg(args, void *);
+                        
+                        // capture the string pointer to generate a symptom
+                        if (context->log && context->log->generate_symptoms && context->arg_idx == 1 && value.type.pch && prec) {
+                            context->symptom_ptr = value.type.p;
+                            context->symptom_ptr_len = prec;
+                        }
+                        
+                        _os_log_encode_arg(value.type.p, prec, OS_LOG_BUFFER_VALUE_TYPE_POINTER, false, context);
+                        prec = 0;
+                        done = true;
+                        break;
+#endif /* !KERNEL */
+                        
+#ifndef KERNEL
+                    case 'L': // long double
+                        long_double = true;
+                        break;
+                        
+                    case 'a': case 'A': case 'e': case 'E': // floating types
+                    case 'f': case 'F': case 'g': case 'G':
+                        if (long_double) {
+                            value.type.ld = va_arg(args, long double);
+                            _os_log_encode_arg(&value.type.ld, sizeof(value.type.ld), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                        } else {
+                            value.type.d = va_arg(args, double);
+                            _os_log_encode_arg(&value.type.d, sizeof(value.type.d), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                        }
+                        done = true;
+                        break;
+#endif /* !KERNEL */
+                        
+                    case 'c': // char
+                        value.type.ch = va_arg(args, int);
+                        _os_log_encode_arg(&value.type.ch, sizeof(value.type.ch), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                        done = true;
+                        break;
+                        
+#ifndef KERNEL
+                    case 'C': // wide-char
+                        value.type.wch = va_arg(args, wint_t);
+                        _os_log_encode_arg(&value.type.wch, sizeof(value.type.wch), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                        done = true;
+                        break;
+#endif /* !KERNEL */
+                        
+                    case 's': // string
+                        value.type.pch = va_arg(args, char *);
+                        if (!prec && value.type.pch != NULL) {
+                            prec = (int) strlen(value.type.pch) + 1;
+                        }
+                        
+#ifndef KERNEL
+                        // capture the string pointer to generate a symptom
+                        if (context->log && context->log->generate_symptoms && context->arg_idx == 0 && value.type.pch) {
+                            context->symptom_str = value.type.pch;
+                        }
+#endif
+                        
+                        context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
+                        _os_log_encode_arg(value.type.pch, prec, OS_LOG_BUFFER_VALUE_TYPE_STRING, false, context);
+                        prec = 0;
+                        done = true;
+                        break;
+                        
+#ifndef KERNEL
+                    case 'S': // wide-string
+                        value.type.pwch = va_arg(args, wchar_t *);
+                        if (!prec && value.type.pwch != NULL) {
+                            prec = (int) wcslen(value.type.pwch) + 1;
+                        }
+                        
+                        context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
+                        _os_log_encode_arg(value.type.pwch, prec, OS_LOG_BUFFER_VALUE_TYPE_STRING, false, context);
+                        prec = 0;
+                        done = true;
+                        break;
+#endif /* !KERNEL */
+                        
+#ifndef KERNEL
+                    case '@': // CFTypeRef aka NSObject *
+                        context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
+                        _os_log_encode_arg(va_arg(args, void *), 0, OS_LOG_BUFFER_VALUE_TYPE_OBJECT, false, context);
+                        done = true;
+                        break;
+#endif /* !KERNEL */
+                        
+                    case 'm':
+                        value.type.i = saved_errno;
+                        _os_log_encode_arg(&value.type.i, sizeof(value.type.i), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
+                        done = true;
+                        break;
+                        
+                    default:
+                        if (isdigit(ch)) { // [0-9]
+                            continue;
+                        }
+                        return false;
+                }
+                
+                if (done) {
+                    percent = strchr(percent, '%'); // Find next format
+                    break;
+                }
+            }
+        } else {
+            percent = strchr(percent+1, '%'); // Find next format after %%
+        }
+    }
+    
+    context->buffer->arg_cnt = context->arg_idx;
+    context->content_sz = context->content_off;
+    context->pubdata_sz = context->pubdata_off;
+    context->privdata_sz = context->privdata_off;
+    context->arg_idx = context->content_off = context->pubdata_off = context->privdata_off = 0;
+    
+    return true;
+}
+
+#endif /* log_encode_h */