├── .clang-format ├── .clang-tidy ├── .gitignore ├── .travis-common.sh ├── .travis-install.sh ├── .travis-script.sh ├── .travis.yml ├── CONTRIBUTING ├── LICENSE ├── Makefile ├── README ├── README.md ├── include └── pblog │ ├── common.h │ ├── event.h │ ├── file.h │ ├── flash.h │ ├── mem.h │ ├── nvram.h │ ├── pblog.h │ └── record.h ├── mk └── pblog.mk ├── proto └── pblog.proto ├── src ├── event.c ├── file.c ├── mem.c ├── nvram.c ├── pblog.c └── record.c └── test ├── common.cc ├── common.hh ├── pblog_test.cc └── record_test.cc /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | { 2 | Checks: '*,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-pro-type-const-cast' 3 | #WarningsAsErrors: '*' 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't include our object directory 2 | .pblog 3 | -------------------------------------------------------------------------------- /.travis-common.sh: -------------------------------------------------------------------------------- 1 | export LOCAL_PREFIX="$(dirname "$(readlink -f "$0")")/../local-prefix" 2 | mkdir -p "$LOCAL_PREFIX" 3 | export PATH="$LOCAL_PREFIX/bin:$HOME/.local/bin:${PATH:+:}$PATH" 4 | python_lib="$(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")" 5 | export PYTHONPATH="$HOME/.local/lib/$(basename "$(dirname "$python_lib")")" 6 | 7 | # Make sure that we properly define dependencies in make 8 | # and can parallel build. 9 | export MAKEFLAGS=("-j" "2") 10 | 11 | # Make sure that we use the right compiler 12 | if [ "${MYCC:0:3}" = "gcc" ]; then 13 | export CC=gcc${MYCC:3} 14 | export CXX=g++${MYCC:3} 15 | elif [ "${MYCC:0:5}" = "clang" ]; then 16 | export CC=clang${MYCC:5} 17 | export CXX=clang++${MYCC:5} 18 | else 19 | export CC=not-a-compiler 20 | export CXX=not-a-compiler 21 | fi 22 | -------------------------------------------------------------------------------- /.travis-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | set -o pipefail 4 | set -x 5 | 6 | source .travis-common.sh 7 | 8 | downloadUnpack() { 9 | local url="$1" 10 | local dirname="$2" 11 | 12 | mkdir -p "$dirname.tmp" 13 | curl -L -o "$dirname.file" "$url" || return 1 14 | tar -xC "$dirname.tmp" -f "$dirname.file" || return 2 15 | test "$(ls "$dirname.tmp" | wc -l)" -eq "1" || return 3 16 | mv "$dirname.tmp"/* "$dirname" 17 | rmdir "$dirname.tmp" 18 | } 19 | 20 | # Don't vendor stuff into the source directory 21 | pushd .. >/dev/null 22 | 23 | # Setup protoc 24 | PROTOBUF_BASE_URL="https://github.com/google/protobuf/releases/download/v$PROTOBUF_VERSION" 25 | PROTOBUF_URLS=( 26 | "$PROTOBUF_BASE_URL/protobuf-cpp-$PROTOBUF_VERSION.tar.gz" 27 | "$PROTOBUF_BASE_URL/protobuf-$PROTOBUF_VERSION.tar.gz" 28 | ) 29 | for url in "${PROTOBUF_URLS[@]}"; do 30 | downloadUnpack "$url" "protobuf" && break 31 | done 32 | pushd "protobuf" >/dev/null 33 | ./configure --prefix="$LOCAL_PREFIX" 34 | make "${MAKEFLAGS[@]}" 35 | make "${MAKEFLAGS[@]}" install 36 | popd >/dev/null 37 | 38 | # Setup python-protobuf 39 | pip install --user --force-reinstall --ignore-installed --upgrade pip 40 | pip install --user "protobuf==$PROTOBUF_VERSION" 41 | 42 | # Setup nanopb 43 | downloadUnpack "https://github.com/nanopb/nanopb/archive/$NANOPB_VERSION.tar.gz" "nanopb" 44 | make "${MAKEFLAGS[@]}" -C nanopb/generator/proto # TOOD(wak): Remove once we fix this in the makefile 45 | 46 | # Setup googletest 47 | downloadUnpack "https://github.com/google/googletest/archive/release-$GOOGLETEST_VERSION.tar.gz" "googletest" 48 | pushd "googletest" >/dev/null 49 | cmake \ 50 | -DCMAKE_INSTALL_PREFIX="$LOCAL_PREFIX" \ 51 | -DMAKE_BUILD_TYPE=Release \ 52 | -DBUILD_GTEST=ON -DBUILD_GMOCK=OFF 53 | make "${MAKEFLAGS[@]}" 54 | make "${MAKEFLAGS[@]}" install 55 | popd >/dev/null 56 | 57 | # Pop back into the source directory 58 | popd >/dev/null 59 | -------------------------------------------------------------------------------- /.travis-script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | set -o pipefail 4 | set -x 5 | 6 | source .travis-common.sh 7 | 8 | clang-tidy() { 9 | clang-tidy${CC:5} "$@" >tmp-tidy-results || true 10 | set +x 11 | if grep -q ' \(warning\|error\):' tmp-tidy-results; then 12 | echo "########## $1 ##########" >> tidy-results 13 | cat tmp-tidy-results >> tidy-results 14 | fi 15 | set -x 16 | } 17 | 18 | if [ "$LINT" = "1" ]; then 19 | make "${MAKEFLAGS[@]}" NANOPB_DIR=../nanopb all 20 | touch tidy-results 21 | for file in $(find include src -name \*.c -or -name \*.h); do 22 | clang-tidy "$file" -- -std=gnu11 -I.pblog/include 23 | done 24 | for file in $(find test -name \*.cc -or -name \*.hh); do 25 | clang-tidy "$file" -- -std=gnu++11 -I.pblog/include -I"$LOCAL_PREFIX"/include 26 | done 27 | set +x 28 | if [ -s "tidy-results" ]; then 29 | cat tidy-results 30 | exit 1 31 | fi 32 | set -x 33 | else 34 | make "${MAKEFLAGS[@]}" NANOPB_DIR=../nanopb all 35 | make "${MAKEFLAGS[@]}" NANOPB_DIR=../nanopb GTEST_DIR="$LOCAL_PREFIX" check 36 | make "${MAKEFLAGs[@]}" NANOPB_DIR=../nanopb PREFIX="$LOCAL_PREFIX" install 37 | 38 | test -f "$LOCAL_PREFIX"/lib/libpblog.so 39 | test -f "$LOCAL_PREFIX"/lib/libpblog.a 40 | test -d "$LOCAL_PREFIX"/include/pblog 41 | fi 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: precise 2 | sudo: false 3 | addons: 4 | apt: 5 | sources: 6 | - ubuntu-toolchain-r-test 7 | - llvm-toolchain-precise-3.8 8 | packages: 9 | - clang-3.8 10 | - clang-tidy-3.8 11 | - gcc-6 12 | - g++-6 13 | language: 14 | - c 15 | - c++ 16 | env: 17 | global: 18 | - GOOGLETEST_VERSION=1.8.0 19 | - NANOPB_VERSION=58af4d1fb733c5348b68dd3980f2a230d95400b4 20 | matrix: 21 | - PROTOBUF_VERSION=2.6.1 MYCC=gcc-6 22 | - PROTOBUF_VERSION=2.6.1 MYCC=clang-3.8 23 | - PROTOBUF_VERSION=3.1.0 MYCC=gcc-6 24 | - PROTOBUF_VERSION=3.1.0 MYCC=clang-3.8 25 | - LINT=1 PROTOBUF_VERSION=3.1.0 MYCC=clang-3.8 26 | install: ./.travis-install.sh 27 | script: ./.travis-script.sh 28 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement] 6 | (https://cla.developers.google.com/about/google-individual) 7 | (CLA), which you can do online. The CLA is necessary mainly because you own the 8 | copyright to your changes, even after your contribution becomes part of our 9 | codebase, so we need your permission to use and distribute your code. We also 10 | need to be sure of various other things—for instance that you'll tell us if you 11 | know that your code infringes on other people's patents. You don't have to sign 12 | the CLA until after you've submitted your code for review and a member has 13 | approved it, but you must do it before we can put your code into our codebase. 14 | Before you start working on a larger contribution, you should get in touch with 15 | us first through the issue tracker with your idea so that we can help out and 16 | possibly guide you. Coordinating up front makes it much easier to avoid 17 | frustration later on. 18 | 19 | ### Code reviews 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. 22 | 23 | ### The small print 24 | Contributions made by corporations are covered by a different agreement than 25 | the one above, the 26 | [Software Grant and Corporate Contributor License Agreement] 27 | (https://cla.developers.google.com/about/google-corporate). 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Google Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Directory and Install parameters 2 | DESTDIR ?= 3 | PREFIX ?= /usr 4 | INCLUDEDIR ?= $(PREFIX)/include 5 | LIBDIR ?= $(PREFIX)/lib 6 | 7 | # Test dependencies 8 | GTEST_DIR ?= /usr 9 | GTEST_LIBDIR ?= $(GTEST_DIR)/lib 10 | GTEST_INCDIR ?= $(GTEST_DIR)/include 11 | 12 | # Command overrides 13 | INSTALL ?= install 14 | CP ?= cp 15 | CXX ?= c++ 16 | 17 | # Compiler Options 18 | CFLAGS ?= -Wall -Werror 19 | 20 | # Build Options 21 | BUILD_STATIC ?= y 22 | BUILD_SHARED ?= y 23 | 24 | PBLOG_BUILD_STATIC = $(BUILD_STATIC) 25 | PBLOG_BUILD_SHARED = $(BUILD_SHARED) 26 | 27 | # Test enumeration 28 | PBLOG_TESTS_SRC = $(wildcard $(PBLOG_DIR)/test/*_test.cc) 29 | PBLOG_TESTS_HEADERS = $(PBLOG_HEADERS) $(wildcard $(PBLOG_DIR)/test/*.h) 30 | PBLOG_TESTS_COMMON_FILES = $(filter-out %_test.cc,$(wildcard $(PBLOG_DIR)/test/*.cc)) 31 | PBLOG_TESTS_COMMON_OBJECTS = $(patsubst $(PBLOG_DIR)/test/%.cc,$(PBLOG_OUT)/test/%.o,$(PBLOG_TESTS_COMMON_FILES)) 32 | PBLOG_TESTS = $(patsubst $(PBLOG_DIR)/test/%.cc,$(PBLOG_OUT)/%,$(PBLOG_TESTS_SRC)) 33 | PBLOG_TESTS_RUN = $(patsubst %,%_run,$(PBLOG_TESTS)) 34 | 35 | # Test Params 36 | PBLOG_TESTS_CFLAGS = $(CFLAGS) $(PBLOG_CFLAGS) -std=gnu++11 \ 37 | -I$(PBLOG_INCLUDE) -I$(GTEST_INCDIR) 38 | PBLOG_TESTS_CFLAGS_LINK = -Wl,-rpath $(PBLOG_OUT) -Wl,-rpath $(GTEST_LIBDIR) 39 | PBLOG_TESTS_LIBS = -L$(PBLOG_OUT) -lpblog -L$(GTEST_LIBDIR) -lgtest_main \ 40 | -lgtest -pthread 41 | 42 | .SECONDARY: $(PBLOG_TESTS) $(PBLOG_SECONDARY) 43 | .PHONY: all all-real check install clean $(PBLOG_PHONY) 44 | 45 | # We need this special rule to make sure all comes before rules in pblog.mk 46 | all: all-real 47 | 48 | include mk/pblog.mk 49 | 50 | # Rule for building common test objects 51 | $(PBLOG_OUT)/test/%.o: $(PBLOG_DIR)/test/%.cc $(PBLOG_TESTS_HEADERS) 52 | @$(PBLOG_MKDIR) -p $(PBLOG_OUT)/test 53 | $(CXX) $(PBLOG_TESTS_CFLAGS) -c $< -o $@ 54 | 55 | # Rule for building test cases 56 | $(PBLOG_OUT)/%_test: $(PBLOG_DIR)/test/%_test.cc $(PBLOG_TESTS_COMMON_OBJECTS) $(PBLOG_TESTS_HEADERS) $(PBLOG_LIBRARIES) 57 | @$(PBLOG_MKDIR) -p $(PBLOG_OUT) 58 | $(CXX) $(PBLOG_TESTS_CFLAGS) $(PBLOG_TESTS_CFLAGS_LINK) $< -o $@ \ 59 | $(PBLOG_TESTS_COMMON_OBJECTS) $(PBLOG_TESTS_LIBS) 60 | 61 | # Rule for running test cases 62 | $(PBLOG_OUT)/%_run: $(PBLOG_OUT)/% 63 | $< 64 | touch $@ 65 | 66 | all-real: $(PBLOG_LIBRARIES) $(PBLOG_HEADERS) 67 | 68 | check: $(PBLOG_TESTS_RUN) 69 | 70 | install: $(PBLOG_LIBRARIES) $(PBLOG_HEADERS) 71 | $(INSTALL) -d -m 0755 $(DESTDIR)$(LIBDIR) 72 | $(INSTALL) -m 0755 $(PBLOG_LIBRARIES) $(DESTDIR)$(LIBDIR) 73 | $(INSTALL) -d -m 0755 $(DESTDIR)$(INCLUDEDIR) 74 | $(CP) -r $(PBLOG_INCLUDE)/* $(DESTDIR)$(INCLUDEDIR)/ 75 | 76 | clean: pblog_clean 77 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pblog - Protocol Buffer Log Library 2 | =================================== 3 | 4 | ![alt text](https://travis-ci.org/google/pblog.svg?branch=master "TravisCI Status") 5 | 6 | pblog is a small, low overhead, structured logging library intended to be used 7 | to log firmware events. It is based on protobufs and uses the nanopb 8 | implementation in order to tackle object size concerns. 9 | 10 | Dependencies 11 | ------------ 12 | Runtime 13 | 14 | - c compiler 15 | - make 16 | - protobuf https://github.com/google/protobuf 17 | - python-protobuf https://pypi.python.org/pypi/protobuf 18 | 19 | Testing 20 | - c++ compiler 21 | 22 | For ubuntu systems these can all be installed with apt 23 | 24 | apt install make protobuf-compiler python-protobuf 25 | 26 | Building 27 | -------- 28 | make NANOPB_DIR= all 29 | 30 | if you don't already have nanopb you can 31 | 32 | git clone https://github.com/nanopb/nanopb 33 | make -C nanopb/generator/proto 34 | make NANOPB_DIR=nanopb all 35 | 36 | Installing 37 | ---------- 38 | make NANOPB_DIR= PREFIX=/usr install 39 | 40 | Testing 41 | ------- 42 | make NANOPB_DIR= GTEST_DIR= check 43 | 44 | if you don't already have gtest you can run 45 | 46 | git clone https://github.com/google/googletest 47 | pushd "googletest" >/dev/null 48 | cmake \ 49 | -DCMAKE_INSTALL_PREFIX="$(pwd)/googletest" \ 50 | -DMAKE_BUILD_TYPE=Release \ 51 | -DBUILD_GTEST=ON -DBUILD_GMOCK=OFF 52 | make 53 | make install 54 | popd >/dev/null 55 | make NANOPB_DIR= GTEST_DIR=googletest check 56 | 57 | Use in a project 58 | ---------------- 59 | If you would like to build pblog into your project, we provide a makefile 60 | mk/pblog.mk which can be included. 61 | 62 | The makefile depends on the following variables: 63 | 64 | - NANOPB\_DIR: The directory containing the source code for nanopb 65 | - PBLOG\_BUILD\_STATIC: Whether or not we should build a static pblog 66 | - PBLOG\_BUILD\_SHARED: Whether or not we should build a shared pblog 67 | 68 | The makefile is guaranteed to export the following variables: 69 | 70 | - PBLOG\_LIBRARIES: The targets from the enabled pblogging libraries 71 | - PBLOG\_STATIC: The target for the static pblog library 72 | - PBLOG\_SHARED: The target for the shared pblog library 73 | -------------------------------------------------------------------------------- /include/pblog/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* Common functions/definitions */ 18 | 19 | #ifndef PBLOG_COMMON_H 20 | #define PBLOG_COMMON_H 21 | 22 | #include 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | /* Logging/debugging support. 29 | * Args: severity (0 == debug, 1 == error) */ 30 | /* The integrating application should define this function */ 31 | #ifdef PBLOG_NO_PRINTF 32 | static inline int pblog_printf(int severity, const char *format, ...) { 33 | return 0; 34 | } 35 | #else 36 | extern int pblog_printf(int severity, const char *format, ...); 37 | #endif 38 | 39 | #define PBLOG_XSTR(s) PBLOG_STR(s) 40 | #define PBLOG_STR(s) #s 41 | 42 | #ifndef PBLOG_DPRINTF 43 | #define PBLOG_DPRINTF(format, ...) \ 44 | do { \ 45 | pblog_printf(0, __FILE__ ":" PBLOG_XSTR(__LINE__) " " format, \ 46 | ##__VA_ARGS__); \ 47 | } while (0) 48 | #endif 49 | 50 | #ifndef PBLOG_ERRF 51 | #define PBLOG_ERRF(format, ...) \ 52 | do { \ 53 | pblog_printf(1, __FILE__ ":" PBLOG_XSTR(__LINE__) " " format, \ 54 | ##__VA_ARGS__); \ 55 | } while (0) 56 | #endif 57 | 58 | /* Return values */ 59 | enum pblog_status { 60 | PBLOG_SUCCESS = 0, 61 | PBLOG_ERR_NO_SPACE = -1, 62 | PBLOG_ERR_INVALID = -2, 63 | PBLOG_ERR_CHECKSUM = -3, 64 | PBLOG_ERR_IO = -4, 65 | }; 66 | 67 | #ifdef __cplusplus 68 | } /* extern "C" */ 69 | #endif 70 | 71 | #endif /* PBLOG_COMMON_H */ 72 | -------------------------------------------------------------------------------- /include/pblog/event.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* Protobuf eventlog event helper functions */ 18 | 19 | #ifndef PBLOG_EVENT_H 20 | #define PBLOG_EVENT_H 21 | 22 | #include 23 | 24 | #include 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | /* Encodes an event and writes it to the provided buffer. 31 | Returns: the encoded length in bytes or <0 on error. */ 32 | int event_encode(const pblog_Event *event, void *buf, size_t len); 33 | int event_decode(const void *buf, size_t len, pblog_Event *event); 34 | /* Returns the encoded length of the event or <0 on error. */ 35 | int event_size(const pblog_Event *event); 36 | 37 | /* Initializes/destroys an event structure. */ 38 | void event_init(pblog_Event *event); 39 | void event_free(pblog_Event *event); 40 | 41 | /* Adds KV string data to an event. */ 42 | void event_add_kv_data(pblog_Event *event, const char *key, const char *value); 43 | 44 | #ifdef __cplusplus 45 | } /* extern "C" */ 46 | #endif 47 | 48 | #endif /* PBLOG_EVENT_H */ 49 | -------------------------------------------------------------------------------- /include/pblog/file.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* File reading emulation for flash operations */ 18 | 19 | #ifndef PBLOG_FILE_H 20 | #define PBLOG_FILE_H 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | extern struct pblog_flash_ops pblog_file_ops; 32 | 33 | #ifdef __cplusplus 34 | } /* extern "C" */ 35 | #endif 36 | 37 | #endif /* PBLOG_FILE_H */ 38 | -------------------------------------------------------------------------------- /include/pblog/flash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* Flash interface */ 18 | 19 | #ifndef PBLOG_FLASH_H 20 | #define PBLOG_FLASH_H 21 | 22 | #include 23 | #include 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | typedef struct pblog_flash_ops { 30 | /* Read/write operations. Returns number of bytes read/written. */ 31 | int (*read)(struct pblog_flash_ops *ops, int offset, size_t len, void *data); 32 | int (*write)(struct pblog_flash_ops *ops, int offset, size_t len, 33 | const void *data); 34 | /* Erase region. Returns 0 on success */ 35 | int (*erase)(struct pblog_flash_ops *ops, int offset, size_t len); 36 | 37 | void *priv; 38 | } pblog_flash_ops; 39 | 40 | #ifdef __cplusplus 41 | } /* extern "C" */ 42 | #endif 43 | 44 | #endif /* PBLOG_FLASH_H */ 45 | -------------------------------------------------------------------------------- /include/pblog/mem.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* In-memory operations support */ 18 | 19 | #ifndef PBLOG_MEM_H 20 | #define PBLOG_MEM_H 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | extern struct pblog_flash_ops pblog_mem_ops; 32 | 33 | #ifdef __cplusplus 34 | } /* extern "C" */ 35 | #endif 36 | 37 | #endif /* PBLOG_MEM_H */ 38 | -------------------------------------------------------------------------------- /include/pblog/nvram.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* Simple NVRAM interface */ 18 | 19 | #ifndef PBLOG_NVRAM_H 20 | #define PBLOG_NVRAM_H 21 | 22 | #include 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | struct record_intf; 29 | 30 | struct nvram_entry { 31 | char *key; // must be a C style nul-terminated string 32 | char *data; // arbitrary bytes not necessarily nul-terminated 33 | size_t data_len; 34 | }; 35 | 36 | typedef struct nvram { 37 | /* Lookup data based on key. 38 | * Args: 39 | * key: key name 40 | * data: output buffer to fill 41 | * max_data_len: length of output buffer 42 | * Returns: number of data bytes read on success, <0 on error 43 | */ 44 | int (*lookup)(struct nvram *nvram, const char *key, char *data, 45 | size_t max_data_len); 46 | 47 | /* Set a key value. 48 | * Args: 49 | * key: key name 50 | * data: data to associate with key 51 | * data_len: size of data 52 | * Returns: 0 on success 53 | */ 54 | int (*set)(struct nvram *nvram, const char *key, const char *data, 55 | size_t data_len); 56 | 57 | /* Unsets the value for a key so future lookups will not return it. 58 | * Args: 59 | * key: key name 60 | * Returns: 0 on success 61 | */ 62 | int (*unset)(struct nvram *nvram, const char *key); 63 | 64 | /* Lists all entries. 65 | * Unset entries do not appear in the list, newer entries override older. 66 | * Args: 67 | * entries: pointer to returned list, must be free'd with nvram_list_free() 68 | * Returns: 0 on success 69 | */ 70 | int (*list)(struct nvram *nvram, struct nvram_entry **entries); 71 | 72 | /* Clear all entries 73 | * Returns: 0 on success 74 | */ 75 | int (*clear)(struct nvram *nvram); 76 | 77 | struct record_intf *ri; 78 | } nvram; 79 | 80 | // Deallocates a single nvram_entry object. 81 | void nvram_entry_free(struct nvram_entry *entry); 82 | 83 | // Deallocates an array of nvram_entrys as returned from nvram.list(). 84 | void nvram_list_free(struct nvram_entry **entries); 85 | 86 | // Finds a particular key in a list of entries and returns a pointer to it. 87 | // Returns NULL if the key is not found. 88 | const struct nvram_entry *nvram_list_find(const struct nvram_entry *entries, 89 | const char *key); 90 | 91 | void pblog_nvram_init(struct nvram *nvram, struct record_intf *ri); 92 | void pblog_nvram_free(struct nvram *nvram); 93 | 94 | #ifdef __cplusplus 95 | } /* extern "C" */ 96 | #endif 97 | 98 | #endif /* PBLOG_NVRAM_H */ 99 | -------------------------------------------------------------------------------- /include/pblog/pblog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* Protobuf eventlog interface . */ 18 | 19 | #ifndef PBLOG_PBLOG_H 20 | #define PBLOG_PBLOG_H 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #ifdef __cplusplus 29 | extern "C" { 30 | #endif 31 | 32 | #define PBLOG_MAX_EVENT_SIZE 4096 33 | 34 | struct record_intf; 35 | 36 | /* Args: 37 | * valid: 1 if event is considered valid, 0 otherwise 38 | * event: decoded event 39 | * priv: user data pointer 40 | */ 41 | typedef enum pblog_status (*pblog_event_cb)(int valid, const pblog_Event *event, 42 | void *priv); 43 | 44 | typedef struct pblog { 45 | /* Adds a single event to the log. event may be modified to add timestamp 46 | * and/or bootnum values. 47 | */ 48 | enum pblog_status (*add_event)(struct pblog *pblog, pblog_Event *event); 49 | 50 | /* Calls provided callback for every event in the log. 51 | * Args: 52 | * callback: will be called in order of oldest to most recent entry. 53 | * event: event struct to use for unserializing each event, must be non-NULL 54 | * priv: opaque pointer that is passed to callback 55 | */ 56 | enum pblog_status (*for_each_event)(struct pblog *pblog, 57 | pblog_event_cb callback, 58 | pblog_Event *event, void *priv); 59 | 60 | /* Clears the entire log. */ 61 | enum pblog_status (*clear)(struct pblog *pblog); 62 | 63 | /* Optional functions that can be provided to fill in bootnum/timestamp for 64 | * events that do not have it. 65 | */ 66 | uint32_t (*get_current_bootnum)(struct pblog *pblog); 67 | uint32_t (*get_time_now)(struct pblog *pblog); 68 | 69 | void *priv; 70 | } pblog; 71 | 72 | /* Initialize the log. 73 | * Args: 74 | * allow_clear_on_add: if the log should allow reclaiming space if full 75 | * during an add operation. The alternative is to return PBLOG_ERR_NO_SPACE 76 | * flash_ri: the record interface to use to store records on flash 77 | * mem_addr: memory address to use for in-memory copy (or NULL for none) 78 | * mem_size: size of in-memory buffer, should be the same as the total region 79 | * size 80 | * Returns: 81 | * number of events found in the log on success, <0 on failure 82 | */ 83 | int pblog_init(struct pblog *pblog, int allow_clear_on_add, 84 | struct record_intf *flash_ri, void *mem_addr, size_t mem_size); 85 | void pblog_free(struct pblog *pblog); 86 | 87 | #ifdef __cplusplus 88 | } /* extern "C" */ 89 | #endif 90 | 91 | #endif /* PBLOG_PBLOG_H */ 92 | -------------------------------------------------------------------------------- /include/pblog/record.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* Log-structured record interface */ 18 | 19 | #ifndef PBLOG_RECORD_H 20 | #define PBLOG_RECORD_H 21 | 22 | #include 23 | #include 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | struct pblog_flash_ops; 30 | 31 | /* Header used for each record */ 32 | typedef struct record_header { 33 | uint8_t length_msb; 34 | uint8_t length_lsb; 35 | uint8_t checksum; 36 | } __attribute__((packed)) record_header; 37 | 38 | /* Header used on each erase block region */ 39 | typedef struct region_header { 40 | /* Magic value used to recognize this as a valid region. */ 41 | uint8_t magic[4]; 42 | /* Sequence number of this region in the circular list. Stored with 43 | * LSB in sequence[0]. Note: lowest sequence number is the first in the list. 44 | */ 45 | uint8_t sequence[4]; 46 | } __attribute__((packed)) region_header; 47 | 48 | typedef struct record_intf { 49 | /* Reads a record. 50 | * Args: 51 | * offset: byte offset of record 52 | * next_offset: set to the offset of the next record or 0 if at end 53 | * len: maximum data length to read, updated with actual read data length 54 | * data: data buffer to write 55 | * Returns: 56 | * 0 on success, <0 on failure 57 | * next_offset set to '0' on end of log 58 | */ 59 | int (*read_record)(struct record_intf *ri, int offset, int *next_offset, 60 | size_t *len, void *data); 61 | 62 | /* Appends a record. 63 | * Args: 64 | * len: length of data to append in bytes 65 | * data: record data 66 | * Returns: 67 | * number of bytes actually written on success (may be greater than len) 68 | */ 69 | int (*append)(struct record_intf *ri, size_t len, const void *data); 70 | 71 | /* Returns the number of free bytes for storing records. */ 72 | int (*get_free_space)(struct record_intf *ri); 73 | 74 | /* Clears num_regions of records, starting from the beginning of the record 75 | * space. If num_regions == 0 then clears all regions. 76 | * Returns: 77 | * number of bytes cleared on success 78 | * <0 on failure 79 | */ 80 | int (*clear)(struct record_intf *ri, int num_regions); 81 | 82 | void *priv; 83 | } record_intf; 84 | 85 | /* Defines an erase block region */ 86 | typedef struct record_region { 87 | uint32_t offset; /* offset of this region */ 88 | uint32_t size; /* total size of region in bytes */ 89 | uint32_t used_size; /* amount of bytes used in this region */ 90 | uint32_t sequence; /* sequence number */ 91 | } __attribute__((packed)) record_region; 92 | 93 | /* Initializes a record interface 94 | * Args: 95 | * regions: array of regions to use (will be copied into internal structures) 96 | */ 97 | int record_intf_init(record_intf *ri, const struct record_region *regions, 98 | int num_regions, struct pblog_flash_ops *flash); 99 | void record_intf_free(record_intf *ri); 100 | 101 | #ifdef __cplusplus 102 | } /* extern "C" */ 103 | #endif 104 | 105 | #endif /* PBLOG_RECORD_H */ 106 | -------------------------------------------------------------------------------- /mk/pblog.mk: -------------------------------------------------------------------------------- 1 | # An include file for Makefiles. It provides rules for building 2 | # a static libpblog.a based on protobuf and nanopb. 3 | 4 | # Path to the pblog root directory 5 | PBLOG_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))../) 6 | PBLOG_OUT := $(CURDIR)/.pblog 7 | 8 | # Path to nanopb. Make sure it's accessible 9 | ifndef NANOPB_DIR 10 | $(error You must provide a NANOPB_DIR that contains the nanopb package sources.) 11 | endif 12 | include $(NANOPB_DIR)/extra/nanopb.mk 13 | 14 | # Build Options 15 | PBLOG_BUILD_STATIC ?= y 16 | PBLOG_BUILD_SHARED ?= n 17 | 18 | PBLOG_BUILD_MODULE_FILE ?= y 19 | 20 | # Parameters 21 | PBLOG_LIBRARIES = 22 | PBLOG_STATIC = $(PBLOG_OUT)/libpblog.a 23 | ifeq ($(BUILD_STATIC),y) 24 | PBLOG_LIBRARIES += $(PBLOG_STATIC) 25 | endif 26 | PBLOG_SHARED = $(PBLOG_OUT)/libpblog.so 27 | ifeq ($(BUILD_SHARED),y) 28 | PBLOG_LIBRARIES += $(PBLOG_SHARED) 29 | endif 30 | 31 | # Command substitution 32 | PBLOG_CC = $(if $(CC),$(CC),cc) 33 | PBLOG_MKDIR = $(if $(MKDIR),$(MKDIR),mkdir) 34 | PBLOG_CP = $(if $(CP),$(CP),cp) 35 | PBLOG_AR = $(if $(AR),$(AR),ar) 36 | 37 | # We need certain CPP / C flags for correctness of struct generation 38 | CPPFLAGS += -DPB_FIELD_32BIT=1 39 | CFLAGS += -DPB_FIELD_32BIT=1 40 | 41 | PBLOG_CFLAGS = $(CPPFLAGS) $(CFLAGS) -Wall -DPB_FIELD_32BIT=1 42 | ifeq ($(PBLOG_BUILD_SHARED),y) 43 | PBLOG_CFLAGS += -fPIC 44 | endif 45 | 46 | HEADER_FILTER = 47 | SOURCE_FILTER = 48 | ifeq ($(PBLOG_BUILD_MODULE_FILE),n) 49 | HEADER_FILTER += %/file.h 50 | SOURCE_FILTER += %/file.c 51 | endif 52 | 53 | PBLOG_SRC_INCLUDE = $(PBLOG_DIR)/include 54 | PBLOG_SRC_HEADERS = $(filter-out $(HEADER_FILTER),$(wildcard $(PBLOG_SRC_INCLUDE)/pblog/*.h)) 55 | PBLOG_SRC_PROTOS = $(wildcard $(PBLOG_DIR)/proto/*.proto) 56 | PBLOG_SRC_FILES = $(filter-out $(SOURCE_FILTER),$(wildcard $(PBLOG_DIR)/src/*.c)) 57 | 58 | PBLOG_INCLUDE = $(PBLOG_OUT)/include 59 | PBLOG_ONLY_HEADERS = $(patsubst $(PBLOG_SRC_INCLUDE)/%,$(PBLOG_INCLUDE)/%,$(PBLOG_SRC_HEADERS)) 60 | PBLOG_PROTO_HEADERS = $(patsubst $(PBLOG_DIR)/proto/%.proto,$(PBLOG_INCLUDE)/pblog/%.pb.h,$(PBLOG_SRC_PROTOS)) 61 | PBLOG_NANOPB_HEADERS = $(patsubst $(NANOPB_DIR)/%.h,$(PBLOG_INCLUDE)/nanopb/%.h,$(wildcard $(NANOPB_DIR)/*.h)) 62 | PBLOG_HEADERS = $(PBLOG_NANOPB_HEADERS) $(PBLOG_ONLY_HEADERS) $(PBLOG_PROTO_HEADERS) 63 | PBLOG_ONLY_OBJECTS = $(patsubst $(PBLOG_DIR)/src/%.c,$(PBLOG_OUT)/pblog/%.o,$(PBLOG_SRC_FILES)) 64 | PBLOG_PROTO_OBJECTS = $(patsubst $(PBLOG_DIR)/proto/%.proto,$(PBLOG_OUT)/pblog/%.pb.o,$(PBLOG_SRC_PROTOS)) 65 | PBLOG_NANOPB_OBJECTS = $(patsubst $(NANOPB_DIR)/%.c,$(PBLOG_OUT)/nanopb/%.o,$(NANOPB_CORE)) 66 | PBLOG_OBJECTS = $(PBLOG_NANOPB_OBJECTS) $(PBLOG_ONLY_OBJECTS) $(PBLOG_PROTO_OBJECTS) 67 | 68 | PBLOG_SECONDARY: $(PBLOG_HEADERS) 69 | PBLOG_PHONY: pblog_clean 70 | 71 | .SECONDARY: $(PBLOG_SECONDARY) 72 | .PHONY: $(PBLOG_PHONY) 73 | 74 | # Nanopb headers 75 | $(PBLOG_INCLUDE)/nanopb/%.h: $(NANOPB_DIR)/%.h 76 | @$(PBLOG_MKDIR) -p $(PBLOG_INCLUDE)/nanopb 77 | $(PBLOG_CP) $< $@ 78 | 79 | # Pblog headers 80 | $(PBLOG_INCLUDE)/pblog/%.h: $(PBLOG_SRC_INCLUDE)/pblog/%.h 81 | @$(PBLOG_MKDIR) -p $(PBLOG_INCLUDE)/pblog 82 | $(PBLOG_CP) $< $@ 83 | 84 | # Protobuf code generation 85 | $(PBLOG_OUT)/pblog/%.pb.c $(PBLOG_OUT)/pblog/%.pb.h: $(PBLOG_DIR)/proto/%.proto 86 | @$(PBLOG_MKDIR) -p $(PBLOG_OUT)/pblog 87 | $(PROTOC) $(PROTOC_OPTS) --nanopb_out="-L '#include ':$(PBLOG_OUT)/pblog" -I$(PBLOG_DIR)/proto -I$(NANOPB_DIR)/generator/proto $< 88 | 89 | # Pblog proto headers 90 | $(PBLOG_INCLUDE)/pblog/%.pb.h: $(PBLOG_OUT)/pblog/%.pb.h 91 | $(PBLOG_CP) $< $@ 92 | 93 | # Pblog proto sources 94 | $(PBLOG_OUT)/pblog/%.pb.o: $(PBLOG_OUT)/pblog/%.pb.c $(PBLOG_PROTO_HEADERS) $(PBLOG_NANOPB_HEADERS) 95 | @$(PBLOG_MKDIR) -p $(PBLOG_OUT)/pblog 96 | $(PBLOG_CC) $(PBLOG_CFLAGS) -I$(PBLOG_INCLUDE) -I$(PBLOG_INCLUDE)/pblog -c $< -o $@ 97 | 98 | # Nanopb sources 99 | $(PBLOG_OUT)/nanopb/%.o: $(NANOPB_DIR)/%.c $(PBLOG_NANOPB_HEADERS) 100 | @$(PBLOG_MKDIR) -p $(PBLOG_OUT)/nanopb 101 | $(PBLOG_CC) $(PBLOG_CFLAGS) -c $< -o $@ 102 | 103 | # Pblog sources 104 | $(PBLOG_OUT)/pblog/%.o: $(PBLOG_DIR)/src/%.c $(PBLOG_HEADERS) 105 | @$(PBLOG_MKDIR) -p $(PBLOG_OUT)/pblog 106 | $(PBLOG_CC) $(PBLOG_CFLAGS) -I$(PBLOG_INCLUDE) -c $< -o $@ 107 | 108 | # Libraries 109 | $(PBLOG_OUT)/libpblog.a: $(PBLOG_OBJECTS) 110 | @$(PBLOG_MKDIR) -p $(PBLOG_OUT) 111 | $(PBLOG_AR) rcs $(PBLOG_OUT)/libpblog.a $(PBLOG_OBJECTS) 112 | 113 | $(PBLOG_OUT)/libpblog.so: $(PBLOG_OBJECTS) 114 | @$(PBLOG_MKDIR) -p $(PBLOG_OUT) 115 | $(PBLOG_CC) -shared -Wl,-soname,libpblog.so $(PBLOG_OBJECTS) -o $(PBLOG_OUT)/libpblog.so 116 | 117 | pblog_clean: 118 | rm -rf $(PBLOG_OUT) 119 | -------------------------------------------------------------------------------- /proto/pblog.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | syntax = "proto2"; 18 | // Event protobuf definitions. 19 | import "nanopb.proto"; 20 | 21 | package pblog; 22 | 23 | option (nanopb_fileopt).long_names = false; 24 | option (nanopb_fileopt).packed_struct = true; 25 | option (nanopb_fileopt).enum_to_string = true; 26 | 27 | // Note: More common events should use lower numbers as they encode to smaller 28 | // values. 29 | enum event_type { 30 | TYPE_UNKNOWN = 0; 31 | // System powered on. 32 | TYPE_POWER_ON = 1; 33 | // System experienced a power failure, this could be logged on power on 34 | // after a power failure. 35 | TYPE_POWER_FAILURE = 2; 36 | // See "reset" / PbLogEventGenericReset. 37 | TYPE_RESET = 3; 38 | // Operating system started to boot the system. 39 | TYPE_BOOT_UP = 4; 40 | // See "shutdown" / PbLogEventGenericShutdown. 41 | TYPE_SHUTDOWN = 5; 42 | // See "log_cleared" / PbLogEventGenericLogCleared. 43 | TYPE_LOG_CLEARED = 6; 44 | // Protocol buffer event log is disabled. 45 | TYPE_LOG_DISABLED = 7; 46 | // Event that is used to indicate a cleared log area without clearing the 47 | // entire area. Any events before the last checkpoint can optionally be 48 | // ignored by tools that parse the log. 49 | TYPE_LOG_CHECKPOINT = 8; 50 | // See "cleared_nvram_location" / PbLogEventGenericNvRamLocation. 51 | TYPE_NVRAM_CLEARED = 9; 52 | // See "nvram_error" / PbLogEventGenericNvRamError. 53 | TYPE_NVRAM_ERROR = 10; 54 | // See "watchdog_timeout" / PbLogEventGenericWatchdogTimeout. 55 | TYPE_WATCHDOG_TIMEOUT = 11; 56 | // System firmware didn't find media to boot from. 57 | TYPE_BOOT_MEDIA_NOT_FOUND = 12; 58 | // See "cpu_error" / PbLogEventGenericCpuError. 59 | TYPE_CPU_ERROR = 13; 60 | // See "memory_configuration_error" / 61 | // PbLogEventGenericMemoryConfigurationError. 62 | TYPE_MEMORY_CONFIGURATION_ERROR = 14; 63 | // See "memory_runtime_error" / PbLogEventGenericMemoryRuntimeError. 64 | TYPE_MEMORY_RUNTIME_ERROR = 15; 65 | // See "pci_error" / PbLogEventGenericPciError. 66 | TYPE_PCI_ERROR = 16; 67 | // See "pcie_aer_error" / PbLogEventGenericPcieAerError. 68 | TYPE_PCIE_AER_ERROR = 19; 69 | // See "thermal_trip" / PbLogEventGenericThermalTrip. 70 | TYPE_THERMAL_TRIP = 20; 71 | // See "coherent_fabric_error" / PbLogEventGenericCoherentFabricError. 72 | TYPE_COHERENT_FABRIC_ERROR = 21; 73 | // See "filesystem_check" / PbLogEventGenericFilesystemCheck. 74 | TYPE_FILESYSTEM_CHECK = 22; 75 | // See "component_changed" / PbLogEventGenericComponentChanged. 76 | TYPE_COMPONENT_CHANGED = 23; 77 | // See "system_firmware_version_changed" / 78 | // PbLogEventGenericSystemFirmwareVersionChanged. 79 | TYPE_SYSTEM_FIRMWARE_VERSION_CHANGED = 24; 80 | // See "system_firmware_validation_error" / 81 | // PbLogEventGenericSystemFirmwareValidationError. 82 | TYPE_SYSTEM_FIRMWARE_VALIDATION_ERROR = 25; 83 | // See "system_firmware_update_error" / 84 | // PbLogEventGenericSystemFirmwareUpdateError. 85 | TYPE_SYSTEM_FIRMWARE_UPDATE_ERROR = 26; 86 | // See "system_firmware_configuration_changed" / 87 | // PbLogEventGenericSystemFirmwareConfigurationChanged. 88 | TYPE_SYSTEM_FIRMWARE_CONFIGURATION_CHANGED = 27; 89 | // See "x86_machine_check" / PbLogEventX86MachineCheck. 90 | TYPE_X86_CPU_MACHINE_CHECK = 28; 91 | // See PbLogEventAcpiSleepStateChange. 92 | TYPE_ACPI_SLEEP_STATE_CHANGED = 29; 93 | // See OpenpowerFirmwareEvent. 94 | TYPE_OPENPOWER_FIRMWARE_EVENT = 1000; 95 | } 96 | 97 | // Any event-specific descriptive data, expressed in key-value strings. 98 | message EventData { 99 | required string key = 1; 100 | required string value = 2; 101 | } 102 | 103 | message DeviceLocator { 104 | // Integer identifier of device, e.g. DIMM number, CPU socket, PCI slot. 105 | optional int64 id = 1; 106 | // Freeform text identifier of device. 107 | optional string path = 2; 108 | } 109 | 110 | message PbLogEventGenericShutdown { 111 | enum Type { 112 | TYPE_UNKNOWN = 0; 113 | // System shut down cleanly. 114 | TYPE_CLEAN = 1; 115 | // OS failed to shutdown cleanly. 116 | TYPE_DIE = 2; 117 | // Linux kernel panic. 118 | TYPE_PANIC = 3; 119 | // Linux kernel oops. 120 | TYPE_OOPS = 4; 121 | // Exception in an exception handler. 122 | TYPE_TRIPLE_FAULT = 5; 123 | } 124 | optional Type type = 1; 125 | } 126 | 127 | // System reset. 128 | message PbLogEventGenericReset { 129 | enum Type { 130 | TYPE_UNKNOWN = 0; 131 | TYPE_BUTTON = 1; 132 | TYPE_SYSTEM_FIRMWARE = 2; 133 | TYPE_OS = 3; 134 | } 135 | // The cause of the reset. 136 | optional Type type = 1; 137 | } 138 | 139 | // Memory error that occurred during memory configuration. 140 | message PbLogEventGenericMemoryConfigurationError { 141 | enum Type { 142 | TYPE_UNKNOWN = 0; 143 | // Memory modules disabled due to a miscellaneous error (Memory BIST etc.) 144 | TYPE_DISABLED_DUE_TO_ERROR = 1; 145 | // Memory modules disabled due to an invalid memory population or due to 146 | // modules that aren't accessible due to an error on modules on the same 147 | // channel. 148 | TYPE_DISABLED_DUE_TO_CONFIGURATION = 2; 149 | // Memory modules which failed training. 150 | TYPE_TRAINING_FAILURE = 3; 151 | // Memory modules which failed coarse delay calibration. 152 | TYPE_COARSE_DELAY_FAILURE = 4; 153 | // Memory modules which failed software based memory test. 154 | TYPE_MEMORY_TEST_FAILURE = 5; 155 | // Memory modules with SPD read failure. 156 | TYPE_SPD_FAILURE = 6; 157 | } 158 | optional Type type = 1; 159 | // List of memory module identifiers affected by the error 160 | // specified by the "type" field. 161 | repeated DeviceLocator device = 2; 162 | } 163 | 164 | // Memory error that occurred during memory operation. 165 | message PbLogEventGenericMemoryRuntimeError { 166 | enum Type { 167 | TYPE_UNKNOWN = 0; 168 | TYPE_CORRECTABLE = 1; 169 | TYPE_UNCORRECTABLE = 2; 170 | } 171 | // Type of error. 172 | optional Type type = 1; 173 | // Board specific identifier for the memory module that experienced an error. 174 | // This could be a DIMM slot number as described on the board's silkscreen. 175 | optional DeviceLocator device = 2; 176 | // Detailed memory location which experienced the error. 177 | optional uint64 physical_address = 3; 178 | optional uint32 rank = 4; 179 | optional uint32 bank = 5; 180 | optional uint32 row = 6; 181 | optional uint32 column = 7; 182 | } 183 | 184 | message OpenpowerFirmwareEvent { 185 | enum Source { 186 | SOURCE_ERRL = 0x0100; 187 | SOURCE_DEVFW = 0x0200; 188 | SOURCE_SCOM = 0x0300; 189 | SOURCE_XSCOM = 0x0400; 190 | SOURCE_INITSVC = 0x0500; 191 | SOURCE_PNOR = 0x0600; 192 | SOURCE_I2C = 0x0700; 193 | SOURCE_VFS = 0x0800; 194 | SOURCE_HWPF = 0x0900; 195 | SOURCE_FSI = 0x0A00; 196 | SOURCE_TARG = 0x0B00; 197 | SOURCE_HWAS = 0x0C00; 198 | SOURCE_FSISCOM = 0x0D00; 199 | SOURCE_EEPROM = 0x0E00; 200 | SOURCE_INTR = 0x0F00; 201 | SOURCE_TOD = 0x1000; 202 | SOURCE_MBOX = 0x1100; 203 | SOURCE_DUMP = 0x1200; 204 | SOURCE_CONSOLE = 0x1300; 205 | SOURCE_MDIA = 0x1400; 206 | SOURCE_TRACE = 0x1500; 207 | SOURCE_UTIL = 0x1600; 208 | SOURCE_ISTEP = 0x1700; 209 | SOURCE_ATTN = 0x1800; 210 | SOURCE_SCAN = 0x1900; 211 | SOURCE_RUNTIME = 0x1A00; 212 | SOURCE_KERNEL = 0x1B00; 213 | SOURCE_IBSCOM = 0x1C00; 214 | SOURCE_VPD = 0x1D00; 215 | SOURCE_SECURE = 0x1E00; 216 | SOURCE_IPC = 0x1F00; 217 | SOURCE_HSVC = 0x2000; 218 | SOURCE_DEVTREE = 0x2100; 219 | SOURCE_SBE = 0x2200; 220 | SOURCE_GPIO = 0x2300; 221 | SOURCE_LPC = 0x2400; 222 | SOURCE_IPMI = 0x2500; 223 | SOURCE_HTMGT = 0x2600; 224 | SOURCE_OCCC = 0x2A00; 225 | SOURCE_HBTRACE = 0x3100; 226 | SOURCE_PRDF = 0xE500; 227 | 228 | SOURCE_EC = 0x4100; 229 | 230 | /* Sapphire/Skiboot components */ 231 | SOURCE_OPAL_CODEUPDATE = 0x4355; /* CU */ 232 | SOURCE_OPAL_CONSOLE = 0x434E; /* CN */ 233 | SOURCE_OPAL_CEC = 0x4345; /* CE */ 234 | SOURCE_OPAL_CHIP = 0x4348; /* CH */ 235 | SOURCE_OPAL_ELOG = 0x454C; /* EL */ 236 | SOURCE_OPAL_NVRAM = 0x4E56; /* NV */ 237 | SOURCE_OPAL_RTC = 0x5254; /* RT */ 238 | SOURCE_OPAL_SURVEILLANCE = 0x5355; /* SU */ 239 | SOURCE_OPAL_SYSPARAM = 0x5350; /* SP */ 240 | SOURCE_OPAL_LPC = 0x4C50; /* LP */ 241 | SOURCE_OPAL_UART = 0x5541; /* UA */ 242 | SOURCE_OPAL_OCC = 0x4F43; /* OC */ 243 | SOURCE_OPAL_OP_PANEL = 0x4F50; /* OP */ 244 | SOURCE_OPAL_PHB3 = 0x5048; /* PH */ 245 | SOURCE_OPAL_PSI = 0x5053; /* PS */ 246 | SOURCE_OPAL_VPD = 0x5650; /* VP */ 247 | SOURCE_OPAL_XSCOM = 0x5853; /* XS */ 248 | SOURCE_OPAL_PCI = 0x5043; /* PC */ 249 | SOURCE_OPAL_MISC = 0x4D49; /* MI */ 250 | SOURCE_OPAL_ATTN = 0x4154; /* AT */ 251 | SOURCE_OPAL_MEM_ERR = 0x4D45; /* ME */ 252 | SOURCE_OPAL_CENTAUR = 0x4354; /* CT */ 253 | SOURCE_OPAL_MFSI = 0x4D46; /* MF */ 254 | SOURCE_OPAL_DUMP = 0x4455; /* DU */ 255 | SOURCE_OPAL_LED = 0x4C45; /* LE */ 256 | SOURCE_OPAL_SENSOR = 0x5345; /* SE */ 257 | SOURCE_OPAL_SLW = 0x534C; /* SL */ 258 | SOURCE_OPAL_FSP = 0x4650; /* FP */ 259 | SOURCE_OPAL_I2C = 0x4943; /* IC */ 260 | } 261 | enum EpubSubsystem { 262 | // Processor subsystem 263 | EPUB_PROCESSOR_SUBSYS = 0x10; 264 | EPUB_PROCESSOR_FRU = 0x11; 265 | EPUB_PROCESSOR_CHIP_CACHE = 0x12; 266 | EPUB_PROCESSOR_UNIT = 0x13; 267 | EPUB_PROCESSOR_BUS_CTL = 0x14; 268 | 269 | // Memory subsystem 270 | EPUB_MEMORY_SUBSYS = 0x20; 271 | EPUB_MEMORY_CONTROLLER = 0x21; 272 | EPUB_MEMORY_BUS = 0x22; 273 | EPUB_MEMORY_DIMM = 0x23; 274 | EPUB_MEMORY_FRU = 0x24; 275 | EPUB_EXTERNAL_CACHE = 0x25; 276 | 277 | EPUB_IO_SUBSYSTEM = 0x30; 278 | EPUB_IO_DEVICES = 0x40; 279 | 280 | // CEC Hardware 281 | EPUB_CEC_HDW_SUBSYS = 0x50; 282 | EPUB_CEC_HDW_VPD_INTF = 0x55; 283 | EPUB_CEC_HDW_I2C_DEVS = 0x56; 284 | EPUB_CEC_HDW_CHIP_INTF = 0x57; // includes JTAG, FSI, etc. 285 | EPUB_CEC_HDW_CLK_CTL = 0x58; 286 | EPUB_CEC_HDW_TOD_HDW = 0x5A; 287 | EPUB_CEC_HDW_SP_PHYP_INTF = 0x5C; 288 | 289 | // Power/Cooling subsystem 290 | EPUB_POWER_SUBSYS = 0x60; 291 | 292 | // Others 293 | EPUB_MISC_SUBSYS = 0x70; 294 | EPUB_MISC_TEST_TOOL = 0x72; 295 | EPUB_MISC_MULTIPLE_SUBSYS = 0x74; 296 | EPUB_MISC_UNKNOWN = 0x75; 297 | EPUB_MISC_INFORMATIONAL = 0x76; 298 | EPUB_SURVEILLANCE_ERR = 0x7A; 299 | 300 | // Platform Firmware 301 | EPUB_FIRMWARE_SUBSYS = 0x80; 302 | EPUB_FIRMWARE_SP = 0x81; 303 | EPUB_FIRMWARE_PHYP = 0x82; 304 | 305 | EPUB_FIRMWARE_HOSTBOOT = 0x8A; 306 | EPUB_FIRMWARE_OPAL = 0x8B; 307 | EPUB_UNKNOWN = 0xFF; 308 | } 309 | optional Source source = 1; 310 | 311 | optional EpubSubsystem epub_subsystem = 2; 312 | 313 | optional string description = 3; 314 | 315 | optional uint32 module_id = 4; 316 | optional string module_name = 5; 317 | 318 | optional uint32 reason_code = 6; 319 | optional string reason_string = 7; 320 | 321 | message UserData { 322 | required uint64 data = 1; 323 | required string description = 2; 324 | } 325 | repeated UserData user_data = 8 [(nanopb).max_count = 3]; 326 | 327 | repeated DeviceLocator target = 9 [(nanopb).max_count = 4]; 328 | } 329 | 330 | message Event { 331 | // Global list of vendors with events specific for their systems. 332 | // Modifications to this enumeration should be shared with other 333 | // vendors so that generic event log parsing tools know how to handle vendor 334 | // specific events. 335 | enum Vendor { 336 | VENDOR_GENERIC = 0; 337 | VENDOR_GOOGLE = 1; 338 | VENDOR_IBM = 2; 339 | } 340 | // Field used to determine which specialized event protocol buffer to use 341 | // to parse the event. 342 | required Vendor vendor = 1 [default = VENDOR_GENERIC]; 343 | // Generic type of event. 344 | optional event_type type = 2 [default = TYPE_UNKNOWN]; 345 | // Time of the event in seconds since unix epoch (UTC). 346 | optional fixed32 timestamp = 3; 347 | // Count of the boot attempt. This will typically increase after each 348 | // system reset / power cycle. 349 | optional uint32 boot_number = 4; 350 | // Generic key / value strings that are associated with the event. 351 | repeated EventData data = 5 [(nanopb).max_count = 5]; 352 | // One of the following messages may be set. 353 | optional PbLogEventGenericShutdown generic_shutdown = 100; 354 | optional PbLogEventGenericReset generic_reset = 101; 355 | optional PbLogEventGenericMemoryConfigurationError 356 | generic_memory_configuration_error = 102; 357 | optional PbLogEventGenericMemoryRuntimeError 358 | generic_memory_runtime_error = 103; 359 | optional OpenpowerFirmwareEvent 360 | openpower_firmware_event = 400; 361 | } 362 | -------------------------------------------------------------------------------- /src/event.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | static bool string_encoder(pb_ostream_t *stream, const pb_field_t *field, 28 | void *const *arg) { 29 | const char *str = (const char *)*arg; 30 | if (str == NULL) { 31 | return true; 32 | } 33 | if (!pb_encode_tag_for_field(stream, field)) { 34 | return false; 35 | } 36 | return pb_encode_string(stream, (uint8_t *)str, strlen(str)); 37 | } 38 | 39 | static bool string_decoder(pb_istream_t *stream, 40 | const pb_field_t *field, // NOLINT 41 | void **arg) { 42 | int strsize = stream->bytes_left; 43 | char *str = (char *)malloc(strsize + 1); 44 | if (str == NULL) { 45 | return false; 46 | } 47 | 48 | pb_read(stream, (uint8_t *)str, strsize); 49 | str[strsize] = '\0'; 50 | if (*arg) { 51 | free(*arg); 52 | } 53 | *arg = str; 54 | return true; 55 | } 56 | 57 | int event_encode(const pblog_Event *event, void *buf, size_t len) { 58 | pb_ostream_t stream = pb_ostream_from_buffer((uint8_t *)buf, len); 59 | 60 | /* Now encode it and check if we succeeded. */ 61 | if (pb_encode(&stream, pblog_Event_fields, event)) { 62 | return stream.bytes_written; 63 | } 64 | 65 | PBLOG_ERRF("event encode error: %s\n", PB_GET_ERROR(&stream)); 66 | return PBLOG_ERR_INVALID; 67 | } 68 | 69 | int event_decode(const void *buf, size_t len, pblog_Event *event) { 70 | pb_istream_t stream = pb_istream_from_buffer((uint8_t *)buf, len); 71 | int i = 0; 72 | 73 | for (i = 0; i < pb_arraysize(pblog_Event, data); ++i) { 74 | event->data[i].key.funcs.decode = string_decoder; 75 | event->data[i].value.funcs.decode = string_decoder; 76 | } 77 | if (pb_decode(&stream, pblog_Event_fields, event)) { 78 | return 0; 79 | } 80 | 81 | PBLOG_ERRF("event decode error: %s\n", PB_GET_ERROR(&stream)); 82 | return PBLOG_ERR_INVALID; 83 | } 84 | 85 | static bool nul_write_callback(pb_ostream_t *stream, const uint8_t *buf, 86 | size_t count) { 87 | (void)stream; 88 | (void)buf; 89 | (void)count; 90 | return true; 91 | } 92 | 93 | int event_size(const pblog_Event *event) { 94 | pb_ostream_t stream; 95 | stream.callback = &nul_write_callback; 96 | stream.max_size = INT_MAX; 97 | 98 | if (pb_encode(&stream, pblog_Event_fields, event)) { 99 | return stream.bytes_written; 100 | } 101 | 102 | return PBLOG_ERR_INVALID; 103 | } 104 | 105 | void event_init(pblog_Event *event) { memset(event, 0, sizeof(*event)); } 106 | 107 | void event_free(pblog_Event *event) { 108 | int i = 0; 109 | for (i = 0; i < event->data_count; ++i) { 110 | free(event->data[i].key.arg); 111 | event->data[i].key.arg = NULL; 112 | free(event->data[i].value.arg); 113 | event->data[i].value.arg = NULL; 114 | } 115 | } 116 | 117 | void event_add_kv_data(pblog_Event *event, const char *key, const char *value) { 118 | if (event->data_count >= pb_arraysize(pblog_Event, data)) { 119 | return; 120 | } 121 | 122 | event->data[event->data_count].key.arg = strdup(key); 123 | event->data[event->data_count].key.funcs.encode = string_encoder; 124 | event->data[event->data_count].value.arg = strdup(value); 125 | event->data[event->data_count].value.funcs.encode = string_encoder; 126 | 127 | event->data_count++; 128 | } 129 | -------------------------------------------------------------------------------- /src/file.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | static int file_read(pblog_flash_ops *ops, int offset, size_t len, void *data) { 28 | const char *filename = ops->priv; 29 | 30 | int fd = open(filename, O_RDONLY); 31 | if (fd < 0) { 32 | return -1; 33 | } 34 | 35 | int rc = pread(fd, data, len, offset); 36 | close(fd); 37 | return rc; 38 | } 39 | 40 | static int file_write(pblog_flash_ops *ops, int offset, size_t len, 41 | const void *data) { 42 | const char *filename = ops->priv; 43 | 44 | int fd = open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); 45 | if (fd < 0) { 46 | return -1; 47 | } 48 | 49 | int rc = pwrite(fd, data, len, offset); 50 | close(fd); 51 | return rc; 52 | } 53 | 54 | static int file_erase(pblog_flash_ops *ops, int offset, size_t len) { 55 | unsigned char *erase_buf = malloc(len); 56 | memset(erase_buf, 0xff, len); 57 | int rc = ops->write(ops, offset, len, erase_buf); 58 | free(erase_buf); 59 | return rc == len ? 0 : -1; 60 | } 61 | 62 | struct pblog_flash_ops pblog_file_ops = { 63 | .read = &file_read, 64 | .write = &file_write, 65 | .erase = &file_erase, 66 | .priv = NULL /* filename to be set on instantiation */ 67 | }; 68 | -------------------------------------------------------------------------------- /src/mem.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | static int mem_read(pblog_flash_ops *ops, int offset, size_t len, void *data) { 23 | unsigned char *addr = ops->priv; 24 | 25 | memcpy(data, addr + offset, len); 26 | return len; 27 | } 28 | 29 | static int mem_write(pblog_flash_ops *ops, int offset, size_t len, 30 | const void *data) { 31 | unsigned char *addr = ops->priv; 32 | 33 | memcpy(addr + offset, data, len); 34 | return len; 35 | } 36 | 37 | static int mem_erase(pblog_flash_ops *ops, int offset, size_t len) { 38 | unsigned char *addr = ops->priv; 39 | 40 | memset(addr + offset, 0xff, len); 41 | return 0; 42 | } 43 | 44 | struct pblog_flash_ops pblog_mem_ops = { 45 | .read = &mem_read, 46 | .write = &mem_write, 47 | .erase = &mem_erase, 48 | .priv = NULL /* set to memory address base upon instantiation */ 49 | }; 50 | -------------------------------------------------------------------------------- /src/nvram.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* NVRAM basic support */ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define MAX_NVRAM_ENTRIES 1024 28 | #define MAX_NVRAM_ENTRY_SIZE 1024 29 | static const char kDelimiter = '\0'; 30 | 31 | static int nvram_addentry(struct nvram *nvram, const char *key, 32 | const char *data, size_t data_len) { 33 | char entry_buf[MAX_NVRAM_ENTRY_SIZE]; 34 | int rc; 35 | int key_len = strlen(key); 36 | int entry_len = key_len + sizeof(kDelimiter) + data_len; 37 | if (entry_len > sizeof(entry_buf)) { 38 | return -1; 39 | } 40 | 41 | memcpy(entry_buf, key, key_len); 42 | entry_buf[key_len] = kDelimiter; 43 | memcpy(entry_buf + key_len + sizeof(kDelimiter), data, data_len); 44 | 45 | rc = nvram->ri->append(nvram->ri, entry_len, entry_buf); 46 | return rc < 0 ? rc : 0; 47 | } 48 | 49 | static int nvram_parse_entry(const char *entry, size_t len, char **key, 50 | char **data) { 51 | int key_len, data_len; 52 | for (key_len = 0; 53 | key_len < len && 54 | memcmp(&entry[key_len], &kDelimiter, sizeof(kDelimiter)) != 0; 55 | ++key_len) { 56 | } 57 | *key = malloc(key_len + sizeof('\0')); 58 | memcpy(*key, entry, key_len); 59 | (*key)[key_len] = '\0'; 60 | 61 | data_len = len - key_len - sizeof(kDelimiter); 62 | if (data_len > 0) { 63 | *data = malloc(data_len + sizeof('\0')); 64 | memcpy(*data, entry + key_len + sizeof(kDelimiter), data_len); 65 | (*data)[data_len] = '\0'; 66 | } else { 67 | *data = NULL; 68 | } 69 | return data_len; 70 | } 71 | 72 | static const struct nvram_entry *find_key(const struct nvram_entry *entries, 73 | const char *key) { 74 | for (; entries->key != NULL; ++entries) { 75 | if (strcmp(entries->key, key) == 0) { 76 | return entries; 77 | } 78 | } 79 | return NULL; 80 | } 81 | 82 | void nvram_entry_free(struct nvram_entry *entry) { 83 | free(entry->key); 84 | free(entry->data); 85 | } 86 | 87 | static int nvram_enumerate(struct nvram *nvram, struct nvram_entry **entries) { 88 | int num_entries = 0; 89 | int offset = 0; 90 | int array_size = 0; 91 | 92 | *entries = NULL; 93 | do { 94 | char entry_buf[MAX_NVRAM_ENTRY_SIZE]; 95 | size_t len = sizeof(entry_buf); 96 | int next_offset; 97 | int rc; 98 | int data_len; 99 | 100 | rc = nvram->ri->read_record(nvram->ri, offset, &next_offset, &len, 101 | entry_buf); 102 | if (rc < 0) { 103 | return rc; 104 | } 105 | if (next_offset == 0) { 106 | break; 107 | } 108 | offset += next_offset; 109 | num_entries++; 110 | if (array_size < num_entries) { 111 | array_size = num_entries * 2 + 1; // grow exponentially for efficiency 112 | *entries = realloc(*entries, array_size * sizeof(struct nvram_entry)); 113 | } 114 | data_len = 115 | nvram_parse_entry(entry_buf, len, &(*entries)[num_entries - 1].key, 116 | &(*entries)[num_entries - 1].data); 117 | (*entries)[num_entries - 1].data_len = data_len; 118 | } while (1); 119 | 120 | *entries = realloc(*entries, (num_entries + 1) * sizeof(struct nvram_entry)); 121 | (*entries)[num_entries].key = NULL; 122 | (*entries)[num_entries].data = NULL; 123 | (*entries)[num_entries].data_len = 0; 124 | 125 | return 0; 126 | } 127 | 128 | void nvram_list_free(struct nvram_entry **entries) { 129 | struct nvram_entry *entry; 130 | for (entry = *entries; entry && entry->key != NULL; entry++) { 131 | nvram_entry_free(entry); 132 | } 133 | free(*entries); 134 | } 135 | 136 | const struct nvram_entry *nvram_list_find(const struct nvram_entry *entries, 137 | const char *key) { 138 | const struct nvram_entry *entry = entries; 139 | const struct nvram_entry *lastentry = NULL; 140 | 141 | while ((entry = find_key(entry, key)) != NULL) { 142 | lastentry = entry; 143 | entry++; 144 | } 145 | 146 | return lastentry; 147 | } 148 | 149 | static int nvram_list_count(struct nvram_entry *entries) { 150 | int num_entries = 0; 151 | for (; entries->key != NULL; ++entries, ++num_entries) { 152 | } 153 | return num_entries; 154 | } 155 | 156 | static void nvram_list_compact(struct nvram_entry **entries, 157 | const char *new_key) { 158 | int num_entries = nvram_list_count(*entries); 159 | int index; 160 | 161 | for (index = 0; index < num_entries;) { 162 | struct nvram_entry *entry = &(*entries)[index]; 163 | int empty_data = entry->data == NULL; 164 | int newer_key_exists = find_key(entry + 1, entry->key) != NULL; 165 | int matches_new_key = new_key && strcmp(entry->key, new_key) == 0; 166 | // If any of these are true then this entry is old/overwritten, 167 | // remove it from the array by the simple "copy-back" algorithm. 168 | if (empty_data || newer_key_exists || matches_new_key) { 169 | nvram_entry_free(entry); 170 | memmove(entry, entry + 1, 171 | (num_entries - index) * sizeof(struct nvram_entry)); 172 | num_entries--; 173 | } else { 174 | index++; 175 | } 176 | } 177 | } 178 | 179 | static int nvram_compact(struct nvram *nvram, const char *new_key) { 180 | // Read all of the NVRAM entries into memory. 181 | struct nvram_entry *entry; 182 | struct nvram_entry *entries = NULL; 183 | int num_new = 0, num_old = 0; 184 | int rc = nvram_enumerate(nvram, &entries); 185 | if (rc < 0) { 186 | return rc; 187 | } 188 | 189 | num_old = nvram_list_count(entries); 190 | if (num_old < 2) { 191 | rc = 0; 192 | goto out; 193 | } 194 | 195 | // Compact in memory 196 | nvram_list_compact(&entries, new_key); 197 | num_new = nvram_list_count(entries); 198 | if (num_new >= num_old) { 199 | PBLOG_ERRF("could not free any entries"); 200 | rc = -1; 201 | goto out; 202 | } 203 | 204 | // Clear the NVRAM storage. 205 | rc = nvram->ri->clear(nvram->ri, 0); 206 | if (rc < 0) { 207 | goto out; 208 | } 209 | 210 | // Write out the more compact version. 211 | for (entry = entries; entry->key != NULL; ++entry) { 212 | rc = nvram_addentry(nvram, entry->key, entry->data, entry->data_len); 213 | if (rc < 0) { 214 | PBLOG_ERRF("failure adding key %s\n", entry->key); 215 | } 216 | } 217 | 218 | out: 219 | // Free our allocated entries. 220 | nvram_list_free(&entries); 221 | 222 | return (rc >= 0) ? num_old - num_new : rc; 223 | } 224 | 225 | static int nvram_set(struct nvram *nvram, const char *key, const char *data, 226 | size_t data_len) { 227 | int length = strlen(key) + data_len + sizeof(kDelimiter); 228 | if (length * 2 > nvram->ri->get_free_space(nvram->ri)) { 229 | // Need to free up some room. 230 | int num_freed = nvram_compact(nvram, key); 231 | PBLOG_DPRINTF("freed %d NVRAM entries\n", num_freed); 232 | if (num_freed < 0) { 233 | return num_freed; 234 | } 235 | } 236 | 237 | return nvram_addentry(nvram, key, data, data_len); 238 | } 239 | 240 | static int nvram_lookup(struct nvram *nvram, const char *key, char *data, 241 | size_t max_data_len) { 242 | int data_len = -1; 243 | int offset = 0; 244 | do { 245 | char entry_buf[MAX_NVRAM_ENTRY_SIZE]; 246 | size_t entry_len = sizeof(entry_buf); 247 | struct nvram_entry entry; 248 | int next_offset; 249 | int rc = nvram->ri->read_record(nvram->ri, offset, &next_offset, &entry_len, 250 | entry_buf); 251 | if (rc < 0 || next_offset == 0) { 252 | break; 253 | } 254 | offset += next_offset; 255 | 256 | data_len = nvram_parse_entry(entry_buf, entry_len, &entry.key, &entry.data); 257 | if (data_len > 0 && data_len <= (max_data_len - sizeof('\0')) && 258 | strcmp(key, entry.key) == 0) { 259 | memcpy(data, entry.data, data_len); 260 | } 261 | nvram_entry_free(&entry); 262 | /* Keep going, we may find a newer version of this key */ 263 | } while (1); 264 | 265 | return data_len; 266 | } 267 | 268 | static int nvram_unset(struct nvram *nvram, const char *key) { 269 | return nvram->set(nvram, key, "", 0); 270 | } 271 | 272 | // Read all of the valid NVRAM entries into memory. 273 | static int nvram_list(struct nvram *nvram, struct nvram_entry **entries) { 274 | int rc = nvram_enumerate(nvram, entries); 275 | if (rc < 0) { 276 | return rc; 277 | } 278 | 279 | nvram_list_compact(entries, NULL); 280 | return 0; 281 | } 282 | 283 | // Clear and reinitialize the entire NVRAM storage. 284 | static int nvram_clear(struct nvram *nvram) { 285 | int rc = nvram->ri->clear(nvram->ri, 0); 286 | if (rc < 0) { 287 | return rc; 288 | } 289 | return 0; 290 | } 291 | 292 | void pblog_nvram_init(struct nvram *nvram, struct record_intf *ri) { 293 | nvram->ri = ri; 294 | 295 | nvram->lookup = nvram_lookup; 296 | nvram->set = nvram_set; 297 | nvram->unset = nvram_unset; 298 | nvram->list = nvram_list; 299 | nvram->clear = nvram_clear; 300 | } 301 | 302 | void pblog_nvram_free(struct nvram *nvram) { record_intf_free(nvram->ri); } 303 | 304 | #ifdef NVRAM_CMDLINE_APP 305 | #include 306 | #include 307 | #include 308 | 309 | #include 310 | 311 | int main(int argc, char *argv[]) { 312 | if (argc < 2) { 313 | fprintf(stderr, "usage: %s [key] [data]\n", argv[0]); 314 | return 1; 315 | } 316 | 317 | const char *filename = argv[1]; 318 | 319 | char *key = NULL; 320 | if (argc >= 3) { 321 | key = argv[2]; 322 | } 323 | 324 | char *data = NULL; 325 | if (argc >= 4) { 326 | data = argv[3]; 327 | } 328 | 329 | int rc; 330 | 331 | pblog_file_ops.priv = (void *)filename; 332 | 333 | struct record_region regions[1]; 334 | regions[0].offset = 0; 335 | regions[0].size = 0xff; 336 | 337 | struct record_intf ri; 338 | record_intf_init(&ri, regions, 1, &pblog_file_ops); 339 | 340 | struct nvram nvram; 341 | nvram_init(&nvram, &ri); 342 | 343 | if (!key) { 344 | struct nvram_entry *entries; 345 | rc = nvram.list(&nvram, &entries); 346 | if (rc < 0) { 347 | return rc; 348 | } 349 | struct nvram_entry *entry = entries; 350 | for (; entry->key != NULL; entry++) { 351 | fprintf(stdout, "%s=%s\n", entry->key, entry->data); 352 | } 353 | nvram_list_free(&entries); 354 | return 0; 355 | } else if (!data) { 356 | data = malloc(MAX_NVRAM_ENTRY_SIZE); 357 | rc = nvram.lookup(&nvram, key, data, MAX_NVRAM_ENTRY_SIZE); 358 | if (rc < 0) { 359 | return rc; 360 | } 361 | fprintf(stdout, "%s=%s\n", key, data); 362 | free(data); 363 | return rc; 364 | } else { 365 | rc = nvram.set(&nvram, key, data, strlen(data)); 366 | return rc; 367 | } 368 | 369 | return 0; 370 | } 371 | #endif 372 | -------------------------------------------------------------------------------- /src/pblog.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* Base support for reading/writing of protobuf log events */ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | struct pblog_metadata { 30 | struct record_intf *flash_ri; 31 | struct record_intf *mem_ri; 32 | int allow_clear_on_add; 33 | }; 34 | 35 | static int sync_events(struct record_intf *source, struct record_intf *dest); 36 | 37 | static int write_event(struct pblog *pblog, pblog_Event *event) { 38 | struct pblog_metadata *meta = pblog->priv; 39 | unsigned char event_buf[PBLOG_MAX_EVENT_SIZE]; 40 | int rc; 41 | int encoded_size; 42 | 43 | // Add current timestamp and bootnum if not set. 44 | if (!event->has_boot_number && pblog->get_current_bootnum) { 45 | event->boot_number = pblog->get_current_bootnum(pblog); 46 | event->has_boot_number = 1; 47 | } 48 | if (!event->has_timestamp && pblog->get_time_now) { 49 | event->timestamp = pblog->get_time_now(pblog); 50 | event->has_timestamp = 1; 51 | } 52 | 53 | // Encode the event and determine the size. 54 | encoded_size = event_encode(event, event_buf, sizeof(event_buf)); 55 | if (encoded_size < 0) { 56 | return encoded_size; 57 | } 58 | 59 | rc = meta->flash_ri->append(meta->flash_ri, encoded_size, event_buf); 60 | if (rc < 0) { 61 | PBLOG_ERRF("pblog: failed to write event to flash\n"); 62 | return rc; 63 | } 64 | if (meta->mem_ri) { 65 | rc = meta->mem_ri->append(meta->mem_ri, encoded_size, event_buf); 66 | if (rc < 0) { 67 | PBLOG_ERRF("pblog: failed to write event to memory\n"); 68 | return rc; 69 | } 70 | } 71 | 72 | return PBLOG_SUCCESS; 73 | } 74 | 75 | static int write_clear_event(struct pblog *pblog) { 76 | pblog_Event *event = (pblog_Event *)malloc(sizeof(pblog_Event)); 77 | int ret; 78 | 79 | if (event == NULL) { 80 | return PBLOG_ERR_NO_SPACE; 81 | } 82 | 83 | event_init(event); 84 | event->has_type = true; 85 | event->type = pblog_TYPE_LOG_CLEARED; 86 | ret = write_event(pblog, event); 87 | event_free(event); 88 | free(event); 89 | return ret; 90 | } 91 | 92 | // Compacts the log by removing the old entries. 93 | static int log_compact(struct pblog *pblog) { 94 | struct pblog_metadata *meta = pblog->priv; 95 | 96 | // Clear the oldest flash region. 97 | int rc = meta->flash_ri->clear(meta->flash_ri, 1); 98 | if (rc < 0) { 99 | return rc; 100 | } 101 | 102 | // Clear the entire mem log. 103 | if (meta->mem_ri) { 104 | rc = meta->mem_ri->clear(meta->mem_ri, 0); 105 | if (rc < 0) { 106 | return rc; 107 | } 108 | 109 | // Sync flash to mem. 110 | rc = sync_events(meta->flash_ri, meta->mem_ri); 111 | if (rc < 0) { 112 | return rc; 113 | } 114 | } 115 | 116 | // Log a clear event (to both logs). 117 | return write_clear_event(pblog); 118 | } 119 | 120 | static enum pblog_status pblog_add_event(struct pblog *pblog, 121 | pblog_Event *event) { 122 | struct pblog_metadata *meta = pblog->priv; 123 | int rc = write_event(pblog, event); 124 | 125 | if (meta->allow_clear_on_add && rc == PBLOG_ERR_NO_SPACE) { 126 | rc = log_compact(pblog); 127 | if (rc < 0) { 128 | PBLOG_ERRF("log full, failed to free space"); 129 | return rc; 130 | } 131 | 132 | rc = write_event(pblog, event); 133 | } 134 | 135 | return rc; 136 | } 137 | 138 | static enum pblog_status pblog_for_each_event(struct pblog *pblog, 139 | pblog_event_cb callback, 140 | pblog_Event *event, void *priv) { 141 | struct pblog_metadata *meta = pblog->priv; 142 | // Prefer reading from the memory-based log if available. 143 | struct record_intf *ri = meta->mem_ri ? meta->mem_ri : meta->flash_ri; 144 | int offset = 0; 145 | 146 | while (1) { 147 | size_t len = PBLOG_MAX_EVENT_SIZE; 148 | unsigned char event_buf[PBLOG_MAX_EVENT_SIZE]; 149 | int next_offset = 0; 150 | int event_valid; 151 | 152 | int rc = ri->read_record(ri, offset, &next_offset, &len, event_buf); 153 | if (rc < 0 && rc != PBLOG_ERR_CHECKSUM) { 154 | return rc; 155 | } 156 | if (next_offset == 0) { // end of log? 157 | break; 158 | } 159 | 160 | event_valid = rc != PBLOG_ERR_CHECKSUM; 161 | // Decode the event. 162 | rc = event_decode(event_buf, len, event); 163 | if (rc < 0) { 164 | event_valid = 0; 165 | } 166 | 167 | // Notify callback. 168 | if (callback && (*callback)(event_valid, event, priv) != PBLOG_SUCCESS) { 169 | break; 170 | } 171 | 172 | offset += next_offset; 173 | } 174 | 175 | return PBLOG_SUCCESS; 176 | } 177 | 178 | static enum pblog_status pblog_for_each_event_internal(struct pblog *pblog, 179 | pblog_event_cb callback, 180 | void *priv) { 181 | enum pblog_status status; 182 | pblog_Event *event = (pblog_Event *)malloc(sizeof(pblog_Event)); 183 | if (event == NULL) { 184 | return PBLOG_ERR_NO_SPACE; 185 | } 186 | event_init(event); 187 | status = pblog_for_each_event(pblog, callback, event, priv); 188 | event_free(event); 189 | free(event); 190 | return status; 191 | } 192 | 193 | static enum pblog_status pblog_clear(struct pblog *pblog) { 194 | struct pblog_metadata *meta = pblog->priv; 195 | // Erase the data. 196 | int rc = meta->flash_ri->clear(meta->flash_ri, 0); 197 | if (rc < 0) { 198 | PBLOG_ERRF("pblog: flash clear error\n"); 199 | return rc; 200 | } 201 | if (meta->mem_ri) { 202 | rc = meta->mem_ri->clear(meta->mem_ri, 0); 203 | if (rc < 0) { 204 | PBLOG_ERRF("pblog: mem clear error\n"); 205 | return rc; 206 | } 207 | } 208 | 209 | // Log a clear event. 210 | return write_clear_event(pblog); 211 | } 212 | 213 | // Synchronizes events between 2 record sources. Skips corrupt/invalid 214 | // records. 215 | static int sync_events(struct record_intf *source, struct record_intf *dest) { 216 | int offset = 0; 217 | 218 | while (1) { 219 | size_t len = PBLOG_MAX_EVENT_SIZE; 220 | unsigned char event_buf[PBLOG_MAX_EVENT_SIZE]; 221 | int next_offset = 0; 222 | int rc = source->read_record(source, offset, &next_offset, &len, event_buf); 223 | if (next_offset == 0) { 224 | break; 225 | } 226 | 227 | if (rc >= 0) { 228 | rc = dest->append(dest, len, event_buf); 229 | if (rc < 0) { 230 | PBLOG_ERRF("pblog: failed to sync event to dest\n"); 231 | return rc; 232 | } 233 | } else { 234 | PBLOG_DPRINTF("pblog: skipping corrupt record at offset %d\n", offset); 235 | } 236 | 237 | offset += next_offset; 238 | } 239 | 240 | return PBLOG_SUCCESS; 241 | } 242 | 243 | static struct record_intf *pblog_init_memlog(void *addr, size_t size, 244 | struct record_intf *flash_ri) { 245 | struct record_region mem_region; 246 | struct record_intf *mem_ri; 247 | int rc; 248 | 249 | pblog_mem_ops.priv = addr; 250 | mem_region.offset = 0; 251 | mem_region.size = size; 252 | mem_ri = (struct record_intf *)malloc(sizeof(struct record_intf)); 253 | record_intf_init(mem_ri, &mem_region, 1, &pblog_mem_ops); 254 | 255 | // Initialize the contents of the mem log with the flash log. 256 | rc = sync_events(flash_ri, mem_ri); 257 | if (rc < 0) { 258 | PBLOG_ERRF("pblog: failed to initialize memlog\n"); 259 | } 260 | 261 | return mem_ri; 262 | } 263 | 264 | static enum pblog_status count_events_callback(int valid, 265 | const pblog_Event *event, 266 | void *priv) { 267 | int *count = priv; 268 | (void)event; 269 | if (valid) { 270 | (*count)++; 271 | } 272 | return PBLOG_SUCCESS; 273 | } 274 | 275 | // Check if this is a newly initialized log due to first time use or corruption. 276 | // If this is the first time write a clear event with the current timestamp. 277 | static int pblog_first_time_init(struct pblog *pblog) { 278 | int count = 0; 279 | int rc = pblog_for_each_event_internal(pblog, count_events_callback, &count); 280 | if (rc < 0) { 281 | return rc; 282 | } 283 | if (count == 0) { 284 | PBLOG_DPRINTF("pblog first time init\n"); 285 | rc = write_clear_event(pblog); 286 | if (rc < 0) { 287 | return rc; 288 | } 289 | count = 1; 290 | } 291 | 292 | return count; 293 | } 294 | 295 | int pblog_init(struct pblog *pblog, int allow_clear_on_add, 296 | struct record_intf *flash_ri, void *mem_addr, size_t mem_size) { 297 | struct pblog_metadata *meta = malloc(sizeof(struct pblog_metadata)); 298 | if (meta == NULL) { 299 | return PBLOG_ERR_NO_SPACE; 300 | } 301 | 302 | meta->flash_ri = flash_ri; 303 | meta->allow_clear_on_add = allow_clear_on_add; 304 | if (mem_addr != NULL) { 305 | meta->mem_ri = pblog_init_memlog(mem_addr, mem_size, flash_ri); 306 | } else { 307 | meta->mem_ri = NULL; 308 | } 309 | 310 | pblog->priv = meta; 311 | 312 | pblog->add_event = pblog_add_event; 313 | pblog->for_each_event = pblog_for_each_event; 314 | pblog->clear = pblog_clear; 315 | 316 | return pblog_first_time_init(pblog); 317 | } 318 | 319 | void pblog_free(struct pblog *pblog) { 320 | struct pblog_metadata *meta = pblog->priv; 321 | record_intf_free(meta->mem_ri); 322 | free(meta->mem_ri); 323 | pblog_mem_ops.priv = NULL; 324 | 325 | free(meta); 326 | } 327 | -------------------------------------------------------------------------------- /src/record.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | unsigned char record_checksum(const void *buf, size_t len) { 24 | unsigned char csum = 0; 25 | const unsigned char *p = buf; 26 | for (; p < (unsigned char *)(buf) + len; p++) { 27 | csum += *p; 28 | } 29 | return csum; 30 | } 31 | 32 | struct log_metadata { 33 | struct record_region *regions; 34 | int num_regions; 35 | int used_regions; // the number of regions in use 36 | int head_region; // the first region (beginning of records) 37 | int next_sequence; // next sequence number to use 38 | struct pblog_flash_ops *flash; 39 | }; 40 | 41 | const uint8_t record_magic[4] = {'R', 'E', 'C', 0xfe}; 42 | 43 | // Helper to return the i-th region starting from the head region. 44 | struct record_region *region_at(struct log_metadata *meta, int i) { 45 | if (i < 0 || i >= meta->num_regions) { 46 | return NULL; 47 | } 48 | 49 | return &meta->regions[(meta->head_region + i) % meta->num_regions]; 50 | } 51 | 52 | // Reads a record within a region. 53 | // Args: 54 | // offset: byte offset within region 55 | // next_offset: set to the offset of the next record 56 | // len: maximum data length to read, updated with actual read data length 57 | // data: data buffer to write 58 | static int region_read_record(struct log_metadata *meta, 59 | struct record_region *region, int offset, 60 | int *next_offset, size_t *len, void *data) { 61 | int rc; 62 | record_header header; 63 | int length; 64 | int data_length; 65 | 66 | PBLOG_DPRINTF("read rseq %d, offset %d\n", region->sequence, offset); 67 | 68 | *next_offset = 0; 69 | 70 | if (offset > region->size - sizeof(header)) { 71 | return PBLOG_ERR_INVALID; 72 | } 73 | 74 | // Read in the record header. 75 | rc = meta->flash->read(meta->flash, region->offset + offset, sizeof(header), 76 | &header); 77 | if (rc != sizeof(header)) { 78 | return rc < 0 ? rc : PBLOG_ERR_IO; 79 | } 80 | 81 | // End of log? 82 | length = header.length_lsb | (header.length_msb << 8); 83 | if (length == 0 || length == 0xffff) { 84 | if (len) { 85 | *len = 0; 86 | } 87 | return PBLOG_SUCCESS; 88 | } 89 | 90 | data_length = length - sizeof(header); 91 | 92 | if (length > region->size - offset) { 93 | PBLOG_ERRF("bad record length found at offset %d: %d\n", offset, length); 94 | return PBLOG_ERR_INVALID; 95 | } 96 | 97 | *next_offset = length; 98 | if (len != NULL) { 99 | // Check we can fit in the user-provided buffer. 100 | if (data_length > *len) { 101 | // Return the required length to the user so they can resize the buffer. 102 | *len = data_length; 103 | return PBLOG_ERR_NO_SPACE; 104 | } 105 | *len = data_length; 106 | 107 | // Read in the record data. 108 | if (data != NULL) { 109 | unsigned char checksum; 110 | rc = meta->flash->read(meta->flash, 111 | region->offset + offset + sizeof(header), 112 | data_length, data); 113 | if (rc != data_length) { 114 | *len = rc > 0 ? rc : 0; 115 | return rc < 0 ? rc : PBLOG_ERR_IO; 116 | } 117 | checksum = record_checksum(&header, sizeof(header)) + 118 | record_checksum(data, data_length); 119 | if (checksum != 0) { 120 | PBLOG_ERRF("checksum failure record off:%d, checksum: %d\n", offset, 121 | checksum); 122 | rc = PBLOG_ERR_CHECKSUM; 123 | } 124 | } 125 | } 126 | 127 | return rc < 0 ? rc : PBLOG_SUCCESS; 128 | } 129 | 130 | static int log_read_record(struct record_intf *ri, int offset, int *next_offset, 131 | size_t *len, void *data) { 132 | struct log_metadata *meta = ri->priv; 133 | 134 | // Determine the region that contains this offset. 135 | int i; 136 | struct record_region *region = NULL; 137 | for (i = 0; i < meta->used_regions; ++i) { 138 | // Account for the region header at the beginning of each region. 139 | offset += sizeof(struct region_header); 140 | 141 | region = region_at(meta, i); 142 | if (offset < region->used_size) { 143 | break; 144 | } 145 | offset -= region->used_size; 146 | } 147 | if (region == NULL) { 148 | return PBLOG_ERR_INVALID; 149 | } 150 | if (i >= meta->used_regions) { 151 | // Check for end of log (reading last record one past end). 152 | // Return success in that case, set next_offset to 0. 153 | if (offset == 0 || offset == region->used_size) { 154 | *next_offset = 0; 155 | if (len) { 156 | *len = 0; 157 | } 158 | return PBLOG_SUCCESS; 159 | } 160 | return PBLOG_ERR_INVALID; 161 | } 162 | 163 | return region_read_record(meta, region, offset, next_offset, len, data); 164 | } 165 | 166 | static int region_append(struct log_metadata *meta, 167 | struct record_region *region, size_t len, 168 | const void *data) { 169 | int rc; 170 | record_header header; 171 | 172 | int record_size = len + sizeof(record_header); 173 | if (record_size > (region->size - region->used_size)) { 174 | PBLOG_ERRF("region rseq %d full\n", region->sequence); 175 | return PBLOG_ERR_NO_SPACE; 176 | } 177 | 178 | // Update and write out the header. 179 | header.length_lsb = record_size & 0xff; 180 | header.length_msb = (record_size >> 8) & 0xff; 181 | // The checksum covers the entire record including the header. 182 | header.checksum = 0; 183 | header.checksum = 184 | -(record_checksum(&header, sizeof(header)) + record_checksum(data, len)); 185 | 186 | // Write out the header then record. 187 | rc = meta->flash->write(meta->flash, region->offset + region->used_size, 188 | sizeof(header), &header); 189 | if (rc != sizeof(header)) { 190 | PBLOG_ERRF("header write error: %d\n", rc); 191 | return rc < 0 ? rc : PBLOG_ERR_IO; 192 | } 193 | rc = meta->flash->write(meta->flash, 194 | region->offset + region->used_size + sizeof(header), 195 | len, data); 196 | if (rc != len) { 197 | PBLOG_ERRF("data write error: %d\n", rc); 198 | return rc < 0 ? rc : PBLOG_ERR_IO; 199 | } 200 | 201 | // Adjust the metadata. 202 | region->used_size += record_size; 203 | return record_size; 204 | } 205 | 206 | static int log_append(struct record_intf *ri, size_t len, const void *data) { 207 | struct log_metadata *meta = ri->priv; 208 | 209 | // Check which region we can fit into. 210 | int record_size = len + sizeof(record_header); 211 | 212 | struct record_region *tail_region = region_at(meta, meta->used_regions - 1); 213 | // Check if we need to go to the next free region. 214 | if (record_size > tail_region->size - tail_region->used_size) { 215 | if (meta->used_regions < meta->num_regions) { 216 | meta->used_regions++; 217 | tail_region = region_at(meta, meta->used_regions - 1); 218 | } else { 219 | PBLOG_ERRF("log full: %d used regions, %d used bytes in tail\n", 220 | meta->used_regions, tail_region->used_size); 221 | return PBLOG_ERR_NO_SPACE; 222 | } 223 | } 224 | 225 | return region_append(meta, tail_region, len, data); 226 | } 227 | 228 | static int log_get_free_space(struct record_intf *ri) { 229 | struct log_metadata *meta = ri->priv; 230 | 231 | int i; 232 | int free_space = 0; 233 | for (i = meta->used_regions - 1; i < meta->num_regions; ++i) { 234 | struct record_region *region = region_at(meta, i); 235 | free_space += region->size - region->used_size; 236 | } 237 | 238 | // Subtract out the size of a single record header as we require atleast 239 | // that much overhead. 240 | free_space -= sizeof(record_header); 241 | return free_space < 0 ? 0 : free_space; 242 | } 243 | 244 | static int region_create(struct log_metadata *meta, 245 | struct record_region *region, uint32_t sequence); 246 | 247 | static int log_clear(struct record_intf *ri, int num_to_clear) { 248 | struct log_metadata *meta = ri->priv; 249 | int i; 250 | int freed_space = 0; 251 | 252 | if (num_to_clear > meta->num_regions || num_to_clear == 0) { 253 | num_to_clear = meta->num_regions; 254 | } 255 | 256 | for (i = 0; i < num_to_clear; ++i) { 257 | struct record_region *region = region_at(meta, i); 258 | const int old_seq = region->sequence; 259 | int rc; 260 | 261 | freed_space += region->size; 262 | rc = region_create(meta, region, meta->next_sequence++); 263 | if (rc != PBLOG_SUCCESS) { 264 | PBLOG_ERRF("error clearing region %d\n", i); 265 | return rc; 266 | } 267 | (void)old_seq; 268 | PBLOG_DPRINTF("region %d cleared, old rseq:%d new rseq:%d\n", i, old_seq, 269 | region->sequence); 270 | } 271 | 272 | meta->head_region = (meta->head_region + num_to_clear) % meta->num_regions; 273 | meta->used_regions -= num_to_clear; 274 | if (meta->used_regions <= 0) { 275 | meta->used_regions = 1; 276 | } 277 | 278 | return freed_space; 279 | } 280 | 281 | // Initialize a region for first time use. 282 | static int region_create(struct log_metadata *meta, 283 | struct record_region *region, uint32_t sequence) { 284 | struct region_header header; 285 | int i; 286 | int rc; 287 | 288 | rc = meta->flash->erase(meta->flash, region->offset, region->size); 289 | if (rc != PBLOG_SUCCESS) { 290 | PBLOG_ERRF("region roff %d erase error: %d\n", region->offset, rc); 291 | return rc; 292 | } 293 | 294 | for (i = 0; i < sizeof(header.magic); ++i) { 295 | header.magic[i] = record_magic[i]; 296 | } 297 | header.sequence[0] = sequence & 0xff; 298 | header.sequence[1] = (sequence >> 8) & 0xff; 299 | header.sequence[2] = (sequence >> 16) & 0xff; 300 | header.sequence[3] = (sequence >> 24) & 0xff; 301 | if (region->size < sizeof(header)) { 302 | PBLOG_ERRF("region roff %d too small\n", region->offset); 303 | return PBLOG_ERR_NO_SPACE; 304 | } 305 | 306 | rc = meta->flash->write(meta->flash, region->offset, sizeof(header), &header); 307 | if (rc != sizeof(header)) { 308 | PBLOG_ERRF("region roff %d header write error: %d\n", region->offset, rc); 309 | return rc < 0 ? rc : PBLOG_ERR_IO; 310 | } 311 | 312 | region->used_size = sizeof(header); 313 | region->sequence = sequence; 314 | 315 | return PBLOG_SUCCESS; 316 | } 317 | 318 | // Reads the number of records in this region to determine the used space. 319 | static int region_calc_used_size(struct log_metadata *meta, 320 | struct record_region *region) { 321 | int offset = sizeof(struct region_header); 322 | while (1) { 323 | int next_offset; 324 | region_read_record(meta, region, offset, &next_offset, NULL, NULL); 325 | if (next_offset == 0) { 326 | break; 327 | } 328 | offset += next_offset; 329 | } 330 | return offset; 331 | } 332 | 333 | // Initializes a single region struct by reading the region header. 334 | // On read failure will create the region. 335 | static int region_init(struct log_metadata *meta, 336 | struct record_region *region) { 337 | int rc; 338 | struct region_header header; 339 | uint32_t sequence; 340 | 341 | // Read in the region header. 342 | rc = meta->flash->read(meta->flash, region->offset, sizeof(header), &header); 343 | 344 | if (rc != sizeof(header)) { 345 | PBLOG_ERRF("region roff %d header read error: %d\n", region->offset, rc); 346 | return region_create(meta, region, meta->next_sequence++); 347 | } 348 | 349 | sequence = header.sequence[0] | header.sequence[1] << 8 | 350 | header.sequence[2] << 16 | header.sequence[3] << 24; 351 | 352 | if (header.magic[0] != record_magic[0] || 353 | header.magic[1] != record_magic[1] || 354 | header.magic[2] != record_magic[2] || 355 | header.magic[3] != record_magic[3]) { 356 | PBLOG_DPRINTF("region roff %d invalid header: %02x%02x%02x%02x\n", 357 | region->offset, header.magic[0], header.magic[1], 358 | header.magic[2], header.magic[3]); 359 | return region_create(meta, region, meta->next_sequence++); 360 | } 361 | 362 | if (sequence >= meta->next_sequence) { 363 | meta->next_sequence = sequence + 1; 364 | } 365 | 366 | region->sequence = sequence; 367 | region->used_size = region_calc_used_size(meta, region); 368 | return PBLOG_SUCCESS; 369 | } 370 | 371 | // Initializes the head region as the one with the lowest sequence number. 372 | static void record_intf_init_head_region(struct log_metadata *meta) { 373 | uint32_t min_sequence = 0xffffffff; 374 | int min_region = 0; 375 | int i; 376 | 377 | meta->head_region = 0; 378 | for (i = 0; i < meta->num_regions; ++i) { 379 | struct record_region *region = region_at(meta, i); 380 | if (region->sequence < min_sequence) { 381 | min_sequence = region->sequence; 382 | min_region = i; 383 | } 384 | } 385 | 386 | meta->head_region = min_region; 387 | } 388 | 389 | // Initializes the number of used regions as the regions with valid stored 390 | // records. 391 | static void record_intf_init_used_regions(struct log_metadata *meta) { 392 | int used_regions = 0; 393 | int i; 394 | for (i = 0; i < meta->num_regions; ++i) { 395 | struct record_region *region = region_at(meta, i); 396 | if (region->used_size > sizeof(struct region_header)) { 397 | used_regions++; 398 | } else { 399 | break; 400 | } 401 | } 402 | // We always have atleast one used region. 403 | meta->used_regions = used_regions <= 0 ? 1 : used_regions; 404 | } 405 | 406 | static int record_intf_init_meta(struct record_intf *log) { 407 | struct log_metadata *meta = log->priv; 408 | int i; 409 | 410 | // Determine the number of records in each region. 411 | for (i = 0; i < meta->num_regions; ++i) { 412 | int rc = region_init(meta, &meta->regions[i]); 413 | if (rc < 0) { 414 | PBLOG_ERRF("region %d init failure, ignoring region\n", i); 415 | // Mark the size of the region as 0 so we don't try to use it. 416 | meta->regions[i].size = 0; 417 | meta->regions[i].used_size = 0; 418 | } 419 | 420 | PBLOG_DPRINTF("region %d. rseq:%d offset:%d size:%d used_size:%d\n", i, 421 | meta->regions[i].sequence, meta->regions[i].offset, 422 | meta->regions[i].size, meta->regions[i].used_size); 423 | } 424 | 425 | record_intf_init_head_region(meta); 426 | record_intf_init_used_regions(meta); 427 | 428 | PBLOG_DPRINTF( 429 | "init num_regions:%d used_regions:%d head_region:%d " 430 | "next_sequence:%d\n", 431 | meta->num_regions, meta->used_regions, meta->head_region, 432 | meta->next_sequence); 433 | return PBLOG_SUCCESS; 434 | } 435 | 436 | int record_intf_init(record_intf *ri, const struct record_region *regions, 437 | int num_regions, struct pblog_flash_ops *flash) { 438 | struct log_metadata *meta; 439 | if (num_regions < 1) { 440 | return PBLOG_ERR_INVALID; 441 | } 442 | meta = malloc(sizeof(struct log_metadata)); 443 | 444 | meta->regions = malloc(sizeof(*regions) * num_regions); 445 | memcpy(meta->regions, regions, sizeof(*regions) * num_regions); 446 | meta->num_regions = num_regions; 447 | meta->next_sequence = 0; 448 | 449 | meta->flash = flash; 450 | 451 | ri->read_record = log_read_record; 452 | ri->append = log_append; 453 | ri->get_free_space = log_get_free_space; 454 | ri->clear = log_clear; 455 | 456 | ri->priv = meta; 457 | 458 | return record_intf_init_meta(ri); 459 | } 460 | 461 | void record_intf_free(record_intf *ri) { 462 | struct log_metadata *meta = ri->priv; 463 | free(meta->regions); 464 | free(meta); 465 | } 466 | -------------------------------------------------------------------------------- /test/common.cc: -------------------------------------------------------------------------------- 1 | #ifndef PBLOG_TEST_COMMON_HH 2 | #define PBLOG_TEST_COMMON_HH 3 | 4 | #include "common.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #define DEFAULT_MIN_SEVERITY 2 13 | 14 | extern "C" { 15 | 16 | int pblog_printf(int severity, const char *format, ...) { 17 | if (DEFAULT_MIN_SEVERITY > severity) { 18 | return 0; 19 | } 20 | 21 | va_list args; 22 | va_start(args, format); // NOLINT 23 | int ret = vfprintf(stderr, format, args); // NOLINT 24 | va_end(args); // NOLINT 25 | 26 | return ret; 27 | } 28 | 29 | } // extern "C" 30 | 31 | namespace pblog_test { 32 | 33 | std::string StringPrintf(const char *format, ...) { // NOLINT 34 | va_list args; 35 | va_start(args, format); // NOLINT 36 | int size = vsnprintf(nullptr, 0, format, args); // NOLINT 37 | va_end(args); // NOLINT 38 | 39 | std::string ret(size + 1, '\0'); 40 | va_start(args, format); // NOLINT 41 | vsnprintf(&ret[0], size + 1, format, args); // NOLINT 42 | va_end(args); // NOLINT 43 | 44 | ret.resize(size); 45 | return ret; 46 | } 47 | 48 | } // namespace pblog_test 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /test/common.hh: -------------------------------------------------------------------------------- 1 | #ifndef PBLOG_TEST_COMMON_HH 2 | #define PBLOG_TEST_COMMON_HH 3 | 4 | #include 5 | 6 | namespace pblog_test { 7 | 8 | std::string StringPrintf(const char *format, ...); 9 | 10 | } // namespace pblog_test 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /test/pblog_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "common.hh" 27 | 28 | namespace { 29 | 30 | using std::string; 31 | using std::vector; 32 | 33 | class PblogFileTest : public ::testing::Test { 34 | public: 35 | PblogFileTest() { 36 | filename_ = "/tmp/pblog.tst"; 37 | flash_ri_ = nullptr; 38 | mem_log_ = nullptr; 39 | pblog_ = nullptr; 40 | events = new vector; 41 | } 42 | 43 | ~PblogFileTest() override { 44 | clear_state(); 45 | delete events; 46 | 47 | unlink(filename_.c_str()); 48 | } 49 | 50 | void init_2regions(int offset0, int size0, int offset1, int size1) { 51 | struct record_region file_regions[2]; 52 | file_regions[0].offset = offset0; 53 | file_regions[0].size = size0; 54 | file_regions[0].used_size = 0; 55 | file_regions[1].offset = offset1; 56 | file_regions[1].size = size1; 57 | file_regions[1].used_size = 0; 58 | 59 | flash_ri_ = 60 | static_cast(malloc(sizeof(struct record_intf))); 61 | pblog_file_ops.priv = static_cast( 62 | const_cast(filename_.c_str())); 63 | record_intf_init(flash_ri_, 64 | static_cast(file_regions), 2, 65 | &pblog_file_ops); 66 | 67 | mem_log_ = malloc(size0 + size1); 68 | 69 | pblog_ = static_cast(malloc(sizeof(struct pblog))); 70 | pblog_->get_current_bootnum = nullptr; 71 | pblog_->get_time_now = nullptr; 72 | pblog_init(pblog_, 0, flash_ri_, mem_log_, size0 + size1); 73 | } 74 | 75 | void clear_state() { 76 | for (auto event : *events) { 77 | delete event; 78 | } 79 | events->clear(); 80 | 81 | pblog_free(pblog_); 82 | free(pblog_); 83 | pblog_ = nullptr; 84 | 85 | record_intf_free(flash_ri_); 86 | free(flash_ri_); 87 | flash_ri_ = nullptr; 88 | 89 | free(mem_log_); 90 | } 91 | 92 | record_intf *flash_ri_; 93 | void *mem_log_; 94 | pblog *pblog_; 95 | 96 | string filename_; 97 | static vector *events; 98 | }; 99 | 100 | vector *PblogFileTest::events; 101 | 102 | pblog_status collect_events_cb(int valid, const pblog_Event *event, 103 | void *priv) { // NOLINT 104 | if (valid == 0) { 105 | return PBLOG_ERR_INVALID; 106 | } 107 | auto new_event = new pblog_Event; 108 | memcpy(new_event, event, sizeof(*event)); 109 | PblogFileTest::events->push_back(new_event); 110 | return PBLOG_SUCCESS; 111 | } 112 | 113 | TEST_F(PblogFileTest, TotallyEmptyLog) { 114 | init_2regions(0, 0xff, 0x100, 0xff); 115 | pblog_Event event; 116 | 117 | EXPECT_EQ(0, 118 | pblog_->for_each_event(pblog_, collect_events_cb, &event, nullptr)); 119 | EXPECT_EQ(static_cast(1), events->size()); 120 | 121 | // Should log a clear event. 122 | EXPECT_EQ(pblog_TYPE_LOG_CLEARED, events->at(0)->type); 123 | } 124 | 125 | TEST_F(PblogFileTest, LogClearedSuccess) { 126 | init_2regions(0, 0xff, 0x100, 0xff); 127 | pblog_Event event; 128 | 129 | EXPECT_EQ(0, pblog_->clear(pblog_)); 130 | 131 | // Should be a single clear event. 132 | EXPECT_EQ(0, 133 | pblog_->for_each_event(pblog_, collect_events_cb, &event, nullptr)); 134 | ASSERT_EQ(static_cast(1), events->size()); 135 | EXPECT_EQ(pblog_TYPE_LOG_CLEARED, events->at(0)->type); 136 | } 137 | 138 | TEST_F(PblogFileTest, ClearNotEnoughRoom) { 139 | // Only allow 1 byte per region. 140 | init_2regions(0, 1, 0x100, 1); 141 | 142 | // Clear should return failure status. 143 | EXPECT_NE(0, pblog_->clear(pblog_)); 144 | } 145 | 146 | TEST_F(PblogFileTest, LogAFewEvents) { 147 | init_2regions(0, 0xff, 0x100, 0xff); 148 | pblog_Event event; 149 | 150 | size_t num_events = 10; 151 | for (size_t i = 0; i < num_events; ++i) { 152 | pblog_Event event; 153 | event_init(&event); 154 | event.type = pblog_TYPE_BOOT_UP; 155 | EXPECT_EQ(0, pblog_->add_event(pblog_, &event)); 156 | event_free(&event); 157 | } 158 | 159 | EXPECT_EQ(0, 160 | pblog_->for_each_event(pblog_, collect_events_cb, &event, nullptr)); 161 | ASSERT_EQ(1 + num_events, events->size()); 162 | } 163 | 164 | TEST_F(PblogFileTest, LogSecondRegion) { 165 | // First region is small. 166 | init_2regions(0, 30, 0x100, 0xff); 167 | pblog_Event event; 168 | 169 | size_t num_events = 10; 170 | for (size_t i = 0; i < num_events; ++i) { 171 | pblog_Event event; 172 | event_init(&event); 173 | event.type = pblog_TYPE_BOOT_UP; 174 | EXPECT_EQ(0, pblog_->add_event(pblog_, &event)); 175 | event_free(&event); 176 | } 177 | 178 | EXPECT_EQ(0, 179 | pblog_->for_each_event(pblog_, collect_events_cb, &event, nullptr)); 180 | ASSERT_EQ(static_cast(1 + num_events), events->size()); 181 | } 182 | 183 | TEST_F(PblogFileTest, LogFull) { 184 | // Both regions are small. 185 | init_2regions(0, 30, 0x100, 30); 186 | pblog_Event event; 187 | 188 | size_t num_events = 8; 189 | for (size_t i = 0; i < num_events; ++i) { 190 | pblog_Event event; 191 | event_init(&event); 192 | event.type = pblog_TYPE_BOOT_UP; 193 | if (i + 1 == num_events) { 194 | EXPECT_NE(0, pblog_->add_event(pblog_, &event)); 195 | } else { 196 | EXPECT_EQ(0, pblog_->add_event(pblog_, &event)); 197 | } 198 | event_free(&event); 199 | } 200 | 201 | EXPECT_EQ(0, 202 | pblog_->for_each_event(pblog_, collect_events_cb, &event, nullptr)); 203 | ASSERT_EQ(1 + num_events - 1, events->size()); 204 | } 205 | 206 | TEST_F(PblogFileTest, LogPersists) { 207 | init_2regions(0, 0xff, 0x100, 0xff); 208 | pblog_Event event; 209 | 210 | size_t num_events = 4; 211 | for (size_t i = 0; i < num_events; ++i) { 212 | pblog_Event event; 213 | event_init(&event); 214 | event.type = pblog_TYPE_BOOT_UP; 215 | EXPECT_EQ(0, pblog_->add_event(pblog_, &event)); 216 | event_free(&event); 217 | } 218 | 219 | EXPECT_EQ(0, 220 | pblog_->for_each_event(pblog_, collect_events_cb, &event, nullptr)); 221 | ASSERT_EQ(1 + num_events, events->size()); 222 | 223 | clear_state(); 224 | init_2regions(0, 0xff, 0x100, 0xff); 225 | 226 | EXPECT_EQ(0, 227 | pblog_->for_each_event(pblog_, collect_events_cb, &event, nullptr)); 228 | ASSERT_EQ(1 + num_events, events->size()); 229 | 230 | clear_state(); 231 | // Switch the order of the region offsets, should not make a difference. 232 | init_2regions(0x100, 0xff, 0, 0xff); 233 | 234 | EXPECT_EQ(0, 235 | pblog_->for_each_event(pblog_, collect_events_cb, &event, nullptr)); 236 | ASSERT_EQ(1 + num_events, events->size()); 237 | 238 | // Clear the log. 239 | EXPECT_EQ(0, pblog_->clear(pblog_)); 240 | 241 | clear_state(); 242 | init_2regions(0, 0xff, 0x100, 0xff); 243 | 244 | EXPECT_EQ(0, 245 | pblog_->for_each_event(pblog_, collect_events_cb, &event, nullptr)); 246 | ASSERT_EQ(static_cast(1), events->size()); 247 | } 248 | 249 | } // namespace 250 | -------------------------------------------------------------------------------- /test/record_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "common.hh" 30 | 31 | namespace { 32 | 33 | using pblog_test::StringPrintf; 34 | using std::make_pair; 35 | using std::pair; 36 | using std::string; 37 | using std::vector; 38 | 39 | class RecordFileTest : public ::testing::Test { 40 | public: 41 | RecordFileTest() { 42 | filename_ = "/tmp/record.tst"; 43 | ri_ = nullptr; 44 | } 45 | 46 | ~RecordFileTest() override { 47 | ClearState(); 48 | 49 | unlink(filename_.c_str()); 50 | } 51 | 52 | void InitRegions(const vector > ®ions) { 53 | auto region_structs = new struct record_region[regions.size()]; 54 | 55 | for (size_t i = 0; i < regions.size(); ++i) { 56 | memset(®ion_structs[i], 0, sizeof(region_structs[i])); 57 | region_structs[i].offset = regions[i].first; 58 | region_structs[i].size = regions[i].second; 59 | } 60 | 61 | ri_ = new struct record_intf; 62 | pblog_file_ops.priv = static_cast( 63 | const_cast(filename_.c_str())); 64 | ASSERT_EQ(0, record_intf_init(ri_, region_structs, regions.size(), 65 | &pblog_file_ops)); 66 | delete[] region_structs; 67 | } 68 | 69 | void ClearState() { 70 | record_intf_free(ri_); 71 | delete ri_; 72 | ri_ = nullptr; 73 | } 74 | 75 | size_t NumValidRecords() { 76 | size_t num_records = 0; 77 | int offset = 0; 78 | 79 | while (true) { 80 | int next_offset = 0; 81 | size_t len = 4096; 82 | string data(len, '\0'); 83 | int rc = ri_->read_record(ri_, offset, &next_offset, &len, &data[0]); 84 | if (len == 0 || next_offset == 0) { 85 | break; 86 | } 87 | offset += next_offset; 88 | num_records += (rc < 0) ? 0 : 1; 89 | } 90 | 91 | return num_records; 92 | } 93 | 94 | int GetRecord(size_t i, string *record_data) { 95 | size_t num_records = 0; 96 | size_t len = 4096; 97 | string data(len, '\0'); 98 | int offset = 0; 99 | int rc = 0; 100 | while (num_records <= i) { 101 | int next_offset = 0; 102 | rc = ri_->read_record(ri_, offset, &next_offset, &len, &data[0]); 103 | if (len == 0 || next_offset == 0) { 104 | return -1; 105 | } 106 | offset += next_offset; 107 | num_records++; 108 | } 109 | 110 | if (record_data != nullptr) { 111 | *record_data = data.substr(0, len); 112 | } 113 | return rc; 114 | } 115 | 116 | size_t FillWithRecords() { 117 | string expected_data; 118 | size_t num_written = 0; 119 | int rc = 0; 120 | while (true) { 121 | expected_data = StringPrintf("%08x", num_written); 122 | rc = ri_->append(ri_, expected_data.size(), &expected_data[0]); 123 | EXPECT_TRUE(rc > 0 || rc == PBLOG_ERR_NO_SPACE); 124 | if (rc == PBLOG_ERR_NO_SPACE) { 125 | break; 126 | } 127 | num_written++; 128 | } 129 | return num_written; 130 | } 131 | 132 | record_intf *ri_; 133 | 134 | string filename_; 135 | }; 136 | 137 | TEST_F(RecordFileTest, FirstTimeInit) { 138 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 139 | 140 | EXPECT_LT(ri_->get_free_space(ri_), 0xff + 0xff); 141 | EXPECT_GT(ri_->get_free_space(ri_), 0xff); 142 | 143 | EXPECT_EQ(static_cast(0), NumValidRecords()); 144 | EXPECT_NE(0, GetRecord(0, nullptr)); 145 | } 146 | 147 | TEST_F(RecordFileTest, InitWithGarbage) { 148 | // Fill the file with garbage. 149 | string garbage(4096, '\0'); 150 | for (size_t i = 0; i < garbage.size(); ++i) { 151 | garbage[i] = i; 152 | } 153 | { 154 | std::unique_ptr file(fopen(filename_.c_str(), "w"), 155 | fclose); 156 | ASSERT_NE(nullptr, file); 157 | ASSERT_EQ(garbage.size(), fwrite(garbage.data(), sizeof(char), // NOLINT 158 | garbage.size(), file.get())); 159 | } 160 | 161 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 162 | 163 | EXPECT_LT(ri_->get_free_space(ri_), 0xff + 0xff); 164 | EXPECT_GT(ri_->get_free_space(ri_), 0xff); 165 | 166 | EXPECT_EQ(static_cast(0), NumValidRecords()); 167 | EXPECT_NE(0, GetRecord(0, nullptr)); 168 | } 169 | 170 | TEST_F(RecordFileTest, AddSingleRecord) { 171 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 172 | 173 | const string expected_data("asdfjkl1111000"); 174 | EXPECT_GE(ri_->append(ri_, expected_data.size(), &expected_data[0]), 175 | static_cast(expected_data.size())); 176 | 177 | EXPECT_EQ(static_cast(1), NumValidRecords()); 178 | string data; 179 | EXPECT_EQ(0, GetRecord(0, &data)); 180 | EXPECT_EQ(expected_data, data); 181 | } 182 | 183 | TEST_F(RecordFileTest, ReadRecordBuffertoSmall) { 184 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 185 | 186 | const string expected_data("asdfjkl1111000"); 187 | EXPECT_GE(ri_->append(ri_, expected_data.size(), &expected_data[0]), 188 | static_cast(expected_data.size())); 189 | 190 | EXPECT_EQ(static_cast(1), NumValidRecords()); 191 | 192 | int next_offset; 193 | string data(expected_data.size() - 1, 0); 194 | size_t len = data.size(); 195 | EXPECT_EQ(PBLOG_ERR_NO_SPACE, 196 | ri_->read_record(ri_, 0, &next_offset, &len, &data)); 197 | EXPECT_EQ(expected_data.size(), len); 198 | EXPECT_GT(next_offset, 0); 199 | } 200 | 201 | TEST_F(RecordFileTest, FillWithRecords) { 202 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 203 | 204 | size_t num_written = FillWithRecords(); 205 | EXPECT_EQ(num_written, NumValidRecords()); 206 | 207 | EXPECT_LT(ri_->get_free_space(ri_), 8); 208 | for (size_t i = 0; i < num_written; ++i) { 209 | string data; 210 | EXPECT_EQ(0, GetRecord(i, &data)); 211 | EXPECT_EQ(StringPrintf("%08x", i), data); 212 | } 213 | } 214 | 215 | TEST_F(RecordFileTest, ClearAllRecords) { 216 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 217 | 218 | size_t num_written = FillWithRecords(); 219 | EXPECT_EQ(num_written, NumValidRecords()); 220 | EXPECT_LT(ri_->get_free_space(ri_), 8); 221 | 222 | EXPECT_EQ(0xff + 0xff, ri_->clear(ri_, 0)); 223 | 224 | EXPECT_EQ(static_cast(0), NumValidRecords()); 225 | EXPECT_NE(0, GetRecord(0, nullptr)); 226 | EXPECT_GT(ri_->get_free_space(ri_), 0xff); 227 | } 228 | 229 | TEST_F(RecordFileTest, ClearOneRegion) { 230 | InitRegions({make_pair(0, 0x7f), make_pair(0x100, 0xff)}); 231 | 232 | size_t num_written = FillWithRecords(); 233 | EXPECT_EQ(num_written, NumValidRecords()); 234 | EXPECT_LT(ri_->get_free_space(ri_), 8); 235 | 236 | EXPECT_EQ(0x7f, ri_->clear(ri_, 1)); 237 | 238 | size_t num_records_after_clear = NumValidRecords(); 239 | ASSERT_GT(num_written, num_records_after_clear); 240 | 241 | size_t num_cleared = num_written - num_records_after_clear; 242 | for (size_t i = 0; i < num_records_after_clear; ++i) { 243 | string data; 244 | EXPECT_EQ(0, GetRecord(i, &data)); 245 | EXPECT_EQ(StringPrintf("%08x", i + num_cleared), data); 246 | } 247 | } 248 | 249 | TEST_F(RecordFileTest, RecordsPersist) { 250 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 251 | 252 | const string expected_data("asdfjkl1111000"); 253 | EXPECT_GE(ri_->append(ri_, expected_data.size(), &expected_data[0]), 254 | static_cast(expected_data.size())); 255 | EXPECT_GE(ri_->append(ri_, expected_data.size(), &expected_data[0]), 256 | static_cast(expected_data.size())); 257 | 258 | EXPECT_EQ(static_cast(2), NumValidRecords()); 259 | 260 | ClearState(); 261 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 262 | 263 | EXPECT_EQ(static_cast(2), NumValidRecords()); 264 | string data; 265 | EXPECT_EQ(0, GetRecord(0, &data)); 266 | EXPECT_EQ(expected_data, data); 267 | EXPECT_EQ(0, GetRecord(1, &data)); 268 | EXPECT_EQ(expected_data, data); 269 | } 270 | 271 | TEST_F(RecordFileTest, CorruptRecordData) { 272 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 273 | 274 | const string expected_data("asdfjkl1111000"); 275 | EXPECT_GE(ri_->append(ri_, expected_data.size(), &expected_data[0]), 276 | static_cast(expected_data.size())); 277 | EXPECT_GE(ri_->append(ri_, expected_data.size(), &expected_data[0]), 278 | static_cast(expected_data.size())); 279 | 280 | EXPECT_EQ(static_cast(2), NumValidRecords()); 281 | 282 | // Corrupt the 1st byte of 1st record data with a NUL byte 283 | size_t offset = sizeof(record_header) + sizeof(region_header); 284 | unsigned char val = 0; 285 | EXPECT_EQ(static_cast(sizeof(val)), 286 | pblog_file_ops.write(&pblog_file_ops, offset, sizeof(val), &val)); 287 | 288 | EXPECT_EQ(static_cast(1), NumValidRecords()); 289 | string data; 290 | // Should return checksum error but still read the data. 291 | EXPECT_EQ(PBLOG_ERR_CHECKSUM, GetRecord(0, &data)); 292 | EXPECT_EQ(expected_data.size(), data.size()); 293 | EXPECT_NE(expected_data, data); 294 | 295 | EXPECT_EQ(0, GetRecord(1, &data)); 296 | EXPECT_EQ(expected_data, data); 297 | 298 | // Make sure the corrupt record does not cause problems on re-init. 299 | ClearState(); 300 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 301 | 302 | EXPECT_EQ(static_cast(1), NumValidRecords()); 303 | // Should return checksum error but still read the data. 304 | EXPECT_EQ(PBLOG_ERR_CHECKSUM, GetRecord(0, &data)); 305 | EXPECT_EQ(expected_data.size(), data.size()); 306 | EXPECT_NE(expected_data, data); 307 | 308 | EXPECT_EQ(0, GetRecord(1, &data)); 309 | EXPECT_EQ(expected_data, data); 310 | } 311 | 312 | TEST_F(RecordFileTest, CorruptRecordLength) { 313 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 314 | 315 | const string expected_data("asdfjkl1111000"); 316 | EXPECT_GE(ri_->append(ri_, expected_data.size(), &expected_data[0]), 317 | static_cast(expected_data.size())); 318 | EXPECT_GE(ri_->append(ri_, expected_data.size(), &expected_data[0]), 319 | static_cast(expected_data.size())); 320 | 321 | EXPECT_EQ(static_cast(2), NumValidRecords()); 322 | 323 | // Try many possible length corruptions. 324 | for (size_t i = 0; i < 0x600; ++i) { 325 | if (i == expected_data.size() + sizeof(record_header)) { 326 | continue; 327 | } 328 | int offset = sizeof(region_header); 329 | uint8_t val[] = {static_cast((i >> 8) & 0xff), 330 | static_cast(i & 0xff)}; 331 | EXPECT_EQ(static_cast(sizeof(val)), 332 | pblog_file_ops.write(&pblog_file_ops, offset, sizeof(val), &val)); 333 | 334 | ASSERT_EQ(static_cast(0), NumValidRecords()) << "for value " << i; 335 | string data; 336 | EXPECT_NE(0, GetRecord(0, &data)); 337 | 338 | ClearState(); 339 | InitRegions({make_pair(0, 0xff), make_pair(0x100, 0xff)}); 340 | 341 | ASSERT_EQ(static_cast(0), NumValidRecords()) << "for value " << i; 342 | EXPECT_NE(0, GetRecord(0, &data)); 343 | } 344 | } 345 | 346 | TEST_F(RecordFileTest, BigLog) { 347 | InitRegions({make_pair(0, 4096), make_pair(4096, 4096), make_pair(8192, 4096), 348 | make_pair(12288, 4096)}); 349 | 350 | // We should be able to write atleast 1000 records. 351 | size_t num_written = FillWithRecords(); 352 | ASSERT_GT(num_written, static_cast(1000)); 353 | EXPECT_EQ(num_written, NumValidRecords()); 354 | 355 | // Clear 1 region. 356 | EXPECT_EQ(4096, ri_->clear(ri_, 1)); 357 | 358 | // Should be able to write atleast 100 new records. 359 | size_t new_written = FillWithRecords(); 360 | ASSERT_GT(new_written, static_cast(100)); 361 | EXPECT_EQ(num_written, NumValidRecords()); 362 | } 363 | 364 | } // namespace 365 | --------------------------------------------------------------------------------