Merge branch 'hn/reftable'
The "reftable" backend for the refs API, without integrating into the refs subsystem, has been added. * hn/reftable: Add "test-tool dump-reftable" command. reftable: add dump utility reftable: implement stack, a mutable database of reftable files. reftable: implement refname validation reftable: add merged table view reftable: add a heap-based priority queue for reftable records reftable: reftable file level tests reftable: read reftable files reftable: generic interface to tables reftable: write reftable files reftable: a generic binary tree implementation reftable: reading/writing blocks Provide zlib's uncompress2 from compat/zlib-compat.c reftable: (de)serialization for the polymorphic record type. reftable: add blocksource, an abstraction for random access reads reftable: utility functions reftable: add error related functionality reftable: add LICENSE hash.h: provide constants for the hash IDsmaint
						commit
						a4bbd13be3
					
				
							
								
								
									
										53
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										53
									
								
								Makefile
								
								
								
								
							|  | @ -256,6 +256,8 @@ all:: | |||
| # | ||||
| # Define NO_DEFLATE_BOUND if your zlib does not have deflateBound. | ||||
| # | ||||
| # Define NO_UNCOMPRESS2 if your zlib does not have uncompress2. | ||||
| # | ||||
| # Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback, | ||||
| # as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299) | ||||
| # | ||||
|  | @ -732,6 +734,7 @@ TEST_BUILTINS_OBJS += test-read-cache.o | |||
| TEST_BUILTINS_OBJS += test-read-graph.o | ||||
| TEST_BUILTINS_OBJS += test-read-midx.o | ||||
| TEST_BUILTINS_OBJS += test-ref-store.o | ||||
| TEST_BUILTINS_OBJS += test-reftable.o | ||||
| TEST_BUILTINS_OBJS += test-regex.o | ||||
| TEST_BUILTINS_OBJS += test-repository.o | ||||
| TEST_BUILTINS_OBJS += test-revision-walking.o | ||||
|  | @ -810,6 +813,8 @@ TEST_SHELL_PATH = $(SHELL_PATH) | |||
|  | ||||
| LIB_FILE = libgit.a | ||||
| XDIFF_LIB = xdiff/lib.a | ||||
| REFTABLE_LIB = reftable/libreftable.a | ||||
| REFTABLE_TEST_LIB = reftable/libreftable_test.a | ||||
|  | ||||
| GENERATED_H += command-list.h | ||||
| GENERATED_H += config-list.h | ||||
|  | @ -1189,7 +1194,7 @@ THIRD_PARTY_SOURCES += compat/regex/% | |||
| THIRD_PARTY_SOURCES += sha1collisiondetection/% | ||||
| THIRD_PARTY_SOURCES += sha1dc/% | ||||
|  | ||||
| GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) | ||||
| GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) | ||||
| EXTLIBS = | ||||
|  | ||||
| GIT_USER_AGENT = git/$(GIT_VERSION) | ||||
|  | @ -1720,6 +1725,11 @@ ifdef NO_DEFLATE_BOUND | |||
| 	BASIC_CFLAGS += -DNO_DEFLATE_BOUND | ||||
| endif | ||||
|  | ||||
| ifdef NO_UNCOMPRESS2 | ||||
| 	BASIC_CFLAGS += -DNO_UNCOMPRESS2 | ||||
| 	REFTABLE_OBJS += compat/zlib-uncompress2.o | ||||
| endif | ||||
|  | ||||
| ifdef NO_POSIX_GOODIES | ||||
| 	BASIC_CFLAGS += -DNO_POSIX_GOODIES | ||||
| endif | ||||
|  | @ -2431,7 +2441,36 @@ XDIFF_OBJS += xdiff/xutils.o | |||
| .PHONY: xdiff-objs | ||||
| xdiff-objs: $(XDIFF_OBJS) | ||||
|  | ||||
| REFTABLE_OBJS += reftable/basics.o | ||||
| REFTABLE_OBJS += reftable/error.o | ||||
| REFTABLE_OBJS += reftable/block.o | ||||
| REFTABLE_OBJS += reftable/blocksource.o | ||||
| REFTABLE_OBJS += reftable/iter.o | ||||
| REFTABLE_OBJS += reftable/publicbasics.o | ||||
| REFTABLE_OBJS += reftable/merged.o | ||||
| REFTABLE_OBJS += reftable/pq.o | ||||
| REFTABLE_OBJS += reftable/reader.o | ||||
| REFTABLE_OBJS += reftable/record.o | ||||
| REFTABLE_OBJS += reftable/refname.o | ||||
| REFTABLE_OBJS += reftable/generic.o | ||||
| REFTABLE_OBJS += reftable/stack.o | ||||
| REFTABLE_OBJS += reftable/tree.o | ||||
| REFTABLE_OBJS += reftable/writer.o | ||||
|  | ||||
| REFTABLE_TEST_OBJS += reftable/basics_test.o | ||||
| REFTABLE_TEST_OBJS += reftable/block_test.o | ||||
| REFTABLE_TEST_OBJS += reftable/dump.o | ||||
| REFTABLE_TEST_OBJS += reftable/merged_test.o | ||||
| REFTABLE_TEST_OBJS += reftable/pq_test.o | ||||
| REFTABLE_TEST_OBJS += reftable/record_test.o | ||||
| REFTABLE_TEST_OBJS += reftable/readwrite_test.o | ||||
| REFTABLE_TEST_OBJS += reftable/refname_test.o | ||||
| REFTABLE_TEST_OBJS += reftable/stack_test.o | ||||
| REFTABLE_TEST_OBJS += reftable/test_framework.o | ||||
| REFTABLE_TEST_OBJS += reftable/tree_test.o | ||||
|  | ||||
| TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) | ||||
|  | ||||
| .PHONY: test-objs | ||||
| test-objs: $(TEST_OBJS) | ||||
|  | ||||
|  | @ -2447,6 +2486,8 @@ OBJECTS += $(PROGRAM_OBJS) | |||
| OBJECTS += $(TEST_OBJS) | ||||
| OBJECTS += $(XDIFF_OBJS) | ||||
| OBJECTS += $(FUZZ_OBJS) | ||||
| OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS) | ||||
|  | ||||
| ifndef NO_CURL | ||||
| 	OBJECTS += http.o http-walker.o remote-curl.o | ||||
| endif | ||||
|  | @ -2589,6 +2630,12 @@ $(LIB_FILE): $(LIB_OBJS) | |||
| $(XDIFF_LIB): $(XDIFF_OBJS) | ||||
| 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ | ||||
|  | ||||
| $(REFTABLE_LIB): $(REFTABLE_OBJS) | ||||
| 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ | ||||
|  | ||||
| $(REFTABLE_TEST_LIB): $(REFTABLE_TEST_OBJS) | ||||
| 	$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ | ||||
|  | ||||
| export DEFAULT_EDITOR DEFAULT_PAGER | ||||
|  | ||||
| Documentation/GIT-EXCLUDED-PROGRAMS: FORCE | ||||
|  | @ -2887,7 +2934,7 @@ perf: all | |||
|  | ||||
| t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) | ||||
|  | ||||
| t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) | ||||
| t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB) | ||||
| 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS) | ||||
|  | ||||
| check-sha1:: t/helper/test-tool$X | ||||
|  | @ -3225,7 +3272,7 @@ cocciclean: | |||
| clean: profile-clean coverage-clean cocciclean | ||||
| 	$(RM) *.res | ||||
| 	$(RM) $(OBJECTS) | ||||
| 	$(RM) $(LIB_FILE) $(XDIFF_LIB) | ||||
| 	$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB) | ||||
| 	$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X | ||||
| 	$(RM) $(TEST_PROGRAMS) | ||||
| 	$(RM) $(FUZZ_PROGRAMS) | ||||
|  |  | |||
|  | @ -224,6 +224,7 @@ linux-gcc-default) | |||
| 	;; | ||||
| Linux32) | ||||
| 	CC=gcc | ||||
| 	MAKEFLAGS="$MAKEFLAGS NO_UNCOMPRESS2=1" | ||||
| 	;; | ||||
| linux-musl) | ||||
| 	CC=gcc | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| /zlib-uncompress2.c	whitespace=-indent-with-non-tab,-trailing-space | ||||
|  | @ -0,0 +1,95 @@ | |||
| /* taken from zlib's uncompr.c | ||||
|  | ||||
|    commit cacf7f1d4e3d44d871b605da3b647f07d718623f | ||||
|    Author: Mark Adler <madler@alumni.caltech.edu> | ||||
|    Date:   Sun Jan 15 09:18:46 2017 -0800 | ||||
|  | ||||
|        zlib 1.2.11 | ||||
|  | ||||
| */ | ||||
|  | ||||
| #include "../reftable/system.h" | ||||
| #define z_const | ||||
|  | ||||
| /* | ||||
|  * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler | ||||
|  * For conditions of distribution and use, see copyright notice in zlib.h | ||||
|  */ | ||||
|  | ||||
| #include <zlib.h> | ||||
|  | ||||
| /* clang-format off */ | ||||
|  | ||||
| /* =========================================================================== | ||||
|      Decompresses the source buffer into the destination buffer.  *sourceLen is | ||||
|    the byte length of the source buffer. Upon entry, *destLen is the total size | ||||
|    of the destination buffer, which must be large enough to hold the entire | ||||
|    uncompressed data. (The size of the uncompressed data must have been saved | ||||
|    previously by the compressor and transmitted to the decompressor by some | ||||
|    mechanism outside the scope of this compression library.) Upon exit, | ||||
|    *destLen is the size of the decompressed data and *sourceLen is the number | ||||
|    of source bytes consumed. Upon return, source + *sourceLen points to the | ||||
|    first unused input byte. | ||||
|  | ||||
|      uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough | ||||
|    memory, Z_BUF_ERROR if there was not enough room in the output buffer, or | ||||
|    Z_DATA_ERROR if the input data was corrupted, including if the input data is | ||||
|    an incomplete zlib stream. | ||||
| */ | ||||
| int ZEXPORT uncompress2 ( | ||||
|     Bytef *dest, | ||||
|     uLongf *destLen, | ||||
|     const Bytef *source, | ||||
|     uLong *sourceLen) { | ||||
|     z_stream stream; | ||||
|     int err; | ||||
|     const uInt max = (uInt)-1; | ||||
|     uLong len, left; | ||||
|     Byte buf[1];    /* for detection of incomplete stream when *destLen == 0 */ | ||||
|  | ||||
|     len = *sourceLen; | ||||
|     if (*destLen) { | ||||
| 	left = *destLen; | ||||
| 	*destLen = 0; | ||||
|     } | ||||
|     else { | ||||
| 	left = 1; | ||||
| 	dest = buf; | ||||
|     } | ||||
|  | ||||
|     stream.next_in = (z_const Bytef *)source; | ||||
|     stream.avail_in = 0; | ||||
|     stream.zalloc = (alloc_func)0; | ||||
|     stream.zfree = (free_func)0; | ||||
|     stream.opaque = (voidpf)0; | ||||
|  | ||||
|     err = inflateInit(&stream); | ||||
|     if (err != Z_OK) return err; | ||||
|  | ||||
|     stream.next_out = dest; | ||||
|     stream.avail_out = 0; | ||||
|  | ||||
|     do { | ||||
| 	if (stream.avail_out == 0) { | ||||
| 	    stream.avail_out = left > (uLong)max ? max : (uInt)left; | ||||
| 	    left -= stream.avail_out; | ||||
| 	} | ||||
| 	if (stream.avail_in == 0) { | ||||
| 	    stream.avail_in = len > (uLong)max ? max : (uInt)len; | ||||
| 	    len -= stream.avail_in; | ||||
| 	} | ||||
| 	err = inflate(&stream, Z_NO_FLUSH); | ||||
|     } while (err == Z_OK); | ||||
|  | ||||
|     *sourceLen -= len + stream.avail_in; | ||||
|     if (dest != buf) | ||||
| 	*destLen = stream.total_out; | ||||
|     else if (stream.total_out && err == Z_BUF_ERROR) | ||||
| 	left = 1; | ||||
|  | ||||
|     inflateEnd(&stream); | ||||
|     return err == Z_STREAM_END ? Z_OK : | ||||
| 	   err == Z_NEED_DICT ? Z_DATA_ERROR  : | ||||
| 	   err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR : | ||||
| 	   err; | ||||
| } | ||||
|  | @ -261,6 +261,10 @@ ifeq ($(uname_S),FreeBSD) | |||
| 	FILENO_IS_A_MACRO = UnfortunatelyYes | ||||
| endif | ||||
| ifeq ($(uname_S),OpenBSD) | ||||
| 	# Versions < 7.0 need compatibility layer | ||||
| 	ifeq ($(shell expr "$(uname_R)" : "[1-6]\."),2) | ||||
| 		NO_UNCOMPRESS2 = UnfortunatelyYes | ||||
| 	endif | ||||
| 	NO_STRCASESTR = YesPlease | ||||
| 	NO_MEMMEM = YesPlease | ||||
| 	USE_ST_TIMESPEC = YesPlease | ||||
|  | @ -516,6 +520,7 @@ ifeq ($(uname_S),Interix) | |||
| 	endif | ||||
| endif | ||||
| ifeq ($(uname_S),Minix) | ||||
| 	NO_UNCOMPRESS2 = YesPlease | ||||
| 	NO_IPV6 = YesPlease | ||||
| 	NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease | ||||
| 	NO_NSEC = YesPlease | ||||
|  |  | |||
							
								
								
									
										13
									
								
								configure.ac
								
								
								
								
							
							
						
						
									
										13
									
								
								configure.ac
								
								
								
								
							|  | @ -664,9 +664,22 @@ AC_LINK_IFELSE([ZLIBTEST_SRC], | |||
| 	NO_DEFLATE_BOUND=yes]) | ||||
| LIBS="$old_LIBS" | ||||
|  | ||||
| AC_DEFUN([ZLIBTEST_UNCOMPRESS2_SRC], [ | ||||
| AC_LANG_PROGRAM([#include <zlib.h>], | ||||
|  [uncompress2(NULL,NULL,NULL,NULL);])]) | ||||
| AC_MSG_CHECKING([for uncompress2 in -lz]) | ||||
| old_LIBS="$LIBS" | ||||
| LIBS="$LIBS -lz" | ||||
| AC_LINK_IFELSE([ZLIBTEST_UNCOMPRESS2_SRC], | ||||
| 	[AC_MSG_RESULT([yes])], | ||||
| 	[AC_MSG_RESULT([no]) | ||||
| 	NO_UNCOMPRESS2=yes]) | ||||
| LIBS="$old_LIBS" | ||||
|  | ||||
| GIT_UNSTASH_FLAGS($ZLIB_PATH) | ||||
|  | ||||
| GIT_CONF_SUBST([NO_DEFLATE_BOUND]) | ||||
| GIT_CONF_SUBST([NO_UNCOMPRESS2]) | ||||
|  | ||||
| # | ||||
| # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, | ||||
|  |  | |||
|  | @ -647,6 +647,12 @@ parse_makefile_for_sources(libxdiff_SOURCES "XDIFF_OBJS") | |||
| list(TRANSFORM libxdiff_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/") | ||||
| add_library(xdiff STATIC ${libxdiff_SOURCES}) | ||||
|  | ||||
| #reftable | ||||
| parse_makefile_for_sources(reftable_SOURCES "REFTABLE_OBJS") | ||||
|  | ||||
| list(TRANSFORM reftable_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/") | ||||
| add_library(reftable STATIC ${reftable_SOURCES}) | ||||
|  | ||||
| if(WIN32) | ||||
| 	if(NOT MSVC)#use windres when compiling with gcc and clang | ||||
| 		add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/git.res | ||||
|  | @ -669,7 +675,7 @@ endif() | |||
| #link all required libraries to common-main | ||||
| add_library(common-main OBJECT ${CMAKE_SOURCE_DIR}/common-main.c) | ||||
|  | ||||
| target_link_libraries(common-main libgit xdiff ${ZLIB_LIBRARIES}) | ||||
| target_link_libraries(common-main libgit xdiff reftable ${ZLIB_LIBRARIES}) | ||||
| if(Intl_FOUND) | ||||
| 	target_link_libraries(common-main ${Intl_LIBRARIES}) | ||||
| endif() | ||||
|  | @ -908,11 +914,15 @@ if(BUILD_TESTING) | |||
| add_executable(test-fake-ssh ${CMAKE_SOURCE_DIR}/t/helper/test-fake-ssh.c) | ||||
| target_link_libraries(test-fake-ssh common-main) | ||||
|  | ||||
| #reftable-tests | ||||
| parse_makefile_for_sources(test-reftable_SOURCES "REFTABLE_TEST_OBJS") | ||||
| list(TRANSFORM test-reftable_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/") | ||||
|  | ||||
| #test-tool | ||||
| parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS") | ||||
|  | ||||
| list(TRANSFORM test-tool_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/t/helper/") | ||||
| add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES}) | ||||
| add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES} ${test-reftable_SOURCES}) | ||||
| target_link_libraries(test-tool common-main) | ||||
|  | ||||
| set_target_properties(test-fake-ssh test-tool | ||||
|  |  | |||
|  | @ -77,7 +77,7 @@ sub createProject { | |||
|     my $libs_release = "\n    "; | ||||
|     my $libs_debug = "\n    "; | ||||
|     if (!$static_library) { | ||||
|       $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}})); | ||||
|       $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib|reftable\/libreftable\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}})); | ||||
|       $libs_debug = $libs_release; | ||||
|       $libs_debug =~ s/zlib\.lib/zlibd\.lib/g; | ||||
|       $libs_debug =~ s/libexpat\.lib/libexpatd\.lib/g; | ||||
|  | @ -232,6 +232,7 @@ EOM | |||
| EOM | ||||
|     if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') { | ||||
|       my $uuid_libgit = $$build_structure{"LIBS_libgit_GUID"}; | ||||
|       my $uuid_libreftable = $$build_structure{"LIBS_reftable/libreftable_GUID"}; | ||||
|       my $uuid_xdiff_lib = $$build_structure{"LIBS_xdiff/lib_GUID"}; | ||||
|  | ||||
|       print F << "EOM"; | ||||
|  | @ -241,6 +242,14 @@ EOM | |||
|       <ReferenceOutputAssembly>false</ReferenceOutputAssembly> | ||||
|     </ProjectReference> | ||||
| EOM | ||||
|       if (!($name =~ /xdiff|libreftable/)) { | ||||
|         print F << "EOM"; | ||||
|     <ProjectReference Include="$cdup\\reftable\\libreftable\\libreftable.vcxproj"> | ||||
|       <Project>$uuid_libreftable</Project> | ||||
|       <ReferenceOutputAssembly>false</ReferenceOutputAssembly> | ||||
|     </ProjectReference> | ||||
| EOM | ||||
|       } | ||||
|       if (!($name =~ 'xdiff')) { | ||||
|         print F << "EOM"; | ||||
|     <ProjectReference Include="$cdup\\xdiff\\lib\\xdiff_lib.vcxproj"> | ||||
|  |  | |||
							
								
								
									
										6
									
								
								hash.h
								
								
								
								
							
							
						
						
									
										6
									
								
								hash.h
								
								
								
								
							|  | @ -95,12 +95,18 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s | |||
| /* Number of algorithms supported (including unknown). */ | ||||
| #define GIT_HASH_NALGOS (GIT_HASH_SHA256 + 1) | ||||
|  | ||||
| /* "sha1", big-endian */ | ||||
| #define GIT_SHA1_FORMAT_ID 0x73686131 | ||||
|  | ||||
| /* The length in bytes and in hex digits of an object name (SHA-1 value). */ | ||||
| #define GIT_SHA1_RAWSZ 20 | ||||
| #define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ) | ||||
| /* The block size of SHA-1. */ | ||||
| #define GIT_SHA1_BLKSZ 64 | ||||
|  | ||||
| /* "s256", big-endian */ | ||||
| #define GIT_SHA256_FORMAT_ID 0x73323536 | ||||
|  | ||||
| /* The length in bytes and in hex digits of an object name (SHA-256 value). */ | ||||
| #define GIT_SHA256_RAWSZ 32 | ||||
| #define GIT_SHA256_HEXSZ (2 * GIT_SHA256_RAWSZ) | ||||
|  |  | |||
|  | @ -165,7 +165,6 @@ static void git_hash_unknown_final_oid(struct object_id *oid, git_hash_ctx *ctx) | |||
| 	BUG("trying to finalize unknown hash"); | ||||
| } | ||||
|  | ||||
|  | ||||
| const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { | ||||
| 	{ | ||||
| 		NULL, | ||||
|  | @ -184,8 +183,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { | |||
| 	}, | ||||
| 	{ | ||||
| 		"sha1", | ||||
| 		/* "sha1", big-endian */ | ||||
| 		0x73686131, | ||||
| 		GIT_SHA1_FORMAT_ID, | ||||
| 		GIT_SHA1_RAWSZ, | ||||
| 		GIT_SHA1_HEXSZ, | ||||
| 		GIT_SHA1_BLKSZ, | ||||
|  | @ -200,8 +198,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { | |||
| 	}, | ||||
| 	{ | ||||
| 		"sha256", | ||||
| 		/* "s256", big-endian */ | ||||
| 		0x73323536, | ||||
| 		GIT_SHA256_FORMAT_ID, | ||||
| 		GIT_SHA256_RAWSZ, | ||||
| 		GIT_SHA256_HEXSZ, | ||||
| 		GIT_SHA256_BLKSZ, | ||||
|  |  | |||
|  | @ -0,0 +1,31 @@ | |||
| BSD License | ||||
|  | ||||
| Copyright (c) 2020, Google LLC | ||||
| All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
|  | ||||
| * Redistributions of source code must retain the above copyright notice, | ||||
| this list of conditions and the following disclaimer. | ||||
|  | ||||
| * Redistributions in binary form must reproduce the above copyright | ||||
| notice, this list of conditions and the following disclaimer in the | ||||
| documentation and/or other materials provided with the distribution. | ||||
|  | ||||
| * Neither the name of Google LLC nor the names of its contributors may | ||||
| be used to endorse or promote products derived from this software | ||||
| without specific prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  | @ -0,0 +1,128 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "basics.h" | ||||
|  | ||||
| void put_be24(uint8_t *out, uint32_t i) | ||||
| { | ||||
| 	out[0] = (uint8_t)((i >> 16) & 0xff); | ||||
| 	out[1] = (uint8_t)((i >> 8) & 0xff); | ||||
| 	out[2] = (uint8_t)(i & 0xff); | ||||
| } | ||||
|  | ||||
| uint32_t get_be24(uint8_t *in) | ||||
| { | ||||
| 	return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 | | ||||
| 	       (uint32_t)(in[2]); | ||||
| } | ||||
|  | ||||
| void put_be16(uint8_t *out, uint16_t i) | ||||
| { | ||||
| 	out[0] = (uint8_t)((i >> 8) & 0xff); | ||||
| 	out[1] = (uint8_t)(i & 0xff); | ||||
| } | ||||
|  | ||||
| int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args) | ||||
| { | ||||
| 	size_t lo = 0; | ||||
| 	size_t hi = sz; | ||||
|  | ||||
| 	/* Invariants: | ||||
| 	 * | ||||
| 	 *  (hi == sz) || f(hi) == true | ||||
| 	 *  (lo == 0 && f(0) == true) || fi(lo) == false | ||||
| 	 */ | ||||
| 	while (hi - lo > 1) { | ||||
| 		size_t mid = lo + (hi - lo) / 2; | ||||
|  | ||||
| 		if (f(mid, args)) | ||||
| 			hi = mid; | ||||
| 		else | ||||
| 			lo = mid; | ||||
| 	} | ||||
|  | ||||
| 	if (lo) | ||||
| 		return hi; | ||||
|  | ||||
| 	return f(0, args) ? 0 : 1; | ||||
| } | ||||
|  | ||||
| void free_names(char **a) | ||||
| { | ||||
| 	char **p; | ||||
| 	if (!a) { | ||||
| 		return; | ||||
| 	} | ||||
| 	for (p = a; *p; p++) { | ||||
| 		reftable_free(*p); | ||||
| 	} | ||||
| 	reftable_free(a); | ||||
| } | ||||
|  | ||||
| int names_length(char **names) | ||||
| { | ||||
| 	char **p = names; | ||||
| 	for (; *p; p++) { | ||||
| 		/* empty */ | ||||
| 	} | ||||
| 	return p - names; | ||||
| } | ||||
|  | ||||
| void parse_names(char *buf, int size, char ***namesp) | ||||
| { | ||||
| 	char **names = NULL; | ||||
| 	size_t names_cap = 0; | ||||
| 	size_t names_len = 0; | ||||
|  | ||||
| 	char *p = buf; | ||||
| 	char *end = buf + size; | ||||
| 	while (p < end) { | ||||
| 		char *next = strchr(p, '\n'); | ||||
| 		if (next && next < end) { | ||||
| 			*next = 0; | ||||
| 		} else { | ||||
| 			next = end; | ||||
| 		} | ||||
| 		if (p < next) { | ||||
| 			if (names_len == names_cap) { | ||||
| 				names_cap = 2 * names_cap + 1; | ||||
| 				names = reftable_realloc( | ||||
| 					names, names_cap * sizeof(*names)); | ||||
| 			} | ||||
| 			names[names_len++] = xstrdup(p); | ||||
| 		} | ||||
| 		p = next + 1; | ||||
| 	} | ||||
|  | ||||
| 	names = reftable_realloc(names, (names_len + 1) * sizeof(*names)); | ||||
| 	names[names_len] = NULL; | ||||
| 	*namesp = names; | ||||
| } | ||||
|  | ||||
| int names_equal(char **a, char **b) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	for (; a[i] && b[i]; i++) { | ||||
| 		if (strcmp(a[i], b[i])) { | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return a[i] == b[i]; | ||||
| } | ||||
|  | ||||
| int common_prefix_size(struct strbuf *a, struct strbuf *b) | ||||
| { | ||||
| 	int p = 0; | ||||
| 	for (; p < a->len && p < b->len; p++) { | ||||
| 		if (a->buf[p] != b->buf[p]) | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	return p; | ||||
| } | ||||
|  | @ -0,0 +1,60 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef BASICS_H | ||||
| #define BASICS_H | ||||
|  | ||||
| /* | ||||
|  * miscellaneous utilities that are not provided by Git. | ||||
|  */ | ||||
|  | ||||
| #include "system.h" | ||||
|  | ||||
| /* Bigendian en/decoding of integers */ | ||||
|  | ||||
| void put_be24(uint8_t *out, uint32_t i); | ||||
| uint32_t get_be24(uint8_t *in); | ||||
| void put_be16(uint8_t *out, uint16_t i); | ||||
|  | ||||
| /* | ||||
|  * find smallest index i in [0, sz) at which f(i) is true, assuming | ||||
|  * that f is ascending. Return sz if f(i) is false for all indices. | ||||
|  * | ||||
|  * Contrary to bsearch(3), this returns something useful if the argument is not | ||||
|  * found. | ||||
|  */ | ||||
| int binsearch(size_t sz, int (*f)(size_t k, void *args), void *args); | ||||
|  | ||||
| /* | ||||
|  * Frees a NULL terminated array of malloced strings. The array itself is also | ||||
|  * freed. | ||||
|  */ | ||||
| void free_names(char **a); | ||||
|  | ||||
| /* parse a newline separated list of names. `size` is the length of the buffer, | ||||
|  * without terminating '\0'. Empty names are discarded. */ | ||||
| void parse_names(char *buf, int size, char ***namesp); | ||||
|  | ||||
| /* compares two NULL-terminated arrays of strings. */ | ||||
| int names_equal(char **a, char **b); | ||||
|  | ||||
| /* returns the array size of a NULL-terminated array of strings. */ | ||||
| int names_length(char **names); | ||||
|  | ||||
| /* Allocation routines; they invoke the functions set through | ||||
|  * reftable_set_alloc() */ | ||||
| void *reftable_malloc(size_t sz); | ||||
| void *reftable_realloc(void *p, size_t sz); | ||||
| void reftable_free(void *p); | ||||
| void *reftable_calloc(size_t sz); | ||||
|  | ||||
| /* Find the longest shared prefix size of `a` and `b` */ | ||||
| struct strbuf; | ||||
| int common_prefix_size(struct strbuf *a, struct strbuf *b); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,98 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "system.h" | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "test_framework.h" | ||||
| #include "reftable-tests.h" | ||||
|  | ||||
| struct binsearch_args { | ||||
| 	int key; | ||||
| 	int *arr; | ||||
| }; | ||||
|  | ||||
| static int binsearch_func(size_t i, void *void_args) | ||||
| { | ||||
| 	struct binsearch_args *args = void_args; | ||||
|  | ||||
| 	return args->key < args->arr[i]; | ||||
| } | ||||
|  | ||||
| static void test_binsearch(void) | ||||
| { | ||||
| 	int arr[] = { 2, 4, 6, 8, 10 }; | ||||
| 	size_t sz = ARRAY_SIZE(arr); | ||||
| 	struct binsearch_args args = { | ||||
| 		.arr = arr, | ||||
| 	}; | ||||
|  | ||||
| 	int i = 0; | ||||
| 	for (i = 1; i < 11; i++) { | ||||
| 		int res; | ||||
| 		args.key = i; | ||||
| 		res = binsearch(sz, &binsearch_func, &args); | ||||
|  | ||||
| 		if (res < sz) { | ||||
| 			EXPECT(args.key < arr[res]); | ||||
| 			if (res > 0) { | ||||
| 				EXPECT(args.key >= arr[res - 1]); | ||||
| 			} | ||||
| 		} else { | ||||
| 			EXPECT(args.key == 10 || args.key == 11); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void test_names_length(void) | ||||
| { | ||||
| 	char *a[] = { "a", "b", NULL }; | ||||
| 	EXPECT(names_length(a) == 2); | ||||
| } | ||||
|  | ||||
| static void test_parse_names_normal(void) | ||||
| { | ||||
| 	char in[] = "a\nb\n"; | ||||
| 	char **out = NULL; | ||||
| 	parse_names(in, strlen(in), &out); | ||||
| 	EXPECT(!strcmp(out[0], "a")); | ||||
| 	EXPECT(!strcmp(out[1], "b")); | ||||
| 	EXPECT(!out[2]); | ||||
| 	free_names(out); | ||||
| } | ||||
|  | ||||
| static void test_parse_names_drop_empty(void) | ||||
| { | ||||
| 	char in[] = "a\n\n"; | ||||
| 	char **out = NULL; | ||||
| 	parse_names(in, strlen(in), &out); | ||||
| 	EXPECT(!strcmp(out[0], "a")); | ||||
| 	EXPECT(!out[1]); | ||||
| 	free_names(out); | ||||
| } | ||||
|  | ||||
| static void test_common_prefix(void) | ||||
| { | ||||
| 	struct strbuf s1 = STRBUF_INIT; | ||||
| 	struct strbuf s2 = STRBUF_INIT; | ||||
| 	strbuf_addstr(&s1, "abcdef"); | ||||
| 	strbuf_addstr(&s2, "abc"); | ||||
| 	EXPECT(common_prefix_size(&s1, &s2) == 3); | ||||
| 	strbuf_release(&s1); | ||||
| 	strbuf_release(&s2); | ||||
| } | ||||
|  | ||||
| int basics_test_main(int argc, const char *argv[]) | ||||
| { | ||||
| 	RUN_TEST(test_common_prefix); | ||||
| 	RUN_TEST(test_parse_names_normal); | ||||
| 	RUN_TEST(test_parse_names_drop_empty); | ||||
| 	RUN_TEST(test_binsearch); | ||||
| 	RUN_TEST(test_names_length); | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -0,0 +1,437 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "block.h" | ||||
|  | ||||
| #include "blocksource.h" | ||||
| #include "constants.h" | ||||
| #include "record.h" | ||||
| #include "reftable-error.h" | ||||
| #include "system.h" | ||||
| #include <zlib.h> | ||||
|  | ||||
| int header_size(int version) | ||||
| { | ||||
| 	switch (version) { | ||||
| 	case 1: | ||||
| 		return 24; | ||||
| 	case 2: | ||||
| 		return 28; | ||||
| 	} | ||||
| 	abort(); | ||||
| } | ||||
|  | ||||
| int footer_size(int version) | ||||
| { | ||||
| 	switch (version) { | ||||
| 	case 1: | ||||
| 		return 68; | ||||
| 	case 2: | ||||
| 		return 72; | ||||
| 	} | ||||
| 	abort(); | ||||
| } | ||||
|  | ||||
| static int block_writer_register_restart(struct block_writer *w, int n, | ||||
| 					 int is_restart, struct strbuf *key) | ||||
| { | ||||
| 	int rlen = w->restart_len; | ||||
| 	if (rlen >= MAX_RESTARTS) { | ||||
| 		is_restart = 0; | ||||
| 	} | ||||
|  | ||||
| 	if (is_restart) { | ||||
| 		rlen++; | ||||
| 	} | ||||
| 	if (2 + 3 * rlen + n > w->block_size - w->next) | ||||
| 		return -1; | ||||
| 	if (is_restart) { | ||||
| 		if (w->restart_len == w->restart_cap) { | ||||
| 			w->restart_cap = w->restart_cap * 2 + 1; | ||||
| 			w->restarts = reftable_realloc( | ||||
| 				w->restarts, sizeof(uint32_t) * w->restart_cap); | ||||
| 		} | ||||
|  | ||||
| 		w->restarts[w->restart_len++] = w->next; | ||||
| 	} | ||||
|  | ||||
| 	w->next += n; | ||||
|  | ||||
| 	strbuf_reset(&w->last_key); | ||||
| 	strbuf_addbuf(&w->last_key, key); | ||||
| 	w->entries++; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf, | ||||
| 		       uint32_t block_size, uint32_t header_off, int hash_size) | ||||
| { | ||||
| 	bw->buf = buf; | ||||
| 	bw->hash_size = hash_size; | ||||
| 	bw->block_size = block_size; | ||||
| 	bw->header_off = header_off; | ||||
| 	bw->buf[header_off] = typ; | ||||
| 	bw->next = header_off + 4; | ||||
| 	bw->restart_interval = 16; | ||||
| 	bw->entries = 0; | ||||
| 	bw->restart_len = 0; | ||||
| 	bw->last_key.len = 0; | ||||
| } | ||||
|  | ||||
| uint8_t block_writer_type(struct block_writer *bw) | ||||
| { | ||||
| 	return bw->buf[bw->header_off]; | ||||
| } | ||||
|  | ||||
| /* adds the reftable_record to the block. Returns -1 if it does not fit, 0 on | ||||
|    success */ | ||||
| int block_writer_add(struct block_writer *w, struct reftable_record *rec) | ||||
| { | ||||
| 	struct strbuf empty = STRBUF_INIT; | ||||
| 	struct strbuf last = | ||||
| 		w->entries % w->restart_interval == 0 ? empty : w->last_key; | ||||
| 	struct string_view out = { | ||||
| 		.buf = w->buf + w->next, | ||||
| 		.len = w->block_size - w->next, | ||||
| 	}; | ||||
|  | ||||
| 	struct string_view start = out; | ||||
|  | ||||
| 	int is_restart = 0; | ||||
| 	struct strbuf key = STRBUF_INIT; | ||||
| 	int n = 0; | ||||
|  | ||||
| 	reftable_record_key(rec, &key); | ||||
| 	n = reftable_encode_key(&is_restart, out, last, key, | ||||
| 				reftable_record_val_type(rec)); | ||||
| 	if (n < 0) | ||||
| 		goto done; | ||||
| 	string_view_consume(&out, n); | ||||
|  | ||||
| 	n = reftable_record_encode(rec, out, w->hash_size); | ||||
| 	if (n < 0) | ||||
| 		goto done; | ||||
| 	string_view_consume(&out, n); | ||||
|  | ||||
| 	if (block_writer_register_restart(w, start.len - out.len, is_restart, | ||||
| 					  &key) < 0) | ||||
| 		goto done; | ||||
|  | ||||
| 	strbuf_release(&key); | ||||
| 	return 0; | ||||
|  | ||||
| done: | ||||
| 	strbuf_release(&key); | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| int block_writer_finish(struct block_writer *w) | ||||
| { | ||||
| 	int i; | ||||
| 	for (i = 0; i < w->restart_len; i++) { | ||||
| 		put_be24(w->buf + w->next, w->restarts[i]); | ||||
| 		w->next += 3; | ||||
| 	} | ||||
|  | ||||
| 	put_be16(w->buf + w->next, w->restart_len); | ||||
| 	w->next += 2; | ||||
| 	put_be24(w->buf + 1 + w->header_off, w->next); | ||||
|  | ||||
| 	if (block_writer_type(w) == BLOCK_TYPE_LOG) { | ||||
| 		int block_header_skip = 4 + w->header_off; | ||||
| 		uLongf src_len = w->next - block_header_skip; | ||||
| 		uLongf dest_cap = src_len * 1.001 + 12; | ||||
|  | ||||
| 		uint8_t *compressed = reftable_malloc(dest_cap); | ||||
| 		while (1) { | ||||
| 			uLongf out_dest_len = dest_cap; | ||||
| 			int zresult = compress2(compressed, &out_dest_len, | ||||
| 						w->buf + block_header_skip, | ||||
| 						src_len, 9); | ||||
| 			if (zresult == Z_BUF_ERROR && dest_cap < LONG_MAX) { | ||||
| 				dest_cap *= 2; | ||||
| 				compressed = | ||||
| 					reftable_realloc(compressed, dest_cap); | ||||
| 				if (compressed) | ||||
| 					continue; | ||||
| 			} | ||||
|  | ||||
| 			if (Z_OK != zresult) { | ||||
| 				reftable_free(compressed); | ||||
| 				return REFTABLE_ZLIB_ERROR; | ||||
| 			} | ||||
|  | ||||
| 			memcpy(w->buf + block_header_skip, compressed, | ||||
| 			       out_dest_len); | ||||
| 			w->next = out_dest_len + block_header_skip; | ||||
| 			reftable_free(compressed); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	return w->next; | ||||
| } | ||||
|  | ||||
| uint8_t block_reader_type(struct block_reader *r) | ||||
| { | ||||
| 	return r->block.data[r->header_off]; | ||||
| } | ||||
|  | ||||
| int block_reader_init(struct block_reader *br, struct reftable_block *block, | ||||
| 		      uint32_t header_off, uint32_t table_block_size, | ||||
| 		      int hash_size) | ||||
| { | ||||
| 	uint32_t full_block_size = table_block_size; | ||||
| 	uint8_t typ = block->data[header_off]; | ||||
| 	uint32_t sz = get_be24(block->data + header_off + 1); | ||||
|  | ||||
| 	uint16_t restart_count = 0; | ||||
| 	uint32_t restart_start = 0; | ||||
| 	uint8_t *restart_bytes = NULL; | ||||
|  | ||||
| 	if (!reftable_is_block_type(typ)) | ||||
| 		return REFTABLE_FORMAT_ERROR; | ||||
|  | ||||
| 	if (typ == BLOCK_TYPE_LOG) { | ||||
| 		int block_header_skip = 4 + header_off; | ||||
| 		uLongf dst_len = sz - block_header_skip; /* total size of dest | ||||
| 							    buffer. */ | ||||
| 		uLongf src_len = block->len - block_header_skip; | ||||
| 		/* Log blocks specify the *uncompressed* size in their header. | ||||
| 		 */ | ||||
| 		uint8_t *uncompressed = reftable_malloc(sz); | ||||
|  | ||||
| 		/* Copy over the block header verbatim. It's not compressed. */ | ||||
| 		memcpy(uncompressed, block->data, block_header_skip); | ||||
|  | ||||
| 		/* Uncompress */ | ||||
| 		if (Z_OK != | ||||
| 		    uncompress2(uncompressed + block_header_skip, &dst_len, | ||||
| 				block->data + block_header_skip, &src_len)) { | ||||
| 			reftable_free(uncompressed); | ||||
| 			return REFTABLE_ZLIB_ERROR; | ||||
| 		} | ||||
|  | ||||
| 		if (dst_len + block_header_skip != sz) | ||||
| 			return REFTABLE_FORMAT_ERROR; | ||||
|  | ||||
| 		/* We're done with the input data. */ | ||||
| 		reftable_block_done(block); | ||||
| 		block->data = uncompressed; | ||||
| 		block->len = sz; | ||||
| 		block->source = malloc_block_source(); | ||||
| 		full_block_size = src_len + block_header_skip; | ||||
| 	} else if (full_block_size == 0) { | ||||
| 		full_block_size = sz; | ||||
| 	} else if (sz < full_block_size && sz < block->len && | ||||
| 		   block->data[sz] != 0) { | ||||
| 		/* If the block is smaller than the full block size, it is | ||||
| 		   padded (data followed by '\0') or the next block is | ||||
| 		   unaligned. */ | ||||
| 		full_block_size = sz; | ||||
| 	} | ||||
|  | ||||
| 	restart_count = get_be16(block->data + sz - 2); | ||||
| 	restart_start = sz - 2 - 3 * restart_count; | ||||
| 	restart_bytes = block->data + restart_start; | ||||
|  | ||||
| 	/* transfer ownership. */ | ||||
| 	br->block = *block; | ||||
| 	block->data = NULL; | ||||
| 	block->len = 0; | ||||
|  | ||||
| 	br->hash_size = hash_size; | ||||
| 	br->block_len = restart_start; | ||||
| 	br->full_block_size = full_block_size; | ||||
| 	br->header_off = header_off; | ||||
| 	br->restart_count = restart_count; | ||||
| 	br->restart_bytes = restart_bytes; | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static uint32_t block_reader_restart_offset(struct block_reader *br, int i) | ||||
| { | ||||
| 	return get_be24(br->restart_bytes + 3 * i); | ||||
| } | ||||
|  | ||||
| void block_reader_start(struct block_reader *br, struct block_iter *it) | ||||
| { | ||||
| 	it->br = br; | ||||
| 	strbuf_reset(&it->last_key); | ||||
| 	it->next_off = br->header_off + 4; | ||||
| } | ||||
|  | ||||
| struct restart_find_args { | ||||
| 	int error; | ||||
| 	struct strbuf key; | ||||
| 	struct block_reader *r; | ||||
| }; | ||||
|  | ||||
| static int restart_key_less(size_t idx, void *args) | ||||
| { | ||||
| 	struct restart_find_args *a = args; | ||||
| 	uint32_t off = block_reader_restart_offset(a->r, idx); | ||||
| 	struct string_view in = { | ||||
| 		.buf = a->r->block.data + off, | ||||
| 		.len = a->r->block_len - off, | ||||
| 	}; | ||||
|  | ||||
| 	/* the restart key is verbatim in the block, so this could avoid the | ||||
| 	   alloc for decoding the key */ | ||||
| 	struct strbuf rkey = STRBUF_INIT; | ||||
| 	struct strbuf last_key = STRBUF_INIT; | ||||
| 	uint8_t unused_extra; | ||||
| 	int n = reftable_decode_key(&rkey, &unused_extra, last_key, in); | ||||
| 	int result; | ||||
| 	if (n < 0) { | ||||
| 		a->error = 1; | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	result = strbuf_cmp(&a->key, &rkey); | ||||
| 	strbuf_release(&rkey); | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| void block_iter_copy_from(struct block_iter *dest, struct block_iter *src) | ||||
| { | ||||
| 	dest->br = src->br; | ||||
| 	dest->next_off = src->next_off; | ||||
| 	strbuf_reset(&dest->last_key); | ||||
| 	strbuf_addbuf(&dest->last_key, &src->last_key); | ||||
| } | ||||
|  | ||||
| int block_iter_next(struct block_iter *it, struct reftable_record *rec) | ||||
| { | ||||
| 	struct string_view in = { | ||||
| 		.buf = it->br->block.data + it->next_off, | ||||
| 		.len = it->br->block_len - it->next_off, | ||||
| 	}; | ||||
| 	struct string_view start = in; | ||||
| 	struct strbuf key = STRBUF_INIT; | ||||
| 	uint8_t extra = 0; | ||||
| 	int n = 0; | ||||
|  | ||||
| 	if (it->next_off >= it->br->block_len) | ||||
| 		return 1; | ||||
|  | ||||
| 	n = reftable_decode_key(&key, &extra, it->last_key, in); | ||||
| 	if (n < 0) | ||||
| 		return -1; | ||||
|  | ||||
| 	string_view_consume(&in, n); | ||||
| 	n = reftable_record_decode(rec, key, extra, in, it->br->hash_size); | ||||
| 	if (n < 0) | ||||
| 		return -1; | ||||
| 	string_view_consume(&in, n); | ||||
|  | ||||
| 	strbuf_reset(&it->last_key); | ||||
| 	strbuf_addbuf(&it->last_key, &key); | ||||
| 	it->next_off += start.len - in.len; | ||||
| 	strbuf_release(&key); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int block_reader_first_key(struct block_reader *br, struct strbuf *key) | ||||
| { | ||||
| 	struct strbuf empty = STRBUF_INIT; | ||||
| 	int off = br->header_off + 4; | ||||
| 	struct string_view in = { | ||||
| 		.buf = br->block.data + off, | ||||
| 		.len = br->block_len - off, | ||||
| 	}; | ||||
|  | ||||
| 	uint8_t extra = 0; | ||||
| 	int n = reftable_decode_key(key, &extra, empty, in); | ||||
| 	if (n < 0) | ||||
| 		return n; | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int block_iter_seek(struct block_iter *it, struct strbuf *want) | ||||
| { | ||||
| 	return block_reader_seek(it->br, it, want); | ||||
| } | ||||
|  | ||||
| void block_iter_close(struct block_iter *it) | ||||
| { | ||||
| 	strbuf_release(&it->last_key); | ||||
| } | ||||
|  | ||||
| int block_reader_seek(struct block_reader *br, struct block_iter *it, | ||||
| 		      struct strbuf *want) | ||||
| { | ||||
| 	struct restart_find_args args = { | ||||
| 		.key = *want, | ||||
| 		.r = br, | ||||
| 	}; | ||||
| 	struct reftable_record rec = reftable_new_record(block_reader_type(br)); | ||||
| 	struct strbuf key = STRBUF_INIT; | ||||
| 	int err = 0; | ||||
| 	struct block_iter next = { | ||||
| 		.last_key = STRBUF_INIT, | ||||
| 	}; | ||||
|  | ||||
| 	int i = binsearch(br->restart_count, &restart_key_less, &args); | ||||
| 	if (args.error) { | ||||
| 		err = REFTABLE_FORMAT_ERROR; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	it->br = br; | ||||
| 	if (i > 0) { | ||||
| 		i--; | ||||
| 		it->next_off = block_reader_restart_offset(br, i); | ||||
| 	} else { | ||||
| 		it->next_off = br->header_off + 4; | ||||
| 	} | ||||
|  | ||||
| 	/* We're looking for the last entry less/equal than the wanted key, so | ||||
| 	   we have to go one entry too far and then back up. | ||||
| 	*/ | ||||
| 	while (1) { | ||||
| 		block_iter_copy_from(&next, it); | ||||
| 		err = block_iter_next(&next, &rec); | ||||
| 		if (err < 0) | ||||
| 			goto done; | ||||
|  | ||||
| 		reftable_record_key(&rec, &key); | ||||
| 		if (err > 0 || strbuf_cmp(&key, want) >= 0) { | ||||
| 			err = 0; | ||||
| 			goto done; | ||||
| 		} | ||||
|  | ||||
| 		block_iter_copy_from(it, &next); | ||||
| 	} | ||||
|  | ||||
| done: | ||||
| 	strbuf_release(&key); | ||||
| 	strbuf_release(&next.last_key); | ||||
| 	reftable_record_destroy(&rec); | ||||
|  | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| void block_writer_release(struct block_writer *bw) | ||||
| { | ||||
| 	FREE_AND_NULL(bw->restarts); | ||||
| 	strbuf_release(&bw->last_key); | ||||
| 	/* the block is not owned. */ | ||||
| } | ||||
|  | ||||
| void reftable_block_done(struct reftable_block *blockp) | ||||
| { | ||||
| 	struct reftable_block_source source = blockp->source; | ||||
| 	if (blockp && source.ops) | ||||
| 		source.ops->return_block(source.arg, blockp); | ||||
| 	blockp->data = NULL; | ||||
| 	blockp->len = 0; | ||||
| 	blockp->source.ops = NULL; | ||||
| 	blockp->source.arg = NULL; | ||||
| } | ||||
|  | @ -0,0 +1,127 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef BLOCK_H | ||||
| #define BLOCK_H | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "record.h" | ||||
| #include "reftable-blocksource.h" | ||||
|  | ||||
| /* | ||||
|  * Writes reftable blocks. The block_writer is reused across blocks to minimize | ||||
|  * allocation overhead. | ||||
|  */ | ||||
| struct block_writer { | ||||
| 	uint8_t *buf; | ||||
| 	uint32_t block_size; | ||||
|  | ||||
| 	/* Offset ofof the global header. Nonzero in the first block only. */ | ||||
| 	uint32_t header_off; | ||||
|  | ||||
| 	/* How often to restart keys. */ | ||||
| 	int restart_interval; | ||||
| 	int hash_size; | ||||
|  | ||||
| 	/* Offset of next uint8_t to write. */ | ||||
| 	uint32_t next; | ||||
| 	uint32_t *restarts; | ||||
| 	uint32_t restart_len; | ||||
| 	uint32_t restart_cap; | ||||
|  | ||||
| 	struct strbuf last_key; | ||||
| 	int entries; | ||||
| }; | ||||
|  | ||||
| /* | ||||
|  * initializes the blockwriter to write `typ` entries, using `buf` as temporary | ||||
|  * storage. `buf` is not owned by the block_writer. */ | ||||
| void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf, | ||||
| 		       uint32_t block_size, uint32_t header_off, int hash_size); | ||||
|  | ||||
| /* returns the block type (eg. 'r' for ref records. */ | ||||
| uint8_t block_writer_type(struct block_writer *bw); | ||||
|  | ||||
| /* appends the record, or -1 if it doesn't fit. */ | ||||
| int block_writer_add(struct block_writer *w, struct reftable_record *rec); | ||||
|  | ||||
| /* appends the key restarts, and compress the block if necessary. */ | ||||
| int block_writer_finish(struct block_writer *w); | ||||
|  | ||||
| /* clears out internally allocated block_writer members. */ | ||||
| void block_writer_release(struct block_writer *bw); | ||||
|  | ||||
| /* Read a block. */ | ||||
| struct block_reader { | ||||
| 	/* offset of the block header; nonzero for the first block in a | ||||
| 	 * reftable. */ | ||||
| 	uint32_t header_off; | ||||
|  | ||||
| 	/* the memory block */ | ||||
| 	struct reftable_block block; | ||||
| 	int hash_size; | ||||
|  | ||||
| 	/* size of the data, excluding restart data. */ | ||||
| 	uint32_t block_len; | ||||
| 	uint8_t *restart_bytes; | ||||
| 	uint16_t restart_count; | ||||
|  | ||||
| 	/* size of the data in the file. For log blocks, this is the compressed | ||||
| 	 * size. */ | ||||
| 	uint32_t full_block_size; | ||||
| }; | ||||
|  | ||||
| /* Iterate over entries in a block */ | ||||
| struct block_iter { | ||||
| 	/* offset within the block of the next entry to read. */ | ||||
| 	uint32_t next_off; | ||||
| 	struct block_reader *br; | ||||
|  | ||||
| 	/* key for last entry we read. */ | ||||
| 	struct strbuf last_key; | ||||
| }; | ||||
|  | ||||
| /* initializes a block reader. */ | ||||
| int block_reader_init(struct block_reader *br, struct reftable_block *bl, | ||||
| 		      uint32_t header_off, uint32_t table_block_size, | ||||
| 		      int hash_size); | ||||
|  | ||||
| /* Position `it` at start of the block */ | ||||
| void block_reader_start(struct block_reader *br, struct block_iter *it); | ||||
|  | ||||
| /* Position `it` to the `want` key in the block */ | ||||
| int block_reader_seek(struct block_reader *br, struct block_iter *it, | ||||
| 		      struct strbuf *want); | ||||
|  | ||||
| /* Returns the block type (eg. 'r' for refs) */ | ||||
| uint8_t block_reader_type(struct block_reader *r); | ||||
|  | ||||
| /* Decodes the first key in the block */ | ||||
| int block_reader_first_key(struct block_reader *br, struct strbuf *key); | ||||
|  | ||||
| void block_iter_copy_from(struct block_iter *dest, struct block_iter *src); | ||||
|  | ||||
| /* return < 0 for error, 0 for OK, > 0 for EOF. */ | ||||
| int block_iter_next(struct block_iter *it, struct reftable_record *rec); | ||||
|  | ||||
| /* Seek to `want` with in the block pointed to by `it` */ | ||||
| int block_iter_seek(struct block_iter *it, struct strbuf *want); | ||||
|  | ||||
| /* deallocate memory for `it`. The block reader and its block is left intact. */ | ||||
| void block_iter_close(struct block_iter *it); | ||||
|  | ||||
| /* size of file header, depending on format version */ | ||||
| int header_size(int version); | ||||
|  | ||||
| /* size of file footer, depending on format version */ | ||||
| int footer_size(int version); | ||||
|  | ||||
| /* returns a block to its source. */ | ||||
| void reftable_block_done(struct reftable_block *ret); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,120 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "block.h" | ||||
|  | ||||
| #include "system.h" | ||||
| #include "blocksource.h" | ||||
| #include "basics.h" | ||||
| #include "constants.h" | ||||
| #include "record.h" | ||||
| #include "test_framework.h" | ||||
| #include "reftable-tests.h" | ||||
|  | ||||
| static void test_block_read_write(void) | ||||
| { | ||||
| 	const int header_off = 21; /* random */ | ||||
| 	char *names[30]; | ||||
| 	const int N = ARRAY_SIZE(names); | ||||
| 	const int block_size = 1024; | ||||
| 	struct reftable_block block = { NULL }; | ||||
| 	struct block_writer bw = { | ||||
| 		.last_key = STRBUF_INIT, | ||||
| 	}; | ||||
| 	struct reftable_ref_record ref = { NULL }; | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	int i = 0; | ||||
| 	int n; | ||||
| 	struct block_reader br = { 0 }; | ||||
| 	struct block_iter it = { .last_key = STRBUF_INIT }; | ||||
| 	int j = 0; | ||||
| 	struct strbuf want = STRBUF_INIT; | ||||
|  | ||||
| 	block.data = reftable_calloc(block_size); | ||||
| 	block.len = block_size; | ||||
| 	block.source = malloc_block_source(); | ||||
| 	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size, | ||||
| 			  header_off, hash_size(GIT_SHA1_FORMAT_ID)); | ||||
| 	reftable_record_from_ref(&rec, &ref); | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		char name[100]; | ||||
| 		uint8_t hash[GIT_SHA1_RAWSZ]; | ||||
| 		snprintf(name, sizeof(name), "branch%02d", i); | ||||
| 		memset(hash, i, sizeof(hash)); | ||||
|  | ||||
| 		ref.refname = name; | ||||
| 		ref.value_type = REFTABLE_REF_VAL1; | ||||
| 		ref.value.val1 = hash; | ||||
|  | ||||
| 		names[i] = xstrdup(name); | ||||
| 		n = block_writer_add(&bw, &rec); | ||||
| 		ref.refname = NULL; | ||||
| 		ref.value_type = REFTABLE_REF_DELETION; | ||||
| 		EXPECT(n == 0); | ||||
| 	} | ||||
|  | ||||
| 	n = block_writer_finish(&bw); | ||||
| 	EXPECT(n > 0); | ||||
|  | ||||
| 	block_writer_release(&bw); | ||||
|  | ||||
| 	block_reader_init(&br, &block, header_off, block_size, GIT_SHA1_RAWSZ); | ||||
|  | ||||
| 	block_reader_start(&br, &it); | ||||
|  | ||||
| 	while (1) { | ||||
| 		int r = block_iter_next(&it, &rec); | ||||
| 		EXPECT(r >= 0); | ||||
| 		if (r > 0) { | ||||
| 			break; | ||||
| 		} | ||||
| 		EXPECT_STREQ(names[j], ref.refname); | ||||
| 		j++; | ||||
| 	} | ||||
|  | ||||
| 	reftable_record_release(&rec); | ||||
| 	block_iter_close(&it); | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		struct block_iter it = { .last_key = STRBUF_INIT }; | ||||
| 		strbuf_reset(&want); | ||||
| 		strbuf_addstr(&want, names[i]); | ||||
|  | ||||
| 		n = block_reader_seek(&br, &it, &want); | ||||
| 		EXPECT(n == 0); | ||||
|  | ||||
| 		n = block_iter_next(&it, &rec); | ||||
| 		EXPECT(n == 0); | ||||
|  | ||||
| 		EXPECT_STREQ(names[i], ref.refname); | ||||
|  | ||||
| 		want.len--; | ||||
| 		n = block_reader_seek(&br, &it, &want); | ||||
| 		EXPECT(n == 0); | ||||
|  | ||||
| 		n = block_iter_next(&it, &rec); | ||||
| 		EXPECT(n == 0); | ||||
| 		EXPECT_STREQ(names[10 * (i / 10)], ref.refname); | ||||
|  | ||||
| 		block_iter_close(&it); | ||||
| 	} | ||||
|  | ||||
| 	reftable_record_release(&rec); | ||||
| 	reftable_block_done(&br.block); | ||||
| 	strbuf_release(&want); | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		reftable_free(names[i]); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int block_test_main(int argc, const char *argv[]) | ||||
| { | ||||
| 	RUN_TEST(test_block_read_write); | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -0,0 +1,148 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "system.h" | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "blocksource.h" | ||||
| #include "reftable-blocksource.h" | ||||
| #include "reftable-error.h" | ||||
|  | ||||
| static void strbuf_return_block(void *b, struct reftable_block *dest) | ||||
| { | ||||
| 	memset(dest->data, 0xff, dest->len); | ||||
| 	reftable_free(dest->data); | ||||
| } | ||||
|  | ||||
| static void strbuf_close(void *b) | ||||
| { | ||||
| } | ||||
|  | ||||
| static int strbuf_read_block(void *v, struct reftable_block *dest, uint64_t off, | ||||
| 			     uint32_t size) | ||||
| { | ||||
| 	struct strbuf *b = v; | ||||
| 	assert(off + size <= b->len); | ||||
| 	dest->data = reftable_calloc(size); | ||||
| 	memcpy(dest->data, b->buf + off, size); | ||||
| 	dest->len = size; | ||||
| 	return size; | ||||
| } | ||||
|  | ||||
| static uint64_t strbuf_size(void *b) | ||||
| { | ||||
| 	return ((struct strbuf *)b)->len; | ||||
| } | ||||
|  | ||||
| static struct reftable_block_source_vtable strbuf_vtable = { | ||||
| 	.size = &strbuf_size, | ||||
| 	.read_block = &strbuf_read_block, | ||||
| 	.return_block = &strbuf_return_block, | ||||
| 	.close = &strbuf_close, | ||||
| }; | ||||
|  | ||||
| void block_source_from_strbuf(struct reftable_block_source *bs, | ||||
| 			      struct strbuf *buf) | ||||
| { | ||||
| 	assert(!bs->ops); | ||||
| 	bs->ops = &strbuf_vtable; | ||||
| 	bs->arg = buf; | ||||
| } | ||||
|  | ||||
| static void malloc_return_block(void *b, struct reftable_block *dest) | ||||
| { | ||||
| 	memset(dest->data, 0xff, dest->len); | ||||
| 	reftable_free(dest->data); | ||||
| } | ||||
|  | ||||
| static struct reftable_block_source_vtable malloc_vtable = { | ||||
| 	.return_block = &malloc_return_block, | ||||
| }; | ||||
|  | ||||
| static struct reftable_block_source malloc_block_source_instance = { | ||||
| 	.ops = &malloc_vtable, | ||||
| }; | ||||
|  | ||||
| struct reftable_block_source malloc_block_source(void) | ||||
| { | ||||
| 	return malloc_block_source_instance; | ||||
| } | ||||
|  | ||||
| struct file_block_source { | ||||
| 	int fd; | ||||
| 	uint64_t size; | ||||
| }; | ||||
|  | ||||
| static uint64_t file_size(void *b) | ||||
| { | ||||
| 	return ((struct file_block_source *)b)->size; | ||||
| } | ||||
|  | ||||
| static void file_return_block(void *b, struct reftable_block *dest) | ||||
| { | ||||
| 	memset(dest->data, 0xff, dest->len); | ||||
| 	reftable_free(dest->data); | ||||
| } | ||||
|  | ||||
| static void file_close(void *b) | ||||
| { | ||||
| 	int fd = ((struct file_block_source *)b)->fd; | ||||
| 	if (fd > 0) { | ||||
| 		close(fd); | ||||
| 		((struct file_block_source *)b)->fd = 0; | ||||
| 	} | ||||
|  | ||||
| 	reftable_free(b); | ||||
| } | ||||
|  | ||||
| static int file_read_block(void *v, struct reftable_block *dest, uint64_t off, | ||||
| 			   uint32_t size) | ||||
| { | ||||
| 	struct file_block_source *b = v; | ||||
| 	assert(off + size <= b->size); | ||||
| 	dest->data = reftable_malloc(size); | ||||
| 	if (pread(b->fd, dest->data, size, off) != size) | ||||
| 		return -1; | ||||
| 	dest->len = size; | ||||
| 	return size; | ||||
| } | ||||
|  | ||||
| static struct reftable_block_source_vtable file_vtable = { | ||||
| 	.size = &file_size, | ||||
| 	.read_block = &file_read_block, | ||||
| 	.return_block = &file_return_block, | ||||
| 	.close = &file_close, | ||||
| }; | ||||
|  | ||||
| int reftable_block_source_from_file(struct reftable_block_source *bs, | ||||
| 				    const char *name) | ||||
| { | ||||
| 	struct stat st = { 0 }; | ||||
| 	int err = 0; | ||||
| 	int fd = open(name, O_RDONLY); | ||||
| 	struct file_block_source *p = NULL; | ||||
| 	if (fd < 0) { | ||||
| 		if (errno == ENOENT) { | ||||
| 			return REFTABLE_NOT_EXIST_ERROR; | ||||
| 		} | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	err = fstat(fd, &st); | ||||
| 	if (err < 0) | ||||
| 		return -1; | ||||
|  | ||||
| 	p = reftable_calloc(sizeof(struct file_block_source)); | ||||
| 	p->size = st.st_size; | ||||
| 	p->fd = fd; | ||||
|  | ||||
| 	assert(!bs->ops); | ||||
| 	bs->ops = &file_vtable; | ||||
| 	bs->arg = p; | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -0,0 +1,22 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef BLOCKSOURCE_H | ||||
| #define BLOCKSOURCE_H | ||||
|  | ||||
| #include "system.h" | ||||
|  | ||||
| struct reftable_block_source; | ||||
|  | ||||
| /* Create an in-memory block source for reading reftables */ | ||||
| void block_source_from_strbuf(struct reftable_block_source *bs, | ||||
| 			      struct strbuf *buf); | ||||
|  | ||||
| struct reftable_block_source malloc_block_source(void); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,21 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef CONSTANTS_H | ||||
| #define CONSTANTS_H | ||||
|  | ||||
| #define BLOCK_TYPE_LOG 'g' | ||||
| #define BLOCK_TYPE_INDEX 'i' | ||||
| #define BLOCK_TYPE_REF 'r' | ||||
| #define BLOCK_TYPE_OBJ 'o' | ||||
| #define BLOCK_TYPE_ANY 0 | ||||
|  | ||||
| #define MAX_RESTARTS ((1 << 16) - 1) | ||||
| #define DEFAULT_BLOCK_SIZE 4096 | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,107 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "git-compat-util.h" | ||||
| #include "hash.h" | ||||
|  | ||||
| #include "reftable-blocksource.h" | ||||
| #include "reftable-error.h" | ||||
| #include "reftable-merged.h" | ||||
| #include "reftable-record.h" | ||||
| #include "reftable-tests.h" | ||||
| #include "reftable-writer.h" | ||||
| #include "reftable-iterator.h" | ||||
| #include "reftable-reader.h" | ||||
| #include "reftable-stack.h" | ||||
| #include "reftable-generic.h" | ||||
|  | ||||
| #include <stddef.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <unistd.h> | ||||
| #include <string.h> | ||||
|  | ||||
| static int compact_stack(const char *stackdir) | ||||
| { | ||||
| 	struct reftable_stack *stack = NULL; | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
|  | ||||
| 	int err = reftable_new_stack(&stack, stackdir, cfg); | ||||
| 	if (err < 0) | ||||
| 		goto done; | ||||
|  | ||||
| 	err = reftable_stack_compact_all(stack, NULL); | ||||
| 	if (err < 0) | ||||
| 		goto done; | ||||
| done: | ||||
| 	if (stack) { | ||||
| 		reftable_stack_destroy(stack); | ||||
| 	} | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static void print_help(void) | ||||
| { | ||||
| 	printf("usage: dump [-cst] arg\n\n" | ||||
| 	       "options: \n" | ||||
| 	       "  -c compact\n" | ||||
| 	       "  -t dump table\n" | ||||
| 	       "  -s dump stack\n" | ||||
| 	       "  -6 sha256 hash format\n" | ||||
| 	       "  -h this help\n" | ||||
| 	       "\n"); | ||||
| } | ||||
|  | ||||
| int reftable_dump_main(int argc, char *const *argv) | ||||
| { | ||||
| 	int err = 0; | ||||
| 	int opt_dump_table = 0; | ||||
| 	int opt_dump_stack = 0; | ||||
| 	int opt_compact = 0; | ||||
| 	uint32_t opt_hash_id = GIT_SHA1_FORMAT_ID; | ||||
| 	const char *arg = NULL, *argv0 = argv[0]; | ||||
|  | ||||
| 	for (; argc > 1; argv++, argc--) | ||||
| 		if (*argv[1] != '-') | ||||
| 			break; | ||||
| 		else if (!strcmp("-t", argv[1])) | ||||
| 			opt_dump_table = 1; | ||||
| 		else if (!strcmp("-6", argv[1])) | ||||
| 			opt_hash_id = GIT_SHA256_FORMAT_ID; | ||||
| 		else if (!strcmp("-s", argv[1])) | ||||
| 			opt_dump_stack = 1; | ||||
| 		else if (!strcmp("-c", argv[1])) | ||||
| 			opt_compact = 1; | ||||
| 		else if (!strcmp("-?", argv[1]) || !strcmp("-h", argv[1])) { | ||||
| 			print_help(); | ||||
| 			return 2; | ||||
| 		} | ||||
|  | ||||
| 	if (argc != 2) { | ||||
| 		fprintf(stderr, "need argument\n"); | ||||
| 		print_help(); | ||||
| 		return 2; | ||||
| 	} | ||||
|  | ||||
| 	arg = argv[1]; | ||||
|  | ||||
| 	if (opt_dump_table) { | ||||
| 		err = reftable_reader_print_file(arg); | ||||
| 	} else if (opt_dump_stack) { | ||||
| 		err = reftable_stack_print_directory(arg, opt_hash_id); | ||||
| 	} else if (opt_compact) { | ||||
| 		err = compact_stack(arg); | ||||
| 	} | ||||
|  | ||||
| 	if (err < 0) { | ||||
| 		fprintf(stderr, "%s: %s: %s\n", argv0, arg, | ||||
| 			reftable_error_str(err)); | ||||
| 		return 1; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -0,0 +1,41 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "reftable-error.h" | ||||
|  | ||||
| #include <stdio.h> | ||||
|  | ||||
| const char *reftable_error_str(int err) | ||||
| { | ||||
| 	static char buf[250]; | ||||
| 	switch (err) { | ||||
| 	case REFTABLE_IO_ERROR: | ||||
| 		return "I/O error"; | ||||
| 	case REFTABLE_FORMAT_ERROR: | ||||
| 		return "corrupt reftable file"; | ||||
| 	case REFTABLE_NOT_EXIST_ERROR: | ||||
| 		return "file does not exist"; | ||||
| 	case REFTABLE_LOCK_ERROR: | ||||
| 		return "data is outdated"; | ||||
| 	case REFTABLE_API_ERROR: | ||||
| 		return "misuse of the reftable API"; | ||||
| 	case REFTABLE_ZLIB_ERROR: | ||||
| 		return "zlib failure"; | ||||
| 	case REFTABLE_NAME_CONFLICT: | ||||
| 		return "file/directory conflict"; | ||||
| 	case REFTABLE_EMPTY_TABLE_ERROR: | ||||
| 		return "wrote empty table"; | ||||
| 	case REFTABLE_REFNAME_ERROR: | ||||
| 		return "invalid refname"; | ||||
| 	case -1: | ||||
| 		return "general error"; | ||||
| 	default: | ||||
| 		snprintf(buf, sizeof(buf), "unknown error code %d", err); | ||||
| 		return buf; | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,169 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "record.h" | ||||
| #include "generic.h" | ||||
| #include "reftable-iterator.h" | ||||
| #include "reftable-generic.h" | ||||
|  | ||||
| int reftable_table_seek_ref(struct reftable_table *tab, | ||||
| 			    struct reftable_iterator *it, const char *name) | ||||
| { | ||||
| 	struct reftable_ref_record ref = { | ||||
| 		.refname = (char *)name, | ||||
| 	}; | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	reftable_record_from_ref(&rec, &ref); | ||||
| 	return tab->ops->seek_record(tab->table_arg, it, &rec); | ||||
| } | ||||
|  | ||||
| int reftable_table_seek_log(struct reftable_table *tab, | ||||
| 			    struct reftable_iterator *it, const char *name) | ||||
| { | ||||
| 	struct reftable_log_record log = { | ||||
| 		.refname = (char *)name, | ||||
| 		.update_index = ~((uint64_t)0), | ||||
| 	}; | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	reftable_record_from_log(&rec, &log); | ||||
| 	return tab->ops->seek_record(tab->table_arg, it, &rec); | ||||
| } | ||||
|  | ||||
| int reftable_table_read_ref(struct reftable_table *tab, const char *name, | ||||
| 			    struct reftable_ref_record *ref) | ||||
| { | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	int err = reftable_table_seek_ref(tab, &it, name); | ||||
| 	if (err) | ||||
| 		goto done; | ||||
|  | ||||
| 	err = reftable_iterator_next_ref(&it, ref); | ||||
| 	if (err) | ||||
| 		goto done; | ||||
|  | ||||
| 	if (strcmp(ref->refname, name) || | ||||
| 	    reftable_ref_record_is_deletion(ref)) { | ||||
| 		reftable_ref_record_release(ref); | ||||
| 		err = 1; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| done: | ||||
| 	reftable_iterator_destroy(&it); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| int reftable_table_print(struct reftable_table *tab) { | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	struct reftable_ref_record ref = { NULL }; | ||||
| 	struct reftable_log_record log = { NULL }; | ||||
| 	uint32_t hash_id = reftable_table_hash_id(tab); | ||||
| 	int err = reftable_table_seek_ref(tab, &it, ""); | ||||
| 	if (err < 0) { | ||||
| 		return err; | ||||
| 	} | ||||
|  | ||||
| 	while (1) { | ||||
| 		err = reftable_iterator_next_ref(&it, &ref); | ||||
| 		if (err > 0) { | ||||
| 			break; | ||||
| 		} | ||||
| 		if (err < 0) { | ||||
| 			return err; | ||||
| 		} | ||||
| 		reftable_ref_record_print(&ref, hash_id); | ||||
| 	} | ||||
| 	reftable_iterator_destroy(&it); | ||||
| 	reftable_ref_record_release(&ref); | ||||
|  | ||||
| 	err = reftable_table_seek_log(tab, &it, ""); | ||||
| 	if (err < 0) { | ||||
| 		return err; | ||||
| 	} | ||||
| 	while (1) { | ||||
| 		err = reftable_iterator_next_log(&it, &log); | ||||
| 		if (err > 0) { | ||||
| 			break; | ||||
| 		} | ||||
| 		if (err < 0) { | ||||
| 			return err; | ||||
| 		} | ||||
| 		reftable_log_record_print(&log, hash_id); | ||||
| 	} | ||||
| 	reftable_iterator_destroy(&it); | ||||
| 	reftable_log_record_release(&log); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| uint64_t reftable_table_max_update_index(struct reftable_table *tab) | ||||
| { | ||||
| 	return tab->ops->max_update_index(tab->table_arg); | ||||
| } | ||||
|  | ||||
| uint64_t reftable_table_min_update_index(struct reftable_table *tab) | ||||
| { | ||||
| 	return tab->ops->min_update_index(tab->table_arg); | ||||
| } | ||||
|  | ||||
| uint32_t reftable_table_hash_id(struct reftable_table *tab) | ||||
| { | ||||
| 	return tab->ops->hash_id(tab->table_arg); | ||||
| } | ||||
|  | ||||
| void reftable_iterator_destroy(struct reftable_iterator *it) | ||||
| { | ||||
| 	if (!it->ops) { | ||||
| 		return; | ||||
| 	} | ||||
| 	it->ops->close(it->iter_arg); | ||||
| 	it->ops = NULL; | ||||
| 	FREE_AND_NULL(it->iter_arg); | ||||
| } | ||||
|  | ||||
| int reftable_iterator_next_ref(struct reftable_iterator *it, | ||||
| 			       struct reftable_ref_record *ref) | ||||
| { | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	reftable_record_from_ref(&rec, ref); | ||||
| 	return iterator_next(it, &rec); | ||||
| } | ||||
|  | ||||
| int reftable_iterator_next_log(struct reftable_iterator *it, | ||||
| 			       struct reftable_log_record *log) | ||||
| { | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	reftable_record_from_log(&rec, log); | ||||
| 	return iterator_next(it, &rec); | ||||
| } | ||||
|  | ||||
| int iterator_next(struct reftable_iterator *it, struct reftable_record *rec) | ||||
| { | ||||
| 	return it->ops->next(it->iter_arg, rec); | ||||
| } | ||||
|  | ||||
| static int empty_iterator_next(void *arg, struct reftable_record *rec) | ||||
| { | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| static void empty_iterator_close(void *arg) | ||||
| { | ||||
| } | ||||
|  | ||||
| static struct reftable_iterator_vtable empty_vtable = { | ||||
| 	.next = &empty_iterator_next, | ||||
| 	.close = &empty_iterator_close, | ||||
| }; | ||||
|  | ||||
| void iterator_set_empty(struct reftable_iterator *it) | ||||
| { | ||||
| 	assert(!it->ops); | ||||
| 	it->iter_arg = NULL; | ||||
| 	it->ops = &empty_vtable; | ||||
| } | ||||
|  | @ -0,0 +1,32 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef GENERIC_H | ||||
| #define GENERIC_H | ||||
|  | ||||
| #include "record.h" | ||||
| #include "reftable-generic.h" | ||||
|  | ||||
| /* generic interface to reftables */ | ||||
| struct reftable_table_vtable { | ||||
| 	int (*seek_record)(void *tab, struct reftable_iterator *it, | ||||
| 			   struct reftable_record *); | ||||
| 	uint32_t (*hash_id)(void *tab); | ||||
| 	uint64_t (*min_update_index)(void *tab); | ||||
| 	uint64_t (*max_update_index)(void *tab); | ||||
| }; | ||||
|  | ||||
| struct reftable_iterator_vtable { | ||||
| 	int (*next)(void *iter_arg, struct reftable_record *rec); | ||||
| 	void (*close)(void *iter_arg); | ||||
| }; | ||||
|  | ||||
| void iterator_set_empty(struct reftable_iterator *it); | ||||
| int iterator_next(struct reftable_iterator *it, struct reftable_record *rec); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,194 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "iter.h" | ||||
|  | ||||
| #include "system.h" | ||||
|  | ||||
| #include "block.h" | ||||
| #include "generic.h" | ||||
| #include "constants.h" | ||||
| #include "reader.h" | ||||
| #include "reftable-error.h" | ||||
|  | ||||
| int iterator_is_null(struct reftable_iterator *it) | ||||
| { | ||||
| 	return !it->ops; | ||||
| } | ||||
|  | ||||
| static void filtering_ref_iterator_close(void *iter_arg) | ||||
| { | ||||
| 	struct filtering_ref_iterator *fri = iter_arg; | ||||
| 	strbuf_release(&fri->oid); | ||||
| 	reftable_iterator_destroy(&fri->it); | ||||
| } | ||||
|  | ||||
| static int filtering_ref_iterator_next(void *iter_arg, | ||||
| 				       struct reftable_record *rec) | ||||
| { | ||||
| 	struct filtering_ref_iterator *fri = iter_arg; | ||||
| 	struct reftable_ref_record *ref = rec->data; | ||||
| 	int err = 0; | ||||
| 	while (1) { | ||||
| 		err = reftable_iterator_next_ref(&fri->it, ref); | ||||
| 		if (err != 0) { | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		if (fri->double_check) { | ||||
| 			struct reftable_iterator it = { NULL }; | ||||
|  | ||||
| 			err = reftable_table_seek_ref(&fri->tab, &it, | ||||
| 						      ref->refname); | ||||
| 			if (err == 0) { | ||||
| 				err = reftable_iterator_next_ref(&it, ref); | ||||
| 			} | ||||
|  | ||||
| 			reftable_iterator_destroy(&it); | ||||
|  | ||||
| 			if (err < 0) { | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			if (err > 0) { | ||||
| 				continue; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (ref->value_type == REFTABLE_REF_VAL2 && | ||||
| 		    (!memcmp(fri->oid.buf, ref->value.val2.target_value, | ||||
| 			     fri->oid.len) || | ||||
| 		     !memcmp(fri->oid.buf, ref->value.val2.value, | ||||
| 			     fri->oid.len))) | ||||
| 			return 0; | ||||
|  | ||||
| 		if (ref->value_type == REFTABLE_REF_VAL1 && | ||||
| 		    !memcmp(fri->oid.buf, ref->value.val1, fri->oid.len)) { | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	reftable_ref_record_release(ref); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static struct reftable_iterator_vtable filtering_ref_iterator_vtable = { | ||||
| 	.next = &filtering_ref_iterator_next, | ||||
| 	.close = &filtering_ref_iterator_close, | ||||
| }; | ||||
|  | ||||
| void iterator_from_filtering_ref_iterator(struct reftable_iterator *it, | ||||
| 					  struct filtering_ref_iterator *fri) | ||||
| { | ||||
| 	assert(!it->ops); | ||||
| 	it->iter_arg = fri; | ||||
| 	it->ops = &filtering_ref_iterator_vtable; | ||||
| } | ||||
|  | ||||
| static void indexed_table_ref_iter_close(void *p) | ||||
| { | ||||
| 	struct indexed_table_ref_iter *it = p; | ||||
| 	block_iter_close(&it->cur); | ||||
| 	reftable_block_done(&it->block_reader.block); | ||||
| 	reftable_free(it->offsets); | ||||
| 	strbuf_release(&it->oid); | ||||
| } | ||||
|  | ||||
| static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it) | ||||
| { | ||||
| 	uint64_t off; | ||||
| 	int err = 0; | ||||
| 	if (it->offset_idx == it->offset_len) { | ||||
| 		it->is_finished = 1; | ||||
| 		return 1; | ||||
| 	} | ||||
|  | ||||
| 	reftable_block_done(&it->block_reader.block); | ||||
|  | ||||
| 	off = it->offsets[it->offset_idx++]; | ||||
| 	err = reader_init_block_reader(it->r, &it->block_reader, off, | ||||
| 				       BLOCK_TYPE_REF); | ||||
| 	if (err < 0) { | ||||
| 		return err; | ||||
| 	} | ||||
| 	if (err > 0) { | ||||
| 		/* indexed block does not exist. */ | ||||
| 		return REFTABLE_FORMAT_ERROR; | ||||
| 	} | ||||
| 	block_reader_start(&it->block_reader, &it->cur); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec) | ||||
| { | ||||
| 	struct indexed_table_ref_iter *it = p; | ||||
| 	struct reftable_ref_record *ref = rec->data; | ||||
|  | ||||
| 	while (1) { | ||||
| 		int err = block_iter_next(&it->cur, rec); | ||||
| 		if (err < 0) { | ||||
| 			return err; | ||||
| 		} | ||||
|  | ||||
| 		if (err > 0) { | ||||
| 			err = indexed_table_ref_iter_next_block(it); | ||||
| 			if (err < 0) { | ||||
| 				return err; | ||||
| 			} | ||||
|  | ||||
| 			if (it->is_finished) { | ||||
| 				return 1; | ||||
| 			} | ||||
| 			continue; | ||||
| 		} | ||||
| 		/* BUG */ | ||||
| 		if (!memcmp(it->oid.buf, ref->value.val2.target_value, | ||||
| 			    it->oid.len) || | ||||
| 		    !memcmp(it->oid.buf, ref->value.val2.value, it->oid.len)) { | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest, | ||||
| 			       struct reftable_reader *r, uint8_t *oid, | ||||
| 			       int oid_len, uint64_t *offsets, int offset_len) | ||||
| { | ||||
| 	struct indexed_table_ref_iter empty = INDEXED_TABLE_REF_ITER_INIT; | ||||
| 	struct indexed_table_ref_iter *itr = | ||||
| 		reftable_calloc(sizeof(struct indexed_table_ref_iter)); | ||||
| 	int err = 0; | ||||
|  | ||||
| 	*itr = empty; | ||||
| 	itr->r = r; | ||||
| 	strbuf_add(&itr->oid, oid, oid_len); | ||||
|  | ||||
| 	itr->offsets = offsets; | ||||
| 	itr->offset_len = offset_len; | ||||
|  | ||||
| 	err = indexed_table_ref_iter_next_block(itr); | ||||
| 	if (err < 0) { | ||||
| 		reftable_free(itr); | ||||
| 	} else { | ||||
| 		*dest = itr; | ||||
| 	} | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static struct reftable_iterator_vtable indexed_table_ref_iter_vtable = { | ||||
| 	.next = &indexed_table_ref_iter_next, | ||||
| 	.close = &indexed_table_ref_iter_close, | ||||
| }; | ||||
|  | ||||
| void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it, | ||||
| 					  struct indexed_table_ref_iter *itr) | ||||
| { | ||||
| 	assert(!it->ops); | ||||
| 	it->iter_arg = itr; | ||||
| 	it->ops = &indexed_table_ref_iter_vtable; | ||||
| } | ||||
|  | @ -0,0 +1,69 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef ITER_H | ||||
| #define ITER_H | ||||
|  | ||||
| #include "system.h" | ||||
| #include "block.h" | ||||
| #include "record.h" | ||||
|  | ||||
| #include "reftable-iterator.h" | ||||
| #include "reftable-generic.h" | ||||
|  | ||||
| /* Returns true for a zeroed out iterator, such as the one returned from | ||||
|  * iterator_destroy. */ | ||||
| int iterator_is_null(struct reftable_iterator *it); | ||||
|  | ||||
| /* iterator that produces only ref records that point to `oid` */ | ||||
| struct filtering_ref_iterator { | ||||
| 	int double_check; | ||||
| 	struct reftable_table tab; | ||||
| 	struct strbuf oid; | ||||
| 	struct reftable_iterator it; | ||||
| }; | ||||
| #define FILTERING_REF_ITERATOR_INIT \ | ||||
| 	{                           \ | ||||
| 		.oid = STRBUF_INIT  \ | ||||
| 	} | ||||
|  | ||||
| void iterator_from_filtering_ref_iterator(struct reftable_iterator *, | ||||
| 					  struct filtering_ref_iterator *); | ||||
|  | ||||
| /* iterator that produces only ref records that point to `oid`, | ||||
|  * but using the object index. | ||||
|  */ | ||||
| struct indexed_table_ref_iter { | ||||
| 	struct reftable_reader *r; | ||||
| 	struct strbuf oid; | ||||
|  | ||||
| 	/* mutable */ | ||||
| 	uint64_t *offsets; | ||||
|  | ||||
| 	/* Points to the next offset to read. */ | ||||
| 	int offset_idx; | ||||
| 	int offset_len; | ||||
| 	struct block_reader block_reader; | ||||
| 	struct block_iter cur; | ||||
| 	int is_finished; | ||||
| }; | ||||
|  | ||||
| #define INDEXED_TABLE_REF_ITER_INIT                                     \ | ||||
| 	{                                                               \ | ||||
| 		.cur = { .last_key = STRBUF_INIT }, .oid = STRBUF_INIT, \ | ||||
| 	} | ||||
|  | ||||
| void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it, | ||||
| 					  struct indexed_table_ref_iter *itr); | ||||
|  | ||||
| /* Takes ownership of `offsets` */ | ||||
| int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest, | ||||
| 			       struct reftable_reader *r, uint8_t *oid, | ||||
| 			       int oid_len, uint64_t *offsets, int offset_len); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,362 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "merged.h" | ||||
|  | ||||
| #include "constants.h" | ||||
| #include "iter.h" | ||||
| #include "pq.h" | ||||
| #include "reader.h" | ||||
| #include "record.h" | ||||
| #include "generic.h" | ||||
| #include "reftable-merged.h" | ||||
| #include "reftable-error.h" | ||||
| #include "system.h" | ||||
|  | ||||
| static int merged_iter_init(struct merged_iter *mi) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	for (i = 0; i < mi->stack_len; i++) { | ||||
| 		struct reftable_record rec = reftable_new_record(mi->typ); | ||||
| 		int err = iterator_next(&mi->stack[i], &rec); | ||||
| 		if (err < 0) { | ||||
| 			return err; | ||||
| 		} | ||||
|  | ||||
| 		if (err > 0) { | ||||
| 			reftable_iterator_destroy(&mi->stack[i]); | ||||
| 			reftable_record_destroy(&rec); | ||||
| 		} else { | ||||
| 			struct pq_entry e = { | ||||
| 				.rec = rec, | ||||
| 				.index = i, | ||||
| 			}; | ||||
| 			merged_iter_pqueue_add(&mi->pq, e); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static void merged_iter_close(void *p) | ||||
| { | ||||
| 	struct merged_iter *mi = p; | ||||
| 	int i = 0; | ||||
| 	merged_iter_pqueue_release(&mi->pq); | ||||
| 	for (i = 0; i < mi->stack_len; i++) { | ||||
| 		reftable_iterator_destroy(&mi->stack[i]); | ||||
| 	} | ||||
| 	reftable_free(mi->stack); | ||||
| } | ||||
|  | ||||
| static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi, | ||||
| 					       size_t idx) | ||||
| { | ||||
| 	struct reftable_record rec = reftable_new_record(mi->typ); | ||||
| 	struct pq_entry e = { | ||||
| 		.rec = rec, | ||||
| 		.index = idx, | ||||
| 	}; | ||||
| 	int err = iterator_next(&mi->stack[idx], &rec); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
|  | ||||
| 	if (err > 0) { | ||||
| 		reftable_iterator_destroy(&mi->stack[idx]); | ||||
| 		reftable_record_destroy(&rec); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	merged_iter_pqueue_add(&mi->pq, e); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int merged_iter_advance_subiter(struct merged_iter *mi, size_t idx) | ||||
| { | ||||
| 	if (iterator_is_null(&mi->stack[idx])) | ||||
| 		return 0; | ||||
| 	return merged_iter_advance_nonnull_subiter(mi, idx); | ||||
| } | ||||
|  | ||||
| static int merged_iter_next_entry(struct merged_iter *mi, | ||||
| 				  struct reftable_record *rec) | ||||
| { | ||||
| 	struct strbuf entry_key = STRBUF_INIT; | ||||
| 	struct pq_entry entry = { 0 }; | ||||
| 	int err = 0; | ||||
|  | ||||
| 	if (merged_iter_pqueue_is_empty(mi->pq)) | ||||
| 		return 1; | ||||
|  | ||||
| 	entry = merged_iter_pqueue_remove(&mi->pq); | ||||
| 	err = merged_iter_advance_subiter(mi, entry.index); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
|  | ||||
| 	/* | ||||
| 	  One can also use reftable as datacenter-local storage, where the ref | ||||
| 	  database is maintained in globally consistent database (eg. | ||||
| 	  CockroachDB or Spanner). In this scenario, replication delays together | ||||
| 	  with compaction may cause newer tables to contain older entries. In | ||||
| 	  such a deployment, the loop below must be changed to collect all | ||||
| 	  entries for the same key, and return new the newest one. | ||||
| 	*/ | ||||
| 	reftable_record_key(&entry.rec, &entry_key); | ||||
| 	while (!merged_iter_pqueue_is_empty(mi->pq)) { | ||||
| 		struct pq_entry top = merged_iter_pqueue_top(mi->pq); | ||||
| 		struct strbuf k = STRBUF_INIT; | ||||
| 		int err = 0, cmp = 0; | ||||
|  | ||||
| 		reftable_record_key(&top.rec, &k); | ||||
|  | ||||
| 		cmp = strbuf_cmp(&k, &entry_key); | ||||
| 		strbuf_release(&k); | ||||
|  | ||||
| 		if (cmp > 0) { | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		merged_iter_pqueue_remove(&mi->pq); | ||||
| 		err = merged_iter_advance_subiter(mi, top.index); | ||||
| 		if (err < 0) { | ||||
| 			return err; | ||||
| 		} | ||||
| 		reftable_record_destroy(&top.rec); | ||||
| 	} | ||||
|  | ||||
| 	reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id)); | ||||
| 	reftable_record_destroy(&entry.rec); | ||||
| 	strbuf_release(&entry_key); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int merged_iter_next(struct merged_iter *mi, struct reftable_record *rec) | ||||
| { | ||||
| 	while (1) { | ||||
| 		int err = merged_iter_next_entry(mi, rec); | ||||
| 		if (err == 0 && mi->suppress_deletions && | ||||
| 		    reftable_record_is_deletion(rec)) { | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		return err; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static int merged_iter_next_void(void *p, struct reftable_record *rec) | ||||
| { | ||||
| 	struct merged_iter *mi = p; | ||||
| 	if (merged_iter_pqueue_is_empty(mi->pq)) | ||||
| 		return 1; | ||||
|  | ||||
| 	return merged_iter_next(mi, rec); | ||||
| } | ||||
|  | ||||
| static struct reftable_iterator_vtable merged_iter_vtable = { | ||||
| 	.next = &merged_iter_next_void, | ||||
| 	.close = &merged_iter_close, | ||||
| }; | ||||
|  | ||||
| static void iterator_from_merged_iter(struct reftable_iterator *it, | ||||
| 				      struct merged_iter *mi) | ||||
| { | ||||
| 	assert(!it->ops); | ||||
| 	it->iter_arg = mi; | ||||
| 	it->ops = &merged_iter_vtable; | ||||
| } | ||||
|  | ||||
| int reftable_new_merged_table(struct reftable_merged_table **dest, | ||||
| 			      struct reftable_table *stack, int n, | ||||
| 			      uint32_t hash_id) | ||||
| { | ||||
| 	struct reftable_merged_table *m = NULL; | ||||
| 	uint64_t last_max = 0; | ||||
| 	uint64_t first_min = 0; | ||||
| 	int i = 0; | ||||
| 	for (i = 0; i < n; i++) { | ||||
| 		uint64_t min = reftable_table_min_update_index(&stack[i]); | ||||
| 		uint64_t max = reftable_table_max_update_index(&stack[i]); | ||||
|  | ||||
| 		if (reftable_table_hash_id(&stack[i]) != hash_id) { | ||||
| 			return REFTABLE_FORMAT_ERROR; | ||||
| 		} | ||||
| 		if (i == 0 || min < first_min) { | ||||
| 			first_min = min; | ||||
| 		} | ||||
| 		if (i == 0 || max > last_max) { | ||||
| 			last_max = max; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	m = reftable_calloc(sizeof(struct reftable_merged_table)); | ||||
| 	m->stack = stack; | ||||
| 	m->stack_len = n; | ||||
| 	m->min = first_min; | ||||
| 	m->max = last_max; | ||||
| 	m->hash_id = hash_id; | ||||
| 	*dest = m; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* clears the list of subtable, without affecting the readers themselves. */ | ||||
| void merged_table_release(struct reftable_merged_table *mt) | ||||
| { | ||||
| 	FREE_AND_NULL(mt->stack); | ||||
| 	mt->stack_len = 0; | ||||
| } | ||||
|  | ||||
| void reftable_merged_table_free(struct reftable_merged_table *mt) | ||||
| { | ||||
| 	if (!mt) { | ||||
| 		return; | ||||
| 	} | ||||
| 	merged_table_release(mt); | ||||
| 	reftable_free(mt); | ||||
| } | ||||
|  | ||||
| uint64_t | ||||
| reftable_merged_table_max_update_index(struct reftable_merged_table *mt) | ||||
| { | ||||
| 	return mt->max; | ||||
| } | ||||
|  | ||||
| uint64_t | ||||
| reftable_merged_table_min_update_index(struct reftable_merged_table *mt) | ||||
| { | ||||
| 	return mt->min; | ||||
| } | ||||
|  | ||||
| static int reftable_table_seek_record(struct reftable_table *tab, | ||||
| 				      struct reftable_iterator *it, | ||||
| 				      struct reftable_record *rec) | ||||
| { | ||||
| 	return tab->ops->seek_record(tab->table_arg, it, rec); | ||||
| } | ||||
|  | ||||
| static int merged_table_seek_record(struct reftable_merged_table *mt, | ||||
| 				    struct reftable_iterator *it, | ||||
| 				    struct reftable_record *rec) | ||||
| { | ||||
| 	struct reftable_iterator *iters = reftable_calloc( | ||||
| 		sizeof(struct reftable_iterator) * mt->stack_len); | ||||
| 	struct merged_iter merged = { | ||||
| 		.stack = iters, | ||||
| 		.typ = reftable_record_type(rec), | ||||
| 		.hash_id = mt->hash_id, | ||||
| 		.suppress_deletions = mt->suppress_deletions, | ||||
| 	}; | ||||
| 	int n = 0; | ||||
| 	int err = 0; | ||||
| 	int i = 0; | ||||
| 	for (i = 0; i < mt->stack_len && err == 0; i++) { | ||||
| 		int e = reftable_table_seek_record(&mt->stack[i], &iters[n], | ||||
| 						   rec); | ||||
| 		if (e < 0) { | ||||
| 			err = e; | ||||
| 		} | ||||
| 		if (e == 0) { | ||||
| 			n++; | ||||
| 		} | ||||
| 	} | ||||
| 	if (err < 0) { | ||||
| 		int i = 0; | ||||
| 		for (i = 0; i < n; i++) { | ||||
| 			reftable_iterator_destroy(&iters[i]); | ||||
| 		} | ||||
| 		reftable_free(iters); | ||||
| 		return err; | ||||
| 	} | ||||
|  | ||||
| 	merged.stack_len = n; | ||||
| 	err = merged_iter_init(&merged); | ||||
| 	if (err < 0) { | ||||
| 		merged_iter_close(&merged); | ||||
| 		return err; | ||||
| 	} else { | ||||
| 		struct merged_iter *p = | ||||
| 			reftable_malloc(sizeof(struct merged_iter)); | ||||
| 		*p = merged; | ||||
| 		iterator_from_merged_iter(it, p); | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int reftable_merged_table_seek_ref(struct reftable_merged_table *mt, | ||||
| 				   struct reftable_iterator *it, | ||||
| 				   const char *name) | ||||
| { | ||||
| 	struct reftable_ref_record ref = { | ||||
| 		.refname = (char *)name, | ||||
| 	}; | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	reftable_record_from_ref(&rec, &ref); | ||||
| 	return merged_table_seek_record(mt, it, &rec); | ||||
| } | ||||
|  | ||||
| int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt, | ||||
| 				      struct reftable_iterator *it, | ||||
| 				      const char *name, uint64_t update_index) | ||||
| { | ||||
| 	struct reftable_log_record log = { | ||||
| 		.refname = (char *)name, | ||||
| 		.update_index = update_index, | ||||
| 	}; | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	reftable_record_from_log(&rec, &log); | ||||
| 	return merged_table_seek_record(mt, it, &rec); | ||||
| } | ||||
|  | ||||
| int reftable_merged_table_seek_log(struct reftable_merged_table *mt, | ||||
| 				   struct reftable_iterator *it, | ||||
| 				   const char *name) | ||||
| { | ||||
| 	uint64_t max = ~((uint64_t)0); | ||||
| 	return reftable_merged_table_seek_log_at(mt, it, name, max); | ||||
| } | ||||
|  | ||||
| uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *mt) | ||||
| { | ||||
| 	return mt->hash_id; | ||||
| } | ||||
|  | ||||
| static int reftable_merged_table_seek_void(void *tab, | ||||
| 					   struct reftable_iterator *it, | ||||
| 					   struct reftable_record *rec) | ||||
| { | ||||
| 	return merged_table_seek_record(tab, it, rec); | ||||
| } | ||||
|  | ||||
| static uint32_t reftable_merged_table_hash_id_void(void *tab) | ||||
| { | ||||
| 	return reftable_merged_table_hash_id(tab); | ||||
| } | ||||
|  | ||||
| static uint64_t reftable_merged_table_min_update_index_void(void *tab) | ||||
| { | ||||
| 	return reftable_merged_table_min_update_index(tab); | ||||
| } | ||||
|  | ||||
| static uint64_t reftable_merged_table_max_update_index_void(void *tab) | ||||
| { | ||||
| 	return reftable_merged_table_max_update_index(tab); | ||||
| } | ||||
|  | ||||
| static struct reftable_table_vtable merged_table_vtable = { | ||||
| 	.seek_record = reftable_merged_table_seek_void, | ||||
| 	.hash_id = reftable_merged_table_hash_id_void, | ||||
| 	.min_update_index = reftable_merged_table_min_update_index_void, | ||||
| 	.max_update_index = reftable_merged_table_max_update_index_void, | ||||
| }; | ||||
|  | ||||
| void reftable_table_from_merged_table(struct reftable_table *tab, | ||||
| 				      struct reftable_merged_table *merged) | ||||
| { | ||||
| 	assert(!tab->ops); | ||||
| 	tab->ops = &merged_table_vtable; | ||||
| 	tab->table_arg = merged; | ||||
| } | ||||
|  | @ -0,0 +1,38 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef MERGED_H | ||||
| #define MERGED_H | ||||
|  | ||||
| #include "pq.h" | ||||
|  | ||||
| struct reftable_merged_table { | ||||
| 	struct reftable_table *stack; | ||||
| 	size_t stack_len; | ||||
| 	uint32_t hash_id; | ||||
|  | ||||
| 	/* If unset, produce deletions. This is useful for compaction. For the | ||||
| 	 * full stack, deletions should be produced. */ | ||||
| 	int suppress_deletions; | ||||
|  | ||||
| 	uint64_t min; | ||||
| 	uint64_t max; | ||||
| }; | ||||
|  | ||||
| struct merged_iter { | ||||
| 	struct reftable_iterator *stack; | ||||
| 	uint32_t hash_id; | ||||
| 	size_t stack_len; | ||||
| 	uint8_t typ; | ||||
| 	int suppress_deletions; | ||||
| 	struct merged_iter_pqueue pq; | ||||
| }; | ||||
|  | ||||
| void merged_table_release(struct reftable_merged_table *mt); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,468 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "merged.h" | ||||
|  | ||||
| #include "system.h" | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "blocksource.h" | ||||
| #include "constants.h" | ||||
| #include "reader.h" | ||||
| #include "record.h" | ||||
| #include "test_framework.h" | ||||
| #include "reftable-merged.h" | ||||
| #include "reftable-tests.h" | ||||
| #include "reftable-generic.h" | ||||
| #include "reftable-writer.h" | ||||
|  | ||||
| static void write_test_table(struct strbuf *buf, | ||||
| 			     struct reftable_ref_record refs[], int n) | ||||
| { | ||||
| 	int min = 0xffffffff; | ||||
| 	int max = 0; | ||||
| 	int i = 0; | ||||
| 	int err; | ||||
|  | ||||
| 	struct reftable_write_options opts = { | ||||
| 		.block_size = 256, | ||||
| 	}; | ||||
| 	struct reftable_writer *w = NULL; | ||||
| 	for (i = 0; i < n; i++) { | ||||
| 		uint64_t ui = refs[i].update_index; | ||||
| 		if (ui > max) { | ||||
| 			max = ui; | ||||
| 		} | ||||
| 		if (ui < min) { | ||||
| 			min = ui; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	w = reftable_new_writer(&strbuf_add_void, buf, &opts); | ||||
| 	reftable_writer_set_limits(w, min, max); | ||||
|  | ||||
| 	for (i = 0; i < n; i++) { | ||||
| 		uint64_t before = refs[i].update_index; | ||||
| 		int n = reftable_writer_add_ref(w, &refs[i]); | ||||
| 		EXPECT(n == 0); | ||||
| 		EXPECT(before == refs[i].update_index); | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_writer_close(w); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	reftable_writer_free(w); | ||||
| } | ||||
|  | ||||
| static void write_test_log_table(struct strbuf *buf, | ||||
| 				 struct reftable_log_record logs[], int n, | ||||
| 				 uint64_t update_index) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	int err; | ||||
|  | ||||
| 	struct reftable_write_options opts = { | ||||
| 		.block_size = 256, | ||||
| 		.exact_log_message = 1, | ||||
| 	}; | ||||
| 	struct reftable_writer *w = NULL; | ||||
| 	w = reftable_new_writer(&strbuf_add_void, buf, &opts); | ||||
| 	reftable_writer_set_limits(w, update_index, update_index); | ||||
|  | ||||
| 	for (i = 0; i < n; i++) { | ||||
| 		int err = reftable_writer_add_log(w, &logs[i]); | ||||
| 		EXPECT_ERR(err); | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_writer_close(w); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	reftable_writer_free(w); | ||||
| } | ||||
|  | ||||
| static struct reftable_merged_table * | ||||
| merged_table_from_records(struct reftable_ref_record **refs, | ||||
| 			  struct reftable_block_source **source, | ||||
| 			  struct reftable_reader ***readers, int *sizes, | ||||
| 			  struct strbuf *buf, int n) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	struct reftable_merged_table *mt = NULL; | ||||
| 	int err; | ||||
| 	struct reftable_table *tabs = | ||||
| 		reftable_calloc(n * sizeof(struct reftable_table)); | ||||
| 	*readers = reftable_calloc(n * sizeof(struct reftable_reader *)); | ||||
| 	*source = reftable_calloc(n * sizeof(**source)); | ||||
| 	for (i = 0; i < n; i++) { | ||||
| 		write_test_table(&buf[i], refs[i], sizes[i]); | ||||
| 		block_source_from_strbuf(&(*source)[i], &buf[i]); | ||||
|  | ||||
| 		err = reftable_new_reader(&(*readers)[i], &(*source)[i], | ||||
| 					  "name"); | ||||
| 		EXPECT_ERR(err); | ||||
| 		reftable_table_from_reader(&tabs[i], (*readers)[i]); | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID); | ||||
| 	EXPECT_ERR(err); | ||||
| 	return mt; | ||||
| } | ||||
|  | ||||
| static void readers_destroy(struct reftable_reader **readers, size_t n) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	for (; i < n; i++) | ||||
| 		reftable_reader_free(readers[i]); | ||||
| 	reftable_free(readers); | ||||
| } | ||||
|  | ||||
| static void test_merged_between(void) | ||||
| { | ||||
| 	uint8_t hash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 0 }; | ||||
|  | ||||
| 	struct reftable_ref_record r1[] = { { | ||||
| 		.refname = "b", | ||||
| 		.update_index = 1, | ||||
| 		.value_type = REFTABLE_REF_VAL1, | ||||
| 		.value.val1 = hash1, | ||||
| 	} }; | ||||
| 	struct reftable_ref_record r2[] = { { | ||||
| 		.refname = "a", | ||||
| 		.update_index = 2, | ||||
| 		.value_type = REFTABLE_REF_DELETION, | ||||
| 	} }; | ||||
|  | ||||
| 	struct reftable_ref_record *refs[] = { r1, r2 }; | ||||
| 	int sizes[] = { 1, 1 }; | ||||
| 	struct strbuf bufs[2] = { STRBUF_INIT, STRBUF_INIT }; | ||||
| 	struct reftable_block_source *bs = NULL; | ||||
| 	struct reftable_reader **readers = NULL; | ||||
| 	struct reftable_merged_table *mt = | ||||
| 		merged_table_from_records(refs, &bs, &readers, sizes, bufs, 2); | ||||
| 	int i; | ||||
| 	struct reftable_ref_record ref = { NULL }; | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	int err = reftable_merged_table_seek_ref(mt, &it, "a"); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_iterator_next_ref(&it, &ref); | ||||
| 	EXPECT_ERR(err); | ||||
| 	EXPECT(ref.update_index == 2); | ||||
| 	reftable_ref_record_release(&ref); | ||||
| 	reftable_iterator_destroy(&it); | ||||
| 	readers_destroy(readers, 2); | ||||
| 	reftable_merged_table_free(mt); | ||||
| 	for (i = 0; i < ARRAY_SIZE(bufs); i++) { | ||||
| 		strbuf_release(&bufs[i]); | ||||
| 	} | ||||
| 	reftable_free(bs); | ||||
| } | ||||
|  | ||||
| static void test_merged(void) | ||||
| { | ||||
| 	uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 }; | ||||
| 	uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 }; | ||||
| 	struct reftable_ref_record r1[] = { | ||||
| 		{ | ||||
| 			.refname = "a", | ||||
| 			.update_index = 1, | ||||
| 			.value_type = REFTABLE_REF_VAL1, | ||||
| 			.value.val1 = hash1, | ||||
| 		}, | ||||
| 		{ | ||||
| 			.refname = "b", | ||||
| 			.update_index = 1, | ||||
| 			.value_type = REFTABLE_REF_VAL1, | ||||
| 			.value.val1 = hash1, | ||||
| 		}, | ||||
| 		{ | ||||
| 			.refname = "c", | ||||
| 			.update_index = 1, | ||||
| 			.value_type = REFTABLE_REF_VAL1, | ||||
| 			.value.val1 = hash1, | ||||
| 		} | ||||
| 	}; | ||||
| 	struct reftable_ref_record r2[] = { { | ||||
| 		.refname = "a", | ||||
| 		.update_index = 2, | ||||
| 		.value_type = REFTABLE_REF_DELETION, | ||||
| 	} }; | ||||
| 	struct reftable_ref_record r3[] = { | ||||
| 		{ | ||||
| 			.refname = "c", | ||||
| 			.update_index = 3, | ||||
| 			.value_type = REFTABLE_REF_VAL1, | ||||
| 			.value.val1 = hash2, | ||||
| 		}, | ||||
| 		{ | ||||
| 			.refname = "d", | ||||
| 			.update_index = 3, | ||||
| 			.value_type = REFTABLE_REF_VAL1, | ||||
| 			.value.val1 = hash1, | ||||
| 		}, | ||||
| 	}; | ||||
|  | ||||
| 	struct reftable_ref_record want[] = { | ||||
| 		r2[0], | ||||
| 		r1[1], | ||||
| 		r3[0], | ||||
| 		r3[1], | ||||
| 	}; | ||||
|  | ||||
| 	struct reftable_ref_record *refs[] = { r1, r2, r3 }; | ||||
| 	int sizes[3] = { 3, 1, 2 }; | ||||
| 	struct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; | ||||
| 	struct reftable_block_source *bs = NULL; | ||||
| 	struct reftable_reader **readers = NULL; | ||||
| 	struct reftable_merged_table *mt = | ||||
| 		merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3); | ||||
|  | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	int err = reftable_merged_table_seek_ref(mt, &it, "a"); | ||||
| 	struct reftable_ref_record *out = NULL; | ||||
| 	size_t len = 0; | ||||
| 	size_t cap = 0; | ||||
| 	int i = 0; | ||||
|  | ||||
| 	EXPECT_ERR(err); | ||||
| 	EXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID); | ||||
| 	EXPECT(reftable_merged_table_min_update_index(mt) == 1); | ||||
|  | ||||
| 	while (len < 100) { /* cap loops/recursion. */ | ||||
| 		struct reftable_ref_record ref = { NULL }; | ||||
| 		int err = reftable_iterator_next_ref(&it, &ref); | ||||
| 		if (err > 0) { | ||||
| 			break; | ||||
| 		} | ||||
| 		if (len == cap) { | ||||
| 			cap = 2 * cap + 1; | ||||
| 			out = reftable_realloc( | ||||
| 				out, sizeof(struct reftable_ref_record) * cap); | ||||
| 		} | ||||
| 		out[len++] = ref; | ||||
| 	} | ||||
| 	reftable_iterator_destroy(&it); | ||||
|  | ||||
| 	EXPECT(ARRAY_SIZE(want) == len); | ||||
| 	for (i = 0; i < len; i++) { | ||||
| 		EXPECT(reftable_ref_record_equal(&want[i], &out[i], | ||||
| 						 GIT_SHA1_RAWSZ)); | ||||
| 	} | ||||
| 	for (i = 0; i < len; i++) { | ||||
| 		reftable_ref_record_release(&out[i]); | ||||
| 	} | ||||
| 	reftable_free(out); | ||||
|  | ||||
| 	for (i = 0; i < 3; i++) { | ||||
| 		strbuf_release(&bufs[i]); | ||||
| 	} | ||||
| 	readers_destroy(readers, 3); | ||||
| 	reftable_merged_table_free(mt); | ||||
| 	reftable_free(bs); | ||||
| } | ||||
|  | ||||
| static struct reftable_merged_table * | ||||
| merged_table_from_log_records(struct reftable_log_record **logs, | ||||
| 			      struct reftable_block_source **source, | ||||
| 			      struct reftable_reader ***readers, int *sizes, | ||||
| 			      struct strbuf *buf, int n) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	struct reftable_merged_table *mt = NULL; | ||||
| 	int err; | ||||
| 	struct reftable_table *tabs = | ||||
| 		reftable_calloc(n * sizeof(struct reftable_table)); | ||||
| 	*readers = reftable_calloc(n * sizeof(struct reftable_reader *)); | ||||
| 	*source = reftable_calloc(n * sizeof(**source)); | ||||
| 	for (i = 0; i < n; i++) { | ||||
| 		write_test_log_table(&buf[i], logs[i], sizes[i], i + 1); | ||||
| 		block_source_from_strbuf(&(*source)[i], &buf[i]); | ||||
|  | ||||
| 		err = reftable_new_reader(&(*readers)[i], &(*source)[i], | ||||
| 					  "name"); | ||||
| 		EXPECT_ERR(err); | ||||
| 		reftable_table_from_reader(&tabs[i], (*readers)[i]); | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID); | ||||
| 	EXPECT_ERR(err); | ||||
| 	return mt; | ||||
| } | ||||
|  | ||||
| static void test_merged_logs(void) | ||||
| { | ||||
| 	uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 }; | ||||
| 	uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 }; | ||||
| 	uint8_t hash3[GIT_SHA1_RAWSZ] = { 3 }; | ||||
| 	struct reftable_log_record r1[] = { | ||||
| 		{ | ||||
| 			.refname = "a", | ||||
| 			.update_index = 2, | ||||
| 			.value_type = REFTABLE_LOG_UPDATE, | ||||
| 			.value.update = { | ||||
| 				.old_hash = hash2, | ||||
| 				/* deletion */ | ||||
| 				.name = "jane doe", | ||||
| 				.email = "jane@invalid", | ||||
| 				.message = "message2", | ||||
| 			} | ||||
| 		}, | ||||
| 		{ | ||||
| 			.refname = "a", | ||||
| 			.update_index = 1, | ||||
| 			.value_type = REFTABLE_LOG_UPDATE, | ||||
| 			.value.update = { | ||||
| 				.old_hash = hash1, | ||||
| 				.new_hash = hash2, | ||||
| 				.name = "jane doe", | ||||
| 				.email = "jane@invalid", | ||||
| 				.message = "message1", | ||||
| 			} | ||||
| 		}, | ||||
| 	}; | ||||
| 	struct reftable_log_record r2[] = { | ||||
| 		{ | ||||
| 			.refname = "a", | ||||
| 			.update_index = 3, | ||||
| 			.value_type = REFTABLE_LOG_UPDATE, | ||||
| 			.value.update = { | ||||
| 				.new_hash = hash3, | ||||
| 				.name = "jane doe", | ||||
| 				.email = "jane@invalid", | ||||
| 				.message = "message3", | ||||
| 			} | ||||
| 		}, | ||||
| 	}; | ||||
| 	struct reftable_log_record r3[] = { | ||||
| 		{ | ||||
| 			.refname = "a", | ||||
| 			.update_index = 2, | ||||
| 			.value_type = REFTABLE_LOG_DELETION, | ||||
| 		}, | ||||
| 	}; | ||||
| 	struct reftable_log_record want[] = { | ||||
| 		r2[0], | ||||
| 		r3[0], | ||||
| 		r1[1], | ||||
| 	}; | ||||
|  | ||||
| 	struct reftable_log_record *logs[] = { r1, r2, r3 }; | ||||
| 	int sizes[3] = { 2, 1, 1 }; | ||||
| 	struct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; | ||||
| 	struct reftable_block_source *bs = NULL; | ||||
| 	struct reftable_reader **readers = NULL; | ||||
| 	struct reftable_merged_table *mt = merged_table_from_log_records( | ||||
| 		logs, &bs, &readers, sizes, bufs, 3); | ||||
|  | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	int err = reftable_merged_table_seek_log(mt, &it, "a"); | ||||
| 	struct reftable_log_record *out = NULL; | ||||
| 	size_t len = 0; | ||||
| 	size_t cap = 0; | ||||
| 	int i = 0; | ||||
|  | ||||
| 	EXPECT_ERR(err); | ||||
| 	EXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID); | ||||
| 	EXPECT(reftable_merged_table_min_update_index(mt) == 1); | ||||
|  | ||||
| 	while (len < 100) { /* cap loops/recursion. */ | ||||
| 		struct reftable_log_record log = { NULL }; | ||||
| 		int err = reftable_iterator_next_log(&it, &log); | ||||
| 		if (err > 0) { | ||||
| 			break; | ||||
| 		} | ||||
| 		if (len == cap) { | ||||
| 			cap = 2 * cap + 1; | ||||
| 			out = reftable_realloc( | ||||
| 				out, sizeof(struct reftable_log_record) * cap); | ||||
| 		} | ||||
| 		out[len++] = log; | ||||
| 	} | ||||
| 	reftable_iterator_destroy(&it); | ||||
|  | ||||
| 	EXPECT(ARRAY_SIZE(want) == len); | ||||
| 	for (i = 0; i < len; i++) { | ||||
| 		EXPECT(reftable_log_record_equal(&want[i], &out[i], | ||||
| 						 GIT_SHA1_RAWSZ)); | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_merged_table_seek_log_at(mt, &it, "a", 2); | ||||
| 	EXPECT_ERR(err); | ||||
| 	reftable_log_record_release(&out[0]); | ||||
| 	err = reftable_iterator_next_log(&it, &out[0]); | ||||
| 	EXPECT_ERR(err); | ||||
| 	EXPECT(reftable_log_record_equal(&out[0], &r3[0], GIT_SHA1_RAWSZ)); | ||||
| 	reftable_iterator_destroy(&it); | ||||
|  | ||||
| 	for (i = 0; i < len; i++) { | ||||
| 		reftable_log_record_release(&out[i]); | ||||
| 	} | ||||
| 	reftable_free(out); | ||||
|  | ||||
| 	for (i = 0; i < 3; i++) { | ||||
| 		strbuf_release(&bufs[i]); | ||||
| 	} | ||||
| 	readers_destroy(readers, 3); | ||||
| 	reftable_merged_table_free(mt); | ||||
| 	reftable_free(bs); | ||||
| } | ||||
|  | ||||
| static void test_default_write_opts(void) | ||||
| { | ||||
| 	struct reftable_write_options opts = { 0 }; | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	struct reftable_writer *w = | ||||
| 		reftable_new_writer(&strbuf_add_void, &buf, &opts); | ||||
|  | ||||
| 	struct reftable_ref_record rec = { | ||||
| 		.refname = "master", | ||||
| 		.update_index = 1, | ||||
| 	}; | ||||
| 	int err; | ||||
| 	struct reftable_block_source source = { NULL }; | ||||
| 	struct reftable_table *tab = reftable_calloc(sizeof(*tab) * 1); | ||||
| 	uint32_t hash_id; | ||||
| 	struct reftable_reader *rd = NULL; | ||||
| 	struct reftable_merged_table *merged = NULL; | ||||
|  | ||||
| 	reftable_writer_set_limits(w, 1, 1); | ||||
|  | ||||
| 	err = reftable_writer_add_ref(w, &rec); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_writer_close(w); | ||||
| 	EXPECT_ERR(err); | ||||
| 	reftable_writer_free(w); | ||||
|  | ||||
| 	block_source_from_strbuf(&source, &buf); | ||||
|  | ||||
| 	err = reftable_new_reader(&rd, &source, "filename"); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	hash_id = reftable_reader_hash_id(rd); | ||||
| 	EXPECT(hash_id == GIT_SHA1_FORMAT_ID); | ||||
|  | ||||
| 	reftable_table_from_reader(&tab[0], rd); | ||||
| 	err = reftable_new_merged_table(&merged, tab, 1, GIT_SHA1_FORMAT_ID); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	reftable_reader_free(rd); | ||||
| 	reftable_merged_table_free(merged); | ||||
| 	strbuf_release(&buf); | ||||
| } | ||||
|  | ||||
| /* XXX test refs_for(oid) */ | ||||
|  | ||||
| int merged_test_main(int argc, const char *argv[]) | ||||
| { | ||||
| 	RUN_TEST(test_merged_logs); | ||||
| 	RUN_TEST(test_merged_between); | ||||
| 	RUN_TEST(test_merged); | ||||
| 	RUN_TEST(test_default_write_opts); | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -0,0 +1,105 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "pq.h" | ||||
|  | ||||
| #include "reftable-record.h" | ||||
| #include "system.h" | ||||
| #include "basics.h" | ||||
|  | ||||
| int pq_less(struct pq_entry *a, struct pq_entry *b) | ||||
| { | ||||
| 	struct strbuf ak = STRBUF_INIT; | ||||
| 	struct strbuf bk = STRBUF_INIT; | ||||
| 	int cmp = 0; | ||||
| 	reftable_record_key(&a->rec, &ak); | ||||
| 	reftable_record_key(&b->rec, &bk); | ||||
|  | ||||
| 	cmp = strbuf_cmp(&ak, &bk); | ||||
|  | ||||
| 	strbuf_release(&ak); | ||||
| 	strbuf_release(&bk); | ||||
|  | ||||
| 	if (cmp == 0) | ||||
| 		return a->index > b->index; | ||||
|  | ||||
| 	return cmp < 0; | ||||
| } | ||||
|  | ||||
| struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq) | ||||
| { | ||||
| 	return pq.heap[0]; | ||||
| } | ||||
|  | ||||
| int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq) | ||||
| { | ||||
| 	return pq.len == 0; | ||||
| } | ||||
|  | ||||
| struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	struct pq_entry e = pq->heap[0]; | ||||
| 	pq->heap[0] = pq->heap[pq->len - 1]; | ||||
| 	pq->len--; | ||||
|  | ||||
| 	i = 0; | ||||
| 	while (i < pq->len) { | ||||
| 		int min = i; | ||||
| 		int j = 2 * i + 1; | ||||
| 		int k = 2 * i + 2; | ||||
| 		if (j < pq->len && pq_less(&pq->heap[j], &pq->heap[i])) { | ||||
| 			min = j; | ||||
| 		} | ||||
| 		if (k < pq->len && pq_less(&pq->heap[k], &pq->heap[min])) { | ||||
| 			min = k; | ||||
| 		} | ||||
|  | ||||
| 		if (min == i) { | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		SWAP(pq->heap[i], pq->heap[min]); | ||||
| 		i = min; | ||||
| 	} | ||||
|  | ||||
| 	return e; | ||||
| } | ||||
|  | ||||
| void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	if (pq->len == pq->cap) { | ||||
| 		pq->cap = 2 * pq->cap + 1; | ||||
| 		pq->heap = reftable_realloc(pq->heap, | ||||
| 					    pq->cap * sizeof(struct pq_entry)); | ||||
| 	} | ||||
|  | ||||
| 	pq->heap[pq->len++] = e; | ||||
| 	i = pq->len - 1; | ||||
| 	while (i > 0) { | ||||
| 		int j = (i - 1) / 2; | ||||
| 		if (pq_less(&pq->heap[j], &pq->heap[i])) { | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		SWAP(pq->heap[j], pq->heap[i]); | ||||
|  | ||||
| 		i = j; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void merged_iter_pqueue_release(struct merged_iter_pqueue *pq) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	for (i = 0; i < pq->len; i++) { | ||||
| 		reftable_record_destroy(&pq->heap[i].rec); | ||||
| 	} | ||||
| 	FREE_AND_NULL(pq->heap); | ||||
| 	pq->len = pq->cap = 0; | ||||
| } | ||||
|  | @ -0,0 +1,33 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef PQ_H | ||||
| #define PQ_H | ||||
|  | ||||
| #include "record.h" | ||||
|  | ||||
| struct pq_entry { | ||||
| 	int index; | ||||
| 	struct reftable_record rec; | ||||
| }; | ||||
|  | ||||
| struct merged_iter_pqueue { | ||||
| 	struct pq_entry *heap; | ||||
| 	size_t len; | ||||
| 	size_t cap; | ||||
| }; | ||||
|  | ||||
| struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq); | ||||
| int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq); | ||||
| void merged_iter_pqueue_check(struct merged_iter_pqueue pq); | ||||
| struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq); | ||||
| void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e); | ||||
| void merged_iter_pqueue_release(struct merged_iter_pqueue *pq); | ||||
| int pq_less(struct pq_entry *a, struct pq_entry *b); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,82 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "system.h" | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "constants.h" | ||||
| #include "pq.h" | ||||
| #include "record.h" | ||||
| #include "reftable-tests.h" | ||||
| #include "test_framework.h" | ||||
|  | ||||
| void merged_iter_pqueue_check(struct merged_iter_pqueue pq) | ||||
| { | ||||
| 	int i; | ||||
| 	for (i = 1; i < pq.len; i++) { | ||||
| 		int parent = (i - 1) / 2; | ||||
|  | ||||
| 		EXPECT(pq_less(&pq.heap[parent], &pq.heap[i])); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void test_pq(void) | ||||
| { | ||||
| 	char *names[54] = { NULL }; | ||||
| 	int N = ARRAY_SIZE(names) - 1; | ||||
|  | ||||
| 	struct merged_iter_pqueue pq = { NULL }; | ||||
| 	const char *last = NULL; | ||||
|  | ||||
| 	int i = 0; | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		char name[100]; | ||||
| 		snprintf(name, sizeof(name), "%02d", i); | ||||
| 		names[i] = xstrdup(name); | ||||
| 	} | ||||
|  | ||||
| 	i = 1; | ||||
| 	do { | ||||
| 		struct reftable_record rec = | ||||
| 			reftable_new_record(BLOCK_TYPE_REF); | ||||
| 		struct pq_entry e = { 0 }; | ||||
|  | ||||
| 		reftable_record_as_ref(&rec)->refname = names[i]; | ||||
| 		e.rec = rec; | ||||
| 		merged_iter_pqueue_add(&pq, e); | ||||
| 		merged_iter_pqueue_check(pq); | ||||
| 		i = (i * 7) % N; | ||||
| 	} while (i != 1); | ||||
|  | ||||
| 	while (!merged_iter_pqueue_is_empty(pq)) { | ||||
| 		struct pq_entry e = merged_iter_pqueue_remove(&pq); | ||||
| 		struct reftable_ref_record *ref = | ||||
| 			reftable_record_as_ref(&e.rec); | ||||
|  | ||||
| 		merged_iter_pqueue_check(pq); | ||||
|  | ||||
| 		if (last) { | ||||
| 			EXPECT(strcmp(last, ref->refname) < 0); | ||||
| 		} | ||||
| 		last = ref->refname; | ||||
| 		ref->refname = NULL; | ||||
| 		reftable_free(ref); | ||||
| 	} | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		reftable_free(names[i]); | ||||
| 	} | ||||
|  | ||||
| 	merged_iter_pqueue_release(&pq); | ||||
| } | ||||
|  | ||||
| int pq_test_main(int argc, const char *argv[]) | ||||
| { | ||||
| 	RUN_TEST(test_pq); | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -0,0 +1,65 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "reftable-malloc.h" | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "system.h" | ||||
|  | ||||
| static void *(*reftable_malloc_ptr)(size_t sz); | ||||
| static void *(*reftable_realloc_ptr)(void *, size_t); | ||||
| static void (*reftable_free_ptr)(void *); | ||||
|  | ||||
| void *reftable_malloc(size_t sz) | ||||
| { | ||||
| 	if (reftable_malloc_ptr) | ||||
| 		return (*reftable_malloc_ptr)(sz); | ||||
| 	return malloc(sz); | ||||
| } | ||||
|  | ||||
| void *reftable_realloc(void *p, size_t sz) | ||||
| { | ||||
| 	if (reftable_realloc_ptr) | ||||
| 		return (*reftable_realloc_ptr)(p, sz); | ||||
| 	return realloc(p, sz); | ||||
| } | ||||
|  | ||||
| void reftable_free(void *p) | ||||
| { | ||||
| 	if (reftable_free_ptr) | ||||
| 		reftable_free_ptr(p); | ||||
| 	else | ||||
| 		free(p); | ||||
| } | ||||
|  | ||||
| void *reftable_calloc(size_t sz) | ||||
| { | ||||
| 	void *p = reftable_malloc(sz); | ||||
| 	memset(p, 0, sz); | ||||
| 	return p; | ||||
| } | ||||
|  | ||||
| void reftable_set_alloc(void *(*malloc)(size_t), | ||||
| 			void *(*realloc)(void *, size_t), void (*free)(void *)) | ||||
| { | ||||
| 	reftable_malloc_ptr = malloc; | ||||
| 	reftable_realloc_ptr = realloc; | ||||
| 	reftable_free_ptr = free; | ||||
| } | ||||
|  | ||||
| int hash_size(uint32_t id) | ||||
| { | ||||
| 	switch (id) { | ||||
| 	case 0: | ||||
| 	case GIT_SHA1_FORMAT_ID: | ||||
| 		return GIT_SHA1_RAWSZ; | ||||
| 	case GIT_SHA256_FORMAT_ID: | ||||
| 		return GIT_SHA256_RAWSZ; | ||||
| 	} | ||||
| 	abort(); | ||||
| } | ||||
|  | @ -0,0 +1,801 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "reader.h" | ||||
|  | ||||
| #include "system.h" | ||||
| #include "block.h" | ||||
| #include "constants.h" | ||||
| #include "generic.h" | ||||
| #include "iter.h" | ||||
| #include "record.h" | ||||
| #include "reftable-error.h" | ||||
| #include "reftable-generic.h" | ||||
| #include "tree.h" | ||||
|  | ||||
| uint64_t block_source_size(struct reftable_block_source *source) | ||||
| { | ||||
| 	return source->ops->size(source->arg); | ||||
| } | ||||
|  | ||||
| int block_source_read_block(struct reftable_block_source *source, | ||||
| 			    struct reftable_block *dest, uint64_t off, | ||||
| 			    uint32_t size) | ||||
| { | ||||
| 	int result = source->ops->read_block(source->arg, dest, off, size); | ||||
| 	dest->source = *source; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| void block_source_close(struct reftable_block_source *source) | ||||
| { | ||||
| 	if (!source->ops) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	source->ops->close(source->arg); | ||||
| 	source->ops = NULL; | ||||
| } | ||||
|  | ||||
| static struct reftable_reader_offsets * | ||||
| reader_offsets_for(struct reftable_reader *r, uint8_t typ) | ||||
| { | ||||
| 	switch (typ) { | ||||
| 	case BLOCK_TYPE_REF: | ||||
| 		return &r->ref_offsets; | ||||
| 	case BLOCK_TYPE_LOG: | ||||
| 		return &r->log_offsets; | ||||
| 	case BLOCK_TYPE_OBJ: | ||||
| 		return &r->obj_offsets; | ||||
| 	} | ||||
| 	abort(); | ||||
| } | ||||
|  | ||||
| static int reader_get_block(struct reftable_reader *r, | ||||
| 			    struct reftable_block *dest, uint64_t off, | ||||
| 			    uint32_t sz) | ||||
| { | ||||
| 	if (off >= r->size) | ||||
| 		return 0; | ||||
|  | ||||
| 	if (off + sz > r->size) { | ||||
| 		sz = r->size - off; | ||||
| 	} | ||||
|  | ||||
| 	return block_source_read_block(&r->source, dest, off, sz); | ||||
| } | ||||
|  | ||||
| uint32_t reftable_reader_hash_id(struct reftable_reader *r) | ||||
| { | ||||
| 	return r->hash_id; | ||||
| } | ||||
|  | ||||
| const char *reader_name(struct reftable_reader *r) | ||||
| { | ||||
| 	return r->name; | ||||
| } | ||||
|  | ||||
| static int parse_footer(struct reftable_reader *r, uint8_t *footer, | ||||
| 			uint8_t *header) | ||||
| { | ||||
| 	uint8_t *f = footer; | ||||
| 	uint8_t first_block_typ; | ||||
| 	int err = 0; | ||||
| 	uint32_t computed_crc; | ||||
| 	uint32_t file_crc; | ||||
|  | ||||
| 	if (memcmp(f, "REFT", 4)) { | ||||
| 		err = REFTABLE_FORMAT_ERROR; | ||||
| 		goto done; | ||||
| 	} | ||||
| 	f += 4; | ||||
|  | ||||
| 	if (memcmp(footer, header, header_size(r->version))) { | ||||
| 		err = REFTABLE_FORMAT_ERROR; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	f++; | ||||
| 	r->block_size = get_be24(f); | ||||
|  | ||||
| 	f += 3; | ||||
| 	r->min_update_index = get_be64(f); | ||||
| 	f += 8; | ||||
| 	r->max_update_index = get_be64(f); | ||||
| 	f += 8; | ||||
|  | ||||
| 	if (r->version == 1) { | ||||
| 		r->hash_id = GIT_SHA1_FORMAT_ID; | ||||
| 	} else { | ||||
| 		r->hash_id = get_be32(f); | ||||
| 		switch (r->hash_id) { | ||||
| 		case GIT_SHA1_FORMAT_ID: | ||||
| 			break; | ||||
| 		case GIT_SHA256_FORMAT_ID: | ||||
| 			break; | ||||
| 		default: | ||||
| 			err = REFTABLE_FORMAT_ERROR; | ||||
| 			goto done; | ||||
| 		} | ||||
| 		f += 4; | ||||
| 	} | ||||
|  | ||||
| 	r->ref_offsets.index_offset = get_be64(f); | ||||
| 	f += 8; | ||||
|  | ||||
| 	r->obj_offsets.offset = get_be64(f); | ||||
| 	f += 8; | ||||
|  | ||||
| 	r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1); | ||||
| 	r->obj_offsets.offset >>= 5; | ||||
|  | ||||
| 	r->obj_offsets.index_offset = get_be64(f); | ||||
| 	f += 8; | ||||
| 	r->log_offsets.offset = get_be64(f); | ||||
| 	f += 8; | ||||
| 	r->log_offsets.index_offset = get_be64(f); | ||||
| 	f += 8; | ||||
|  | ||||
| 	computed_crc = crc32(0, footer, f - footer); | ||||
| 	file_crc = get_be32(f); | ||||
| 	f += 4; | ||||
| 	if (computed_crc != file_crc) { | ||||
| 		err = REFTABLE_FORMAT_ERROR; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	first_block_typ = header[header_size(r->version)]; | ||||
| 	r->ref_offsets.is_present = (first_block_typ == BLOCK_TYPE_REF); | ||||
| 	r->ref_offsets.offset = 0; | ||||
| 	r->log_offsets.is_present = (first_block_typ == BLOCK_TYPE_LOG || | ||||
| 				     r->log_offsets.offset > 0); | ||||
| 	r->obj_offsets.is_present = r->obj_offsets.offset > 0; | ||||
| 	err = 0; | ||||
| done: | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| int init_reader(struct reftable_reader *r, struct reftable_block_source *source, | ||||
| 		const char *name) | ||||
| { | ||||
| 	struct reftable_block footer = { NULL }; | ||||
| 	struct reftable_block header = { NULL }; | ||||
| 	int err = 0; | ||||
| 	uint64_t file_size = block_source_size(source); | ||||
|  | ||||
| 	/* Need +1 to read type of first block. */ | ||||
| 	uint32_t read_size = header_size(2) + 1; /* read v2 because it's larger.  */ | ||||
| 	memset(r, 0, sizeof(struct reftable_reader)); | ||||
|  | ||||
| 	if (read_size > file_size) { | ||||
| 		err = REFTABLE_FORMAT_ERROR; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	err = block_source_read_block(source, &header, 0, read_size); | ||||
| 	if (err != read_size) { | ||||
| 		err = REFTABLE_IO_ERROR; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	if (memcmp(header.data, "REFT", 4)) { | ||||
| 		err = REFTABLE_FORMAT_ERROR; | ||||
| 		goto done; | ||||
| 	} | ||||
| 	r->version = header.data[4]; | ||||
| 	if (r->version != 1 && r->version != 2) { | ||||
| 		err = REFTABLE_FORMAT_ERROR; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	r->size = file_size - footer_size(r->version); | ||||
| 	r->source = *source; | ||||
| 	r->name = xstrdup(name); | ||||
| 	r->hash_id = 0; | ||||
|  | ||||
| 	err = block_source_read_block(source, &footer, r->size, | ||||
| 				      footer_size(r->version)); | ||||
| 	if (err != footer_size(r->version)) { | ||||
| 		err = REFTABLE_IO_ERROR; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	err = parse_footer(r, footer.data, header.data); | ||||
| done: | ||||
| 	reftable_block_done(&footer); | ||||
| 	reftable_block_done(&header); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| struct table_iter { | ||||
| 	struct reftable_reader *r; | ||||
| 	uint8_t typ; | ||||
| 	uint64_t block_off; | ||||
| 	struct block_iter bi; | ||||
| 	int is_finished; | ||||
| }; | ||||
| #define TABLE_ITER_INIT                          \ | ||||
| 	{                                        \ | ||||
| 		.bi = {.last_key = STRBUF_INIT } \ | ||||
| 	} | ||||
|  | ||||
| static void table_iter_copy_from(struct table_iter *dest, | ||||
| 				 struct table_iter *src) | ||||
| { | ||||
| 	dest->r = src->r; | ||||
| 	dest->typ = src->typ; | ||||
| 	dest->block_off = src->block_off; | ||||
| 	dest->is_finished = src->is_finished; | ||||
| 	block_iter_copy_from(&dest->bi, &src->bi); | ||||
| } | ||||
|  | ||||
| static int table_iter_next_in_block(struct table_iter *ti, | ||||
| 				    struct reftable_record *rec) | ||||
| { | ||||
| 	int res = block_iter_next(&ti->bi, rec); | ||||
| 	if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) { | ||||
| 		((struct reftable_ref_record *)rec->data)->update_index += | ||||
| 			ti->r->min_update_index; | ||||
| 	} | ||||
|  | ||||
| 	return res; | ||||
| } | ||||
|  | ||||
| static void table_iter_block_done(struct table_iter *ti) | ||||
| { | ||||
| 	if (!ti->bi.br) { | ||||
| 		return; | ||||
| 	} | ||||
| 	reftable_block_done(&ti->bi.br->block); | ||||
| 	FREE_AND_NULL(ti->bi.br); | ||||
|  | ||||
| 	ti->bi.last_key.len = 0; | ||||
| 	ti->bi.next_off = 0; | ||||
| } | ||||
|  | ||||
| static int32_t extract_block_size(uint8_t *data, uint8_t *typ, uint64_t off, | ||||
| 				  int version) | ||||
| { | ||||
| 	int32_t result = 0; | ||||
|  | ||||
| 	if (off == 0) { | ||||
| 		data += header_size(version); | ||||
| 	} | ||||
|  | ||||
| 	*typ = data[0]; | ||||
| 	if (reftable_is_block_type(*typ)) { | ||||
| 		result = get_be24(data + 1); | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br, | ||||
| 			     uint64_t next_off, uint8_t want_typ) | ||||
| { | ||||
| 	int32_t guess_block_size = r->block_size ? r->block_size : | ||||
| 							 DEFAULT_BLOCK_SIZE; | ||||
| 	struct reftable_block block = { NULL }; | ||||
| 	uint8_t block_typ = 0; | ||||
| 	int err = 0; | ||||
| 	uint32_t header_off = next_off ? 0 : header_size(r->version); | ||||
| 	int32_t block_size = 0; | ||||
|  | ||||
| 	if (next_off >= r->size) | ||||
| 		return 1; | ||||
|  | ||||
| 	err = reader_get_block(r, &block, next_off, guess_block_size); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
|  | ||||
| 	block_size = extract_block_size(block.data, &block_typ, next_off, | ||||
| 					r->version); | ||||
| 	if (block_size < 0) | ||||
| 		return block_size; | ||||
|  | ||||
| 	if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) { | ||||
| 		reftable_block_done(&block); | ||||
| 		return 1; | ||||
| 	} | ||||
|  | ||||
| 	if (block_size > guess_block_size) { | ||||
| 		reftable_block_done(&block); | ||||
| 		err = reader_get_block(r, &block, next_off, block_size); | ||||
| 		if (err < 0) { | ||||
| 			return err; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return block_reader_init(br, &block, header_off, r->block_size, | ||||
| 				 hash_size(r->hash_id)); | ||||
| } | ||||
|  | ||||
| static int table_iter_next_block(struct table_iter *dest, | ||||
| 				 struct table_iter *src) | ||||
| { | ||||
| 	uint64_t next_block_off = src->block_off + src->bi.br->full_block_size; | ||||
| 	struct block_reader br = { 0 }; | ||||
| 	int err = 0; | ||||
|  | ||||
| 	dest->r = src->r; | ||||
| 	dest->typ = src->typ; | ||||
| 	dest->block_off = next_block_off; | ||||
|  | ||||
| 	err = reader_init_block_reader(src->r, &br, next_block_off, src->typ); | ||||
| 	if (err > 0) { | ||||
| 		dest->is_finished = 1; | ||||
| 		return 1; | ||||
| 	} | ||||
| 	if (err != 0) | ||||
| 		return err; | ||||
| 	else { | ||||
| 		struct block_reader *brp = | ||||
| 			reftable_malloc(sizeof(struct block_reader)); | ||||
| 		*brp = br; | ||||
|  | ||||
| 		dest->is_finished = 0; | ||||
| 		block_reader_start(brp, &dest->bi); | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int table_iter_next(struct table_iter *ti, struct reftable_record *rec) | ||||
| { | ||||
| 	if (reftable_record_type(rec) != ti->typ) | ||||
| 		return REFTABLE_API_ERROR; | ||||
|  | ||||
| 	while (1) { | ||||
| 		struct table_iter next = TABLE_ITER_INIT; | ||||
| 		int err = 0; | ||||
| 		if (ti->is_finished) { | ||||
| 			return 1; | ||||
| 		} | ||||
|  | ||||
| 		err = table_iter_next_in_block(ti, rec); | ||||
| 		if (err <= 0) { | ||||
| 			return err; | ||||
| 		} | ||||
|  | ||||
| 		err = table_iter_next_block(&next, ti); | ||||
| 		if (err != 0) { | ||||
| 			ti->is_finished = 1; | ||||
| 		} | ||||
| 		table_iter_block_done(ti); | ||||
| 		if (err != 0) { | ||||
| 			return err; | ||||
| 		} | ||||
| 		table_iter_copy_from(ti, &next); | ||||
| 		block_iter_close(&next.bi); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static int table_iter_next_void(void *ti, struct reftable_record *rec) | ||||
| { | ||||
| 	return table_iter_next(ti, rec); | ||||
| } | ||||
|  | ||||
| static void table_iter_close(void *p) | ||||
| { | ||||
| 	struct table_iter *ti = p; | ||||
| 	table_iter_block_done(ti); | ||||
| 	block_iter_close(&ti->bi); | ||||
| } | ||||
|  | ||||
| static struct reftable_iterator_vtable table_iter_vtable = { | ||||
| 	.next = &table_iter_next_void, | ||||
| 	.close = &table_iter_close, | ||||
| }; | ||||
|  | ||||
| static void iterator_from_table_iter(struct reftable_iterator *it, | ||||
| 				     struct table_iter *ti) | ||||
| { | ||||
| 	assert(!it->ops); | ||||
| 	it->iter_arg = ti; | ||||
| 	it->ops = &table_iter_vtable; | ||||
| } | ||||
|  | ||||
| static int reader_table_iter_at(struct reftable_reader *r, | ||||
| 				struct table_iter *ti, uint64_t off, | ||||
| 				uint8_t typ) | ||||
| { | ||||
| 	struct block_reader br = { 0 }; | ||||
| 	struct block_reader *brp = NULL; | ||||
|  | ||||
| 	int err = reader_init_block_reader(r, &br, off, typ); | ||||
| 	if (err != 0) | ||||
| 		return err; | ||||
|  | ||||
| 	brp = reftable_malloc(sizeof(struct block_reader)); | ||||
| 	*brp = br; | ||||
| 	ti->r = r; | ||||
| 	ti->typ = block_reader_type(brp); | ||||
| 	ti->block_off = off; | ||||
| 	block_reader_start(brp, &ti->bi); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int reader_start(struct reftable_reader *r, struct table_iter *ti, | ||||
| 			uint8_t typ, int index) | ||||
| { | ||||
| 	struct reftable_reader_offsets *offs = reader_offsets_for(r, typ); | ||||
| 	uint64_t off = offs->offset; | ||||
| 	if (index) { | ||||
| 		off = offs->index_offset; | ||||
| 		if (off == 0) { | ||||
| 			return 1; | ||||
| 		} | ||||
| 		typ = BLOCK_TYPE_INDEX; | ||||
| 	} | ||||
|  | ||||
| 	return reader_table_iter_at(r, ti, off, typ); | ||||
| } | ||||
|  | ||||
| static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti, | ||||
| 			      struct reftable_record *want) | ||||
| { | ||||
| 	struct reftable_record rec = | ||||
| 		reftable_new_record(reftable_record_type(want)); | ||||
| 	struct strbuf want_key = STRBUF_INIT; | ||||
| 	struct strbuf got_key = STRBUF_INIT; | ||||
| 	struct table_iter next = TABLE_ITER_INIT; | ||||
| 	int err = -1; | ||||
|  | ||||
| 	reftable_record_key(want, &want_key); | ||||
|  | ||||
| 	while (1) { | ||||
| 		err = table_iter_next_block(&next, ti); | ||||
| 		if (err < 0) | ||||
| 			goto done; | ||||
|  | ||||
| 		if (err > 0) { | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		err = block_reader_first_key(next.bi.br, &got_key); | ||||
| 		if (err < 0) | ||||
| 			goto done; | ||||
|  | ||||
| 		if (strbuf_cmp(&got_key, &want_key) > 0) { | ||||
| 			table_iter_block_done(&next); | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		table_iter_block_done(ti); | ||||
| 		table_iter_copy_from(ti, &next); | ||||
| 	} | ||||
|  | ||||
| 	err = block_iter_seek(&ti->bi, &want_key); | ||||
| 	if (err < 0) | ||||
| 		goto done; | ||||
| 	err = 0; | ||||
|  | ||||
| done: | ||||
| 	block_iter_close(&next.bi); | ||||
| 	reftable_record_destroy(&rec); | ||||
| 	strbuf_release(&want_key); | ||||
| 	strbuf_release(&got_key); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static int reader_seek_indexed(struct reftable_reader *r, | ||||
| 			       struct reftable_iterator *it, | ||||
| 			       struct reftable_record *rec) | ||||
| { | ||||
| 	struct reftable_index_record want_index = { .last_key = STRBUF_INIT }; | ||||
| 	struct reftable_record want_index_rec = { NULL }; | ||||
| 	struct reftable_index_record index_result = { .last_key = STRBUF_INIT }; | ||||
| 	struct reftable_record index_result_rec = { NULL }; | ||||
| 	struct table_iter index_iter = TABLE_ITER_INIT; | ||||
| 	struct table_iter next = TABLE_ITER_INIT; | ||||
| 	int err = 0; | ||||
|  | ||||
| 	reftable_record_key(rec, &want_index.last_key); | ||||
| 	reftable_record_from_index(&want_index_rec, &want_index); | ||||
| 	reftable_record_from_index(&index_result_rec, &index_result); | ||||
|  | ||||
| 	err = reader_start(r, &index_iter, reftable_record_type(rec), 1); | ||||
| 	if (err < 0) | ||||
| 		goto done; | ||||
|  | ||||
| 	err = reader_seek_linear(r, &index_iter, &want_index_rec); | ||||
| 	while (1) { | ||||
| 		err = table_iter_next(&index_iter, &index_result_rec); | ||||
| 		table_iter_block_done(&index_iter); | ||||
| 		if (err != 0) | ||||
| 			goto done; | ||||
|  | ||||
| 		err = reader_table_iter_at(r, &next, index_result.offset, 0); | ||||
| 		if (err != 0) | ||||
| 			goto done; | ||||
|  | ||||
| 		err = block_iter_seek(&next.bi, &want_index.last_key); | ||||
| 		if (err < 0) | ||||
| 			goto done; | ||||
|  | ||||
| 		if (next.typ == reftable_record_type(rec)) { | ||||
| 			err = 0; | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		if (next.typ != BLOCK_TYPE_INDEX) { | ||||
| 			err = REFTABLE_FORMAT_ERROR; | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		table_iter_copy_from(&index_iter, &next); | ||||
| 	} | ||||
|  | ||||
| 	if (err == 0) { | ||||
| 		struct table_iter empty = TABLE_ITER_INIT; | ||||
| 		struct table_iter *malloced = | ||||
| 			reftable_calloc(sizeof(struct table_iter)); | ||||
| 		*malloced = empty; | ||||
| 		table_iter_copy_from(malloced, &next); | ||||
| 		iterator_from_table_iter(it, malloced); | ||||
| 	} | ||||
| done: | ||||
| 	block_iter_close(&next.bi); | ||||
| 	table_iter_close(&index_iter); | ||||
| 	reftable_record_release(&want_index_rec); | ||||
| 	reftable_record_release(&index_result_rec); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static int reader_seek_internal(struct reftable_reader *r, | ||||
| 				struct reftable_iterator *it, | ||||
| 				struct reftable_record *rec) | ||||
| { | ||||
| 	struct reftable_reader_offsets *offs = | ||||
| 		reader_offsets_for(r, reftable_record_type(rec)); | ||||
| 	uint64_t idx = offs->index_offset; | ||||
| 	struct table_iter ti = TABLE_ITER_INIT; | ||||
| 	int err = 0; | ||||
| 	if (idx > 0) | ||||
| 		return reader_seek_indexed(r, it, rec); | ||||
|  | ||||
| 	err = reader_start(r, &ti, reftable_record_type(rec), 0); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
| 	err = reader_seek_linear(r, &ti, rec); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
| 	else { | ||||
| 		struct table_iter *p = | ||||
| 			reftable_malloc(sizeof(struct table_iter)); | ||||
| 		*p = ti; | ||||
| 		iterator_from_table_iter(it, p); | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it, | ||||
| 		       struct reftable_record *rec) | ||||
| { | ||||
| 	uint8_t typ = reftable_record_type(rec); | ||||
|  | ||||
| 	struct reftable_reader_offsets *offs = reader_offsets_for(r, typ); | ||||
| 	if (!offs->is_present) { | ||||
| 		iterator_set_empty(it); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	return reader_seek_internal(r, it, rec); | ||||
| } | ||||
|  | ||||
| int reftable_reader_seek_ref(struct reftable_reader *r, | ||||
| 			     struct reftable_iterator *it, const char *name) | ||||
| { | ||||
| 	struct reftable_ref_record ref = { | ||||
| 		.refname = (char *)name, | ||||
| 	}; | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	reftable_record_from_ref(&rec, &ref); | ||||
| 	return reader_seek(r, it, &rec); | ||||
| } | ||||
|  | ||||
| int reftable_reader_seek_log_at(struct reftable_reader *r, | ||||
| 				struct reftable_iterator *it, const char *name, | ||||
| 				uint64_t update_index) | ||||
| { | ||||
| 	struct reftable_log_record log = { | ||||
| 		.refname = (char *)name, | ||||
| 		.update_index = update_index, | ||||
| 	}; | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	reftable_record_from_log(&rec, &log); | ||||
| 	return reader_seek(r, it, &rec); | ||||
| } | ||||
|  | ||||
| int reftable_reader_seek_log(struct reftable_reader *r, | ||||
| 			     struct reftable_iterator *it, const char *name) | ||||
| { | ||||
| 	uint64_t max = ~((uint64_t)0); | ||||
| 	return reftable_reader_seek_log_at(r, it, name, max); | ||||
| } | ||||
|  | ||||
| void reader_close(struct reftable_reader *r) | ||||
| { | ||||
| 	block_source_close(&r->source); | ||||
| 	FREE_AND_NULL(r->name); | ||||
| } | ||||
|  | ||||
| int reftable_new_reader(struct reftable_reader **p, | ||||
| 			struct reftable_block_source *src, char const *name) | ||||
| { | ||||
| 	struct reftable_reader *rd = | ||||
| 		reftable_calloc(sizeof(struct reftable_reader)); | ||||
| 	int err = init_reader(rd, src, name); | ||||
| 	if (err == 0) { | ||||
| 		*p = rd; | ||||
| 	} else { | ||||
| 		block_source_close(src); | ||||
| 		reftable_free(rd); | ||||
| 	} | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| void reftable_reader_free(struct reftable_reader *r) | ||||
| { | ||||
| 	reader_close(r); | ||||
| 	reftable_free(r); | ||||
| } | ||||
|  | ||||
| static int reftable_reader_refs_for_indexed(struct reftable_reader *r, | ||||
| 					    struct reftable_iterator *it, | ||||
| 					    uint8_t *oid) | ||||
| { | ||||
| 	struct reftable_obj_record want = { | ||||
| 		.hash_prefix = oid, | ||||
| 		.hash_prefix_len = r->object_id_len, | ||||
| 	}; | ||||
| 	struct reftable_record want_rec = { NULL }; | ||||
| 	struct reftable_iterator oit = { NULL }; | ||||
| 	struct reftable_obj_record got = { NULL }; | ||||
| 	struct reftable_record got_rec = { NULL }; | ||||
| 	int err = 0; | ||||
| 	struct indexed_table_ref_iter *itr = NULL; | ||||
|  | ||||
| 	/* Look through the reverse index. */ | ||||
| 	reftable_record_from_obj(&want_rec, &want); | ||||
| 	err = reader_seek(r, &oit, &want_rec); | ||||
| 	if (err != 0) | ||||
| 		goto done; | ||||
|  | ||||
| 	/* read out the reftable_obj_record */ | ||||
| 	reftable_record_from_obj(&got_rec, &got); | ||||
| 	err = iterator_next(&oit, &got_rec); | ||||
| 	if (err < 0) | ||||
| 		goto done; | ||||
|  | ||||
| 	if (err > 0 || | ||||
| 	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) { | ||||
| 		/* didn't find it; return empty iterator */ | ||||
| 		iterator_set_empty(it); | ||||
| 		err = 0; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id), | ||||
| 					 got.offsets, got.offset_len); | ||||
| 	if (err < 0) | ||||
| 		goto done; | ||||
| 	got.offsets = NULL; | ||||
| 	iterator_from_indexed_table_ref_iter(it, itr); | ||||
|  | ||||
| done: | ||||
| 	reftable_iterator_destroy(&oit); | ||||
| 	reftable_record_release(&got_rec); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static int reftable_reader_refs_for_unindexed(struct reftable_reader *r, | ||||
| 					      struct reftable_iterator *it, | ||||
| 					      uint8_t *oid) | ||||
| { | ||||
| 	struct table_iter ti_empty = TABLE_ITER_INIT; | ||||
| 	struct table_iter *ti = reftable_calloc(sizeof(struct table_iter)); | ||||
| 	struct filtering_ref_iterator *filter = NULL; | ||||
| 	struct filtering_ref_iterator empty = FILTERING_REF_ITERATOR_INIT; | ||||
| 	int oid_len = hash_size(r->hash_id); | ||||
| 	int err; | ||||
|  | ||||
| 	*ti = ti_empty; | ||||
| 	err = reader_start(r, ti, BLOCK_TYPE_REF, 0); | ||||
| 	if (err < 0) { | ||||
| 		reftable_free(ti); | ||||
| 		return err; | ||||
| 	} | ||||
|  | ||||
| 	filter = reftable_malloc(sizeof(struct filtering_ref_iterator)); | ||||
| 	*filter = empty; | ||||
|  | ||||
| 	strbuf_add(&filter->oid, oid, oid_len); | ||||
| 	reftable_table_from_reader(&filter->tab, r); | ||||
| 	filter->double_check = 0; | ||||
| 	iterator_from_table_iter(&filter->it, ti); | ||||
|  | ||||
| 	iterator_from_filtering_ref_iterator(it, filter); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int reftable_reader_refs_for(struct reftable_reader *r, | ||||
| 			     struct reftable_iterator *it, uint8_t *oid) | ||||
| { | ||||
| 	if (r->obj_offsets.is_present) | ||||
| 		return reftable_reader_refs_for_indexed(r, it, oid); | ||||
| 	return reftable_reader_refs_for_unindexed(r, it, oid); | ||||
| } | ||||
|  | ||||
| uint64_t reftable_reader_max_update_index(struct reftable_reader *r) | ||||
| { | ||||
| 	return r->max_update_index; | ||||
| } | ||||
|  | ||||
| uint64_t reftable_reader_min_update_index(struct reftable_reader *r) | ||||
| { | ||||
| 	return r->min_update_index; | ||||
| } | ||||
|  | ||||
| /* generic table interface. */ | ||||
|  | ||||
| static int reftable_reader_seek_void(void *tab, struct reftable_iterator *it, | ||||
| 				     struct reftable_record *rec) | ||||
| { | ||||
| 	return reader_seek(tab, it, rec); | ||||
| } | ||||
|  | ||||
| static uint32_t reftable_reader_hash_id_void(void *tab) | ||||
| { | ||||
| 	return reftable_reader_hash_id(tab); | ||||
| } | ||||
|  | ||||
| static uint64_t reftable_reader_min_update_index_void(void *tab) | ||||
| { | ||||
| 	return reftable_reader_min_update_index(tab); | ||||
| } | ||||
|  | ||||
| static uint64_t reftable_reader_max_update_index_void(void *tab) | ||||
| { | ||||
| 	return reftable_reader_max_update_index(tab); | ||||
| } | ||||
|  | ||||
| static struct reftable_table_vtable reader_vtable = { | ||||
| 	.seek_record = reftable_reader_seek_void, | ||||
| 	.hash_id = reftable_reader_hash_id_void, | ||||
| 	.min_update_index = reftable_reader_min_update_index_void, | ||||
| 	.max_update_index = reftable_reader_max_update_index_void, | ||||
| }; | ||||
|  | ||||
| void reftable_table_from_reader(struct reftable_table *tab, | ||||
| 				struct reftable_reader *reader) | ||||
| { | ||||
| 	assert(!tab->ops); | ||||
| 	tab->ops = &reader_vtable; | ||||
| 	tab->table_arg = reader; | ||||
| } | ||||
|  | ||||
|  | ||||
| int reftable_reader_print_file(const char *tablename) | ||||
| { | ||||
| 	struct reftable_block_source src = { NULL }; | ||||
| 	int err = reftable_block_source_from_file(&src, tablename); | ||||
| 	struct reftable_reader *r = NULL; | ||||
| 	struct reftable_table tab = { NULL }; | ||||
| 	if (err < 0) | ||||
| 		goto done; | ||||
|  | ||||
| 	err = reftable_new_reader(&r, &src, tablename); | ||||
| 	if (err < 0) | ||||
| 		goto done; | ||||
|  | ||||
| 	reftable_table_from_reader(&tab, r); | ||||
| 	err = reftable_table_print(&tab); | ||||
| done: | ||||
| 	reftable_reader_free(r); | ||||
| 	return err; | ||||
| } | ||||
|  | @ -0,0 +1,64 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef READER_H | ||||
| #define READER_H | ||||
|  | ||||
| #include "block.h" | ||||
| #include "record.h" | ||||
| #include "reftable-iterator.h" | ||||
| #include "reftable-reader.h" | ||||
|  | ||||
| uint64_t block_source_size(struct reftable_block_source *source); | ||||
|  | ||||
| int block_source_read_block(struct reftable_block_source *source, | ||||
| 			    struct reftable_block *dest, uint64_t off, | ||||
| 			    uint32_t size); | ||||
| void block_source_close(struct reftable_block_source *source); | ||||
|  | ||||
| /* metadata for a block type */ | ||||
| struct reftable_reader_offsets { | ||||
| 	int is_present; | ||||
| 	uint64_t offset; | ||||
| 	uint64_t index_offset; | ||||
| }; | ||||
|  | ||||
| /* The state for reading a reftable file. */ | ||||
| struct reftable_reader { | ||||
| 	/* for convience, associate a name with the instance. */ | ||||
| 	char *name; | ||||
| 	struct reftable_block_source source; | ||||
|  | ||||
| 	/* Size of the file, excluding the footer. */ | ||||
| 	uint64_t size; | ||||
|  | ||||
| 	/* 'sha1' for SHA1, 's256' for SHA-256 */ | ||||
| 	uint32_t hash_id; | ||||
|  | ||||
| 	uint32_t block_size; | ||||
| 	uint64_t min_update_index; | ||||
| 	uint64_t max_update_index; | ||||
| 	/* Length of the OID keys in the 'o' section */ | ||||
| 	int object_id_len; | ||||
| 	int version; | ||||
|  | ||||
| 	struct reftable_reader_offsets ref_offsets; | ||||
| 	struct reftable_reader_offsets obj_offsets; | ||||
| 	struct reftable_reader_offsets log_offsets; | ||||
| }; | ||||
|  | ||||
| int init_reader(struct reftable_reader *r, struct reftable_block_source *source, | ||||
| 		const char *name); | ||||
| void reader_close(struct reftable_reader *r); | ||||
| const char *reader_name(struct reftable_reader *r); | ||||
|  | ||||
| /* initialize a block reader to read from `r` */ | ||||
| int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br, | ||||
| 			     uint64_t next_off, uint8_t want_typ); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,652 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "system.h" | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "block.h" | ||||
| #include "blocksource.h" | ||||
| #include "constants.h" | ||||
| #include "reader.h" | ||||
| #include "record.h" | ||||
| #include "test_framework.h" | ||||
| #include "reftable-tests.h" | ||||
| #include "reftable-writer.h" | ||||
|  | ||||
| static const int update_index = 5; | ||||
|  | ||||
| static void test_buffer(void) | ||||
| { | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	struct reftable_block_source source = { NULL }; | ||||
| 	struct reftable_block out = { NULL }; | ||||
| 	int n; | ||||
| 	uint8_t in[] = "hello"; | ||||
| 	strbuf_add(&buf, in, sizeof(in)); | ||||
| 	block_source_from_strbuf(&source, &buf); | ||||
| 	EXPECT(block_source_size(&source) == 6); | ||||
| 	n = block_source_read_block(&source, &out, 0, sizeof(in)); | ||||
| 	EXPECT(n == sizeof(in)); | ||||
| 	EXPECT(!memcmp(in, out.data, n)); | ||||
| 	reftable_block_done(&out); | ||||
|  | ||||
| 	n = block_source_read_block(&source, &out, 1, 2); | ||||
| 	EXPECT(n == 2); | ||||
| 	EXPECT(!memcmp(out.data, "el", 2)); | ||||
|  | ||||
| 	reftable_block_done(&out); | ||||
| 	block_source_close(&source); | ||||
| 	strbuf_release(&buf); | ||||
| } | ||||
|  | ||||
| static void write_table(char ***names, struct strbuf *buf, int N, | ||||
| 			int block_size, uint32_t hash_id) | ||||
| { | ||||
| 	struct reftable_write_options opts = { | ||||
| 		.block_size = block_size, | ||||
| 		.hash_id = hash_id, | ||||
| 	}; | ||||
| 	struct reftable_writer *w = | ||||
| 		reftable_new_writer(&strbuf_add_void, buf, &opts); | ||||
| 	struct reftable_ref_record ref = { NULL }; | ||||
| 	int i = 0, n; | ||||
| 	struct reftable_log_record log = { NULL }; | ||||
| 	const struct reftable_stats *stats = NULL; | ||||
| 	*names = reftable_calloc(sizeof(char *) * (N + 1)); | ||||
| 	reftable_writer_set_limits(w, update_index, update_index); | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		uint8_t hash[GIT_SHA256_RAWSZ] = { 0 }; | ||||
| 		char name[100]; | ||||
| 		int n; | ||||
|  | ||||
| 		set_test_hash(hash, i); | ||||
|  | ||||
| 		snprintf(name, sizeof(name), "refs/heads/branch%02d", i); | ||||
|  | ||||
| 		ref.refname = name; | ||||
| 		ref.update_index = update_index; | ||||
| 		ref.value_type = REFTABLE_REF_VAL1; | ||||
| 		ref.value.val1 = hash; | ||||
| 		(*names)[i] = xstrdup(name); | ||||
|  | ||||
| 		n = reftable_writer_add_ref(w, &ref); | ||||
| 		EXPECT(n == 0); | ||||
| 	} | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		uint8_t hash[GIT_SHA256_RAWSZ] = { 0 }; | ||||
| 		char name[100]; | ||||
| 		int n; | ||||
|  | ||||
| 		set_test_hash(hash, i); | ||||
|  | ||||
| 		snprintf(name, sizeof(name), "refs/heads/branch%02d", i); | ||||
|  | ||||
| 		log.refname = name; | ||||
| 		log.update_index = update_index; | ||||
| 		log.value_type = REFTABLE_LOG_UPDATE; | ||||
| 		log.value.update.new_hash = hash; | ||||
| 		log.value.update.message = "message"; | ||||
|  | ||||
| 		n = reftable_writer_add_log(w, &log); | ||||
| 		EXPECT(n == 0); | ||||
| 	} | ||||
|  | ||||
| 	n = reftable_writer_close(w); | ||||
| 	EXPECT(n == 0); | ||||
|  | ||||
| 	stats = writer_stats(w); | ||||
| 	for (i = 0; i < stats->ref_stats.blocks; i++) { | ||||
| 		int off = i * opts.block_size; | ||||
| 		if (off == 0) { | ||||
| 			off = header_size( | ||||
| 				(hash_id == GIT_SHA256_FORMAT_ID) ? 2 : 1); | ||||
| 		} | ||||
| 		EXPECT(buf->buf[off] == 'r'); | ||||
| 	} | ||||
|  | ||||
| 	EXPECT(stats->log_stats.blocks > 0); | ||||
| 	reftable_writer_free(w); | ||||
| } | ||||
|  | ||||
| static void test_log_buffer_size(void) | ||||
| { | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	struct reftable_write_options opts = { | ||||
| 		.block_size = 4096, | ||||
| 	}; | ||||
| 	int err; | ||||
| 	int i; | ||||
| 	struct reftable_log_record | ||||
| 		log = { .refname = "refs/heads/master", | ||||
| 			.update_index = 0xa, | ||||
| 			.value_type = REFTABLE_LOG_UPDATE, | ||||
| 			.value = { .update = { | ||||
| 					   .name = "Han-Wen Nienhuys", | ||||
| 					   .email = "hanwen@google.com", | ||||
| 					   .tz_offset = 100, | ||||
| 					   .time = 0x5e430672, | ||||
| 					   .message = "commit: 9\n", | ||||
| 				   } } }; | ||||
| 	struct reftable_writer *w = | ||||
| 		reftable_new_writer(&strbuf_add_void, &buf, &opts); | ||||
|  | ||||
| 	/* This tests buffer extension for log compression. Must use a random | ||||
| 	   hash, to ensure that the compressed part is larger than the original. | ||||
| 	*/ | ||||
| 	uint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ]; | ||||
| 	for (i = 0; i < GIT_SHA1_RAWSZ; i++) { | ||||
| 		hash1[i] = (uint8_t)(rand() % 256); | ||||
| 		hash2[i] = (uint8_t)(rand() % 256); | ||||
| 	} | ||||
| 	log.value.update.old_hash = hash1; | ||||
| 	log.value.update.new_hash = hash2; | ||||
| 	reftable_writer_set_limits(w, update_index, update_index); | ||||
| 	err = reftable_writer_add_log(w, &log); | ||||
| 	EXPECT_ERR(err); | ||||
| 	err = reftable_writer_close(w); | ||||
| 	EXPECT_ERR(err); | ||||
| 	reftable_writer_free(w); | ||||
| 	strbuf_release(&buf); | ||||
| } | ||||
|  | ||||
| static void test_log_write_read(void) | ||||
| { | ||||
| 	int N = 2; | ||||
| 	char **names = reftable_calloc(sizeof(char *) * (N + 1)); | ||||
| 	int err; | ||||
| 	struct reftable_write_options opts = { | ||||
| 		.block_size = 256, | ||||
| 	}; | ||||
| 	struct reftable_ref_record ref = { NULL }; | ||||
| 	int i = 0; | ||||
| 	struct reftable_log_record log = { NULL }; | ||||
| 	int n; | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	struct reftable_reader rd = { NULL }; | ||||
| 	struct reftable_block_source source = { NULL }; | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	struct reftable_writer *w = | ||||
| 		reftable_new_writer(&strbuf_add_void, &buf, &opts); | ||||
| 	const struct reftable_stats *stats = NULL; | ||||
| 	reftable_writer_set_limits(w, 0, N); | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		char name[256]; | ||||
| 		struct reftable_ref_record ref = { NULL }; | ||||
| 		snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7); | ||||
| 		names[i] = xstrdup(name); | ||||
| 		ref.refname = name; | ||||
| 		ref.update_index = i; | ||||
|  | ||||
| 		err = reftable_writer_add_ref(w, &ref); | ||||
| 		EXPECT_ERR(err); | ||||
| 	} | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		uint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ]; | ||||
| 		struct reftable_log_record log = { NULL }; | ||||
| 		set_test_hash(hash1, i); | ||||
| 		set_test_hash(hash2, i + 1); | ||||
|  | ||||
| 		log.refname = names[i]; | ||||
| 		log.update_index = i; | ||||
| 		log.value_type = REFTABLE_LOG_UPDATE; | ||||
| 		log.value.update.old_hash = hash1; | ||||
| 		log.value.update.new_hash = hash2; | ||||
|  | ||||
| 		err = reftable_writer_add_log(w, &log); | ||||
| 		EXPECT_ERR(err); | ||||
| 	} | ||||
|  | ||||
| 	n = reftable_writer_close(w); | ||||
| 	EXPECT(n == 0); | ||||
|  | ||||
| 	stats = writer_stats(w); | ||||
| 	EXPECT(stats->log_stats.blocks > 0); | ||||
| 	reftable_writer_free(w); | ||||
| 	w = NULL; | ||||
|  | ||||
| 	block_source_from_strbuf(&source, &buf); | ||||
|  | ||||
| 	err = init_reader(&rd, &source, "file.log"); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_reader_seek_ref(&rd, &it, names[N - 1]); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_iterator_next_ref(&it, &ref); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	/* end of iteration. */ | ||||
| 	err = reftable_iterator_next_ref(&it, &ref); | ||||
| 	EXPECT(0 < err); | ||||
|  | ||||
| 	reftable_iterator_destroy(&it); | ||||
| 	reftable_ref_record_release(&ref); | ||||
|  | ||||
| 	err = reftable_reader_seek_log(&rd, &it, ""); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	i = 0; | ||||
| 	while (1) { | ||||
| 		int err = reftable_iterator_next_log(&it, &log); | ||||
| 		if (err > 0) { | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		EXPECT_ERR(err); | ||||
| 		EXPECT_STREQ(names[i], log.refname); | ||||
| 		EXPECT(i == log.update_index); | ||||
| 		i++; | ||||
| 		reftable_log_record_release(&log); | ||||
| 	} | ||||
|  | ||||
| 	EXPECT(i == N); | ||||
| 	reftable_iterator_destroy(&it); | ||||
|  | ||||
| 	/* cleanup. */ | ||||
| 	strbuf_release(&buf); | ||||
| 	free_names(names); | ||||
| 	reader_close(&rd); | ||||
| } | ||||
|  | ||||
| static void test_table_read_write_sequential(void) | ||||
| { | ||||
| 	char **names; | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	int N = 50; | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	struct reftable_block_source source = { NULL }; | ||||
| 	struct reftable_reader rd = { NULL }; | ||||
| 	int err = 0; | ||||
| 	int j = 0; | ||||
|  | ||||
| 	write_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID); | ||||
|  | ||||
| 	block_source_from_strbuf(&source, &buf); | ||||
|  | ||||
| 	err = init_reader(&rd, &source, "file.ref"); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_reader_seek_ref(&rd, &it, ""); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	while (1) { | ||||
| 		struct reftable_ref_record ref = { NULL }; | ||||
| 		int r = reftable_iterator_next_ref(&it, &ref); | ||||
| 		EXPECT(r >= 0); | ||||
| 		if (r > 0) { | ||||
| 			break; | ||||
| 		} | ||||
| 		EXPECT(0 == strcmp(names[j], ref.refname)); | ||||
| 		EXPECT(update_index == ref.update_index); | ||||
|  | ||||
| 		j++; | ||||
| 		reftable_ref_record_release(&ref); | ||||
| 	} | ||||
| 	EXPECT(j == N); | ||||
| 	reftable_iterator_destroy(&it); | ||||
| 	strbuf_release(&buf); | ||||
| 	free_names(names); | ||||
|  | ||||
| 	reader_close(&rd); | ||||
| } | ||||
|  | ||||
| static void test_table_write_small_table(void) | ||||
| { | ||||
| 	char **names; | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	int N = 1; | ||||
| 	write_table(&names, &buf, N, 4096, GIT_SHA1_FORMAT_ID); | ||||
| 	EXPECT(buf.len < 200); | ||||
| 	strbuf_release(&buf); | ||||
| 	free_names(names); | ||||
| } | ||||
|  | ||||
| static void test_table_read_api(void) | ||||
| { | ||||
| 	char **names; | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	int N = 50; | ||||
| 	struct reftable_reader rd = { NULL }; | ||||
| 	struct reftable_block_source source = { NULL }; | ||||
| 	int err; | ||||
| 	int i; | ||||
| 	struct reftable_log_record log = { NULL }; | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
|  | ||||
| 	write_table(&names, &buf, N, 256, GIT_SHA1_FORMAT_ID); | ||||
|  | ||||
| 	block_source_from_strbuf(&source, &buf); | ||||
|  | ||||
| 	err = init_reader(&rd, &source, "file.ref"); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_reader_seek_ref(&rd, &it, names[0]); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_iterator_next_log(&it, &log); | ||||
| 	EXPECT(err == REFTABLE_API_ERROR); | ||||
|  | ||||
| 	strbuf_release(&buf); | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		reftable_free(names[i]); | ||||
| 	} | ||||
| 	reftable_iterator_destroy(&it); | ||||
| 	reftable_free(names); | ||||
| 	reader_close(&rd); | ||||
| 	strbuf_release(&buf); | ||||
| } | ||||
|  | ||||
| static void test_table_read_write_seek(int index, int hash_id) | ||||
| { | ||||
| 	char **names; | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	int N = 50; | ||||
| 	struct reftable_reader rd = { NULL }; | ||||
| 	struct reftable_block_source source = { NULL }; | ||||
| 	int err; | ||||
| 	int i = 0; | ||||
|  | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	struct strbuf pastLast = STRBUF_INIT; | ||||
| 	struct reftable_ref_record ref = { NULL }; | ||||
|  | ||||
| 	write_table(&names, &buf, N, 256, hash_id); | ||||
|  | ||||
| 	block_source_from_strbuf(&source, &buf); | ||||
|  | ||||
| 	err = init_reader(&rd, &source, "file.ref"); | ||||
| 	EXPECT_ERR(err); | ||||
| 	EXPECT(hash_id == reftable_reader_hash_id(&rd)); | ||||
|  | ||||
| 	if (!index) { | ||||
| 		rd.ref_offsets.index_offset = 0; | ||||
| 	} else { | ||||
| 		EXPECT(rd.ref_offsets.index_offset > 0); | ||||
| 	} | ||||
|  | ||||
| 	for (i = 1; i < N; i++) { | ||||
| 		int err = reftable_reader_seek_ref(&rd, &it, names[i]); | ||||
| 		EXPECT_ERR(err); | ||||
| 		err = reftable_iterator_next_ref(&it, &ref); | ||||
| 		EXPECT_ERR(err); | ||||
| 		EXPECT(0 == strcmp(names[i], ref.refname)); | ||||
| 		EXPECT(REFTABLE_REF_VAL1 == ref.value_type); | ||||
| 		EXPECT(i == ref.value.val1[0]); | ||||
|  | ||||
| 		reftable_ref_record_release(&ref); | ||||
| 		reftable_iterator_destroy(&it); | ||||
| 	} | ||||
|  | ||||
| 	strbuf_addstr(&pastLast, names[N - 1]); | ||||
| 	strbuf_addstr(&pastLast, "/"); | ||||
|  | ||||
| 	err = reftable_reader_seek_ref(&rd, &it, pastLast.buf); | ||||
| 	if (err == 0) { | ||||
| 		struct reftable_ref_record ref = { NULL }; | ||||
| 		int err = reftable_iterator_next_ref(&it, &ref); | ||||
| 		EXPECT(err > 0); | ||||
| 	} else { | ||||
| 		EXPECT(err > 0); | ||||
| 	} | ||||
|  | ||||
| 	strbuf_release(&pastLast); | ||||
| 	reftable_iterator_destroy(&it); | ||||
|  | ||||
| 	strbuf_release(&buf); | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		reftable_free(names[i]); | ||||
| 	} | ||||
| 	reftable_free(names); | ||||
| 	reader_close(&rd); | ||||
| } | ||||
|  | ||||
| static void test_table_read_write_seek_linear(void) | ||||
| { | ||||
| 	test_table_read_write_seek(0, GIT_SHA1_FORMAT_ID); | ||||
| } | ||||
|  | ||||
| static void test_table_read_write_seek_linear_sha256(void) | ||||
| { | ||||
| 	test_table_read_write_seek(0, GIT_SHA256_FORMAT_ID); | ||||
| } | ||||
|  | ||||
| static void test_table_read_write_seek_index(void) | ||||
| { | ||||
| 	test_table_read_write_seek(1, GIT_SHA1_FORMAT_ID); | ||||
| } | ||||
|  | ||||
| static void test_table_refs_for(int indexed) | ||||
| { | ||||
| 	int N = 50; | ||||
| 	char **want_names = reftable_calloc(sizeof(char *) * (N + 1)); | ||||
| 	int want_names_len = 0; | ||||
| 	uint8_t want_hash[GIT_SHA1_RAWSZ]; | ||||
|  | ||||
| 	struct reftable_write_options opts = { | ||||
| 		.block_size = 256, | ||||
| 	}; | ||||
| 	struct reftable_ref_record ref = { NULL }; | ||||
| 	int i = 0; | ||||
| 	int n; | ||||
| 	int err; | ||||
| 	struct reftable_reader rd; | ||||
| 	struct reftable_block_source source = { NULL }; | ||||
|  | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	struct reftable_writer *w = | ||||
| 		reftable_new_writer(&strbuf_add_void, &buf, &opts); | ||||
|  | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	int j; | ||||
|  | ||||
| 	set_test_hash(want_hash, 4); | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		uint8_t hash[GIT_SHA1_RAWSZ]; | ||||
| 		char fill[51] = { 0 }; | ||||
| 		char name[100]; | ||||
| 		uint8_t hash1[GIT_SHA1_RAWSZ]; | ||||
| 		uint8_t hash2[GIT_SHA1_RAWSZ]; | ||||
| 		struct reftable_ref_record ref = { NULL }; | ||||
|  | ||||
| 		memset(hash, i, sizeof(hash)); | ||||
| 		memset(fill, 'x', 50); | ||||
| 		/* Put the variable part in the start */ | ||||
| 		snprintf(name, sizeof(name), "br%02d%s", i, fill); | ||||
| 		name[40] = 0; | ||||
| 		ref.refname = name; | ||||
|  | ||||
| 		set_test_hash(hash1, i / 4); | ||||
| 		set_test_hash(hash2, 3 + i / 4); | ||||
| 		ref.value_type = REFTABLE_REF_VAL2; | ||||
| 		ref.value.val2.value = hash1; | ||||
| 		ref.value.val2.target_value = hash2; | ||||
|  | ||||
| 		/* 80 bytes / entry, so 3 entries per block. Yields 17 | ||||
| 		 */ | ||||
| 		/* blocks. */ | ||||
| 		n = reftable_writer_add_ref(w, &ref); | ||||
| 		EXPECT(n == 0); | ||||
|  | ||||
| 		if (!memcmp(hash1, want_hash, GIT_SHA1_RAWSZ) || | ||||
| 		    !memcmp(hash2, want_hash, GIT_SHA1_RAWSZ)) { | ||||
| 			want_names[want_names_len++] = xstrdup(name); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	n = reftable_writer_close(w); | ||||
| 	EXPECT(n == 0); | ||||
|  | ||||
| 	reftable_writer_free(w); | ||||
| 	w = NULL; | ||||
|  | ||||
| 	block_source_from_strbuf(&source, &buf); | ||||
|  | ||||
| 	err = init_reader(&rd, &source, "file.ref"); | ||||
| 	EXPECT_ERR(err); | ||||
| 	if (!indexed) { | ||||
| 		rd.obj_offsets.is_present = 0; | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_reader_seek_ref(&rd, &it, ""); | ||||
| 	EXPECT_ERR(err); | ||||
| 	reftable_iterator_destroy(&it); | ||||
|  | ||||
| 	err = reftable_reader_refs_for(&rd, &it, want_hash); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	j = 0; | ||||
| 	while (1) { | ||||
| 		int err = reftable_iterator_next_ref(&it, &ref); | ||||
| 		EXPECT(err >= 0); | ||||
| 		if (err > 0) { | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		EXPECT(j < want_names_len); | ||||
| 		EXPECT(0 == strcmp(ref.refname, want_names[j])); | ||||
| 		j++; | ||||
| 		reftable_ref_record_release(&ref); | ||||
| 	} | ||||
| 	EXPECT(j == want_names_len); | ||||
|  | ||||
| 	strbuf_release(&buf); | ||||
| 	free_names(want_names); | ||||
| 	reftable_iterator_destroy(&it); | ||||
| 	reader_close(&rd); | ||||
| } | ||||
|  | ||||
| static void test_table_refs_for_no_index(void) | ||||
| { | ||||
| 	test_table_refs_for(0); | ||||
| } | ||||
|  | ||||
| static void test_table_refs_for_obj_index(void) | ||||
| { | ||||
| 	test_table_refs_for(1); | ||||
| } | ||||
|  | ||||
| static void test_write_empty_table(void) | ||||
| { | ||||
| 	struct reftable_write_options opts = { 0 }; | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	struct reftable_writer *w = | ||||
| 		reftable_new_writer(&strbuf_add_void, &buf, &opts); | ||||
| 	struct reftable_block_source source = { NULL }; | ||||
| 	struct reftable_reader *rd = NULL; | ||||
| 	struct reftable_ref_record rec = { NULL }; | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	int err; | ||||
|  | ||||
| 	reftable_writer_set_limits(w, 1, 1); | ||||
|  | ||||
| 	err = reftable_writer_close(w); | ||||
| 	EXPECT(err == REFTABLE_EMPTY_TABLE_ERROR); | ||||
| 	reftable_writer_free(w); | ||||
|  | ||||
| 	EXPECT(buf.len == header_size(1) + footer_size(1)); | ||||
|  | ||||
| 	block_source_from_strbuf(&source, &buf); | ||||
|  | ||||
| 	err = reftable_new_reader(&rd, &source, "filename"); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_reader_seek_ref(rd, &it, ""); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_iterator_next_ref(&it, &rec); | ||||
| 	EXPECT(err > 0); | ||||
|  | ||||
| 	reftable_iterator_destroy(&it); | ||||
| 	reftable_reader_free(rd); | ||||
| 	strbuf_release(&buf); | ||||
| } | ||||
|  | ||||
| static void test_write_key_order(void) | ||||
| { | ||||
| 	struct reftable_write_options opts = { 0 }; | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	struct reftable_writer *w = | ||||
| 		reftable_new_writer(&strbuf_add_void, &buf, &opts); | ||||
| 	struct reftable_ref_record refs[2] = { | ||||
| 		{ | ||||
| 			.refname = "b", | ||||
| 			.update_index = 1, | ||||
| 			.value_type = REFTABLE_REF_SYMREF, | ||||
| 			.value = { | ||||
| 				.symref = "target", | ||||
| 			}, | ||||
| 		}, { | ||||
| 			.refname = "a", | ||||
| 			.update_index = 1, | ||||
| 			.value_type = REFTABLE_REF_SYMREF, | ||||
| 			.value = { | ||||
| 				.symref = "target", | ||||
| 			}, | ||||
| 		} | ||||
| 	}; | ||||
| 	int err; | ||||
|  | ||||
| 	reftable_writer_set_limits(w, 1, 1); | ||||
| 	err = reftable_writer_add_ref(w, &refs[0]); | ||||
| 	EXPECT_ERR(err); | ||||
| 	err = reftable_writer_add_ref(w, &refs[1]); | ||||
| 	printf("%d\n", err); | ||||
| 	EXPECT(err == REFTABLE_API_ERROR); | ||||
| 	reftable_writer_close(w); | ||||
| 	reftable_writer_free(w); | ||||
| 	strbuf_release(&buf); | ||||
| } | ||||
|  | ||||
| static void test_corrupt_table_empty(void) | ||||
| { | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	struct reftable_block_source source = { NULL }; | ||||
| 	struct reftable_reader rd = { NULL }; | ||||
| 	int err; | ||||
|  | ||||
| 	block_source_from_strbuf(&source, &buf); | ||||
| 	err = init_reader(&rd, &source, "file.log"); | ||||
| 	EXPECT(err == REFTABLE_FORMAT_ERROR); | ||||
| } | ||||
|  | ||||
| static void test_corrupt_table(void) | ||||
| { | ||||
| 	uint8_t zeros[1024] = { 0 }; | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	struct reftable_block_source source = { NULL }; | ||||
| 	struct reftable_reader rd = { NULL }; | ||||
| 	int err; | ||||
| 	strbuf_add(&buf, zeros, sizeof(zeros)); | ||||
|  | ||||
| 	block_source_from_strbuf(&source, &buf); | ||||
| 	err = init_reader(&rd, &source, "file.log"); | ||||
| 	EXPECT(err == REFTABLE_FORMAT_ERROR); | ||||
| 	strbuf_release(&buf); | ||||
| } | ||||
|  | ||||
| int readwrite_test_main(int argc, const char *argv[]) | ||||
| { | ||||
| 	RUN_TEST(test_corrupt_table); | ||||
| 	RUN_TEST(test_corrupt_table_empty); | ||||
| 	RUN_TEST(test_log_write_read); | ||||
| 	RUN_TEST(test_write_key_order); | ||||
| 	RUN_TEST(test_table_read_write_seek_linear_sha256); | ||||
| 	RUN_TEST(test_log_buffer_size); | ||||
| 	RUN_TEST(test_table_write_small_table); | ||||
| 	RUN_TEST(test_buffer); | ||||
| 	RUN_TEST(test_table_read_api); | ||||
| 	RUN_TEST(test_table_read_write_sequential); | ||||
| 	RUN_TEST(test_table_read_write_seek_linear); | ||||
| 	RUN_TEST(test_table_read_write_seek_index); | ||||
| 	RUN_TEST(test_table_refs_for_no_index); | ||||
| 	RUN_TEST(test_table_refs_for_obj_index); | ||||
| 	RUN_TEST(test_write_empty_table); | ||||
| 	return 0; | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,139 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef RECORD_H | ||||
| #define RECORD_H | ||||
|  | ||||
| #include "system.h" | ||||
|  | ||||
| #include <stdint.h> | ||||
|  | ||||
| #include "reftable-record.h" | ||||
|  | ||||
| /* | ||||
|  * A substring of existing string data. This structure takes no responsibility | ||||
|  * for the lifetime of the data it points to. | ||||
|  */ | ||||
| struct string_view { | ||||
| 	uint8_t *buf; | ||||
| 	size_t len; | ||||
| }; | ||||
|  | ||||
| /* Advance `s.buf` by `n`, and decrease length. */ | ||||
| void string_view_consume(struct string_view *s, int n); | ||||
|  | ||||
| /* utilities for de/encoding varints */ | ||||
|  | ||||
| int get_var_int(uint64_t *dest, struct string_view *in); | ||||
| int put_var_int(struct string_view *dest, uint64_t val); | ||||
|  | ||||
| /* Methods for records. */ | ||||
| struct reftable_record_vtable { | ||||
| 	/* encode the key of to a uint8_t strbuf. */ | ||||
| 	void (*key)(const void *rec, struct strbuf *dest); | ||||
|  | ||||
| 	/* The record type of ('r' for ref). */ | ||||
| 	uint8_t type; | ||||
|  | ||||
| 	void (*copy_from)(void *dest, const void *src, int hash_size); | ||||
|  | ||||
| 	/* a value of [0..7], indicating record subvariants (eg. ref vs. symref | ||||
| 	 * vs ref deletion) */ | ||||
| 	uint8_t (*val_type)(const void *rec); | ||||
|  | ||||
| 	/* encodes rec into dest, returning how much space was used. */ | ||||
| 	int (*encode)(const void *rec, struct string_view dest, int hash_size); | ||||
|  | ||||
| 	/* decode data from `src` into the record. */ | ||||
| 	int (*decode)(void *rec, struct strbuf key, uint8_t extra, | ||||
| 		      struct string_view src, int hash_size); | ||||
|  | ||||
| 	/* deallocate and null the record. */ | ||||
| 	void (*release)(void *rec); | ||||
|  | ||||
| 	/* is this a tombstone? */ | ||||
| 	int (*is_deletion)(const void *rec); | ||||
| }; | ||||
|  | ||||
| /* record is a generic wrapper for different types of records. */ | ||||
| struct reftable_record { | ||||
| 	void *data; | ||||
| 	struct reftable_record_vtable *ops; | ||||
| }; | ||||
|  | ||||
| /* returns true for recognized block types. Block start with the block type. */ | ||||
| int reftable_is_block_type(uint8_t typ); | ||||
|  | ||||
| /* creates a malloced record of the given type. Dispose with record_destroy */ | ||||
| struct reftable_record reftable_new_record(uint8_t typ); | ||||
|  | ||||
| /* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns | ||||
|  * number of bytes written. */ | ||||
| int reftable_encode_key(int *is_restart, struct string_view dest, | ||||
| 			struct strbuf prev_key, struct strbuf key, | ||||
| 			uint8_t extra); | ||||
|  | ||||
| /* Decode into `key` and `extra` from `in` */ | ||||
| int reftable_decode_key(struct strbuf *key, uint8_t *extra, | ||||
| 			struct strbuf last_key, struct string_view in); | ||||
|  | ||||
| /* reftable_index_record are used internally to speed up lookups. */ | ||||
| struct reftable_index_record { | ||||
| 	uint64_t offset; /* Offset of block */ | ||||
| 	struct strbuf last_key; /* Last key of the block. */ | ||||
| }; | ||||
|  | ||||
| /* reftable_obj_record stores an object ID => ref mapping. */ | ||||
| struct reftable_obj_record { | ||||
| 	uint8_t *hash_prefix; /* leading bytes of the object ID */ | ||||
| 	int hash_prefix_len; /* number of leading bytes. Constant | ||||
| 			      * across a single table. */ | ||||
| 	uint64_t *offsets; /* a vector of file offsets. */ | ||||
| 	int offset_len; | ||||
| }; | ||||
|  | ||||
| /* see struct record_vtable */ | ||||
|  | ||||
| void reftable_record_key(struct reftable_record *rec, struct strbuf *dest); | ||||
| uint8_t reftable_record_type(struct reftable_record *rec); | ||||
| void reftable_record_copy_from(struct reftable_record *rec, | ||||
| 			       struct reftable_record *src, int hash_size); | ||||
| uint8_t reftable_record_val_type(struct reftable_record *rec); | ||||
| int reftable_record_encode(struct reftable_record *rec, struct string_view dest, | ||||
| 			   int hash_size); | ||||
| int reftable_record_decode(struct reftable_record *rec, struct strbuf key, | ||||
| 			   uint8_t extra, struct string_view src, | ||||
| 			   int hash_size); | ||||
| int reftable_record_is_deletion(struct reftable_record *rec); | ||||
|  | ||||
| /* zeroes out the embedded record */ | ||||
| void reftable_record_release(struct reftable_record *rec); | ||||
|  | ||||
| /* clear and deallocate embedded record, and zero `rec`. */ | ||||
| void reftable_record_destroy(struct reftable_record *rec); | ||||
|  | ||||
| /* initialize generic records from concrete records. The generic record should | ||||
|  * be zeroed out. */ | ||||
| void reftable_record_from_obj(struct reftable_record *rec, | ||||
| 			      struct reftable_obj_record *objrec); | ||||
| void reftable_record_from_index(struct reftable_record *rec, | ||||
| 				struct reftable_index_record *idxrec); | ||||
| void reftable_record_from_ref(struct reftable_record *rec, | ||||
| 			      struct reftable_ref_record *refrec); | ||||
| void reftable_record_from_log(struct reftable_record *rec, | ||||
| 			      struct reftable_log_record *logrec); | ||||
| struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref); | ||||
| struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref); | ||||
|  | ||||
| /* for qsort. */ | ||||
| int reftable_ref_record_compare_name(const void *a, const void *b); | ||||
|  | ||||
| /* for qsort. */ | ||||
| int reftable_log_record_compare_key(const void *a, const void *b); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,412 @@ | |||
| /* | ||||
|   Copyright 2020 Google LLC | ||||
|  | ||||
|   Use of this source code is governed by a BSD-style | ||||
|   license that can be found in the LICENSE file or at | ||||
|   https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "record.h" | ||||
|  | ||||
| #include "system.h" | ||||
| #include "basics.h" | ||||
| #include "constants.h" | ||||
| #include "test_framework.h" | ||||
| #include "reftable-tests.h" | ||||
|  | ||||
| static void test_copy(struct reftable_record *rec) | ||||
| { | ||||
| 	struct reftable_record copy = | ||||
| 		reftable_new_record(reftable_record_type(rec)); | ||||
| 	reftable_record_copy_from(©, rec, GIT_SHA1_RAWSZ); | ||||
| 	/* do it twice to catch memory leaks */ | ||||
| 	reftable_record_copy_from(©, rec, GIT_SHA1_RAWSZ); | ||||
| 	switch (reftable_record_type(©)) { | ||||
| 	case BLOCK_TYPE_REF: | ||||
| 		EXPECT(reftable_ref_record_equal(reftable_record_as_ref(©), | ||||
| 						 reftable_record_as_ref(rec), | ||||
| 						 GIT_SHA1_RAWSZ)); | ||||
| 		break; | ||||
| 	case BLOCK_TYPE_LOG: | ||||
| 		EXPECT(reftable_log_record_equal(reftable_record_as_log(©), | ||||
| 						 reftable_record_as_log(rec), | ||||
| 						 GIT_SHA1_RAWSZ)); | ||||
| 		break; | ||||
| 	} | ||||
| 	reftable_record_destroy(©); | ||||
| } | ||||
|  | ||||
| static void test_varint_roundtrip(void) | ||||
| { | ||||
| 	uint64_t inputs[] = { 0, | ||||
| 			      1, | ||||
| 			      27, | ||||
| 			      127, | ||||
| 			      128, | ||||
| 			      257, | ||||
| 			      4096, | ||||
| 			      ((uint64_t)1 << 63), | ||||
| 			      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 }; | ||||
| 	int i = 0; | ||||
| 	for (i = 0; i < ARRAY_SIZE(inputs); i++) { | ||||
| 		uint8_t dest[10]; | ||||
|  | ||||
| 		struct string_view out = { | ||||
| 			.buf = dest, | ||||
| 			.len = sizeof(dest), | ||||
| 		}; | ||||
| 		uint64_t in = inputs[i]; | ||||
| 		int n = put_var_int(&out, in); | ||||
| 		uint64_t got = 0; | ||||
|  | ||||
| 		EXPECT(n > 0); | ||||
| 		out.len = n; | ||||
| 		n = get_var_int(&got, &out); | ||||
| 		EXPECT(n > 0); | ||||
|  | ||||
| 		EXPECT(got == in); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void test_common_prefix(void) | ||||
| { | ||||
| 	struct { | ||||
| 		const char *a, *b; | ||||
| 		int want; | ||||
| 	} cases[] = { | ||||
| 		{ "abc", "ab", 2 }, | ||||
| 		{ "", "abc", 0 }, | ||||
| 		{ "abc", "abd", 2 }, | ||||
| 		{ "abc", "pqr", 0 }, | ||||
| 	}; | ||||
|  | ||||
| 	int i = 0; | ||||
| 	for (i = 0; i < ARRAY_SIZE(cases); i++) { | ||||
| 		struct strbuf a = STRBUF_INIT; | ||||
| 		struct strbuf b = STRBUF_INIT; | ||||
| 		strbuf_addstr(&a, cases[i].a); | ||||
| 		strbuf_addstr(&b, cases[i].b); | ||||
| 		EXPECT(common_prefix_size(&a, &b) == cases[i].want); | ||||
|  | ||||
| 		strbuf_release(&a); | ||||
| 		strbuf_release(&b); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void set_hash(uint8_t *h, int j) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	for (i = 0; i < hash_size(GIT_SHA1_FORMAT_ID); i++) { | ||||
| 		h[i] = (j >> i) & 0xff; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void test_reftable_ref_record_roundtrip(void) | ||||
| { | ||||
| 	int i = 0; | ||||
|  | ||||
| 	for (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) { | ||||
| 		struct reftable_ref_record in = { NULL }; | ||||
| 		struct reftable_ref_record out = { NULL }; | ||||
| 		struct reftable_record rec_out = { NULL }; | ||||
| 		struct strbuf key = STRBUF_INIT; | ||||
| 		struct reftable_record rec = { NULL }; | ||||
| 		uint8_t buffer[1024] = { 0 }; | ||||
| 		struct string_view dest = { | ||||
| 			.buf = buffer, | ||||
| 			.len = sizeof(buffer), | ||||
| 		}; | ||||
|  | ||||
| 		int n, m; | ||||
|  | ||||
| 		in.value_type = i; | ||||
| 		switch (i) { | ||||
| 		case REFTABLE_REF_DELETION: | ||||
| 			break; | ||||
| 		case REFTABLE_REF_VAL1: | ||||
| 			in.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ); | ||||
| 			set_hash(in.value.val1, 1); | ||||
| 			break; | ||||
| 		case REFTABLE_REF_VAL2: | ||||
| 			in.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ); | ||||
| 			set_hash(in.value.val2.value, 1); | ||||
| 			in.value.val2.target_value = | ||||
| 				reftable_malloc(GIT_SHA1_RAWSZ); | ||||
| 			set_hash(in.value.val2.target_value, 2); | ||||
| 			break; | ||||
| 		case REFTABLE_REF_SYMREF: | ||||
| 			in.value.symref = xstrdup("target"); | ||||
| 			break; | ||||
| 		} | ||||
| 		in.refname = xstrdup("refs/heads/master"); | ||||
|  | ||||
| 		reftable_record_from_ref(&rec, &in); | ||||
| 		test_copy(&rec); | ||||
|  | ||||
| 		EXPECT(reftable_record_val_type(&rec) == i); | ||||
|  | ||||
| 		reftable_record_key(&rec, &key); | ||||
| 		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ); | ||||
| 		EXPECT(n > 0); | ||||
|  | ||||
| 		/* decode into a non-zero reftable_record to test for leaks. */ | ||||
|  | ||||
| 		reftable_record_from_ref(&rec_out, &out); | ||||
| 		m = reftable_record_decode(&rec_out, key, i, dest, | ||||
| 					   GIT_SHA1_RAWSZ); | ||||
| 		EXPECT(n == m); | ||||
|  | ||||
| 		EXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ)); | ||||
| 		reftable_record_release(&rec_out); | ||||
|  | ||||
| 		strbuf_release(&key); | ||||
| 		reftable_ref_record_release(&in); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void test_reftable_log_record_equal(void) | ||||
| { | ||||
| 	struct reftable_log_record in[2] = { | ||||
| 		{ | ||||
| 			.refname = xstrdup("refs/heads/master"), | ||||
| 			.update_index = 42, | ||||
| 		}, | ||||
| 		{ | ||||
| 			.refname = xstrdup("refs/heads/master"), | ||||
| 			.update_index = 22, | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	EXPECT(!reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ)); | ||||
| 	in[1].update_index = in[0].update_index; | ||||
| 	EXPECT(reftable_log_record_equal(&in[0], &in[1], GIT_SHA1_RAWSZ)); | ||||
| 	reftable_log_record_release(&in[0]); | ||||
| 	reftable_log_record_release(&in[1]); | ||||
| } | ||||
|  | ||||
| static void test_reftable_log_record_roundtrip(void) | ||||
| { | ||||
| 	int i; | ||||
| 	struct reftable_log_record in[2] = { | ||||
| 		{ | ||||
| 			.refname = xstrdup("refs/heads/master"), | ||||
| 			.update_index = 42, | ||||
| 			.value_type = REFTABLE_LOG_UPDATE, | ||||
| 			.value = { | ||||
| 				.update = { | ||||
| 					.old_hash = reftable_malloc(GIT_SHA1_RAWSZ), | ||||
| 					.new_hash = reftable_malloc(GIT_SHA1_RAWSZ), | ||||
| 					.name = xstrdup("han-wen"), | ||||
| 					.email = xstrdup("hanwen@google.com"), | ||||
| 					.message = xstrdup("test"), | ||||
| 					.time = 1577123507, | ||||
| 					.tz_offset = 100, | ||||
| 				}, | ||||
| 			} | ||||
| 		}, | ||||
| 		{ | ||||
| 			.refname = xstrdup("refs/heads/master"), | ||||
| 			.update_index = 22, | ||||
| 			.value_type = REFTABLE_LOG_DELETION, | ||||
| 		} | ||||
| 	}; | ||||
| 	set_test_hash(in[0].value.update.new_hash, 1); | ||||
| 	set_test_hash(in[0].value.update.old_hash, 2); | ||||
| 	for (i = 0; i < ARRAY_SIZE(in); i++) { | ||||
| 		struct reftable_record rec = { NULL }; | ||||
| 		struct strbuf key = STRBUF_INIT; | ||||
| 		uint8_t buffer[1024] = { 0 }; | ||||
| 		struct string_view dest = { | ||||
| 			.buf = buffer, | ||||
| 			.len = sizeof(buffer), | ||||
| 		}; | ||||
| 		/* populate out, to check for leaks. */ | ||||
| 		struct reftable_log_record out = { | ||||
| 			.refname = xstrdup("old name"), | ||||
| 			.value_type = REFTABLE_LOG_UPDATE, | ||||
| 			.value = { | ||||
| 				.update = { | ||||
| 					.new_hash = reftable_calloc(GIT_SHA1_RAWSZ), | ||||
| 					.old_hash = reftable_calloc(GIT_SHA1_RAWSZ), | ||||
| 					.name = xstrdup("old name"), | ||||
| 					.email = xstrdup("old@email"), | ||||
| 					.message = xstrdup("old message"), | ||||
| 				}, | ||||
| 			}, | ||||
| 		}; | ||||
| 		struct reftable_record rec_out = { NULL }; | ||||
| 		int n, m, valtype; | ||||
|  | ||||
| 		reftable_record_from_log(&rec, &in[i]); | ||||
|  | ||||
| 		test_copy(&rec); | ||||
|  | ||||
| 		reftable_record_key(&rec, &key); | ||||
|  | ||||
| 		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ); | ||||
| 		EXPECT(n >= 0); | ||||
| 		reftable_record_from_log(&rec_out, &out); | ||||
| 		valtype = reftable_record_val_type(&rec); | ||||
| 		m = reftable_record_decode(&rec_out, key, valtype, dest, | ||||
| 					   GIT_SHA1_RAWSZ); | ||||
| 		EXPECT(n == m); | ||||
|  | ||||
| 		EXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ)); | ||||
| 		reftable_log_record_release(&in[i]); | ||||
| 		strbuf_release(&key); | ||||
| 		reftable_record_release(&rec_out); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void test_u24_roundtrip(void) | ||||
| { | ||||
| 	uint32_t in = 0x112233; | ||||
| 	uint8_t dest[3]; | ||||
| 	uint32_t out; | ||||
| 	put_be24(dest, in); | ||||
| 	out = get_be24(dest); | ||||
| 	EXPECT(in == out); | ||||
| } | ||||
|  | ||||
| static void test_key_roundtrip(void) | ||||
| { | ||||
| 	uint8_t buffer[1024] = { 0 }; | ||||
| 	struct string_view dest = { | ||||
| 		.buf = buffer, | ||||
| 		.len = sizeof(buffer), | ||||
| 	}; | ||||
| 	struct strbuf last_key = STRBUF_INIT; | ||||
| 	struct strbuf key = STRBUF_INIT; | ||||
| 	struct strbuf roundtrip = STRBUF_INIT; | ||||
| 	int restart; | ||||
| 	uint8_t extra; | ||||
| 	int n, m; | ||||
| 	uint8_t rt_extra; | ||||
|  | ||||
| 	strbuf_addstr(&last_key, "refs/heads/master"); | ||||
| 	strbuf_addstr(&key, "refs/tags/bla"); | ||||
| 	extra = 6; | ||||
| 	n = reftable_encode_key(&restart, dest, last_key, key, extra); | ||||
| 	EXPECT(!restart); | ||||
| 	EXPECT(n > 0); | ||||
|  | ||||
| 	m = reftable_decode_key(&roundtrip, &rt_extra, last_key, dest); | ||||
| 	EXPECT(n == m); | ||||
| 	EXPECT(0 == strbuf_cmp(&key, &roundtrip)); | ||||
| 	EXPECT(rt_extra == extra); | ||||
|  | ||||
| 	strbuf_release(&last_key); | ||||
| 	strbuf_release(&key); | ||||
| 	strbuf_release(&roundtrip); | ||||
| } | ||||
|  | ||||
| static void test_reftable_obj_record_roundtrip(void) | ||||
| { | ||||
| 	uint8_t testHash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 4, 0 }; | ||||
| 	uint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 }; | ||||
| 	struct reftable_obj_record recs[3] = { { | ||||
| 						       .hash_prefix = testHash1, | ||||
| 						       .hash_prefix_len = 5, | ||||
| 						       .offsets = till9, | ||||
| 						       .offset_len = 3, | ||||
| 					       }, | ||||
| 					       { | ||||
| 						       .hash_prefix = testHash1, | ||||
| 						       .hash_prefix_len = 5, | ||||
| 						       .offsets = till9, | ||||
| 						       .offset_len = 9, | ||||
| 					       }, | ||||
| 					       { | ||||
| 						       .hash_prefix = testHash1, | ||||
| 						       .hash_prefix_len = 5, | ||||
| 					       } }; | ||||
| 	int i = 0; | ||||
| 	for (i = 0; i < ARRAY_SIZE(recs); i++) { | ||||
| 		struct reftable_obj_record in = recs[i]; | ||||
| 		uint8_t buffer[1024] = { 0 }; | ||||
| 		struct string_view dest = { | ||||
| 			.buf = buffer, | ||||
| 			.len = sizeof(buffer), | ||||
| 		}; | ||||
| 		struct reftable_record rec = { NULL }; | ||||
| 		struct strbuf key = STRBUF_INIT; | ||||
| 		struct reftable_obj_record out = { NULL }; | ||||
| 		struct reftable_record rec_out = { NULL }; | ||||
| 		int n, m; | ||||
| 		uint8_t extra; | ||||
|  | ||||
| 		reftable_record_from_obj(&rec, &in); | ||||
| 		test_copy(&rec); | ||||
| 		reftable_record_key(&rec, &key); | ||||
| 		n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ); | ||||
| 		EXPECT(n > 0); | ||||
| 		extra = reftable_record_val_type(&rec); | ||||
| 		reftable_record_from_obj(&rec_out, &out); | ||||
| 		m = reftable_record_decode(&rec_out, key, extra, dest, | ||||
| 					   GIT_SHA1_RAWSZ); | ||||
| 		EXPECT(n == m); | ||||
|  | ||||
| 		EXPECT(in.hash_prefix_len == out.hash_prefix_len); | ||||
| 		EXPECT(in.offset_len == out.offset_len); | ||||
|  | ||||
| 		EXPECT(!memcmp(in.hash_prefix, out.hash_prefix, | ||||
| 			       in.hash_prefix_len)); | ||||
| 		EXPECT(0 == memcmp(in.offsets, out.offsets, | ||||
| 				   sizeof(uint64_t) * in.offset_len)); | ||||
| 		strbuf_release(&key); | ||||
| 		reftable_record_release(&rec_out); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void test_reftable_index_record_roundtrip(void) | ||||
| { | ||||
| 	struct reftable_index_record in = { | ||||
| 		.offset = 42, | ||||
| 		.last_key = STRBUF_INIT, | ||||
| 	}; | ||||
| 	uint8_t buffer[1024] = { 0 }; | ||||
| 	struct string_view dest = { | ||||
| 		.buf = buffer, | ||||
| 		.len = sizeof(buffer), | ||||
| 	}; | ||||
| 	struct strbuf key = STRBUF_INIT; | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	struct reftable_index_record out = { .last_key = STRBUF_INIT }; | ||||
| 	struct reftable_record out_rec = { NULL }; | ||||
| 	int n, m; | ||||
| 	uint8_t extra; | ||||
|  | ||||
| 	strbuf_addstr(&in.last_key, "refs/heads/master"); | ||||
| 	reftable_record_from_index(&rec, &in); | ||||
| 	reftable_record_key(&rec, &key); | ||||
| 	test_copy(&rec); | ||||
|  | ||||
| 	EXPECT(0 == strbuf_cmp(&key, &in.last_key)); | ||||
| 	n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ); | ||||
| 	EXPECT(n > 0); | ||||
|  | ||||
| 	extra = reftable_record_val_type(&rec); | ||||
| 	reftable_record_from_index(&out_rec, &out); | ||||
| 	m = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ); | ||||
| 	EXPECT(m == n); | ||||
|  | ||||
| 	EXPECT(in.offset == out.offset); | ||||
|  | ||||
| 	reftable_record_release(&out_rec); | ||||
| 	strbuf_release(&key); | ||||
| 	strbuf_release(&in.last_key); | ||||
| } | ||||
|  | ||||
| int record_test_main(int argc, const char *argv[]) | ||||
| { | ||||
| 	RUN_TEST(test_reftable_log_record_equal); | ||||
| 	RUN_TEST(test_reftable_log_record_roundtrip); | ||||
| 	RUN_TEST(test_reftable_ref_record_roundtrip); | ||||
| 	RUN_TEST(test_varint_roundtrip); | ||||
| 	RUN_TEST(test_key_roundtrip); | ||||
| 	RUN_TEST(test_common_prefix); | ||||
| 	RUN_TEST(test_reftable_obj_record_roundtrip); | ||||
| 	RUN_TEST(test_reftable_index_record_roundtrip); | ||||
| 	RUN_TEST(test_u24_roundtrip); | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -0,0 +1,209 @@ | |||
| /* | ||||
|   Copyright 2020 Google LLC | ||||
|  | ||||
|   Use of this source code is governed by a BSD-style | ||||
|   license that can be found in the LICENSE file or at | ||||
|   https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "system.h" | ||||
| #include "reftable-error.h" | ||||
| #include "basics.h" | ||||
| #include "refname.h" | ||||
| #include "reftable-iterator.h" | ||||
|  | ||||
| struct find_arg { | ||||
| 	char **names; | ||||
| 	const char *want; | ||||
| }; | ||||
|  | ||||
| static int find_name(size_t k, void *arg) | ||||
| { | ||||
| 	struct find_arg *f_arg = arg; | ||||
| 	return strcmp(f_arg->names[k], f_arg->want) >= 0; | ||||
| } | ||||
|  | ||||
| static int modification_has_ref(struct modification *mod, const char *name) | ||||
| { | ||||
| 	struct reftable_ref_record ref = { NULL }; | ||||
| 	int err = 0; | ||||
|  | ||||
| 	if (mod->add_len > 0) { | ||||
| 		struct find_arg arg = { | ||||
| 			.names = mod->add, | ||||
| 			.want = name, | ||||
| 		}; | ||||
| 		int idx = binsearch(mod->add_len, find_name, &arg); | ||||
| 		if (idx < mod->add_len && !strcmp(mod->add[idx], name)) { | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (mod->del_len > 0) { | ||||
| 		struct find_arg arg = { | ||||
| 			.names = mod->del, | ||||
| 			.want = name, | ||||
| 		}; | ||||
| 		int idx = binsearch(mod->del_len, find_name, &arg); | ||||
| 		if (idx < mod->del_len && !strcmp(mod->del[idx], name)) { | ||||
| 			return 1; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_table_read_ref(&mod->tab, name, &ref); | ||||
| 	reftable_ref_record_release(&ref); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static void modification_release(struct modification *mod) | ||||
| { | ||||
| 	/* don't delete the strings themselves; they're owned by ref records. | ||||
| 	 */ | ||||
| 	FREE_AND_NULL(mod->add); | ||||
| 	FREE_AND_NULL(mod->del); | ||||
| 	mod->add_len = 0; | ||||
| 	mod->del_len = 0; | ||||
| } | ||||
|  | ||||
| static int modification_has_ref_with_prefix(struct modification *mod, | ||||
| 					    const char *prefix) | ||||
| { | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	struct reftable_ref_record ref = { NULL }; | ||||
| 	int err = 0; | ||||
|  | ||||
| 	if (mod->add_len > 0) { | ||||
| 		struct find_arg arg = { | ||||
| 			.names = mod->add, | ||||
| 			.want = prefix, | ||||
| 		}; | ||||
| 		int idx = binsearch(mod->add_len, find_name, &arg); | ||||
| 		if (idx < mod->add_len && | ||||
| 		    !strncmp(prefix, mod->add[idx], strlen(prefix))) | ||||
| 			goto done; | ||||
| 	} | ||||
| 	err = reftable_table_seek_ref(&mod->tab, &it, prefix); | ||||
| 	if (err) | ||||
| 		goto done; | ||||
|  | ||||
| 	while (1) { | ||||
| 		err = reftable_iterator_next_ref(&it, &ref); | ||||
| 		if (err) | ||||
| 			goto done; | ||||
|  | ||||
| 		if (mod->del_len > 0) { | ||||
| 			struct find_arg arg = { | ||||
| 				.names = mod->del, | ||||
| 				.want = ref.refname, | ||||
| 			}; | ||||
| 			int idx = binsearch(mod->del_len, find_name, &arg); | ||||
| 			if (idx < mod->del_len && | ||||
| 			    !strcmp(ref.refname, mod->del[idx])) { | ||||
| 				continue; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (strncmp(ref.refname, prefix, strlen(prefix))) { | ||||
| 			err = 1; | ||||
| 			goto done; | ||||
| 		} | ||||
| 		err = 0; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| done: | ||||
| 	reftable_ref_record_release(&ref); | ||||
| 	reftable_iterator_destroy(&it); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static int validate_refname(const char *name) | ||||
| { | ||||
| 	while (1) { | ||||
| 		char *next = strchr(name, '/'); | ||||
| 		if (!*name) { | ||||
| 			return REFTABLE_REFNAME_ERROR; | ||||
| 		} | ||||
| 		if (!next) { | ||||
| 			return 0; | ||||
| 		} | ||||
| 		if (next - name == 0 || (next - name == 1 && *name == '.') || | ||||
| 		    (next - name == 2 && name[0] == '.' && name[1] == '.')) | ||||
| 			return REFTABLE_REFNAME_ERROR; | ||||
| 		name = next + 1; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int validate_ref_record_addition(struct reftable_table tab, | ||||
| 				 struct reftable_ref_record *recs, size_t sz) | ||||
| { | ||||
| 	struct modification mod = { | ||||
| 		.tab = tab, | ||||
| 		.add = reftable_calloc(sizeof(char *) * sz), | ||||
| 		.del = reftable_calloc(sizeof(char *) * sz), | ||||
| 	}; | ||||
| 	int i = 0; | ||||
| 	int err = 0; | ||||
| 	for (; i < sz; i++) { | ||||
| 		if (reftable_ref_record_is_deletion(&recs[i])) { | ||||
| 			mod.del[mod.del_len++] = recs[i].refname; | ||||
| 		} else { | ||||
| 			mod.add[mod.add_len++] = recs[i].refname; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = modification_validate(&mod); | ||||
| 	modification_release(&mod); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static void strbuf_trim_component(struct strbuf *sl) | ||||
| { | ||||
| 	while (sl->len > 0) { | ||||
| 		int is_slash = (sl->buf[sl->len - 1] == '/'); | ||||
| 		strbuf_setlen(sl, sl->len - 1); | ||||
| 		if (is_slash) | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int modification_validate(struct modification *mod) | ||||
| { | ||||
| 	struct strbuf slashed = STRBUF_INIT; | ||||
| 	int err = 0; | ||||
| 	int i = 0; | ||||
| 	for (; i < mod->add_len; i++) { | ||||
| 		err = validate_refname(mod->add[i]); | ||||
| 		if (err) | ||||
| 			goto done; | ||||
| 		strbuf_reset(&slashed); | ||||
| 		strbuf_addstr(&slashed, mod->add[i]); | ||||
| 		strbuf_addstr(&slashed, "/"); | ||||
|  | ||||
| 		err = modification_has_ref_with_prefix(mod, slashed.buf); | ||||
| 		if (err == 0) { | ||||
| 			err = REFTABLE_NAME_CONFLICT; | ||||
| 			goto done; | ||||
| 		} | ||||
| 		if (err < 0) | ||||
| 			goto done; | ||||
|  | ||||
| 		strbuf_reset(&slashed); | ||||
| 		strbuf_addstr(&slashed, mod->add[i]); | ||||
| 		while (slashed.len) { | ||||
| 			strbuf_trim_component(&slashed); | ||||
| 			err = modification_has_ref(mod, slashed.buf); | ||||
| 			if (err == 0) { | ||||
| 				err = REFTABLE_NAME_CONFLICT; | ||||
| 				goto done; | ||||
| 			} | ||||
| 			if (err < 0) | ||||
| 				goto done; | ||||
| 		} | ||||
| 	} | ||||
| 	err = 0; | ||||
| done: | ||||
| 	strbuf_release(&slashed); | ||||
| 	return err; | ||||
| } | ||||
|  | @ -0,0 +1,29 @@ | |||
| /* | ||||
|   Copyright 2020 Google LLC | ||||
|  | ||||
|   Use of this source code is governed by a BSD-style | ||||
|   license that can be found in the LICENSE file or at | ||||
|   https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
| #ifndef REFNAME_H | ||||
| #define REFNAME_H | ||||
|  | ||||
| #include "reftable-record.h" | ||||
| #include "reftable-generic.h" | ||||
|  | ||||
| struct modification { | ||||
| 	struct reftable_table tab; | ||||
|  | ||||
| 	char **add; | ||||
| 	size_t add_len; | ||||
|  | ||||
| 	char **del; | ||||
| 	size_t del_len; | ||||
| }; | ||||
|  | ||||
| int validate_ref_record_addition(struct reftable_table tab, | ||||
| 				 struct reftable_ref_record *recs, size_t sz); | ||||
|  | ||||
| int modification_validate(struct modification *mod); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,102 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "block.h" | ||||
| #include "blocksource.h" | ||||
| #include "constants.h" | ||||
| #include "reader.h" | ||||
| #include "record.h" | ||||
| #include "refname.h" | ||||
| #include "reftable-error.h" | ||||
| #include "reftable-writer.h" | ||||
| #include "system.h" | ||||
|  | ||||
| #include "test_framework.h" | ||||
| #include "reftable-tests.h" | ||||
|  | ||||
| struct testcase { | ||||
| 	char *add; | ||||
| 	char *del; | ||||
| 	int error_code; | ||||
| }; | ||||
|  | ||||
| static void test_conflict(void) | ||||
| { | ||||
| 	struct reftable_write_options opts = { 0 }; | ||||
| 	struct strbuf buf = STRBUF_INIT; | ||||
| 	struct reftable_writer *w = | ||||
| 		reftable_new_writer(&strbuf_add_void, &buf, &opts); | ||||
| 	struct reftable_ref_record rec = { | ||||
| 		.refname = "a/b", | ||||
| 		.value_type = REFTABLE_REF_SYMREF, | ||||
| 		.value.symref = "destination", /* make sure it's not a symref. | ||||
| 						*/ | ||||
| 		.update_index = 1, | ||||
| 	}; | ||||
| 	int err; | ||||
| 	int i; | ||||
| 	struct reftable_block_source source = { NULL }; | ||||
| 	struct reftable_reader *rd = NULL; | ||||
| 	struct reftable_table tab = { NULL }; | ||||
| 	struct testcase cases[] = { | ||||
| 		{ "a/b/c", NULL, REFTABLE_NAME_CONFLICT }, | ||||
| 		{ "b", NULL, 0 }, | ||||
| 		{ "a", NULL, REFTABLE_NAME_CONFLICT }, | ||||
| 		{ "a", "a/b", 0 }, | ||||
|  | ||||
| 		{ "p/", NULL, REFTABLE_REFNAME_ERROR }, | ||||
| 		{ "p//q", NULL, REFTABLE_REFNAME_ERROR }, | ||||
| 		{ "p/./q", NULL, REFTABLE_REFNAME_ERROR }, | ||||
| 		{ "p/../q", NULL, REFTABLE_REFNAME_ERROR }, | ||||
|  | ||||
| 		{ "a/b/c", "a/b", 0 }, | ||||
| 		{ NULL, "a//b", 0 }, | ||||
| 	}; | ||||
| 	reftable_writer_set_limits(w, 1, 1); | ||||
|  | ||||
| 	err = reftable_writer_add_ref(w, &rec); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_writer_close(w); | ||||
| 	EXPECT_ERR(err); | ||||
| 	reftable_writer_free(w); | ||||
|  | ||||
| 	block_source_from_strbuf(&source, &buf); | ||||
| 	err = reftable_new_reader(&rd, &source, "filename"); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	reftable_table_from_reader(&tab, rd); | ||||
|  | ||||
| 	for (i = 0; i < ARRAY_SIZE(cases); i++) { | ||||
| 		struct modification mod = { | ||||
| 			.tab = tab, | ||||
| 		}; | ||||
|  | ||||
| 		if (cases[i].add) { | ||||
| 			mod.add = &cases[i].add; | ||||
| 			mod.add_len = 1; | ||||
| 		} | ||||
| 		if (cases[i].del) { | ||||
| 			mod.del = &cases[i].del; | ||||
| 			mod.del_len = 1; | ||||
| 		} | ||||
|  | ||||
| 		err = modification_validate(&mod); | ||||
| 		EXPECT(err == cases[i].error_code); | ||||
| 	} | ||||
|  | ||||
| 	reftable_reader_free(rd); | ||||
| 	strbuf_release(&buf); | ||||
| } | ||||
|  | ||||
| int refname_test_main(int argc, const char *argv[]) | ||||
| { | ||||
| 	RUN_TEST(test_conflict); | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -0,0 +1,49 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef REFTABLE_BLOCKSOURCE_H | ||||
| #define REFTABLE_BLOCKSOURCE_H | ||||
|  | ||||
| #include <stdint.h> | ||||
|  | ||||
| /* block_source is a generic wrapper for a seekable readable file. | ||||
|  */ | ||||
| struct reftable_block_source { | ||||
| 	struct reftable_block_source_vtable *ops; | ||||
| 	void *arg; | ||||
| }; | ||||
|  | ||||
| /* a contiguous segment of bytes. It keeps track of its generating block_source | ||||
|  * so it can return itself into the pool. */ | ||||
| struct reftable_block { | ||||
| 	uint8_t *data; | ||||
| 	int len; | ||||
| 	struct reftable_block_source source; | ||||
| }; | ||||
|  | ||||
| /* block_source_vtable are the operations that make up block_source */ | ||||
| struct reftable_block_source_vtable { | ||||
| 	/* returns the size of a block source */ | ||||
| 	uint64_t (*size)(void *source); | ||||
|  | ||||
| 	/* reads a segment from the block source. It is an error to read | ||||
| 	   beyond the end of the block */ | ||||
| 	int (*read_block)(void *source, struct reftable_block *dest, | ||||
| 			  uint64_t off, uint32_t size); | ||||
| 	/* mark the block as read; may return the data back to malloc */ | ||||
| 	void (*return_block)(void *source, struct reftable_block *blockp); | ||||
|  | ||||
| 	/* release all resources associated with the block source */ | ||||
| 	void (*close)(void *source); | ||||
| }; | ||||
|  | ||||
| /* opens a file on the file system as a block_source */ | ||||
| int reftable_block_source_from_file(struct reftable_block_source *block_src, | ||||
| 				    const char *name); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,62 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef REFTABLE_ERROR_H | ||||
| #define REFTABLE_ERROR_H | ||||
|  | ||||
| /* | ||||
|  * Errors in reftable calls are signaled with negative integer return values. 0 | ||||
|  * means success. | ||||
|  */ | ||||
| enum reftable_error { | ||||
| 	/* Unexpected file system behavior */ | ||||
| 	REFTABLE_IO_ERROR = -2, | ||||
|  | ||||
| 	/* Format inconsistency on reading data */ | ||||
| 	REFTABLE_FORMAT_ERROR = -3, | ||||
|  | ||||
| 	/* File does not exist. Returned from block_source_from_file(), because | ||||
| 	 * it needs special handling in stack. | ||||
| 	 */ | ||||
| 	REFTABLE_NOT_EXIST_ERROR = -4, | ||||
|  | ||||
| 	/* Trying to write out-of-date data. */ | ||||
| 	REFTABLE_LOCK_ERROR = -5, | ||||
|  | ||||
| 	/* Misuse of the API: | ||||
| 	 *  - on writing a record with NULL refname. | ||||
| 	 *  - on writing a reftable_ref_record outside the table limits | ||||
| 	 *  - on writing a ref or log record before the stack's | ||||
| 	 * next_update_inde*x | ||||
| 	 *  - on writing a log record with multiline message with | ||||
| 	 *  exact_log_message unset | ||||
| 	 *  - on reading a reftable_ref_record from log iterator, or vice versa. | ||||
| 	 * | ||||
| 	 * When a call misuses the API, the internal state of the library is | ||||
| 	 * kept unchanged. | ||||
| 	 */ | ||||
| 	REFTABLE_API_ERROR = -6, | ||||
|  | ||||
| 	/* Decompression error */ | ||||
| 	REFTABLE_ZLIB_ERROR = -7, | ||||
|  | ||||
| 	/* Wrote a table without blocks. */ | ||||
| 	REFTABLE_EMPTY_TABLE_ERROR = -8, | ||||
|  | ||||
| 	/* Dir/file conflict. */ | ||||
| 	REFTABLE_NAME_CONFLICT = -9, | ||||
|  | ||||
| 	/* Invalid ref name. */ | ||||
| 	REFTABLE_REFNAME_ERROR = -10, | ||||
| }; | ||||
|  | ||||
| /* convert the numeric error code to a string. The string should not be | ||||
|  * deallocated. */ | ||||
| const char *reftable_error_str(int err); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,47 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef REFTABLE_GENERIC_H | ||||
| #define REFTABLE_GENERIC_H | ||||
|  | ||||
| #include "reftable-iterator.h" | ||||
|  | ||||
| struct reftable_table_vtable; | ||||
|  | ||||
| /* | ||||
|  * Provides a unified API for reading tables, either merged tables, or single | ||||
|  * readers. */ | ||||
| struct reftable_table { | ||||
| 	struct reftable_table_vtable *ops; | ||||
| 	void *table_arg; | ||||
| }; | ||||
|  | ||||
| int reftable_table_seek_log(struct reftable_table *tab, | ||||
| 			    struct reftable_iterator *it, const char *name); | ||||
|  | ||||
| int reftable_table_seek_ref(struct reftable_table *tab, | ||||
| 			    struct reftable_iterator *it, const char *name); | ||||
|  | ||||
| /* returns the hash ID from a generic reftable_table */ | ||||
| uint32_t reftable_table_hash_id(struct reftable_table *tab); | ||||
|  | ||||
| /* returns the max update_index covered by this table. */ | ||||
| uint64_t reftable_table_max_update_index(struct reftable_table *tab); | ||||
|  | ||||
| /* returns the min update_index covered by this table. */ | ||||
| uint64_t reftable_table_min_update_index(struct reftable_table *tab); | ||||
|  | ||||
| /* convenience function to read a single ref. Returns < 0 for error, 0 | ||||
|    for success, and 1 if ref not found. */ | ||||
| int reftable_table_read_ref(struct reftable_table *tab, const char *name, | ||||
| 			    struct reftable_ref_record *ref); | ||||
|  | ||||
| /* dump table contents onto stdout for debugging */ | ||||
| int reftable_table_print(struct reftable_table *tab); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,39 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef REFTABLE_ITERATOR_H | ||||
| #define REFTABLE_ITERATOR_H | ||||
|  | ||||
| #include "reftable-record.h" | ||||
|  | ||||
| struct reftable_iterator_vtable; | ||||
|  | ||||
| /* iterator is the generic interface for walking over data stored in a | ||||
|  * reftable. | ||||
|  */ | ||||
| struct reftable_iterator { | ||||
| 	struct reftable_iterator_vtable *ops; | ||||
| 	void *iter_arg; | ||||
| }; | ||||
|  | ||||
| /* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0: | ||||
|  * end of iteration. | ||||
|  */ | ||||
| int reftable_iterator_next_ref(struct reftable_iterator *it, | ||||
| 			       struct reftable_ref_record *ref); | ||||
|  | ||||
| /* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0: | ||||
|  * end of iteration. | ||||
|  */ | ||||
| int reftable_iterator_next_log(struct reftable_iterator *it, | ||||
| 			       struct reftable_log_record *log); | ||||
|  | ||||
| /* releases resources associated with an iterator. */ | ||||
| void reftable_iterator_destroy(struct reftable_iterator *it); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,18 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef REFTABLE_H | ||||
| #define REFTABLE_H | ||||
|  | ||||
| #include <stddef.h> | ||||
|  | ||||
| /* Overrides the functions to use for memory management. */ | ||||
| void reftable_set_alloc(void *(*malloc)(size_t), | ||||
| 			void *(*realloc)(void *, size_t), void (*free)(void *)); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,72 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef REFTABLE_MERGED_H | ||||
| #define REFTABLE_MERGED_H | ||||
|  | ||||
| #include "reftable-iterator.h" | ||||
|  | ||||
| /* | ||||
|  * Merged tables | ||||
|  * | ||||
|  * A ref database kept in a sequence of table files. The merged_table presents a | ||||
|  * unified view to reading (seeking, iterating) a sequence of immutable tables. | ||||
|  * | ||||
|  * The merged tables are on purpose kept disconnected from their actual storage | ||||
|  * (eg. files on disk), because it is useful to merge tables aren't files. For | ||||
|  * example, the per-workspace and global ref namespace can be implemented as a | ||||
|  * merged table of two stacks of file-backed reftables. | ||||
|  */ | ||||
|  | ||||
| /* A merged table is implements seeking/iterating over a stack of tables. */ | ||||
| struct reftable_merged_table; | ||||
|  | ||||
| /* A generic reftable; see below. */ | ||||
| struct reftable_table; | ||||
|  | ||||
| /* reftable_new_merged_table creates a new merged table. It takes ownership of | ||||
|    the stack array. | ||||
| */ | ||||
| int reftable_new_merged_table(struct reftable_merged_table **dest, | ||||
| 			      struct reftable_table *stack, int n, | ||||
| 			      uint32_t hash_id); | ||||
|  | ||||
| /* returns an iterator positioned just before 'name' */ | ||||
| int reftable_merged_table_seek_ref(struct reftable_merged_table *mt, | ||||
| 				   struct reftable_iterator *it, | ||||
| 				   const char *name); | ||||
|  | ||||
| /* returns an iterator for log entry, at given update_index */ | ||||
| int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt, | ||||
| 				      struct reftable_iterator *it, | ||||
| 				      const char *name, uint64_t update_index); | ||||
|  | ||||
| /* like reftable_merged_table_seek_log_at but look for the newest entry. */ | ||||
| int reftable_merged_table_seek_log(struct reftable_merged_table *mt, | ||||
| 				   struct reftable_iterator *it, | ||||
| 				   const char *name); | ||||
|  | ||||
| /* returns the max update_index covered by this merged table. */ | ||||
| uint64_t | ||||
| reftable_merged_table_max_update_index(struct reftable_merged_table *mt); | ||||
|  | ||||
| /* returns the min update_index covered by this merged table. */ | ||||
| uint64_t | ||||
| reftable_merged_table_min_update_index(struct reftable_merged_table *mt); | ||||
|  | ||||
| /* releases memory for the merged_table */ | ||||
| void reftable_merged_table_free(struct reftable_merged_table *m); | ||||
|  | ||||
| /* return the hash ID of the merged table. */ | ||||
| uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m); | ||||
|  | ||||
| /* create a generic table from reftable_merged_table */ | ||||
| void reftable_table_from_merged_table(struct reftable_table *tab, | ||||
| 				      struct reftable_merged_table *table); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,101 @@ | |||
| /* | ||||
|   Copyright 2020 Google LLC | ||||
|  | ||||
|   Use of this source code is governed by a BSD-style | ||||
|   license that can be found in the LICENSE file or at | ||||
|   https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef REFTABLE_READER_H | ||||
| #define REFTABLE_READER_H | ||||
|  | ||||
| #include "reftable-iterator.h" | ||||
| #include "reftable-blocksource.h" | ||||
|  | ||||
| /* | ||||
|  * Reading single tables | ||||
|  * | ||||
|  * The follow routines are for reading single files. For an | ||||
|  * application-level interface, skip ahead to struct | ||||
|  * reftable_merged_table and struct reftable_stack. | ||||
|  */ | ||||
|  | ||||
| /* The reader struct is a handle to an open reftable file. */ | ||||
| struct reftable_reader; | ||||
|  | ||||
| /* Generic table. */ | ||||
| struct reftable_table; | ||||
|  | ||||
| /* reftable_new_reader opens a reftable for reading. If successful, | ||||
|  * returns 0 code and sets pp. The name is used for creating a | ||||
|  * stack. Typically, it is the basename of the file. The block source | ||||
|  * `src` is owned by the reader, and is closed on calling | ||||
|  * reftable_reader_destroy(). On error, the block source `src` is | ||||
|  * closed as well. | ||||
|  */ | ||||
| int reftable_new_reader(struct reftable_reader **pp, | ||||
| 			struct reftable_block_source *src, const char *name); | ||||
|  | ||||
| /* reftable_reader_seek_ref returns an iterator where 'name' would be inserted | ||||
|    in the table.  To seek to the start of the table, use name = "". | ||||
|  | ||||
|    example: | ||||
|  | ||||
|    struct reftable_reader *r = NULL; | ||||
|    int err = reftable_new_reader(&r, &src, "filename"); | ||||
|    if (err < 0) { ... } | ||||
|    struct reftable_iterator it  = {0}; | ||||
|    err = reftable_reader_seek_ref(r, &it, "refs/heads/master"); | ||||
|    if (err < 0) { ... } | ||||
|    struct reftable_ref_record ref  = {0}; | ||||
|    while (1) { | ||||
|    err = reftable_iterator_next_ref(&it, &ref); | ||||
|    if (err > 0) { | ||||
|    break; | ||||
|    } | ||||
|    if (err < 0) { | ||||
|    ..error handling.. | ||||
|    } | ||||
|    ..found.. | ||||
|    } | ||||
|    reftable_iterator_destroy(&it); | ||||
|    reftable_ref_record_release(&ref); | ||||
| */ | ||||
| int reftable_reader_seek_ref(struct reftable_reader *r, | ||||
| 			     struct reftable_iterator *it, const char *name); | ||||
|  | ||||
| /* returns the hash ID used in this table. */ | ||||
| uint32_t reftable_reader_hash_id(struct reftable_reader *r); | ||||
|  | ||||
| /* seek to logs for the given name, older than update_index. To seek to the | ||||
|    start of the table, use name = "". | ||||
| */ | ||||
| int reftable_reader_seek_log_at(struct reftable_reader *r, | ||||
| 				struct reftable_iterator *it, const char *name, | ||||
| 				uint64_t update_index); | ||||
|  | ||||
| /* seek to newest log entry for given name. */ | ||||
| int reftable_reader_seek_log(struct reftable_reader *r, | ||||
| 			     struct reftable_iterator *it, const char *name); | ||||
|  | ||||
| /* closes and deallocates a reader. */ | ||||
| void reftable_reader_free(struct reftable_reader *); | ||||
|  | ||||
| /* return an iterator for the refs pointing to `oid`. */ | ||||
| int reftable_reader_refs_for(struct reftable_reader *r, | ||||
| 			     struct reftable_iterator *it, uint8_t *oid); | ||||
|  | ||||
| /* return the max_update_index for a table */ | ||||
| uint64_t reftable_reader_max_update_index(struct reftable_reader *r); | ||||
|  | ||||
| /* return the min_update_index for a table */ | ||||
| uint64_t reftable_reader_min_update_index(struct reftable_reader *r); | ||||
|  | ||||
| /* creates a generic table from a file reader. */ | ||||
| void reftable_table_from_reader(struct reftable_table *tab, | ||||
| 				struct reftable_reader *reader); | ||||
|  | ||||
| /* print table onto stdout for debugging. */ | ||||
| int reftable_reader_print_file(const char *tablename); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,114 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef REFTABLE_RECORD_H | ||||
| #define REFTABLE_RECORD_H | ||||
|  | ||||
| #include <stdint.h> | ||||
|  | ||||
| /* | ||||
|  * Basic data types | ||||
|  * | ||||
|  * Reftables store the state of each ref in struct reftable_ref_record, and they | ||||
|  * store a sequence of reflog updates in struct reftable_log_record. | ||||
|  */ | ||||
|  | ||||
| /* reftable_ref_record holds a ref database entry target_value */ | ||||
| struct reftable_ref_record { | ||||
| 	char *refname; /* Name of the ref, malloced. */ | ||||
| 	uint64_t update_index; /* Logical timestamp at which this value is | ||||
| 				* written */ | ||||
|  | ||||
| 	enum { | ||||
| 		/* tombstone to hide deletions from earlier tables */ | ||||
| 		REFTABLE_REF_DELETION = 0x0, | ||||
|  | ||||
| 		/* a simple ref */ | ||||
| 		REFTABLE_REF_VAL1 = 0x1, | ||||
| 		/* a tag, plus its peeled hash */ | ||||
| 		REFTABLE_REF_VAL2 = 0x2, | ||||
|  | ||||
| 		/* a symbolic reference */ | ||||
| 		REFTABLE_REF_SYMREF = 0x3, | ||||
| #define REFTABLE_NR_REF_VALUETYPES 4 | ||||
| 	} value_type; | ||||
| 	union { | ||||
| 		uint8_t *val1; /* malloced hash. */ | ||||
| 		struct { | ||||
| 			uint8_t *value; /* first value, malloced hash  */ | ||||
| 			uint8_t *target_value; /* second value, malloced hash */ | ||||
| 		} val2; | ||||
| 		char *symref; /* referent, malloced 0-terminated string */ | ||||
| 	} value; | ||||
| }; | ||||
|  | ||||
| /* Returns the first hash, or NULL if `rec` is not of type | ||||
|  * REFTABLE_REF_VAL1 or REFTABLE_REF_VAL2. */ | ||||
| uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec); | ||||
|  | ||||
| /* Returns the second hash, or NULL if `rec` is not of type | ||||
|  * REFTABLE_REF_VAL2. */ | ||||
| uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec); | ||||
|  | ||||
| /* returns whether 'ref' represents a deletion */ | ||||
| int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref); | ||||
|  | ||||
| /* prints a reftable_ref_record onto stdout. Useful for debugging. */ | ||||
| void reftable_ref_record_print(struct reftable_ref_record *ref, | ||||
| 			       uint32_t hash_id); | ||||
|  | ||||
| /* frees and nulls all pointer values inside `ref`. */ | ||||
| void reftable_ref_record_release(struct reftable_ref_record *ref); | ||||
|  | ||||
| /* returns whether two reftable_ref_records are the same. Useful for testing. */ | ||||
| int reftable_ref_record_equal(struct reftable_ref_record *a, | ||||
| 			      struct reftable_ref_record *b, int hash_size); | ||||
|  | ||||
| /* reftable_log_record holds a reflog entry */ | ||||
| struct reftable_log_record { | ||||
| 	char *refname; | ||||
| 	uint64_t update_index; /* logical timestamp of a transactional update. | ||||
| 				*/ | ||||
|  | ||||
| 	enum { | ||||
| 		/* tombstone to hide deletions from earlier tables */ | ||||
| 		REFTABLE_LOG_DELETION = 0x0, | ||||
|  | ||||
| 		/* a simple update */ | ||||
| 		REFTABLE_LOG_UPDATE = 0x1, | ||||
| #define REFTABLE_NR_LOG_VALUETYPES 2 | ||||
| 	} value_type; | ||||
|  | ||||
| 	union { | ||||
| 		struct { | ||||
| 			uint8_t *new_hash; | ||||
| 			uint8_t *old_hash; | ||||
| 			char *name; | ||||
| 			char *email; | ||||
| 			uint64_t time; | ||||
| 			int16_t tz_offset; | ||||
| 			char *message; | ||||
| 		} update; | ||||
| 	} value; | ||||
| }; | ||||
|  | ||||
| /* returns whether 'ref' represents the deletion of a log record. */ | ||||
| int reftable_log_record_is_deletion(const struct reftable_log_record *log); | ||||
|  | ||||
| /* frees and nulls all pointer values. */ | ||||
| void reftable_log_record_release(struct reftable_log_record *log); | ||||
|  | ||||
| /* returns whether two records are equal. Useful for testing. */ | ||||
| int reftable_log_record_equal(struct reftable_log_record *a, | ||||
| 			      struct reftable_log_record *b, int hash_size); | ||||
|  | ||||
| /* dumps a reftable_log_record on stdout, for debugging/testing. */ | ||||
| void reftable_log_record_print(struct reftable_log_record *log, | ||||
| 			       uint32_t hash_id); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,128 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef REFTABLE_STACK_H | ||||
| #define REFTABLE_STACK_H | ||||
|  | ||||
| #include "reftable-writer.h" | ||||
|  | ||||
| /* | ||||
|  * The stack presents an interface to a mutable sequence of reftables. | ||||
|  | ||||
|  * A stack can be mutated by pushing a table to the top of the stack. | ||||
|  | ||||
|  * The reftable_stack automatically compacts files on disk to ensure good | ||||
|  * amortized performance. | ||||
|  * | ||||
|  * For windows and other platforms that cannot have open files as rename | ||||
|  * destinations, concurrent access from multiple processes needs the rand() | ||||
|  * random seed to be randomized. | ||||
|  */ | ||||
| struct reftable_stack; | ||||
|  | ||||
| /* open a new reftable stack. The tables along with the table list will be | ||||
|  *  stored in 'dir'. Typically, this should be .git/reftables. | ||||
|  */ | ||||
| int reftable_new_stack(struct reftable_stack **dest, const char *dir, | ||||
| 		       struct reftable_write_options config); | ||||
|  | ||||
| /* returns the update_index at which a next table should be written. */ | ||||
| uint64_t reftable_stack_next_update_index(struct reftable_stack *st); | ||||
|  | ||||
| /* holds a transaction to add tables at the top of a stack. */ | ||||
| struct reftable_addition; | ||||
|  | ||||
| /* | ||||
|  * returns a new transaction to add reftables to the given stack. As a side | ||||
|  * effect, the ref database is locked. | ||||
|  */ | ||||
| int reftable_stack_new_addition(struct reftable_addition **dest, | ||||
| 				struct reftable_stack *st); | ||||
|  | ||||
| /* Adds a reftable to transaction. */ | ||||
| int reftable_addition_add(struct reftable_addition *add, | ||||
| 			  int (*write_table)(struct reftable_writer *wr, | ||||
| 					     void *arg), | ||||
| 			  void *arg); | ||||
|  | ||||
| /* Commits the transaction, releasing the lock. After calling this, | ||||
|  * reftable_addition_destroy should still be called. | ||||
|  */ | ||||
| int reftable_addition_commit(struct reftable_addition *add); | ||||
|  | ||||
| /* Release all non-committed data from the transaction, and deallocate the | ||||
|  * transaction. Releases the lock if held. */ | ||||
| void reftable_addition_destroy(struct reftable_addition *add); | ||||
|  | ||||
| /* add a new table to the stack. The write_table function must call | ||||
|  * reftable_writer_set_limits, add refs and return an error value. */ | ||||
| int reftable_stack_add(struct reftable_stack *st, | ||||
| 		       int (*write_table)(struct reftable_writer *wr, | ||||
| 					  void *write_arg), | ||||
| 		       void *write_arg); | ||||
|  | ||||
| /* returns the merged_table for seeking. This table is valid until the | ||||
|  * next write or reload, and should not be closed or deleted. | ||||
|  */ | ||||
| struct reftable_merged_table * | ||||
| reftable_stack_merged_table(struct reftable_stack *st); | ||||
|  | ||||
| /* frees all resources associated with the stack. */ | ||||
| void reftable_stack_destroy(struct reftable_stack *st); | ||||
|  | ||||
| /* Reloads the stack if necessary. This is very cheap to run if the stack was up | ||||
|  * to date */ | ||||
| int reftable_stack_reload(struct reftable_stack *st); | ||||
|  | ||||
| /* Policy for expiring reflog entries. */ | ||||
| struct reftable_log_expiry_config { | ||||
| 	/* Drop entries older than this timestamp */ | ||||
| 	uint64_t time; | ||||
|  | ||||
| 	/* Drop older entries */ | ||||
| 	uint64_t min_update_index; | ||||
| }; | ||||
|  | ||||
| /* compacts all reftables into a giant table. Expire reflog entries if config is | ||||
|  * non-NULL */ | ||||
| int reftable_stack_compact_all(struct reftable_stack *st, | ||||
| 			       struct reftable_log_expiry_config *config); | ||||
|  | ||||
| /* heuristically compact unbalanced table stack. */ | ||||
| int reftable_stack_auto_compact(struct reftable_stack *st); | ||||
|  | ||||
| /* delete stale .ref tables. */ | ||||
| int reftable_stack_clean(struct reftable_stack *st); | ||||
|  | ||||
| /* convenience function to read a single ref. Returns < 0 for error, 0 for | ||||
|  * success, and 1 if ref not found. */ | ||||
| int reftable_stack_read_ref(struct reftable_stack *st, const char *refname, | ||||
| 			    struct reftable_ref_record *ref); | ||||
|  | ||||
| /* convenience function to read a single log. Returns < 0 for error, 0 for | ||||
|  * success, and 1 if ref not found. */ | ||||
| int reftable_stack_read_log(struct reftable_stack *st, const char *refname, | ||||
| 			    struct reftable_log_record *log); | ||||
|  | ||||
| /* statistics on past compactions. */ | ||||
| struct reftable_compaction_stats { | ||||
| 	uint64_t bytes; /* total number of bytes written */ | ||||
| 	uint64_t entries_written; /* total number of entries written, including | ||||
| 				     failures. */ | ||||
| 	int attempts; /* how often we tried to compact */ | ||||
| 	int failures; /* failures happen on concurrent updates */ | ||||
| }; | ||||
|  | ||||
| /* return statistics for compaction up till now. */ | ||||
| struct reftable_compaction_stats * | ||||
| reftable_stack_compaction_stats(struct reftable_stack *st); | ||||
|  | ||||
| /* print the entire stack represented by the directory */ | ||||
| int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,23 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef REFTABLE_TESTS_H | ||||
| #define REFTABLE_TESTS_H | ||||
|  | ||||
| int basics_test_main(int argc, const char **argv); | ||||
| int block_test_main(int argc, const char **argv); | ||||
| int merged_test_main(int argc, const char **argv); | ||||
| int pq_test_main(int argc, const char **argv); | ||||
| int record_test_main(int argc, const char **argv); | ||||
| int refname_test_main(int argc, const char **argv); | ||||
| int readwrite_test_main(int argc, const char **argv); | ||||
| int stack_test_main(int argc, const char **argv); | ||||
| int tree_test_main(int argc, const char **argv); | ||||
| int reftable_dump_main(int argc, char *const *argv); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,148 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef REFTABLE_WRITER_H | ||||
| #define REFTABLE_WRITER_H | ||||
|  | ||||
| #include "reftable-record.h" | ||||
|  | ||||
| #include <stdint.h> | ||||
| #include <unistd.h> /* ssize_t */ | ||||
|  | ||||
| /* Writing single reftables */ | ||||
|  | ||||
| /* reftable_write_options sets options for writing a single reftable. */ | ||||
| struct reftable_write_options { | ||||
| 	/* boolean: do not pad out blocks to block size. */ | ||||
| 	unsigned unpadded : 1; | ||||
|  | ||||
| 	/* the blocksize. Should be less than 2^24. */ | ||||
| 	uint32_t block_size; | ||||
|  | ||||
| 	/* boolean: do not generate a SHA1 => ref index. */ | ||||
| 	unsigned skip_index_objects : 1; | ||||
|  | ||||
| 	/* how often to write complete keys in each block. */ | ||||
| 	int restart_interval; | ||||
|  | ||||
| 	/* 4-byte identifier ("sha1", "s256") of the hash. | ||||
| 	 * Defaults to SHA1 if unset | ||||
| 	 */ | ||||
| 	uint32_t hash_id; | ||||
|  | ||||
| 	/* boolean: do not check ref names for validity or dir/file conflicts. | ||||
| 	 */ | ||||
| 	unsigned skip_name_check : 1; | ||||
|  | ||||
| 	/* boolean: copy log messages exactly. If unset, check that the message | ||||
| 	 *   is a single line, and add '\n' if missing. | ||||
| 	 */ | ||||
| 	unsigned exact_log_message : 1; | ||||
| }; | ||||
|  | ||||
| /* reftable_block_stats holds statistics for a single block type */ | ||||
| struct reftable_block_stats { | ||||
| 	/* total number of entries written */ | ||||
| 	int entries; | ||||
| 	/* total number of key restarts */ | ||||
| 	int restarts; | ||||
| 	/* total number of blocks */ | ||||
| 	int blocks; | ||||
| 	/* total number of index blocks */ | ||||
| 	int index_blocks; | ||||
| 	/* depth of the index */ | ||||
| 	int max_index_level; | ||||
|  | ||||
| 	/* offset of the first block for this type */ | ||||
| 	uint64_t offset; | ||||
| 	/* offset of the top level index block for this type, or 0 if not | ||||
| 	 * present */ | ||||
| 	uint64_t index_offset; | ||||
| }; | ||||
|  | ||||
| /* stats holds overall statistics for a single reftable */ | ||||
| struct reftable_stats { | ||||
| 	/* total number of blocks written. */ | ||||
| 	int blocks; | ||||
| 	/* stats for ref data */ | ||||
| 	struct reftable_block_stats ref_stats; | ||||
| 	/* stats for the SHA1 to ref map. */ | ||||
| 	struct reftable_block_stats obj_stats; | ||||
| 	/* stats for index blocks */ | ||||
| 	struct reftable_block_stats idx_stats; | ||||
| 	/* stats for log blocks */ | ||||
| 	struct reftable_block_stats log_stats; | ||||
|  | ||||
| 	/* disambiguation length of shortened object IDs. */ | ||||
| 	int object_id_len; | ||||
| }; | ||||
|  | ||||
| /* reftable_new_writer creates a new writer */ | ||||
| struct reftable_writer * | ||||
| reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t), | ||||
| 		    void *writer_arg, struct reftable_write_options *opts); | ||||
|  | ||||
| /* Set the range of update indices for the records we will add. When writing a | ||||
|    table into a stack, the min should be at least | ||||
|    reftable_stack_next_update_index(), or REFTABLE_API_ERROR is returned. | ||||
|  | ||||
|    For transactional updates to a stack, typically min==max, and the | ||||
|    update_index can be obtained by inspeciting the stack. When converting an | ||||
|    existing ref database into a single reftable, this would be a range of | ||||
|    update-index timestamps. | ||||
|  */ | ||||
| void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min, | ||||
| 				uint64_t max); | ||||
|  | ||||
| /* | ||||
|   Add a reftable_ref_record. The record should have names that come after | ||||
|   already added records. | ||||
|  | ||||
|   The update_index must be within the limits set by | ||||
|   reftable_writer_set_limits(), or REFTABLE_API_ERROR is returned. It is an | ||||
|   REFTABLE_API_ERROR error to write a ref record after a log record. | ||||
| */ | ||||
| int reftable_writer_add_ref(struct reftable_writer *w, | ||||
| 			    struct reftable_ref_record *ref); | ||||
|  | ||||
| /* | ||||
|   Convenience function to add multiple reftable_ref_records; the function sorts | ||||
|   the records before adding them, reordering the records array passed in. | ||||
| */ | ||||
| int reftable_writer_add_refs(struct reftable_writer *w, | ||||
| 			     struct reftable_ref_record *refs, int n); | ||||
|  | ||||
| /* | ||||
|   adds reftable_log_records. Log records are keyed by (refname, decreasing | ||||
|   update_index). The key for the record added must come after the already added | ||||
|   log records. | ||||
| */ | ||||
| int reftable_writer_add_log(struct reftable_writer *w, | ||||
| 			    struct reftable_log_record *log); | ||||
|  | ||||
| /* | ||||
|   Convenience function to add multiple reftable_log_records; the function sorts | ||||
|   the records before adding them, reordering records array passed in. | ||||
| */ | ||||
| int reftable_writer_add_logs(struct reftable_writer *w, | ||||
| 			     struct reftable_log_record *logs, int n); | ||||
|  | ||||
| /* reftable_writer_close finalizes the reftable. The writer is retained so | ||||
|  * statistics can be inspected. */ | ||||
| int reftable_writer_close(struct reftable_writer *w); | ||||
|  | ||||
| /* writer_stats returns the statistics on the reftable being written. | ||||
|  | ||||
|    This struct becomes invalid when the writer is freed. | ||||
|  */ | ||||
| const struct reftable_stats *writer_stats(struct reftable_writer *w); | ||||
|  | ||||
| /* reftable_writer_free deallocates memory for the writer */ | ||||
| void reftable_writer_free(struct reftable_writer *w); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,115 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "record.h" | ||||
| #include "generic.h" | ||||
| #include "reftable-iterator.h" | ||||
| #include "reftable-generic.h" | ||||
|  | ||||
| int reftable_table_seek_ref(struct reftable_table *tab, | ||||
| 			    struct reftable_iterator *it, const char *name) | ||||
| { | ||||
| 	struct reftable_ref_record ref = { | ||||
| 		.refname = (char *)name, | ||||
| 	}; | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	reftable_record_from_ref(&rec, &ref); | ||||
| 	return tab->ops->seek_record(tab->table_arg, it, &rec); | ||||
| } | ||||
|  | ||||
| int reftable_table_read_ref(struct reftable_table *tab, const char *name, | ||||
| 			    struct reftable_ref_record *ref) | ||||
| { | ||||
| 	struct reftable_iterator it = { NULL }; | ||||
| 	int err = reftable_table_seek_ref(tab, &it, name); | ||||
| 	if (err) | ||||
| 		goto done; | ||||
|  | ||||
| 	err = reftable_iterator_next_ref(&it, ref); | ||||
| 	if (err) | ||||
| 		goto done; | ||||
|  | ||||
| 	if (strcmp(ref->refname, name) || | ||||
| 	    reftable_ref_record_is_deletion(ref)) { | ||||
| 		reftable_ref_record_release(ref); | ||||
| 		err = 1; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| done: | ||||
| 	reftable_iterator_destroy(&it); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| uint64_t reftable_table_max_update_index(struct reftable_table *tab) | ||||
| { | ||||
| 	return tab->ops->max_update_index(tab->table_arg); | ||||
| } | ||||
|  | ||||
| uint64_t reftable_table_min_update_index(struct reftable_table *tab) | ||||
| { | ||||
| 	return tab->ops->min_update_index(tab->table_arg); | ||||
| } | ||||
|  | ||||
| uint32_t reftable_table_hash_id(struct reftable_table *tab) | ||||
| { | ||||
| 	return tab->ops->hash_id(tab->table_arg); | ||||
| } | ||||
|  | ||||
| void reftable_iterator_destroy(struct reftable_iterator *it) | ||||
| { | ||||
| 	if (!it->ops) { | ||||
| 		return; | ||||
| 	} | ||||
| 	it->ops->close(it->iter_arg); | ||||
| 	it->ops = NULL; | ||||
| 	FREE_AND_NULL(it->iter_arg); | ||||
| } | ||||
|  | ||||
| int reftable_iterator_next_ref(struct reftable_iterator *it, | ||||
| 			       struct reftable_ref_record *ref) | ||||
| { | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	reftable_record_from_ref(&rec, ref); | ||||
| 	return iterator_next(it, &rec); | ||||
| } | ||||
|  | ||||
| int reftable_iterator_next_log(struct reftable_iterator *it, | ||||
| 			       struct reftable_log_record *log) | ||||
| { | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	reftable_record_from_log(&rec, log); | ||||
| 	return iterator_next(it, &rec); | ||||
| } | ||||
|  | ||||
| int iterator_next(struct reftable_iterator *it, struct reftable_record *rec) | ||||
| { | ||||
| 	return it->ops->next(it->iter_arg, rec); | ||||
| } | ||||
|  | ||||
| static int empty_iterator_next(void *arg, struct reftable_record *rec) | ||||
| { | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| static void empty_iterator_close(void *arg) | ||||
| { | ||||
| } | ||||
|  | ||||
| static struct reftable_iterator_vtable empty_vtable = { | ||||
| 	.next = &empty_iterator_next, | ||||
| 	.close = &empty_iterator_close, | ||||
| }; | ||||
|  | ||||
| void iterator_set_empty(struct reftable_iterator *it) | ||||
| { | ||||
| 	assert(!it->ops); | ||||
| 	it->iter_arg = NULL; | ||||
| 	it->ops = &empty_vtable; | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,41 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef STACK_H | ||||
| #define STACK_H | ||||
|  | ||||
| #include "system.h" | ||||
| #include "reftable-writer.h" | ||||
| #include "reftable-stack.h" | ||||
|  | ||||
| struct reftable_stack { | ||||
| 	char *list_file; | ||||
| 	char *reftable_dir; | ||||
| 	int disable_auto_compact; | ||||
|  | ||||
| 	struct reftable_write_options config; | ||||
|  | ||||
| 	struct reftable_reader **readers; | ||||
| 	size_t readers_len; | ||||
| 	struct reftable_merged_table *merged; | ||||
| 	struct reftable_compaction_stats stats; | ||||
| }; | ||||
|  | ||||
| int read_lines(const char *filename, char ***lines); | ||||
|  | ||||
| struct segment { | ||||
| 	int start, end; | ||||
| 	int log; | ||||
| 	uint64_t bytes; | ||||
| }; | ||||
|  | ||||
| int fastlog2(uint64_t sz); | ||||
| struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n); | ||||
| struct segment suggest_compaction_segment(uint64_t *sizes, int n); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,953 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "stack.h" | ||||
|  | ||||
| #include "system.h" | ||||
|  | ||||
| #include "reftable-reader.h" | ||||
| #include "merged.h" | ||||
| #include "basics.h" | ||||
| #include "constants.h" | ||||
| #include "record.h" | ||||
| #include "test_framework.h" | ||||
| #include "reftable-tests.h" | ||||
|  | ||||
| #include <sys/types.h> | ||||
| #include <dirent.h> | ||||
|  | ||||
| static void clear_dir(const char *dirname) | ||||
| { | ||||
| 	struct strbuf path = STRBUF_INIT; | ||||
| 	strbuf_addstr(&path, dirname); | ||||
| 	remove_dir_recursively(&path, 0); | ||||
| 	strbuf_release(&path); | ||||
| } | ||||
|  | ||||
| static int count_dir_entries(const char *dirname) | ||||
| { | ||||
| 	DIR *dir = opendir(dirname); | ||||
| 	int len = 0; | ||||
| 	struct dirent *d; | ||||
| 	if (dir == NULL) | ||||
| 		return 0; | ||||
|  | ||||
| 	while ((d = readdir(dir))) { | ||||
| 		if (!strcmp(d->d_name, "..") || !strcmp(d->d_name, ".")) | ||||
| 			continue; | ||||
| 		len++; | ||||
| 	} | ||||
| 	closedir(dir); | ||||
| 	return len; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Work linenumber into the tempdir, so we can see which tests forget to | ||||
|  * cleanup. | ||||
|  */ | ||||
| static char *get_tmp_template(int linenumber) | ||||
| { | ||||
| 	const char *tmp = getenv("TMPDIR"); | ||||
| 	static char template[1024]; | ||||
| 	snprintf(template, sizeof(template) - 1, "%s/stack_test-%d.XXXXXX", | ||||
| 		 tmp ? tmp : "/tmp", linenumber); | ||||
| 	return template; | ||||
| } | ||||
|  | ||||
| static char *get_tmp_dir(int linenumber) | ||||
| { | ||||
| 	char *dir = get_tmp_template(linenumber); | ||||
| 	EXPECT(mkdtemp(dir)); | ||||
| 	return dir; | ||||
| } | ||||
|  | ||||
| static void test_read_file(void) | ||||
| { | ||||
| 	char *fn = get_tmp_template(__LINE__); | ||||
| 	int fd = mkstemp(fn); | ||||
| 	char out[1024] = "line1\n\nline2\nline3"; | ||||
| 	int n, err; | ||||
| 	char **names = NULL; | ||||
| 	char *want[] = { "line1", "line2", "line3" }; | ||||
| 	int i = 0; | ||||
|  | ||||
| 	EXPECT(fd > 0); | ||||
| 	n = write(fd, out, strlen(out)); | ||||
| 	EXPECT(n == strlen(out)); | ||||
| 	err = close(fd); | ||||
| 	EXPECT(err >= 0); | ||||
|  | ||||
| 	err = read_lines(fn, &names); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	for (i = 0; names[i]; i++) { | ||||
| 		EXPECT(0 == strcmp(want[i], names[i])); | ||||
| 	} | ||||
| 	free_names(names); | ||||
| 	remove(fn); | ||||
| } | ||||
|  | ||||
| static void test_parse_names(void) | ||||
| { | ||||
| 	char buf[] = "line\n"; | ||||
| 	char **names = NULL; | ||||
| 	parse_names(buf, strlen(buf), &names); | ||||
|  | ||||
| 	EXPECT(NULL != names[0]); | ||||
| 	EXPECT(0 == strcmp(names[0], "line")); | ||||
| 	EXPECT(NULL == names[1]); | ||||
| 	free_names(names); | ||||
| } | ||||
|  | ||||
| static void test_names_equal(void) | ||||
| { | ||||
| 	char *a[] = { "a", "b", "c", NULL }; | ||||
| 	char *b[] = { "a", "b", "d", NULL }; | ||||
| 	char *c[] = { "a", "b", NULL }; | ||||
|  | ||||
| 	EXPECT(names_equal(a, a)); | ||||
| 	EXPECT(!names_equal(a, b)); | ||||
| 	EXPECT(!names_equal(a, c)); | ||||
| } | ||||
|  | ||||
| static int write_test_ref(struct reftable_writer *wr, void *arg) | ||||
| { | ||||
| 	struct reftable_ref_record *ref = arg; | ||||
| 	reftable_writer_set_limits(wr, ref->update_index, ref->update_index); | ||||
| 	return reftable_writer_add_ref(wr, ref); | ||||
| } | ||||
|  | ||||
| struct write_log_arg { | ||||
| 	struct reftable_log_record *log; | ||||
| 	uint64_t update_index; | ||||
| }; | ||||
|  | ||||
| static int write_test_log(struct reftable_writer *wr, void *arg) | ||||
| { | ||||
| 	struct write_log_arg *wla = arg; | ||||
|  | ||||
| 	reftable_writer_set_limits(wr, wla->update_index, wla->update_index); | ||||
| 	return reftable_writer_add_log(wr, wla->log); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_add_one(void) | ||||
| { | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	int err; | ||||
| 	struct reftable_ref_record ref = { | ||||
| 		.refname = "HEAD", | ||||
| 		.update_index = 1, | ||||
| 		.value_type = REFTABLE_REF_SYMREF, | ||||
| 		.value.symref = "master", | ||||
| 	}; | ||||
| 	struct reftable_ref_record dest = { NULL }; | ||||
|  | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_add(st, &write_test_ref, &ref); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_read_ref(st, ref.refname, &dest); | ||||
| 	EXPECT_ERR(err); | ||||
| 	EXPECT(0 == strcmp("master", dest.value.symref)); | ||||
|  | ||||
| 	printf("testing print functionality:\n"); | ||||
| 	err = reftable_stack_print_directory(dir, GIT_SHA1_FORMAT_ID); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_print_directory(dir, GIT_SHA256_FORMAT_ID); | ||||
| 	EXPECT(err == REFTABLE_FORMAT_ERROR); | ||||
|  | ||||
| 	reftable_ref_record_release(&dest); | ||||
| 	reftable_stack_destroy(st); | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_uptodate(void) | ||||
| { | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st1 = NULL; | ||||
| 	struct reftable_stack *st2 = NULL; | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	int err; | ||||
| 	struct reftable_ref_record ref1 = { | ||||
| 		.refname = "HEAD", | ||||
| 		.update_index = 1, | ||||
| 		.value_type = REFTABLE_REF_SYMREF, | ||||
| 		.value.symref = "master", | ||||
| 	}; | ||||
| 	struct reftable_ref_record ref2 = { | ||||
| 		.refname = "branch2", | ||||
| 		.update_index = 2, | ||||
| 		.value_type = REFTABLE_REF_SYMREF, | ||||
| 		.value.symref = "master", | ||||
| 	}; | ||||
|  | ||||
|  | ||||
| 	/* simulate multi-process access to the same stack | ||||
| 	   by creating two stacks for the same directory. | ||||
| 	 */ | ||||
| 	err = reftable_new_stack(&st1, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_new_stack(&st2, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_add(st1, &write_test_ref, &ref1); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_add(st2, &write_test_ref, &ref2); | ||||
| 	EXPECT(err == REFTABLE_LOCK_ERROR); | ||||
|  | ||||
| 	err = reftable_stack_reload(st2); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_add(st2, &write_test_ref, &ref2); | ||||
| 	EXPECT_ERR(err); | ||||
| 	reftable_stack_destroy(st1); | ||||
| 	reftable_stack_destroy(st2); | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_transaction_api(void) | ||||
| { | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	int err; | ||||
| 	struct reftable_addition *add = NULL; | ||||
|  | ||||
| 	struct reftable_ref_record ref = { | ||||
| 		.refname = "HEAD", | ||||
| 		.update_index = 1, | ||||
| 		.value_type = REFTABLE_REF_SYMREF, | ||||
| 		.value.symref = "master", | ||||
| 	}; | ||||
| 	struct reftable_ref_record dest = { NULL }; | ||||
|  | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	reftable_addition_destroy(add); | ||||
|  | ||||
| 	err = reftable_stack_new_addition(&add, st); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_addition_add(add, &write_test_ref, &ref); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_addition_commit(add); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	reftable_addition_destroy(add); | ||||
|  | ||||
| 	err = reftable_stack_read_ref(st, ref.refname, &dest); | ||||
| 	EXPECT_ERR(err); | ||||
| 	EXPECT(REFTABLE_REF_SYMREF == dest.value_type); | ||||
| 	EXPECT(0 == strcmp("master", dest.value.symref)); | ||||
|  | ||||
| 	reftable_ref_record_release(&dest); | ||||
| 	reftable_stack_destroy(st); | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_validate_refname(void) | ||||
| { | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	int err; | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	int i; | ||||
| 	struct reftable_ref_record ref = { | ||||
| 		.refname = "a/b", | ||||
| 		.update_index = 1, | ||||
| 		.value_type = REFTABLE_REF_SYMREF, | ||||
| 		.value.symref = "master", | ||||
| 	}; | ||||
| 	char *additions[] = { "a", "a/b/c" }; | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_add(st, &write_test_ref, &ref); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	for (i = 0; i < ARRAY_SIZE(additions); i++) { | ||||
| 		struct reftable_ref_record ref = { | ||||
| 			.refname = additions[i], | ||||
| 			.update_index = 1, | ||||
| 			.value_type = REFTABLE_REF_SYMREF, | ||||
| 			.value.symref = "master", | ||||
| 		}; | ||||
|  | ||||
| 		err = reftable_stack_add(st, &write_test_ref, &ref); | ||||
| 		EXPECT(err == REFTABLE_NAME_CONFLICT); | ||||
| 	} | ||||
|  | ||||
| 	reftable_stack_destroy(st); | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static int write_error(struct reftable_writer *wr, void *arg) | ||||
| { | ||||
| 	return *((int *)arg); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_update_index_check(void) | ||||
| { | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	int err; | ||||
| 	struct reftable_ref_record ref1 = { | ||||
| 		.refname = "name1", | ||||
| 		.update_index = 1, | ||||
| 		.value_type = REFTABLE_REF_SYMREF, | ||||
| 		.value.symref = "master", | ||||
| 	}; | ||||
| 	struct reftable_ref_record ref2 = { | ||||
| 		.refname = "name2", | ||||
| 		.update_index = 1, | ||||
| 		.value_type = REFTABLE_REF_SYMREF, | ||||
| 		.value.symref = "master", | ||||
| 	}; | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_add(st, &write_test_ref, &ref1); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_add(st, &write_test_ref, &ref2); | ||||
| 	EXPECT(err == REFTABLE_API_ERROR); | ||||
| 	reftable_stack_destroy(st); | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_lock_failure(void) | ||||
| { | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	int err, i; | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
| 	for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) { | ||||
| 		err = reftable_stack_add(st, &write_error, &i); | ||||
| 		EXPECT(err == i); | ||||
| 	} | ||||
|  | ||||
| 	reftable_stack_destroy(st); | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_add(void) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	int err = 0; | ||||
| 	struct reftable_write_options cfg = { | ||||
| 		.exact_log_message = 1, | ||||
| 	}; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	struct reftable_ref_record refs[2] = { { NULL } }; | ||||
| 	struct reftable_log_record logs[2] = { { NULL } }; | ||||
| 	int N = ARRAY_SIZE(refs); | ||||
|  | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
| 	st->disable_auto_compact = 1; | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		char buf[256]; | ||||
| 		snprintf(buf, sizeof(buf), "branch%02d", i); | ||||
| 		refs[i].refname = xstrdup(buf); | ||||
| 		refs[i].update_index = i + 1; | ||||
| 		refs[i].value_type = REFTABLE_REF_VAL1; | ||||
| 		refs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ); | ||||
| 		set_test_hash(refs[i].value.val1, i); | ||||
|  | ||||
| 		logs[i].refname = xstrdup(buf); | ||||
| 		logs[i].update_index = N + i + 1; | ||||
| 		logs[i].value_type = REFTABLE_LOG_UPDATE; | ||||
|  | ||||
| 		logs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ); | ||||
| 		logs[i].value.update.email = xstrdup("identity@invalid"); | ||||
| 		set_test_hash(logs[i].value.update.new_hash, i); | ||||
| 	} | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		int err = reftable_stack_add(st, &write_test_ref, &refs[i]); | ||||
| 		EXPECT_ERR(err); | ||||
| 	} | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		struct write_log_arg arg = { | ||||
| 			.log = &logs[i], | ||||
| 			.update_index = reftable_stack_next_update_index(st), | ||||
| 		}; | ||||
| 		int err = reftable_stack_add(st, &write_test_log, &arg); | ||||
| 		EXPECT_ERR(err); | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_stack_compact_all(st, NULL); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		struct reftable_ref_record dest = { NULL }; | ||||
|  | ||||
| 		int err = reftable_stack_read_ref(st, refs[i].refname, &dest); | ||||
| 		EXPECT_ERR(err); | ||||
| 		EXPECT(reftable_ref_record_equal(&dest, refs + i, | ||||
| 						 GIT_SHA1_RAWSZ)); | ||||
| 		reftable_ref_record_release(&dest); | ||||
| 	} | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		struct reftable_log_record dest = { NULL }; | ||||
| 		int err = reftable_stack_read_log(st, refs[i].refname, &dest); | ||||
| 		EXPECT_ERR(err); | ||||
| 		EXPECT(reftable_log_record_equal(&dest, logs + i, | ||||
| 						 GIT_SHA1_RAWSZ)); | ||||
| 		reftable_log_record_release(&dest); | ||||
| 	} | ||||
|  | ||||
| 	/* cleanup */ | ||||
| 	reftable_stack_destroy(st); | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		reftable_ref_record_release(&refs[i]); | ||||
| 		reftable_log_record_release(&logs[i]); | ||||
| 	} | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_log_normalize(void) | ||||
| { | ||||
| 	int err = 0; | ||||
| 	struct reftable_write_options cfg = { | ||||
| 		0, | ||||
| 	}; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	uint8_t h1[GIT_SHA1_RAWSZ] = { 0x01 }, h2[GIT_SHA1_RAWSZ] = { 0x02 }; | ||||
|  | ||||
| 	struct reftable_log_record input = { .refname = "branch", | ||||
| 					     .update_index = 1, | ||||
| 					     .value_type = REFTABLE_LOG_UPDATE, | ||||
| 					     .value = { .update = { | ||||
| 								.new_hash = h1, | ||||
| 								.old_hash = h2, | ||||
| 							} } }; | ||||
| 	struct reftable_log_record dest = { | ||||
| 		.update_index = 0, | ||||
| 	}; | ||||
| 	struct write_log_arg arg = { | ||||
| 		.log = &input, | ||||
| 		.update_index = 1, | ||||
| 	}; | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	input.value.update.message = "one\ntwo"; | ||||
| 	err = reftable_stack_add(st, &write_test_log, &arg); | ||||
| 	EXPECT(err == REFTABLE_API_ERROR); | ||||
|  | ||||
| 	input.value.update.message = "one"; | ||||
| 	err = reftable_stack_add(st, &write_test_log, &arg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_read_log(st, input.refname, &dest); | ||||
| 	EXPECT_ERR(err); | ||||
| 	EXPECT(0 == strcmp(dest.value.update.message, "one\n")); | ||||
|  | ||||
| 	input.value.update.message = "two\n"; | ||||
| 	arg.update_index = 2; | ||||
| 	err = reftable_stack_add(st, &write_test_log, &arg); | ||||
| 	EXPECT_ERR(err); | ||||
| 	err = reftable_stack_read_log(st, input.refname, &dest); | ||||
| 	EXPECT_ERR(err); | ||||
| 	EXPECT(0 == strcmp(dest.value.update.message, "two\n")); | ||||
|  | ||||
| 	/* cleanup */ | ||||
| 	reftable_stack_destroy(st); | ||||
| 	reftable_log_record_release(&dest); | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_tombstone(void) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	int err; | ||||
| 	struct reftable_ref_record refs[2] = { { NULL } }; | ||||
| 	struct reftable_log_record logs[2] = { { NULL } }; | ||||
| 	int N = ARRAY_SIZE(refs); | ||||
| 	struct reftable_ref_record dest = { NULL }; | ||||
| 	struct reftable_log_record log_dest = { NULL }; | ||||
|  | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	/* even entries add the refs, odd entries delete them. */ | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		const char *buf = "branch"; | ||||
| 		refs[i].refname = xstrdup(buf); | ||||
| 		refs[i].update_index = i + 1; | ||||
| 		if (i % 2 == 0) { | ||||
| 			refs[i].value_type = REFTABLE_REF_VAL1; | ||||
| 			refs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ); | ||||
| 			set_test_hash(refs[i].value.val1, i); | ||||
| 		} | ||||
|  | ||||
| 		logs[i].refname = xstrdup(buf); | ||||
| 		/* update_index is part of the key. */ | ||||
| 		logs[i].update_index = 42; | ||||
| 		if (i % 2 == 0) { | ||||
| 			logs[i].value_type = REFTABLE_LOG_UPDATE; | ||||
| 			logs[i].value.update.new_hash = | ||||
| 				reftable_malloc(GIT_SHA1_RAWSZ); | ||||
| 			set_test_hash(logs[i].value.update.new_hash, i); | ||||
| 			logs[i].value.update.email = | ||||
| 				xstrdup("identity@invalid"); | ||||
| 		} | ||||
| 	} | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		int err = reftable_stack_add(st, &write_test_ref, &refs[i]); | ||||
| 		EXPECT_ERR(err); | ||||
| 	} | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		struct write_log_arg arg = { | ||||
| 			.log = &logs[i], | ||||
| 			.update_index = reftable_stack_next_update_index(st), | ||||
| 		}; | ||||
| 		int err = reftable_stack_add(st, &write_test_log, &arg); | ||||
| 		EXPECT_ERR(err); | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_stack_read_ref(st, "branch", &dest); | ||||
| 	EXPECT(err == 1); | ||||
| 	reftable_ref_record_release(&dest); | ||||
|  | ||||
| 	err = reftable_stack_read_log(st, "branch", &log_dest); | ||||
| 	EXPECT(err == 1); | ||||
| 	reftable_log_record_release(&log_dest); | ||||
|  | ||||
| 	err = reftable_stack_compact_all(st, NULL); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_read_ref(st, "branch", &dest); | ||||
| 	EXPECT(err == 1); | ||||
|  | ||||
| 	err = reftable_stack_read_log(st, "branch", &log_dest); | ||||
| 	EXPECT(err == 1); | ||||
| 	reftable_ref_record_release(&dest); | ||||
| 	reftable_log_record_release(&log_dest); | ||||
|  | ||||
| 	/* cleanup */ | ||||
| 	reftable_stack_destroy(st); | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		reftable_ref_record_release(&refs[i]); | ||||
| 		reftable_log_record_release(&logs[i]); | ||||
| 	} | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_hash_id(void) | ||||
| { | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	int err; | ||||
|  | ||||
| 	struct reftable_ref_record ref = { | ||||
| 		.refname = "master", | ||||
| 		.value_type = REFTABLE_REF_SYMREF, | ||||
| 		.value.symref = "target", | ||||
| 		.update_index = 1, | ||||
| 	}; | ||||
| 	struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID }; | ||||
| 	struct reftable_stack *st32 = NULL; | ||||
| 	struct reftable_write_options cfg_default = { 0 }; | ||||
| 	struct reftable_stack *st_default = NULL; | ||||
| 	struct reftable_ref_record dest = { NULL }; | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_add(st, &write_test_ref, &ref); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	/* can't read it with the wrong hash ID. */ | ||||
| 	err = reftable_new_stack(&st32, dir, cfg32); | ||||
| 	EXPECT(err == REFTABLE_FORMAT_ERROR); | ||||
|  | ||||
| 	/* check that we can read it back with default config too. */ | ||||
| 	err = reftable_new_stack(&st_default, dir, cfg_default); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_read_ref(st_default, "master", &dest); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	EXPECT(reftable_ref_record_equal(&ref, &dest, GIT_SHA1_RAWSZ)); | ||||
| 	reftable_ref_record_release(&dest); | ||||
| 	reftable_stack_destroy(st); | ||||
| 	reftable_stack_destroy(st_default); | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static void test_log2(void) | ||||
| { | ||||
| 	EXPECT(1 == fastlog2(3)); | ||||
| 	EXPECT(2 == fastlog2(4)); | ||||
| 	EXPECT(2 == fastlog2(5)); | ||||
| } | ||||
|  | ||||
| static void test_sizes_to_segments(void) | ||||
| { | ||||
| 	uint64_t sizes[] = { 2, 3, 4, 5, 7, 9 }; | ||||
| 	/* .................0  1  2  3  4  5 */ | ||||
|  | ||||
| 	int seglen = 0; | ||||
| 	struct segment *segs = | ||||
| 		sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes)); | ||||
| 	EXPECT(segs[2].log == 3); | ||||
| 	EXPECT(segs[2].start == 5); | ||||
| 	EXPECT(segs[2].end == 6); | ||||
|  | ||||
| 	EXPECT(segs[1].log == 2); | ||||
| 	EXPECT(segs[1].start == 2); | ||||
| 	EXPECT(segs[1].end == 5); | ||||
| 	reftable_free(segs); | ||||
| } | ||||
|  | ||||
| static void test_sizes_to_segments_empty(void) | ||||
| { | ||||
| 	int seglen = 0; | ||||
| 	struct segment *segs = sizes_to_segments(&seglen, NULL, 0); | ||||
| 	EXPECT(seglen == 0); | ||||
| 	reftable_free(segs); | ||||
| } | ||||
|  | ||||
| static void test_sizes_to_segments_all_equal(void) | ||||
| { | ||||
| 	uint64_t sizes[] = { 5, 5 }; | ||||
|  | ||||
| 	int seglen = 0; | ||||
| 	struct segment *segs = | ||||
| 		sizes_to_segments(&seglen, sizes, ARRAY_SIZE(sizes)); | ||||
| 	EXPECT(seglen == 1); | ||||
| 	EXPECT(segs[0].start == 0); | ||||
| 	EXPECT(segs[0].end == 2); | ||||
| 	reftable_free(segs); | ||||
| } | ||||
|  | ||||
| static void test_suggest_compaction_segment(void) | ||||
| { | ||||
| 	uint64_t sizes[] = { 128, 64, 17, 16, 9, 9, 9, 16, 16 }; | ||||
| 	/* .................0    1    2  3   4  5  6 */ | ||||
| 	struct segment min = | ||||
| 		suggest_compaction_segment(sizes, ARRAY_SIZE(sizes)); | ||||
| 	EXPECT(min.start == 2); | ||||
| 	EXPECT(min.end == 7); | ||||
| } | ||||
|  | ||||
| static void test_suggest_compaction_segment_nothing(void) | ||||
| { | ||||
| 	uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 }; | ||||
| 	struct segment result = | ||||
| 		suggest_compaction_segment(sizes, ARRAY_SIZE(sizes)); | ||||
| 	EXPECT(result.start == result.end); | ||||
| } | ||||
|  | ||||
| static void test_reflog_expire(void) | ||||
| { | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	struct reftable_log_record logs[20] = { { NULL } }; | ||||
| 	int N = ARRAY_SIZE(logs) - 1; | ||||
| 	int i = 0; | ||||
| 	int err; | ||||
| 	struct reftable_log_expiry_config expiry = { | ||||
| 		.time = 10, | ||||
| 	}; | ||||
| 	struct reftable_log_record log = { NULL }; | ||||
|  | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	for (i = 1; i <= N; i++) { | ||||
| 		char buf[256]; | ||||
| 		snprintf(buf, sizeof(buf), "branch%02d", i); | ||||
|  | ||||
| 		logs[i].refname = xstrdup(buf); | ||||
| 		logs[i].update_index = i; | ||||
| 		logs[i].value_type = REFTABLE_LOG_UPDATE; | ||||
| 		logs[i].value.update.time = i; | ||||
| 		logs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ); | ||||
| 		logs[i].value.update.email = xstrdup("identity@invalid"); | ||||
| 		set_test_hash(logs[i].value.update.new_hash, i); | ||||
| 	} | ||||
|  | ||||
| 	for (i = 1; i <= N; i++) { | ||||
| 		struct write_log_arg arg = { | ||||
| 			.log = &logs[i], | ||||
| 			.update_index = reftable_stack_next_update_index(st), | ||||
| 		}; | ||||
| 		int err = reftable_stack_add(st, &write_test_log, &arg); | ||||
| 		EXPECT_ERR(err); | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_stack_compact_all(st, NULL); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_compact_all(st, &expiry); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_read_log(st, logs[9].refname, &log); | ||||
| 	EXPECT(err == 1); | ||||
|  | ||||
| 	err = reftable_stack_read_log(st, logs[11].refname, &log); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	expiry.min_update_index = 15; | ||||
| 	err = reftable_stack_compact_all(st, &expiry); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_read_log(st, logs[14].refname, &log); | ||||
| 	EXPECT(err == 1); | ||||
|  | ||||
| 	err = reftable_stack_read_log(st, logs[16].refname, &log); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	/* cleanup */ | ||||
| 	reftable_stack_destroy(st); | ||||
| 	for (i = 0; i <= N; i++) { | ||||
| 		reftable_log_record_release(&logs[i]); | ||||
| 	} | ||||
| 	clear_dir(dir); | ||||
| 	reftable_log_record_release(&log); | ||||
| } | ||||
|  | ||||
| static int write_nothing(struct reftable_writer *wr, void *arg) | ||||
| { | ||||
| 	reftable_writer_set_limits(wr, 1, 1); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static void test_empty_add(void) | ||||
| { | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	int err; | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	struct reftable_stack *st2 = NULL; | ||||
|  | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_add(st, &write_nothing, NULL); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_new_stack(&st2, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
| 	clear_dir(dir); | ||||
| 	reftable_stack_destroy(st); | ||||
| 	reftable_stack_destroy(st2); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_auto_compaction(void) | ||||
| { | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st = NULL; | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	int err, i; | ||||
| 	int N = 100; | ||||
|  | ||||
| 	err = reftable_new_stack(&st, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	st->disable_auto_compact = 1; /* call manually below for coverage. */ | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		char name[100]; | ||||
| 		struct reftable_ref_record ref = { | ||||
| 			.refname = name, | ||||
| 			.update_index = reftable_stack_next_update_index(st), | ||||
| 			.value_type = REFTABLE_REF_SYMREF, | ||||
| 			.value.symref = "master", | ||||
| 		}; | ||||
| 		snprintf(name, sizeof(name), "branch%04d", i); | ||||
|  | ||||
| 		err = reftable_stack_add(st, &write_test_ref, &ref); | ||||
| 		EXPECT_ERR(err); | ||||
|  | ||||
| 		err = reftable_stack_auto_compact(st); | ||||
| 		EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i)); | ||||
| 	} | ||||
|  | ||||
| 	EXPECT(reftable_stack_compaction_stats(st)->entries_written < | ||||
| 	       (uint64_t)(N * fastlog2(N))); | ||||
|  | ||||
| 	reftable_stack_destroy(st); | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_compaction_concurrent(void) | ||||
| { | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st1 = NULL, *st2 = NULL; | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	int err, i; | ||||
| 	int N = 3; | ||||
|  | ||||
| 	err = reftable_new_stack(&st1, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		char name[100]; | ||||
| 		struct reftable_ref_record ref = { | ||||
| 			.refname = name, | ||||
| 			.update_index = reftable_stack_next_update_index(st1), | ||||
| 			.value_type = REFTABLE_REF_SYMREF, | ||||
| 			.value.symref = "master", | ||||
| 		}; | ||||
| 		snprintf(name, sizeof(name), "branch%04d", i); | ||||
|  | ||||
| 		err = reftable_stack_add(st1, &write_test_ref, &ref); | ||||
| 		EXPECT_ERR(err); | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_new_stack(&st2, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_compact_all(st1, NULL); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	reftable_stack_destroy(st1); | ||||
| 	reftable_stack_destroy(st2); | ||||
|  | ||||
| 	EXPECT(count_dir_entries(dir) == 2); | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| static void unclean_stack_close(struct reftable_stack *st) | ||||
| { | ||||
| 	/* break abstraction boundary to simulate unclean shutdown. */ | ||||
| 	int i = 0; | ||||
| 	for (; i < st->readers_len; i++) { | ||||
| 		reftable_reader_free(st->readers[i]); | ||||
| 	} | ||||
| 	st->readers_len = 0; | ||||
| 	FREE_AND_NULL(st->readers); | ||||
| } | ||||
|  | ||||
| static void test_reftable_stack_compaction_concurrent_clean(void) | ||||
| { | ||||
| 	struct reftable_write_options cfg = { 0 }; | ||||
| 	struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL; | ||||
| 	char *dir = get_tmp_dir(__LINE__); | ||||
|  | ||||
| 	int err, i; | ||||
| 	int N = 3; | ||||
|  | ||||
| 	err = reftable_new_stack(&st1, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	for (i = 0; i < N; i++) { | ||||
| 		char name[100]; | ||||
| 		struct reftable_ref_record ref = { | ||||
| 			.refname = name, | ||||
| 			.update_index = reftable_stack_next_update_index(st1), | ||||
| 			.value_type = REFTABLE_REF_SYMREF, | ||||
| 			.value.symref = "master", | ||||
| 		}; | ||||
| 		snprintf(name, sizeof(name), "branch%04d", i); | ||||
|  | ||||
| 		err = reftable_stack_add(st1, &write_test_ref, &ref); | ||||
| 		EXPECT_ERR(err); | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_new_stack(&st2, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_compact_all(st1, NULL); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	unclean_stack_close(st1); | ||||
| 	unclean_stack_close(st2); | ||||
|  | ||||
| 	err = reftable_new_stack(&st3, dir, cfg); | ||||
| 	EXPECT_ERR(err); | ||||
|  | ||||
| 	err = reftable_stack_clean(st3); | ||||
| 	EXPECT_ERR(err); | ||||
| 	EXPECT(count_dir_entries(dir) == 2); | ||||
|  | ||||
| 	reftable_stack_destroy(st1); | ||||
| 	reftable_stack_destroy(st2); | ||||
| 	reftable_stack_destroy(st3); | ||||
|  | ||||
| 	clear_dir(dir); | ||||
| } | ||||
|  | ||||
| int stack_test_main(int argc, const char *argv[]) | ||||
| { | ||||
| 	RUN_TEST(test_empty_add); | ||||
| 	RUN_TEST(test_log2); | ||||
| 	RUN_TEST(test_names_equal); | ||||
| 	RUN_TEST(test_parse_names); | ||||
| 	RUN_TEST(test_read_file); | ||||
| 	RUN_TEST(test_reflog_expire); | ||||
| 	RUN_TEST(test_reftable_stack_add); | ||||
| 	RUN_TEST(test_reftable_stack_add_one); | ||||
| 	RUN_TEST(test_reftable_stack_auto_compaction); | ||||
| 	RUN_TEST(test_reftable_stack_compaction_concurrent); | ||||
| 	RUN_TEST(test_reftable_stack_compaction_concurrent_clean); | ||||
| 	RUN_TEST(test_reftable_stack_hash_id); | ||||
| 	RUN_TEST(test_reftable_stack_lock_failure); | ||||
| 	RUN_TEST(test_reftable_stack_log_normalize); | ||||
| 	RUN_TEST(test_reftable_stack_tombstone); | ||||
| 	RUN_TEST(test_reftable_stack_transaction_api); | ||||
| 	RUN_TEST(test_reftable_stack_update_index_check); | ||||
| 	RUN_TEST(test_reftable_stack_uptodate); | ||||
| 	RUN_TEST(test_reftable_stack_validate_refname); | ||||
| 	RUN_TEST(test_sizes_to_segments); | ||||
| 	RUN_TEST(test_sizes_to_segments_all_equal); | ||||
| 	RUN_TEST(test_sizes_to_segments_empty); | ||||
| 	RUN_TEST(test_suggest_compaction_segment); | ||||
| 	RUN_TEST(test_suggest_compaction_segment_nothing); | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -0,0 +1,32 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef SYSTEM_H | ||||
| #define SYSTEM_H | ||||
|  | ||||
| /* This header glues the reftable library to the rest of Git */ | ||||
|  | ||||
| #include "git-compat-util.h" | ||||
| #include "strbuf.h" | ||||
| #include "hash.h" /* hash ID, sizes.*/ | ||||
| #include "dir.h" /* remove_dir_recursively, for tests.*/ | ||||
|  | ||||
| #include <zlib.h> | ||||
|  | ||||
| #ifdef NO_UNCOMPRESS2 | ||||
| /* | ||||
|  * This is uncompress2, which is only available in zlib >= 1.2.9 | ||||
|  * (released as of early 2017) | ||||
|  */ | ||||
| int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source, | ||||
| 		uLong *sourceLen); | ||||
| #endif | ||||
|  | ||||
| int hash_size(uint32_t id); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,23 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "system.h" | ||||
| #include "test_framework.h" | ||||
|  | ||||
| #include "basics.h" | ||||
|  | ||||
| void set_test_hash(uint8_t *p, int i) | ||||
| { | ||||
| 	memset(p, (uint8_t)i, hash_size(GIT_SHA1_FORMAT_ID)); | ||||
| } | ||||
|  | ||||
| ssize_t strbuf_add_void(void *b, const void *data, size_t sz) | ||||
| { | ||||
| 	strbuf_add(b, data, sz); | ||||
| 	return sz; | ||||
| } | ||||
|  | @ -0,0 +1,53 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef TEST_FRAMEWORK_H | ||||
| #define TEST_FRAMEWORK_H | ||||
|  | ||||
| #include "system.h" | ||||
| #include "reftable-error.h" | ||||
|  | ||||
| #define EXPECT_ERR(c)                                                  \ | ||||
| 	if (c != 0) {                                                  \ | ||||
| 		fflush(stderr);                                        \ | ||||
| 		fflush(stdout);                                        \ | ||||
| 		fprintf(stderr, "%s: %d: error == %d (%s), want 0\n",  \ | ||||
| 			__FILE__, __LINE__, c, reftable_error_str(c)); \ | ||||
| 		abort();                                               \ | ||||
| 	} | ||||
|  | ||||
| #define EXPECT_STREQ(a, b)                                               \ | ||||
| 	if (strcmp(a, b)) {                                              \ | ||||
| 		fflush(stderr);                                          \ | ||||
| 		fflush(stdout);                                          \ | ||||
| 		fprintf(stderr, "%s:%d: %s (%s) != %s (%s)\n", __FILE__, \ | ||||
| 			__LINE__, #a, a, #b, b);                         \ | ||||
| 		abort();                                                 \ | ||||
| 	} | ||||
|  | ||||
| #define EXPECT(c)                                                          \ | ||||
| 	if (!(c)) {                                                        \ | ||||
| 		fflush(stderr);                                            \ | ||||
| 		fflush(stdout);                                            \ | ||||
| 		fprintf(stderr, "%s: %d: failed assertion %s\n", __FILE__, \ | ||||
| 			__LINE__, #c);                                     \ | ||||
| 		abort();                                                   \ | ||||
| 	} | ||||
|  | ||||
| #define RUN_TEST(f)                          \ | ||||
| 	fprintf(stderr, "running %s\n", #f); \ | ||||
| 	fflush(stderr);                      \ | ||||
| 	f(); | ||||
|  | ||||
| void set_test_hash(uint8_t *p, int i); | ||||
|  | ||||
| /* Like strbuf_add, but suitable for passing to reftable_new_writer | ||||
|  */ | ||||
| ssize_t strbuf_add_void(void *b, const void *data, size_t sz); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,63 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "tree.h" | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "system.h" | ||||
|  | ||||
| struct tree_node *tree_search(void *key, struct tree_node **rootp, | ||||
| 			      int (*compare)(const void *, const void *), | ||||
| 			      int insert) | ||||
| { | ||||
| 	int res; | ||||
| 	if (*rootp == NULL) { | ||||
| 		if (!insert) { | ||||
| 			return NULL; | ||||
| 		} else { | ||||
| 			struct tree_node *n = | ||||
| 				reftable_calloc(sizeof(struct tree_node)); | ||||
| 			n->key = key; | ||||
| 			*rootp = n; | ||||
| 			return *rootp; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	res = compare(key, (*rootp)->key); | ||||
| 	if (res < 0) | ||||
| 		return tree_search(key, &(*rootp)->left, compare, insert); | ||||
| 	else if (res > 0) | ||||
| 		return tree_search(key, &(*rootp)->right, compare, insert); | ||||
| 	return *rootp; | ||||
| } | ||||
|  | ||||
| void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key), | ||||
| 		void *arg) | ||||
| { | ||||
| 	if (t->left) { | ||||
| 		infix_walk(t->left, action, arg); | ||||
| 	} | ||||
| 	action(arg, t->key); | ||||
| 	if (t->right) { | ||||
| 		infix_walk(t->right, action, arg); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void tree_free(struct tree_node *t) | ||||
| { | ||||
| 	if (t == NULL) { | ||||
| 		return; | ||||
| 	} | ||||
| 	if (t->left) { | ||||
| 		tree_free(t->left); | ||||
| 	} | ||||
| 	if (t->right) { | ||||
| 		tree_free(t->right); | ||||
| 	} | ||||
| 	reftable_free(t); | ||||
| } | ||||
|  | @ -0,0 +1,34 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef TREE_H | ||||
| #define TREE_H | ||||
|  | ||||
| /* tree_node is a generic binary search tree. */ | ||||
| struct tree_node { | ||||
| 	void *key; | ||||
| 	struct tree_node *left, *right; | ||||
| }; | ||||
|  | ||||
| /* looks for `key` in `rootp` using `compare` as comparison function. If insert | ||||
|  * is set, insert the key if it's not found. Else, return NULL. | ||||
|  */ | ||||
| struct tree_node *tree_search(void *key, struct tree_node **rootp, | ||||
| 			      int (*compare)(const void *, const void *), | ||||
| 			      int insert); | ||||
|  | ||||
| /* performs an infix walk of the tree. */ | ||||
| void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key), | ||||
| 		void *arg); | ||||
|  | ||||
| /* | ||||
|  * deallocates the tree nodes recursively. Keys should be deallocated separately | ||||
|  * by walking over the tree. */ | ||||
| void tree_free(struct tree_node *t); | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,61 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "tree.h" | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "record.h" | ||||
| #include "test_framework.h" | ||||
| #include "reftable-tests.h" | ||||
|  | ||||
| static int test_compare(const void *a, const void *b) | ||||
| { | ||||
| 	return (char *)a - (char *)b; | ||||
| } | ||||
|  | ||||
| struct curry { | ||||
| 	void *last; | ||||
| }; | ||||
|  | ||||
| static void check_increasing(void *arg, void *key) | ||||
| { | ||||
| 	struct curry *c = arg; | ||||
| 	if (c->last) { | ||||
| 		EXPECT(test_compare(c->last, key) < 0); | ||||
| 	} | ||||
| 	c->last = key; | ||||
| } | ||||
|  | ||||
| static void test_tree(void) | ||||
| { | ||||
| 	struct tree_node *root = NULL; | ||||
|  | ||||
| 	void *values[11] = { NULL }; | ||||
| 	struct tree_node *nodes[11] = { NULL }; | ||||
| 	int i = 1; | ||||
| 	struct curry c = { NULL }; | ||||
| 	do { | ||||
| 		nodes[i] = tree_search(values + i, &root, &test_compare, 1); | ||||
| 		i = (i * 7) % 11; | ||||
| 	} while (i != 1); | ||||
|  | ||||
| 	for (i = 1; i < ARRAY_SIZE(nodes); i++) { | ||||
| 		EXPECT(values + i == nodes[i]->key); | ||||
| 		EXPECT(nodes[i] == | ||||
| 		       tree_search(values + i, &root, &test_compare, 0)); | ||||
| 	} | ||||
|  | ||||
| 	infix_walk(root, check_increasing, &c); | ||||
| 	tree_free(root); | ||||
| } | ||||
|  | ||||
| int tree_test_main(int argc, const char *argv[]) | ||||
| { | ||||
| 	RUN_TEST(test_tree); | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -0,0 +1,690 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #include "writer.h" | ||||
|  | ||||
| #include "system.h" | ||||
|  | ||||
| #include "block.h" | ||||
| #include "constants.h" | ||||
| #include "record.h" | ||||
| #include "tree.h" | ||||
| #include "reftable-error.h" | ||||
|  | ||||
| /* finishes a block, and writes it to storage */ | ||||
| static int writer_flush_block(struct reftable_writer *w); | ||||
|  | ||||
| /* deallocates memory related to the index */ | ||||
| static void writer_clear_index(struct reftable_writer *w); | ||||
|  | ||||
| /* finishes writing a 'r' (refs) or 'g' (reflogs) section */ | ||||
| static int writer_finish_public_section(struct reftable_writer *w); | ||||
|  | ||||
| static struct reftable_block_stats * | ||||
| writer_reftable_block_stats(struct reftable_writer *w, uint8_t typ) | ||||
| { | ||||
| 	switch (typ) { | ||||
| 	case 'r': | ||||
| 		return &w->stats.ref_stats; | ||||
| 	case 'o': | ||||
| 		return &w->stats.obj_stats; | ||||
| 	case 'i': | ||||
| 		return &w->stats.idx_stats; | ||||
| 	case 'g': | ||||
| 		return &w->stats.log_stats; | ||||
| 	} | ||||
| 	abort(); | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| /* write data, queuing the padding for the next write. Returns negative for | ||||
|  * error. */ | ||||
| static int padded_write(struct reftable_writer *w, uint8_t *data, size_t len, | ||||
| 			int padding) | ||||
| { | ||||
| 	int n = 0; | ||||
| 	if (w->pending_padding > 0) { | ||||
| 		uint8_t *zeroed = reftable_calloc(w->pending_padding); | ||||
| 		int n = w->write(w->write_arg, zeroed, w->pending_padding); | ||||
| 		if (n < 0) | ||||
| 			return n; | ||||
|  | ||||
| 		w->pending_padding = 0; | ||||
| 		reftable_free(zeroed); | ||||
| 	} | ||||
|  | ||||
| 	w->pending_padding = padding; | ||||
| 	n = w->write(w->write_arg, data, len); | ||||
| 	if (n < 0) | ||||
| 		return n; | ||||
| 	n += padding; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static void options_set_defaults(struct reftable_write_options *opts) | ||||
| { | ||||
| 	if (opts->restart_interval == 0) { | ||||
| 		opts->restart_interval = 16; | ||||
| 	} | ||||
|  | ||||
| 	if (opts->hash_id == 0) { | ||||
| 		opts->hash_id = GIT_SHA1_FORMAT_ID; | ||||
| 	} | ||||
| 	if (opts->block_size == 0) { | ||||
| 		opts->block_size = DEFAULT_BLOCK_SIZE; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static int writer_version(struct reftable_writer *w) | ||||
| { | ||||
| 	return (w->opts.hash_id == 0 || w->opts.hash_id == GIT_SHA1_FORMAT_ID) ? | ||||
| 			     1 : | ||||
| 			     2; | ||||
| } | ||||
|  | ||||
| static int writer_write_header(struct reftable_writer *w, uint8_t *dest) | ||||
| { | ||||
| 	memcpy(dest, "REFT", 4); | ||||
|  | ||||
| 	dest[4] = writer_version(w); | ||||
|  | ||||
| 	put_be24(dest + 5, w->opts.block_size); | ||||
| 	put_be64(dest + 8, w->min_update_index); | ||||
| 	put_be64(dest + 16, w->max_update_index); | ||||
| 	if (writer_version(w) == 2) { | ||||
| 		put_be32(dest + 24, w->opts.hash_id); | ||||
| 	} | ||||
| 	return header_size(writer_version(w)); | ||||
| } | ||||
|  | ||||
| static void writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ) | ||||
| { | ||||
| 	int block_start = 0; | ||||
| 	if (w->next == 0) { | ||||
| 		block_start = header_size(writer_version(w)); | ||||
| 	} | ||||
|  | ||||
| 	strbuf_release(&w->last_key); | ||||
| 	block_writer_init(&w->block_writer_data, typ, w->block, | ||||
| 			  w->opts.block_size, block_start, | ||||
| 			  hash_size(w->opts.hash_id)); | ||||
| 	w->block_writer = &w->block_writer_data; | ||||
| 	w->block_writer->restart_interval = w->opts.restart_interval; | ||||
| } | ||||
|  | ||||
| static struct strbuf reftable_empty_strbuf = STRBUF_INIT; | ||||
|  | ||||
| struct reftable_writer * | ||||
| reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t), | ||||
| 		    void *writer_arg, struct reftable_write_options *opts) | ||||
| { | ||||
| 	struct reftable_writer *wp = | ||||
| 		reftable_calloc(sizeof(struct reftable_writer)); | ||||
| 	strbuf_init(&wp->block_writer_data.last_key, 0); | ||||
| 	options_set_defaults(opts); | ||||
| 	if (opts->block_size >= (1 << 24)) { | ||||
| 		/* TODO - error return? */ | ||||
| 		abort(); | ||||
| 	} | ||||
| 	wp->last_key = reftable_empty_strbuf; | ||||
| 	wp->block = reftable_calloc(opts->block_size); | ||||
| 	wp->write = writer_func; | ||||
| 	wp->write_arg = writer_arg; | ||||
| 	wp->opts = *opts; | ||||
| 	writer_reinit_block_writer(wp, BLOCK_TYPE_REF); | ||||
|  | ||||
| 	return wp; | ||||
| } | ||||
|  | ||||
| void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min, | ||||
| 				uint64_t max) | ||||
| { | ||||
| 	w->min_update_index = min; | ||||
| 	w->max_update_index = max; | ||||
| } | ||||
|  | ||||
| void reftable_writer_free(struct reftable_writer *w) | ||||
| { | ||||
| 	reftable_free(w->block); | ||||
| 	reftable_free(w); | ||||
| } | ||||
|  | ||||
| struct obj_index_tree_node { | ||||
| 	struct strbuf hash; | ||||
| 	uint64_t *offsets; | ||||
| 	size_t offset_len; | ||||
| 	size_t offset_cap; | ||||
| }; | ||||
|  | ||||
| #define OBJ_INDEX_TREE_NODE_INIT    \ | ||||
| 	{                           \ | ||||
| 		.hash = STRBUF_INIT \ | ||||
| 	} | ||||
|  | ||||
| static int obj_index_tree_node_compare(const void *a, const void *b) | ||||
| { | ||||
| 	return strbuf_cmp(&((const struct obj_index_tree_node *)a)->hash, | ||||
| 			  &((const struct obj_index_tree_node *)b)->hash); | ||||
| } | ||||
|  | ||||
| static void writer_index_hash(struct reftable_writer *w, struct strbuf *hash) | ||||
| { | ||||
| 	uint64_t off = w->next; | ||||
|  | ||||
| 	struct obj_index_tree_node want = { .hash = *hash }; | ||||
|  | ||||
| 	struct tree_node *node = tree_search(&want, &w->obj_index_tree, | ||||
| 					     &obj_index_tree_node_compare, 0); | ||||
| 	struct obj_index_tree_node *key = NULL; | ||||
| 	if (node == NULL) { | ||||
| 		struct obj_index_tree_node empty = OBJ_INDEX_TREE_NODE_INIT; | ||||
| 		key = reftable_malloc(sizeof(struct obj_index_tree_node)); | ||||
| 		*key = empty; | ||||
|  | ||||
| 		strbuf_reset(&key->hash); | ||||
| 		strbuf_addbuf(&key->hash, hash); | ||||
| 		tree_search((void *)key, &w->obj_index_tree, | ||||
| 			    &obj_index_tree_node_compare, 1); | ||||
| 	} else { | ||||
| 		key = node->key; | ||||
| 	} | ||||
|  | ||||
| 	if (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (key->offset_len == key->offset_cap) { | ||||
| 		key->offset_cap = 2 * key->offset_cap + 1; | ||||
| 		key->offsets = reftable_realloc( | ||||
| 			key->offsets, sizeof(uint64_t) * key->offset_cap); | ||||
| 	} | ||||
|  | ||||
| 	key->offsets[key->offset_len++] = off; | ||||
| } | ||||
|  | ||||
| static int writer_add_record(struct reftable_writer *w, | ||||
| 			     struct reftable_record *rec) | ||||
| { | ||||
| 	struct strbuf key = STRBUF_INIT; | ||||
| 	int err = -1; | ||||
| 	reftable_record_key(rec, &key); | ||||
| 	if (strbuf_cmp(&w->last_key, &key) >= 0) { | ||||
| 		err = REFTABLE_API_ERROR; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	strbuf_reset(&w->last_key); | ||||
| 	strbuf_addbuf(&w->last_key, &key); | ||||
| 	if (w->block_writer == NULL) { | ||||
| 		writer_reinit_block_writer(w, reftable_record_type(rec)); | ||||
| 	} | ||||
|  | ||||
| 	assert(block_writer_type(w->block_writer) == reftable_record_type(rec)); | ||||
|  | ||||
| 	if (block_writer_add(w->block_writer, rec) == 0) { | ||||
| 		err = 0; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	err = writer_flush_block(w); | ||||
| 	if (err < 0) { | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	writer_reinit_block_writer(w, reftable_record_type(rec)); | ||||
| 	err = block_writer_add(w->block_writer, rec); | ||||
| 	if (err < 0) { | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| 	err = 0; | ||||
| done: | ||||
| 	strbuf_release(&key); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| int reftable_writer_add_ref(struct reftable_writer *w, | ||||
| 			    struct reftable_ref_record *ref) | ||||
| { | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	struct reftable_ref_record copy = *ref; | ||||
| 	int err = 0; | ||||
|  | ||||
| 	if (ref->refname == NULL) | ||||
| 		return REFTABLE_API_ERROR; | ||||
| 	if (ref->update_index < w->min_update_index || | ||||
| 	    ref->update_index > w->max_update_index) | ||||
| 		return REFTABLE_API_ERROR; | ||||
|  | ||||
| 	reftable_record_from_ref(&rec, ©); | ||||
| 	copy.update_index -= w->min_update_index; | ||||
|  | ||||
| 	err = writer_add_record(w, &rec); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
|  | ||||
| 	if (!w->opts.skip_index_objects && reftable_ref_record_val1(ref)) { | ||||
| 		struct strbuf h = STRBUF_INIT; | ||||
| 		strbuf_add(&h, (char *)reftable_ref_record_val1(ref), | ||||
| 			   hash_size(w->opts.hash_id)); | ||||
| 		writer_index_hash(w, &h); | ||||
| 		strbuf_release(&h); | ||||
| 	} | ||||
|  | ||||
| 	if (!w->opts.skip_index_objects && reftable_ref_record_val2(ref)) { | ||||
| 		struct strbuf h = STRBUF_INIT; | ||||
| 		strbuf_add(&h, reftable_ref_record_val2(ref), | ||||
| 			   hash_size(w->opts.hash_id)); | ||||
| 		writer_index_hash(w, &h); | ||||
| 		strbuf_release(&h); | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int reftable_writer_add_refs(struct reftable_writer *w, | ||||
| 			     struct reftable_ref_record *refs, int n) | ||||
| { | ||||
| 	int err = 0; | ||||
| 	int i = 0; | ||||
| 	QSORT(refs, n, reftable_ref_record_compare_name); | ||||
| 	for (i = 0; err == 0 && i < n; i++) { | ||||
| 		err = reftable_writer_add_ref(w, &refs[i]); | ||||
| 	} | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static int reftable_writer_add_log_verbatim(struct reftable_writer *w, | ||||
| 					    struct reftable_log_record *log) | ||||
| { | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	if (w->block_writer && | ||||
| 	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) { | ||||
| 		int err = writer_finish_public_section(w); | ||||
| 		if (err < 0) | ||||
| 			return err; | ||||
| 	} | ||||
|  | ||||
| 	w->next -= w->pending_padding; | ||||
| 	w->pending_padding = 0; | ||||
|  | ||||
| 	reftable_record_from_log(&rec, log); | ||||
| 	return writer_add_record(w, &rec); | ||||
| } | ||||
|  | ||||
| int reftable_writer_add_log(struct reftable_writer *w, | ||||
| 			    struct reftable_log_record *log) | ||||
| { | ||||
| 	char *input_log_message = NULL; | ||||
| 	struct strbuf cleaned_message = STRBUF_INIT; | ||||
| 	int err = 0; | ||||
|  | ||||
| 	if (log->value_type == REFTABLE_LOG_DELETION) | ||||
| 		return reftable_writer_add_log_verbatim(w, log); | ||||
|  | ||||
| 	if (log->refname == NULL) | ||||
| 		return REFTABLE_API_ERROR; | ||||
|  | ||||
| 	input_log_message = log->value.update.message; | ||||
| 	if (!w->opts.exact_log_message && log->value.update.message) { | ||||
| 		strbuf_addstr(&cleaned_message, log->value.update.message); | ||||
| 		while (cleaned_message.len && | ||||
| 		       cleaned_message.buf[cleaned_message.len - 1] == '\n') | ||||
| 			strbuf_setlen(&cleaned_message, | ||||
| 				      cleaned_message.len - 1); | ||||
| 		if (strchr(cleaned_message.buf, '\n')) { | ||||
| 			/* multiple lines not allowed. */ | ||||
| 			err = REFTABLE_API_ERROR; | ||||
| 			goto done; | ||||
| 		} | ||||
| 		strbuf_addstr(&cleaned_message, "\n"); | ||||
| 		log->value.update.message = cleaned_message.buf; | ||||
| 	} | ||||
|  | ||||
| 	err = reftable_writer_add_log_verbatim(w, log); | ||||
| 	log->value.update.message = input_log_message; | ||||
| done: | ||||
| 	strbuf_release(&cleaned_message); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| int reftable_writer_add_logs(struct reftable_writer *w, | ||||
| 			     struct reftable_log_record *logs, int n) | ||||
| { | ||||
| 	int err = 0; | ||||
| 	int i = 0; | ||||
| 	QSORT(logs, n, reftable_log_record_compare_key); | ||||
|  | ||||
| 	for (i = 0; err == 0 && i < n; i++) { | ||||
| 		err = reftable_writer_add_log(w, &logs[i]); | ||||
| 	} | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static int writer_finish_section(struct reftable_writer *w) | ||||
| { | ||||
| 	uint8_t typ = block_writer_type(w->block_writer); | ||||
| 	uint64_t index_start = 0; | ||||
| 	int max_level = 0; | ||||
| 	int threshold = w->opts.unpadded ? 1 : 3; | ||||
| 	int before_blocks = w->stats.idx_stats.blocks; | ||||
| 	int err = writer_flush_block(w); | ||||
| 	int i = 0; | ||||
| 	struct reftable_block_stats *bstats = NULL; | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
|  | ||||
| 	while (w->index_len > threshold) { | ||||
| 		struct reftable_index_record *idx = NULL; | ||||
| 		int idx_len = 0; | ||||
|  | ||||
| 		max_level++; | ||||
| 		index_start = w->next; | ||||
| 		writer_reinit_block_writer(w, BLOCK_TYPE_INDEX); | ||||
|  | ||||
| 		idx = w->index; | ||||
| 		idx_len = w->index_len; | ||||
|  | ||||
| 		w->index = NULL; | ||||
| 		w->index_len = 0; | ||||
| 		w->index_cap = 0; | ||||
| 		for (i = 0; i < idx_len; i++) { | ||||
| 			struct reftable_record rec = { NULL }; | ||||
| 			reftable_record_from_index(&rec, idx + i); | ||||
| 			if (block_writer_add(w->block_writer, &rec) == 0) { | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			err = writer_flush_block(w); | ||||
| 			if (err < 0) | ||||
| 				return err; | ||||
|  | ||||
| 			writer_reinit_block_writer(w, BLOCK_TYPE_INDEX); | ||||
|  | ||||
| 			err = block_writer_add(w->block_writer, &rec); | ||||
| 			if (err != 0) { | ||||
| 				/* write into fresh block should always succeed | ||||
| 				 */ | ||||
| 				abort(); | ||||
| 			} | ||||
| 		} | ||||
| 		for (i = 0; i < idx_len; i++) { | ||||
| 			strbuf_release(&idx[i].last_key); | ||||
| 		} | ||||
| 		reftable_free(idx); | ||||
| 	} | ||||
|  | ||||
| 	writer_clear_index(w); | ||||
|  | ||||
| 	err = writer_flush_block(w); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
|  | ||||
| 	bstats = writer_reftable_block_stats(w, typ); | ||||
| 	bstats->index_blocks = w->stats.idx_stats.blocks - before_blocks; | ||||
| 	bstats->index_offset = index_start; | ||||
| 	bstats->max_index_level = max_level; | ||||
|  | ||||
| 	/* Reinit lastKey, as the next section can start with any key. */ | ||||
| 	w->last_key.len = 0; | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| struct common_prefix_arg { | ||||
| 	struct strbuf *last; | ||||
| 	int max; | ||||
| }; | ||||
|  | ||||
| static void update_common(void *void_arg, void *key) | ||||
| { | ||||
| 	struct common_prefix_arg *arg = void_arg; | ||||
| 	struct obj_index_tree_node *entry = key; | ||||
| 	if (arg->last) { | ||||
| 		int n = common_prefix_size(&entry->hash, arg->last); | ||||
| 		if (n > arg->max) { | ||||
| 			arg->max = n; | ||||
| 		} | ||||
| 	} | ||||
| 	arg->last = &entry->hash; | ||||
| } | ||||
|  | ||||
| struct write_record_arg { | ||||
| 	struct reftable_writer *w; | ||||
| 	int err; | ||||
| }; | ||||
|  | ||||
| static void write_object_record(void *void_arg, void *key) | ||||
| { | ||||
| 	struct write_record_arg *arg = void_arg; | ||||
| 	struct obj_index_tree_node *entry = key; | ||||
| 	struct reftable_obj_record obj_rec = { | ||||
| 		.hash_prefix = (uint8_t *)entry->hash.buf, | ||||
| 		.hash_prefix_len = arg->w->stats.object_id_len, | ||||
| 		.offsets = entry->offsets, | ||||
| 		.offset_len = entry->offset_len, | ||||
| 	}; | ||||
| 	struct reftable_record rec = { NULL }; | ||||
| 	if (arg->err < 0) | ||||
| 		goto done; | ||||
|  | ||||
| 	reftable_record_from_obj(&rec, &obj_rec); | ||||
| 	arg->err = block_writer_add(arg->w->block_writer, &rec); | ||||
| 	if (arg->err == 0) | ||||
| 		goto done; | ||||
|  | ||||
| 	arg->err = writer_flush_block(arg->w); | ||||
| 	if (arg->err < 0) | ||||
| 		goto done; | ||||
|  | ||||
| 	writer_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ); | ||||
| 	arg->err = block_writer_add(arg->w->block_writer, &rec); | ||||
| 	if (arg->err == 0) | ||||
| 		goto done; | ||||
| 	obj_rec.offset_len = 0; | ||||
| 	arg->err = block_writer_add(arg->w->block_writer, &rec); | ||||
|  | ||||
| 	/* Should be able to write into a fresh block. */ | ||||
| 	assert(arg->err == 0); | ||||
|  | ||||
| done:; | ||||
| } | ||||
|  | ||||
| static void object_record_free(void *void_arg, void *key) | ||||
| { | ||||
| 	struct obj_index_tree_node *entry = key; | ||||
|  | ||||
| 	FREE_AND_NULL(entry->offsets); | ||||
| 	strbuf_release(&entry->hash); | ||||
| 	reftable_free(entry); | ||||
| } | ||||
|  | ||||
| static int writer_dump_object_index(struct reftable_writer *w) | ||||
| { | ||||
| 	struct write_record_arg closure = { .w = w }; | ||||
| 	struct common_prefix_arg common = { NULL }; | ||||
| 	if (w->obj_index_tree) { | ||||
| 		infix_walk(w->obj_index_tree, &update_common, &common); | ||||
| 	} | ||||
| 	w->stats.object_id_len = common.max + 1; | ||||
|  | ||||
| 	writer_reinit_block_writer(w, BLOCK_TYPE_OBJ); | ||||
|  | ||||
| 	if (w->obj_index_tree) { | ||||
| 		infix_walk(w->obj_index_tree, &write_object_record, &closure); | ||||
| 	} | ||||
|  | ||||
| 	if (closure.err < 0) | ||||
| 		return closure.err; | ||||
| 	return writer_finish_section(w); | ||||
| } | ||||
|  | ||||
| static int writer_finish_public_section(struct reftable_writer *w) | ||||
| { | ||||
| 	uint8_t typ = 0; | ||||
| 	int err = 0; | ||||
|  | ||||
| 	if (w->block_writer == NULL) | ||||
| 		return 0; | ||||
|  | ||||
| 	typ = block_writer_type(w->block_writer); | ||||
| 	err = writer_finish_section(w); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
| 	if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects && | ||||
| 	    w->stats.ref_stats.index_blocks > 0) { | ||||
| 		err = writer_dump_object_index(w); | ||||
| 		if (err < 0) | ||||
| 			return err; | ||||
| 	} | ||||
|  | ||||
| 	if (w->obj_index_tree) { | ||||
| 		infix_walk(w->obj_index_tree, &object_record_free, NULL); | ||||
| 		tree_free(w->obj_index_tree); | ||||
| 		w->obj_index_tree = NULL; | ||||
| 	} | ||||
|  | ||||
| 	w->block_writer = NULL; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int reftable_writer_close(struct reftable_writer *w) | ||||
| { | ||||
| 	uint8_t footer[72]; | ||||
| 	uint8_t *p = footer; | ||||
| 	int err = writer_finish_public_section(w); | ||||
| 	int empty_table = w->next == 0; | ||||
| 	if (err != 0) | ||||
| 		goto done; | ||||
| 	w->pending_padding = 0; | ||||
| 	if (empty_table) { | ||||
| 		/* Empty tables need a header anyway. */ | ||||
| 		uint8_t header[28]; | ||||
| 		int n = writer_write_header(w, header); | ||||
| 		err = padded_write(w, header, n, 0); | ||||
| 		if (err < 0) | ||||
| 			goto done; | ||||
| 	} | ||||
|  | ||||
| 	p += writer_write_header(w, footer); | ||||
| 	put_be64(p, w->stats.ref_stats.index_offset); | ||||
| 	p += 8; | ||||
| 	put_be64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len); | ||||
| 	p += 8; | ||||
| 	put_be64(p, w->stats.obj_stats.index_offset); | ||||
| 	p += 8; | ||||
|  | ||||
| 	put_be64(p, w->stats.log_stats.offset); | ||||
| 	p += 8; | ||||
| 	put_be64(p, w->stats.log_stats.index_offset); | ||||
| 	p += 8; | ||||
|  | ||||
| 	put_be32(p, crc32(0, footer, p - footer)); | ||||
| 	p += 4; | ||||
|  | ||||
| 	err = padded_write(w, footer, footer_size(writer_version(w)), 0); | ||||
| 	if (err < 0) | ||||
| 		goto done; | ||||
|  | ||||
| 	if (empty_table) { | ||||
| 		err = REFTABLE_EMPTY_TABLE_ERROR; | ||||
| 		goto done; | ||||
| 	} | ||||
|  | ||||
| done: | ||||
| 	/* free up memory. */ | ||||
| 	block_writer_release(&w->block_writer_data); | ||||
| 	writer_clear_index(w); | ||||
| 	strbuf_release(&w->last_key); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static void writer_clear_index(struct reftable_writer *w) | ||||
| { | ||||
| 	int i = 0; | ||||
| 	for (i = 0; i < w->index_len; i++) { | ||||
| 		strbuf_release(&w->index[i].last_key); | ||||
| 	} | ||||
|  | ||||
| 	FREE_AND_NULL(w->index); | ||||
| 	w->index_len = 0; | ||||
| 	w->index_cap = 0; | ||||
| } | ||||
|  | ||||
| static const int debug = 0; | ||||
|  | ||||
| static int writer_flush_nonempty_block(struct reftable_writer *w) | ||||
| { | ||||
| 	uint8_t typ = block_writer_type(w->block_writer); | ||||
| 	struct reftable_block_stats *bstats = | ||||
| 		writer_reftable_block_stats(w, typ); | ||||
| 	uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0; | ||||
| 	int raw_bytes = block_writer_finish(w->block_writer); | ||||
| 	int padding = 0; | ||||
| 	int err = 0; | ||||
| 	struct reftable_index_record ir = { .last_key = STRBUF_INIT }; | ||||
| 	if (raw_bytes < 0) | ||||
| 		return raw_bytes; | ||||
|  | ||||
| 	if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) { | ||||
| 		padding = w->opts.block_size - raw_bytes; | ||||
| 	} | ||||
|  | ||||
| 	if (block_typ_off > 0) { | ||||
| 		bstats->offset = block_typ_off; | ||||
| 	} | ||||
|  | ||||
| 	bstats->entries += w->block_writer->entries; | ||||
| 	bstats->restarts += w->block_writer->restart_len; | ||||
| 	bstats->blocks++; | ||||
| 	w->stats.blocks++; | ||||
|  | ||||
| 	if (debug) { | ||||
| 		fprintf(stderr, "block %c off %" PRIu64 " sz %d (%d)\n", typ, | ||||
| 			w->next, raw_bytes, | ||||
| 			get_be24(w->block + w->block_writer->header_off + 1)); | ||||
| 	} | ||||
|  | ||||
| 	if (w->next == 0) { | ||||
| 		writer_write_header(w, w->block); | ||||
| 	} | ||||
|  | ||||
| 	err = padded_write(w, w->block, raw_bytes, padding); | ||||
| 	if (err < 0) | ||||
| 		return err; | ||||
|  | ||||
| 	if (w->index_cap == w->index_len) { | ||||
| 		w->index_cap = 2 * w->index_cap + 1; | ||||
| 		w->index = reftable_realloc( | ||||
| 			w->index, | ||||
| 			sizeof(struct reftable_index_record) * w->index_cap); | ||||
| 	} | ||||
|  | ||||
| 	ir.offset = w->next; | ||||
| 	strbuf_reset(&ir.last_key); | ||||
| 	strbuf_addbuf(&ir.last_key, &w->block_writer->last_key); | ||||
| 	w->index[w->index_len] = ir; | ||||
|  | ||||
| 	w->index_len++; | ||||
| 	w->next += padding + raw_bytes; | ||||
| 	w->block_writer = NULL; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int writer_flush_block(struct reftable_writer *w) | ||||
| { | ||||
| 	if (w->block_writer == NULL) | ||||
| 		return 0; | ||||
| 	if (w->block_writer->entries == 0) | ||||
| 		return 0; | ||||
| 	return writer_flush_nonempty_block(w); | ||||
| } | ||||
|  | ||||
| const struct reftable_stats *writer_stats(struct reftable_writer *w) | ||||
| { | ||||
| 	return &w->stats; | ||||
| } | ||||
|  | @ -0,0 +1,50 @@ | |||
| /* | ||||
| Copyright 2020 Google LLC | ||||
|  | ||||
| Use of this source code is governed by a BSD-style | ||||
| license that can be found in the LICENSE file or at | ||||
| https://developers.google.com/open-source/licenses/bsd | ||||
| */ | ||||
|  | ||||
| #ifndef WRITER_H | ||||
| #define WRITER_H | ||||
|  | ||||
| #include "basics.h" | ||||
| #include "block.h" | ||||
| #include "tree.h" | ||||
| #include "reftable-writer.h" | ||||
|  | ||||
| struct reftable_writer { | ||||
| 	ssize_t (*write)(void *, const void *, size_t); | ||||
| 	void *write_arg; | ||||
| 	int pending_padding; | ||||
| 	struct strbuf last_key; | ||||
|  | ||||
| 	/* offset of next block to write. */ | ||||
| 	uint64_t next; | ||||
| 	uint64_t min_update_index, max_update_index; | ||||
| 	struct reftable_write_options opts; | ||||
|  | ||||
| 	/* memory buffer for writing */ | ||||
| 	uint8_t *block; | ||||
|  | ||||
| 	/* writer for the current section. NULL or points to | ||||
| 	 * block_writer_data */ | ||||
| 	struct block_writer *block_writer; | ||||
|  | ||||
| 	struct block_writer block_writer_data; | ||||
|  | ||||
| 	/* pending index records for the current section */ | ||||
| 	struct reftable_index_record *index; | ||||
| 	size_t index_len; | ||||
| 	size_t index_cap; | ||||
|  | ||||
| 	/* | ||||
| 	 * tree for use with tsearch; used to populate the 'o' inverse OID | ||||
| 	 * map */ | ||||
| 	struct tree_node *obj_index_tree; | ||||
|  | ||||
| 	struct reftable_stats stats; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|  | @ -0,0 +1,21 @@ | |||
| #include "reftable/reftable-tests.h" | ||||
| #include "test-tool.h" | ||||
|  | ||||
| int cmd__reftable(int argc, const char **argv) | ||||
| { | ||||
| 	basics_test_main(argc, argv); | ||||
| 	block_test_main(argc, argv); | ||||
| 	merged_test_main(argc, argv); | ||||
| 	pq_test_main(argc, argv); | ||||
| 	record_test_main(argc, argv); | ||||
| 	refname_test_main(argc, argv); | ||||
| 	readwrite_test_main(argc, argv); | ||||
| 	stack_test_main(argc, argv); | ||||
| 	tree_test_main(argc, argv); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int cmd__dump_reftable(int argc, const char **argv) | ||||
| { | ||||
| 	return reftable_dump_main(argc, (char *const *)argv); | ||||
| } | ||||
|  | @ -53,13 +53,15 @@ static struct test_cmd cmds[] = { | |||
| 	{ "pcre2-config", cmd__pcre2_config }, | ||||
| 	{ "pkt-line", cmd__pkt_line }, | ||||
| 	{ "prio-queue", cmd__prio_queue }, | ||||
| 	{ "proc-receive", cmd__proc_receive}, | ||||
| 	{ "proc-receive", cmd__proc_receive }, | ||||
| 	{ "progress", cmd__progress }, | ||||
| 	{ "reach", cmd__reach }, | ||||
| 	{ "read-cache", cmd__read_cache }, | ||||
| 	{ "read-graph", cmd__read_graph }, | ||||
| 	{ "read-midx", cmd__read_midx }, | ||||
| 	{ "ref-store", cmd__ref_store }, | ||||
| 	{ "reftable", cmd__reftable }, | ||||
| 	{ "dump-reftable", cmd__dump_reftable }, | ||||
| 	{ "regex", cmd__regex }, | ||||
| 	{ "repository", cmd__repository }, | ||||
| 	{ "revision-walking", cmd__revision_walking }, | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ int cmd__dump_cache_tree(int argc, const char **argv); | |||
| int cmd__dump_fsmonitor(int argc, const char **argv); | ||||
| int cmd__dump_split_index(int argc, const char **argv); | ||||
| int cmd__dump_untracked_cache(int argc, const char **argv); | ||||
| int cmd__dump_reftable(int argc, const char **argv); | ||||
| int cmd__example_decorate(int argc, const char **argv); | ||||
| int cmd__fast_rebase(int argc, const char **argv); | ||||
| int cmd__genrandom(int argc, const char **argv); | ||||
|  | @ -49,6 +50,7 @@ int cmd__read_cache(int argc, const char **argv); | |||
| int cmd__read_graph(int argc, const char **argv); | ||||
| int cmd__read_midx(int argc, const char **argv); | ||||
| int cmd__ref_store(int argc, const char **argv); | ||||
| int cmd__reftable(int argc, const char **argv); | ||||
| int cmd__regex(int argc, const char **argv); | ||||
| int cmd__repository(int argc, const char **argv); | ||||
| int cmd__revision_walking(int argc, const char **argv); | ||||
|  |  | |||
|  | @ -0,0 +1,15 @@ | |||
| #!/bin/sh | ||||
| # | ||||
| # Copyright (c) 2020 Google LLC | ||||
| # | ||||
|  | ||||
| test_description='reftable unittests' | ||||
|  | ||||
| . ./test-lib.sh | ||||
|  | ||||
| test_expect_success 'unittests' ' | ||||
| 	TMPDIR=$(pwd) && export TMPDIR && | ||||
| 	test-tool reftable | ||||
| ' | ||||
|  | ||||
| test_done | ||||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano