got rid of all the absolute paths in the fitpc code
Previously, there were 2 places (BinaryLogReader and the HTTP file server) that
had "/home/driver/" hard coded. I changed both of those to use paths relative to
the location of the executable (retrieved from /proc/self/exe).
git-svn-id: https://robotics.mvla.net/svn/frc971/2013/trunk/src@4162 f308d9b7-e957-4cde-b6ac-9a88185e7312
diff --git a/aos/atom_code/core/BinaryLogReader.cpp b/aos/atom_code/core/BinaryLogReader.cpp
index 703a219..b673c26 100644
--- a/aos/atom_code/core/BinaryLogReader.cpp
+++ b/aos/atom_code/core/BinaryLogReader.cpp
@@ -13,25 +13,16 @@
#include "aos/aos_core.h"
#include "aos/atom_code/core/LogFileCommon.h"
+#include "aos/common/Configuration.h"
static const char *const kCRIOName = "CRIO";
int main() {
aos::InitNRT();
- char *folder_tmp;
- if (asprintf(&folder_tmp, "%s/tmp/robot_logs", getpwuid(getuid())->pw_dir) == -1) {
- LOG(ERROR, "couldn't figure out what folder to use because of %d (%s)\n",
- errno, strerror(errno));
- return EXIT_FAILURE;
- }
- std::string hack("/home/driver/tmp/robot_logs"); // TODO(brians) remove this hack
- const char *folder = hack.c_str();
+ const char *folder = aos::configuration::GetLoggingDirectory();
if (access(folder, R_OK | W_OK) == -1) {
- fprintf(stderr,
- "LogReader: error: folder '%s' does not exist. please create it\n",
- folder);
- return EXIT_FAILURE;
+ LOG(FATAL, "folder '%s' does not exist. please create it\n", folder);
}
LOG(INFO, "logging to folder '%s'\n", folder);
diff --git a/aos/atom_code/output/ctemplate_cache.cc b/aos/atom_code/output/ctemplate_cache.cc
new file mode 100644
index 0000000..a73e4ad
--- /dev/null
+++ b/aos/atom_code/output/ctemplate_cache.cc
@@ -0,0 +1,24 @@
+#include "aos/atom_code/output/ctemplate_cache.h"
+
+#include "aos/common/Configuration.h"
+#include "aos/common/once.h"
+
+namespace aos {
+namespace http {
+
+namespace {
+ctemplate::TemplateCache *CreateTemplateCache() {
+ ctemplate::TemplateCache *r = new ctemplate::TemplateCache();
+
+ r->SetTemplateRootDirectory(configuration::GetRootDirectory());
+
+ return r;
+}
+} // namespace
+ctemplate::TemplateCache *get_template_cache() {
+ static Once<ctemplate::TemplateCache> once(CreateTemplateCache);
+ return once.Get();
+}
+
+} // namespace http
+} // namespace aos
diff --git a/aos/atom_code/output/ctemplate_cache.h b/aos/atom_code/output/ctemplate_cache.h
new file mode 100644
index 0000000..7e5dc3d
--- /dev/null
+++ b/aos/atom_code/output/ctemplate_cache.h
@@ -0,0 +1,12 @@
+#include "ctemplate/template_cache.h"
+
+namespace aos {
+namespace http {
+
+// Retrieves the cache used by all of the aos functions etc.
+// This cache will have its root directory set to the directory where the
+// executable is running from.
+ctemplate::TemplateCache *get_template_cache();
+
+} // namespace http
+} // namespace aos
diff --git a/aos/atom_code/output/output.gyp b/aos/atom_code/output/output.gyp
index 3e4abd4..d41fe7d 100644
--- a/aos/atom_code/output/output.gyp
+++ b/aos/atom_code/output/output.gyp
@@ -6,11 +6,13 @@
'sources': [
'HTTPServer.cpp',
'evhttp_ctemplate_emitter.cc',
+ 'ctemplate_cache.cc',
],
'dependencies': [
'<(AOS)/build/aos.gyp:libaos',
'<(EXTERNALS):libevent',
'<(EXTERNALS):ctemplate',
+ '<(AOS)/common/common.gyp:once',
],
'export_dependent_settings': [
# Our headers #include headers from both of these.
diff --git a/aos/build/aos_all.gyp b/aos/build/aos_all.gyp
index a65909d..d235f14 100644
--- a/aos/build/aos_all.gyp
+++ b/aos/build/aos_all.gyp
@@ -21,7 +21,7 @@
#'../common/messages/messages.gyp:*', # TODO(brians) did this test ever exist?
'../atom_code/logging/logging.gyp:*',
'../common/common.gyp:die_test',
- ':Common',
+ 'Common',
],
},
{
@@ -29,7 +29,7 @@
'type': 'none',
'dependencies': [
'../crio/googletest/googletest.gyp:*',
- ':Common',
+ 'Common',
],
},
{
@@ -42,6 +42,7 @@
'<(AOS)/common/common.gyp:type_traits_test',
'<(AOS)/common/common.gyp:time_test',
'<(AOS)/common/common.gyp:mutex_test',
+ '<(AOS)/common/common.gyp:once_test',
],
},
],
diff --git a/aos/build/externals.gyp b/aos/build/externals.gyp
index 2ed0478..796fdeb 100644
--- a/aos/build/externals.gyp
+++ b/aos/build/externals.gyp
@@ -81,11 +81,30 @@
},
},
{
+# Dependents should only use the "gtest/gtest_prod.h" header.
+# This target is NOT the correct one for "aos/common/gtest_prod.h". That one is
+# aos/common/common.gyp:gtest_prod. This target just deals with setting up to
+# use the gtest header.
+ 'target_name': 'gtest_prod',
+ 'type': 'static_library',
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(externals)/gtest-<(gtest_version)/include'
+ ],
+ },
+ },
+ {
'target_name': 'gtest',
'type': 'static_library',
'sources': [
'<(externals)/gtest-<(gtest_version)/fused-src/gtest/gtest-all.cc',
],
+ 'dependencies': [
+ 'gtest_prod',
+ ],
+ 'export_dependent_settings': [
+ 'gtest_prod',
+ ],
'conditions': [['OS=="crio"', {
'defines': [
'GTEST_HAS_TR1_TUPLE=0',
@@ -104,10 +123,6 @@
'<(externals)/gtest-<(gtest_version)/fused-src/gtest/gtest_main.cc',
],
}]],
- 'include_dirs': [
- '<(externals)/gtest-<(gtest_version)',
- '<(externals)/gtest-<(gtest_version)/include'
- ],
'cflags!': ['-Werror'],
'direct_dependent_settings': {
'include_dirs': ['<(externals)/gtest-<(gtest_version)/include'],
diff --git a/aos/common/Configuration.cpp b/aos/common/Configuration.cpp
index e0b56ea..90de05d 100644
--- a/aos/common/Configuration.cpp
+++ b/aos/common/Configuration.cpp
@@ -19,6 +19,7 @@
#ifndef __VXWORKS__
#include "aos/common/unique_malloc_ptr.h"
#endif
+#include "aos/common/once.h"
namespace aos {
namespace configuration {
@@ -146,5 +147,59 @@
return NULL;
}
+namespace {
+const char *DoGetRootDirectory() {
+#ifdef __VXWORKS__
+ return "/";
+#else
+ ssize_t size = 0;
+ char *r = NULL;
+ while (true) {
+ if (r != NULL) delete r;
+ size += 256;
+ r = new char[size];
+
+ ssize_t ret = readlink("/proc/self/exe", r, size);
+ if (ret < 0) {
+ if (ret != -1) {
+ LOG(WARNING, "it returned %zd, not -1\n", ret);
+ }
+ LOG(FATAL, "readlink(\"/proc/self/exe\", %p, %zu) failed with %d: %s\n",
+ r, size, errno, strerror(errno));
+ }
+ if (ret < size) {
+ void *last_slash = memrchr(r, '/', size);
+ if (last_slash == NULL) {
+ r[ret] = '\0';
+ LOG(FATAL, "couldn't find a '/' in \"%s\"\n", r);
+ }
+ *static_cast<char *>(last_slash) = '\0';
+ LOG(INFO, "got a root dir of \"%s\"\n", r);
+ return r;
+ }
+ }
+#endif
+}
+
+const char *DoGetLoggingDirectory() {
+ static const char kSuffix[] = "/../../tmp/robot_logs";
+ const char *root = GetRootDirectory();
+ char *r = new char[strlen(root) + sizeof(kSuffix)];
+ strcpy(r, root);
+ strcat(r, kSuffix);
+ return r;
+}
+} // namespace
+
+const char *GetRootDirectory() {
+ static aos::Once<const char> once(DoGetRootDirectory);
+ return once.Get();
+}
+
+const char *GetLoggingDirectory() {
+ static aos::Once<const char> once(DoGetLoggingDirectory);
+ return once.Get();
+}
+
} // namespace configuration
} // namespace aos
diff --git a/aos/common/Configuration.h b/aos/common/Configuration.h
index bf2dc80..2f7e777 100644
--- a/aos/common/Configuration.h
+++ b/aos/common/Configuration.h
@@ -22,8 +22,8 @@
kCameraStreamer = 9714,
};
-// Holds global configuration data. All of the public static functions are safe
-// to call concurrently (the ones that need to create locks on the cRIO).
+// Holds global configuration data. All of the functions are safe to call
+// from wherever (the ones that need to create locks on the cRIO).
namespace configuration {
// Constants indentifying various devices on the network.
@@ -36,6 +36,17 @@
// The return value should be passed to free(3) if it is no longer needed.
const char *GetIPAddress(NetworkDevice device);
+// Returns the "root directory" for this run. Under linux, this is the
+// directory where the executable is located (from /proc/self/exe) and under
+// vxworks it is just "/".
+// The return value will always be to a static string, so no freeing is
+// necessary.
+const char *GetRootDirectory();
+// Returns the directory where logs get written. Relative to GetRootDirectory().
+// The return value will always be to a static string, so no freeing is
+// necessary.
+const char *GetLoggingDirectory();
+
} // namespace configuration
} // namespace aos
diff --git a/aos/common/common.gyp b/aos/common/common.gyp
index 6dca969..967eb51 100644
--- a/aos/common/common.gyp
+++ b/aos/common/common.gyp
@@ -45,6 +45,10 @@
],
'dependencies': [
'<(AOS)/build/aos.gyp:logging',
+ 'once',
+ ],
+ 'export_dependent_settings': [
+ 'once',
],
'conditions': [
['OS=="crio"', {
@@ -158,6 +162,38 @@
'dependencies': [
'<(EXTERNALS):gtest',
'<(AOS)/build/aos.gyp:libaos',
+ ':common',
+ ],
+ },
+ {
+ 'target_name': 'gtest_prod',
+ 'type': 'static_library',
+ 'dependencies': [
+ '<(EXTERNALS):gtest_prod',
+ ],
+ 'export_dependent_settings': [
+ '<(EXTERNALS):gtest_prod',
+ ],
+ },
+ {
+ 'target_name': 'once',
+ 'type': 'static_library',
+ 'dependencies': [
+ '<(EXTERNALS):gtest_prod',
+ ],
+ 'export_dependent_settings': [
+ '<(EXTERNALS):gtest_prod',
+ ],
+ },
+ {
+ 'target_name': 'once_test',
+ 'type': '<(aos_target)',
+ 'sources': [
+ 'once_test.cc',
+ ],
+ 'dependencies': [
+ '<(EXTERNALS):gtest',
+ '<(AOS)/build/aos.gyp:libaos',
],
},
{
diff --git a/aos/common/gtest_prod.h b/aos/common/gtest_prod.h
new file mode 100644
index 0000000..fe0b056
--- /dev/null
+++ b/aos/common/gtest_prod.h
@@ -0,0 +1,37 @@
+#ifndef AOS_COMMON_GTEST_PROD_H_
+#define AOS_COMMON_GTEST_PROD_H_
+
+#include "gtest/gtest_prod.h"
+
+// These macros replace gtest's FRIEND_TEST if the test is in a different
+// namespace than the code that needs to make it a friend.
+// Example:
+// foo.h:
+// namespace bla {
+// namespace testing {
+//
+// FORWARD_DECLARE_TEST_CASE(FooTest, Bar);
+//
+// } // namespace testing
+//
+// class Foo {
+// FRIEND_TEST_NAMESPACE(FooTest, Bar, testing);
+// };
+//
+// } // namespace bla
+// foo_test.cc:
+// namespace bla {
+// namespace testing {
+//
+// TEST(FooTest, Bar) {
+// access private members of Foo
+// }
+//
+// } // namespace testing
+// } // namespace bla
+#define FORWARD_DECLARE_TEST_CASE(test_case_name, test_name) \
+ class test_case_name##_##test_name##_Test;
+#define FRIEND_TEST_NAMESPACE(test_case_name, test_name, namespace_name) \
+ friend class namespace_name::test_case_name##_##test_name##_Test
+
+#endif // AOS_COMMON_GTEST_PROD_H_
diff --git a/aos/common/once-tmpl.h b/aos/common/once-tmpl.h
new file mode 100644
index 0000000..9b9c8c5
--- /dev/null
+++ b/aos/common/once-tmpl.h
@@ -0,0 +1,47 @@
+#ifdef __VXWORKS__
+#include <taskLib.h>
+#else
+#include <sched.h>
+#endif
+
+#include "aos/common/type_traits.h"
+
+// It doesn't use pthread_once, because Brian looked at the pthreads
+// implementation for vxworks and noticed that it is completely and entirely
+// broken for doing just about anything (including its pthread_once). It has the
+// same implementation on the atom for simplicity.
+
+namespace aos {
+
+// Setting function_ multiple times would be OK because it'll get set to the
+// same value each time.
+template<typename T>
+Once<T>::Once(Function function)
+ : function_(function) {
+ static_assert(shm_ok<Once<T>>::value, "Once should work in shared memory");
+}
+
+template<typename T>
+void Once<T>::Reset() {
+ done_ = false;
+ run_ = 0;
+}
+
+template<typename T>
+T *Once<T>::Get() {
+ if (__sync_lock_test_and_set(&run_, 1) == 0) {
+ result_ = function_();
+ done_ = true;
+ } else {
+ while (!done_) {
+#ifdef __VXWORKS__
+ taskDelay(1);
+#else
+ sched_yield();
+#endif
+ }
+ }
+ return result_;
+}
+
+} // namespace aos
diff --git a/aos/common/once.h b/aos/common/once.h
new file mode 100644
index 0000000..026b1bd
--- /dev/null
+++ b/aos/common/once.h
@@ -0,0 +1,69 @@
+#ifndef AOS_COMMON_ONCE_H_
+#define AOS_COMMON_ONCE_H_
+
+#include <stdint.h>
+
+#include "aos/common/gtest_prod.h"
+
+namespace aos {
+namespace testing {
+
+FORWARD_DECLARE_TEST_CASE(OnceTest, MemoryClearing);
+
+} // namespace testing
+
+// Designed for the same thing as pthread_once: to run something exactly 1 time.
+//
+// Intended use case:
+// const char *CalculateSomethingCool() {
+// static ::aos::Once<const char> once(DoCalculateSomethingCool);
+// return once.Get();
+// }
+//
+// IMPORTANT: Instances _must_ be placed in memory that gets 0-initialized
+// automatically or Reset() must be called exactly once!!
+// The expected use case is to use one of these as a static variable, and those
+// do get 0-initialized under the C++ standard. Global variables are treated the
+// same way by the C++ Standard.
+// Placing an instance in shared memory (and using Reset()) is also supported.
+// The constructor does not initialize all of the member variables!
+// This is because initializing them in the constructor creates a race condition
+// if initialization of static variables isn't thread safe.
+template<typename T>
+class Once {
+ public:
+ typedef T *(*Function)();
+ explicit Once(Function function);
+
+ // Returns the result of calling function_. The first call will actually run
+ // it and then any other ones will block (if necessary) until it's finished
+ // and then return the same thing.
+ T *Get();
+
+ // Will clear out all the member variables. If this is going to be used
+ // instead of creating an instance in 0-initialized memory, then this method
+ // must be called exactly once before Get() is called anywhere.
+ // This method can also be called to run the function again the next time
+ // Get() is called. However, calling it again is not thread safe.
+ void Reset();
+
+ private:
+ // The function to run to calculate result_.
+ Function function_;
+ // Whether or not it is running. Gets atomically swapped from 0 to 1 by the
+ // thread that actually runs function_.
+ volatile int run_;
+ // Whether or not it is done. Gets set to true after the thread that is
+ // running function_ finishes running it and storing the result in result_.
+ volatile bool done_;
+ // What function_ returned when it was executed.
+ T *result_;
+
+ FRIEND_TEST_NAMESPACE(OnceTest, MemoryClearing, testing);
+};
+
+} // namespace aos
+
+#include "aos/common/once-tmpl.h"
+
+#endif // AOS_COMMON_ONCE_H_
diff --git a/aos/common/once_test.cc b/aos/common/once_test.cc
new file mode 100644
index 0000000..ed21b37
--- /dev/null
+++ b/aos/common/once_test.cc
@@ -0,0 +1,128 @@
+#include "aos/common/once.h"
+
+#include "stdlib.h"
+#include "limits.h"
+
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace testing {
+
+class OnceTest : public ::testing::Test {
+ public:
+ static int *Function() {
+ ++times_run_;
+ value_ = rand() % INT_MAX;
+ return &value_;
+ }
+
+ protected:
+ void SetUp() {
+ value_ = 0;
+ times_run_ = 0;
+ }
+
+ static int value_;
+ static int times_run_;
+};
+int OnceTest::value_, OnceTest::times_run_;
+
+// Makes sure that it calls the function at the right time and that it correctly
+// passes the result out.
+TEST_F(OnceTest, Works) {
+ static Once<int> once(Function);
+
+ EXPECT_EQ(0, value_);
+ EXPECT_EQ(0, times_run_);
+
+ EXPECT_EQ(value_, *once.Get());
+ EXPECT_NE(0, value_);
+ EXPECT_NE(0, times_run_);
+ // Make sure it's not passing it through an assignment by value or something
+ // else weird.
+ EXPECT_EQ(&value_, once.Get());
+}
+
+// Makes sure that having a Once at namespace scope works correctly.
+namespace {
+
+Once<int> global_once(OnceTest::Function);
+
+} // namespace
+
+TEST_F(OnceTest, Global) {
+ EXPECT_EQ(value_, *global_once.Get());
+ EXPECT_NE(0, value_);
+ EXPECT_NE(0, times_run_);
+}
+
+// Makes sure that an instance keeps returning the same value without running
+// the function again.
+TEST_F(OnceTest, MultipleGets) {
+ static Once<int> once(Function);
+
+ EXPECT_EQ(value_, *once.Get());
+ EXPECT_EQ(1, times_run_);
+ EXPECT_EQ(value_, *once.Get());
+ EXPECT_EQ(1, times_run_);
+ EXPECT_EQ(value_, *once.Get());
+ EXPECT_EQ(1, times_run_);
+}
+
+// Tests to make sure that the right methods clear out the instance variables at
+// the right times.
+TEST_F(OnceTest, MemoryClearing) {
+ Once<int> once(NULL);
+
+ once.run_ = 1;
+ once.done_ = true;
+ // Run the constructor again to make sure it doesn't touch the variables set
+ // above.
+ new (&once)Once<int>(Function);
+
+ // Should return a random, (potentially) uninitialized value.
+ once.Get();
+ EXPECT_EQ(0, times_run_);
+
+ once.Reset();
+ EXPECT_EQ(0, times_run_);
+ EXPECT_EQ(value_, *once.Get());
+ EXPECT_EQ(1, times_run_);
+}
+
+namespace {
+
+int second_result = 0;
+int *SecondFunction() {
+ second_result = rand() % INT_MAX;
+ return &second_result;
+}
+
+} // namespace
+
+// Makes sure that multiple instances don't interfere with each other.
+TEST_F(OnceTest, MultipleInstances) {
+ static Once<int> once1(Function);
+ static Once<int> once2(SecondFunction);
+
+ EXPECT_EQ(&value_, once1.Get());
+ EXPECT_EQ(&second_result, once2.Get());
+ EXPECT_EQ(&value_, once1.Get());
+ EXPECT_EQ(&second_result, once2.Get());
+}
+
+// Tests calling Reset() to run the function a second time.
+TEST_F(OnceTest, Recalculate) {
+ Once<int> once(Function);
+
+ EXPECT_EQ(value_, *once.Get());
+ EXPECT_EQ(1, times_run_);
+
+ value_ = 0;
+ once.Reset();
+ EXPECT_EQ(value_, *once.Get());
+ EXPECT_EQ(2, times_run_);
+}
+
+} // namespace testing
+} // namespace aos
diff --git a/frc971/output/CameraServer.cc b/frc971/output/CameraServer.cc
index 96f698d..d3c3bc8 100644
--- a/frc971/output/CameraServer.cc
+++ b/frc971/output/CameraServer.cc
@@ -3,6 +3,9 @@
#include "aos/aos_core.h"
#include "aos/atom_code/output/HTTPServer.h"
#include "aos/atom_code/output/evhttp_ctemplate_emitter.h"
+#include "aos/atom_code/output/ctemplate_cache.h"
+#include "aos/common/Configuration.h"
+#include "aos/common/messages/RobotState.q.h"
#include "ctemplate/template.h"
#include "frc971/constants.h"
@@ -11,12 +14,10 @@
namespace frc971 {
-const char *const kPath = "/home/driver/robot_code/bin/";
-//const char *const kPath = "/home/brians/Desktop/git_frc971/2012/trunk/src/frc971/output";
-
class CameraServer : public aos::http::HTTPServer {
public:
- CameraServer() : HTTPServer(kPath, 8080), buf_(NULL) {
+ CameraServer() : HTTPServer(aos::configuration::GetRootDirectory(), 8080),
+ buf_(NULL) {
AddPage<CameraServer>("/robot.html", &CameraServer::RobotHTML, this);
}
@@ -53,6 +54,11 @@
// after it.
dict.SetValue("HOST", ctemplate::TemplateString(host, length));
+ if (!aos::robot_state.FetchLatest()) {
+ LOG(WARNING, "getting a RobotState message failed\n");
+ evhttp_send_error(request, HTTP_INTERNAL, NULL);
+ return;
+ }
int center;
if (!constants::camera_center(¢er)) {
evhttp_send_error(request, HTTP_INTERNAL, NULL);
@@ -61,8 +67,9 @@
dict.SetIntValue("CENTER", center);
aos::http::EvhttpCtemplateEmitter emitter(buf_);
- if (!ctemplate::ExpandTemplate(ROBOT_HTML, ctemplate::STRIP_WHITESPACE,
- &dict, &emitter)) {
+ if (!aos::http::get_template_cache()->
+ ExpandWithData(ROBOT_HTML, ctemplate::STRIP_WHITESPACE,
+ &dict, NULL, &emitter)) {
LOG(ERROR, "expanding the template failed\n");
evhttp_send_error(request, HTTP_INTERNAL, NULL);
return;
@@ -78,4 +85,3 @@
} // namespace frc971
AOS_RUN_NRT(frc971::CameraServer)
-