Finish <search.h>.

I'm unable to find a bug, but we've had requests for this internally
once or twice (though I pointed those folks at the STL), and there's
code we build for the host or in our bootloaders that would use this,
and there's reasonable-looking FreeBSD implementation ready and waiting.

Bug: N/A
Test: ran tests
Change-Id: I6ddee4b71bea4c22ed015debd31d3eaac4fcdd35
diff --git a/tests/search_test.cpp b/tests/search_test.cpp
index d368f9f..d5ab67c 100644
--- a/tests/search_test.cpp
+++ b/tests/search_test.cpp
@@ -208,3 +208,80 @@
 
   remque(&zero);
 }
+
+static void AssertEntry(ENTRY* e, const char* expected_key, const char* expected_data) {
+  ASSERT_TRUE(e != nullptr);
+  ASSERT_STREQ(expected_key, reinterpret_cast<char*>(e->key));
+  ASSERT_STREQ(expected_data, reinterpret_cast<char*>(e->data));
+}
+
+TEST(search, hcreate_hsearch_hdestroy) {
+  ASSERT_NE(0, hcreate(13));
+
+  // Add some initial entries.
+  ENTRY* e;
+  e = hsearch(ENTRY{.key = const_cast<char*>("a"), .data = const_cast<char*>("A")}, ENTER);
+  AssertEntry(e, "a", "A");
+  e = hsearch(ENTRY{.key = const_cast<char*>("aa"), .data = const_cast<char*>("B")}, ENTER);
+  AssertEntry(e, "aa", "B");
+  e = hsearch(ENTRY{.key = const_cast<char*>("aaa"), .data = const_cast<char*>("C")}, ENTER);
+  AssertEntry(e, "aaa", "C");
+
+  // Check missing.
+  e = hsearch(ENTRY{.key = const_cast<char*>("aaaa"), .data = nullptr}, FIND);
+  ASSERT_FALSE(e != nullptr);
+
+  // Check present.
+  e = hsearch(ENTRY{.key = const_cast<char*>("aa"), .data = nullptr}, FIND);
+  AssertEntry(e, "aa", "B");
+
+  // ENTER with an existing key just returns the existing ENTRY.
+  e = hsearch(ENTRY{.key = const_cast<char*>("aa"), .data = const_cast<char*>("X")}, ENTER);
+  AssertEntry(e, "aa", "B");
+  e->data = const_cast<char*>("X");
+
+  // Check present and updated.
+  e = hsearch(ENTRY{.key = const_cast<char*>("aa"), .data = nullptr}, FIND);
+  AssertEntry(e, "aa", "X");
+  // But other entries stayed the same.
+  e = hsearch(ENTRY{.key = const_cast<char*>("a"), .data = nullptr}, FIND);
+  AssertEntry(e, "a", "A");
+  e = hsearch(ENTRY{.key = const_cast<char*>("aaa"), .data = nullptr}, FIND);
+  AssertEntry(e, "aaa", "C");
+
+  hdestroy();
+}
+
+TEST(search, hcreate_r_hsearch_r_hdestroy_r) {
+  hsearch_data h1 = {};
+  ASSERT_EQ(1, hcreate_r(13, &h1));
+
+  hsearch_data h2 = {};
+  ASSERT_EQ(1, hcreate_r(128, &h2));
+
+  // Add some initial entries.
+  ENTRY* e;
+  ASSERT_EQ(1, hsearch_r(ENTRY{.key = const_cast<char*>("a"), .data = const_cast<char*>("A")},
+                         ENTER, &e, &h1));
+  AssertEntry(e, "a", "A");
+  ASSERT_EQ(1, hsearch_r(ENTRY{.key = const_cast<char*>("a"), .data = const_cast<char*>("B")},
+                         ENTER, &e, &h2));
+  AssertEntry(e, "a", "B");
+
+  // Check missing.
+  errno = 0;
+  ASSERT_EQ(0, hsearch_r(ENTRY{.key = const_cast<char*>("b"), .data = nullptr}, FIND, &e, &h1));
+  ASSERT_EQ(ESRCH, errno);
+
+  // Check present.
+  ASSERT_EQ(1, hsearch_r(ENTRY{.key = const_cast<char*>("a"), .data = nullptr}, FIND, &e, &h1));
+  AssertEntry(e, "a", "A");
+  ASSERT_EQ(1, hsearch_r(ENTRY{.key = const_cast<char*>("a"), .data = nullptr}, FIND, &e, &h2));
+  AssertEntry(e, "a", "B");
+
+  // Destroying one doesn't affect the other.
+  hdestroy_r(&h1);
+  ASSERT_EQ(1, hsearch_r(ENTRY{.key = const_cast<char*>("a"), .data = nullptr}, FIND, &e, &h2));
+  AssertEntry(e, "a", "B");
+  hdestroy_r(&h2);
+}