diff --git a/Makefile b/Makefile index 6523170..7788b31 100755 --- a/Makefile +++ b/Makefile @@ -13,15 +13,17 @@ OPT ?= -O2 -DNDEBUG # (A) Production use (optimized mode) # Use default if no configuration specified. CXX ?= g++ AR ?= ar +CC ?= gcc # Version number of lmctfy. VERSION = "\"0.4.5\"" # TODO(vmarmol): Ensure our dependencies are installed PROTOC = protoc +PROTOC_C = protoc-c # Function for getting a set of source files. -get_srcs = $(shell find $(1) -name \*.cc -a ! -name \*_test.cc | tr "\n" " ") +get_srcs = $(shell find $(1) -name \*.cc -a ! -name \*_test.cc -a ! -name \*_ctest.cc | tr "\n" " ") INCLUDE_PROTOS = include/virtual_host include/lmctfy include/namespaces UTIL_PROTOS = util/task/codes @@ -37,6 +39,8 @@ LIBLMCTFY_SOURCES =$(shell find lmctfy/ -name \*.cc -a ! -name \*_test.cc \ CLI_SOURCES = $(call get_srcs,lmctfy/cli/) NSINIT_SOURCES = nscon/init.cc nscon/init_impl.cc NSCON_SOURCES = $(filter-out $(NSINIT_SOURCES),$(call get_srcs,nscon/)) +CPROTOS_SOURCES = $(addsuffix .pb-c.c,$(INCLUDE_PROTOS)) $(addsuffix .pb.cc,$(UTIL_PROTOS)) +LIBCLMCTFY_SOURCES = $(call get_srcs,clmctfy/) $(CPROTOS_SOURCES) # The objects for the system API (both release and test versions). SYSTEM_API_OBJS = global_utils/mount_utils.o \ @@ -61,6 +65,7 @@ SYSTEM_API_TEST_OBJS = global_utils/mount_utils_test_util.o \ # Gets all *_test.cc files in lmtcfy/. TESTS = $(basename $(shell find lmctfy/ nscon/ -name \*_test.cc \ -a ! -name \*_integration_test.cc)) +CLMCTFY_TESTS = $(basename $(shell find clmctfy/ -name \*_ctest.cc)) # Where to place the binary outputs. OUT_DIR = bin @@ -96,15 +101,32 @@ CXXFLAGS += -pthread -lrt -lre2 -lgflags CXXFLAGS += -I. -I./include -I./base -I./lmctfy -I$(GTEST_DIR)/include \ -I$(GMOCK_DIR)/include -I/usr/local/include -L/usr/local/lib \ -I/usr/include -L/usr/lib +# Add include for c binding +CXXFLAGS += -I./clmctfy # Add proto flags. CXXFLAGS += `pkg-config --cflags --libs protobuf` +# Add proto-c flags. +CXXFLAGS += -lprotobuf-c + +# flags for linker +LDFLAGS = -lprotobuf-c +LDFLAGS += `pkg-config --cflags --libs protobuf` +LDFLAGS += -lpthread +LDFLAGS += -pthread +LDFLAGS += -lrt -lre2 -lgflags -lm + +# linker's flag for C bindings +CLDFLAGS = -lstdc++ + + CLI = lmctfy NSCON = lmctfy-nscon NSINIT = lmctfy-nsinit LIBRARY = liblmctfy.a CREAPER = lmctfy-creaper +CLIBRARY = libclmctfy.a # Function for ensuring the output directory has been created. create_bin = mkdir -p $(dir $(OUT_DIR)/$@) @@ -128,6 +150,8 @@ install: all chmod +x /usr/local/bin/$(NSINIT) cp ./bin/$(CREAPER) /usr/local/bin chmod +x /usr/local/bin/$(CREAPER) + +cbinding: $(CLIBRARY) checkc TEST_TMPDIR = "/tmp/lmctfy_test.$$" check: $(TESTS) @@ -141,6 +165,16 @@ check: $(TESTS) rm -rf $(TEST_TMPDIR) echo "All tests pass!" +checkc: $(CLMCTFY_TESTS) + for t in $(addprefix $(OUT_DIR)/,$^); \ + do \ + echo "***** Running $$t"; \ + rm -rf $(TEST_TMPDIR); \ + mkdir $(TEST_TMPDIR); \ + ./$$t --test_tmpdir=$(TEST_TMPDIR); \ + done; \ + rm -rf $(TEST_TMPDIR) + clean: -rm -rf $(OUT_DIR) -rm -f `find . -type f -name '*.pb.*'` @@ -152,9 +186,16 @@ examples/simple_existing: examples/simple_existing.o $(LIBRARY) # All common base sources (non-lmctfy and non-nscon). COMMON_SOURCES = $(INCLUDE_SOURCES) $(BASE_SOURCES) $(STRINGS_SOURCES) \ $(FILE_SOURCES) $(THREAD_SOURCES) $(UTIL_SOURCES) +examples/clmctfy_simple_existing: examples/clmctfy_simple_existing.o $(CLIBRARY) + $(create_bin) + $(CC) -o $(OUT_DIR)/$@ $(addprefix $(OUT_DIR)/,$^) $(LDFLAGS) $(CLDFLAGS) # All sources needed by the library (minus the system API). LIBRARY_SOURCES = $(COMMON_SOURCES) $(LIBLMCTFY_SOURCES) $(NSCON_SOURCES) +CLIBRARY_ONLY_SOURCES = $(INCLUDE_SOURCES) $(BASE_SOURCES) $(LIBCLMCTFY_SOURCES) \ + $(STRINGS_SOURCES) $(FILE_SOURCES) $(THREAD_SOURCES) \ + $(UTIL_SOURCES) +CLIBRARY_SOURCES = $(LIBRARY_SOURCES) $(LIBCLMCTFY_SOURCES) # The lmctfy library without the system API. This is primarily an internal @@ -163,11 +204,19 @@ lmctfy_no_system_api.a: $(call source_to_object,$(LIBRARY_SOURCES)) $(create_bin) $(archive_all) +clmctfy_only_api.a: $(call source_to_object,$(CLIBRARY_ONLY_SOURCES)) + $(create_bin) + $(archive_all) + # The lmctfy library with the real system API. $(LIBRARY): $(call source_to_object,$(LIBRARY_SOURCES)) $(SYSTEM_API_OBJS) $(create_bin) $(archive_all) +$(CLIBRARY): $(call source_to_object,$(CLIBRARY_SOURCES)) $(SYSTEM_API_OBJS) + $(create_bin) + $(archive_all) + # Objects of the lmctfy CLI. lmctfy_cli.a: $(call source_to_object,$(CLI_SOURCES)) $(create_bin) @@ -204,19 +253,41 @@ $(CREAPER): lmctfy-creaper.go $(CXX) -o $(OUT_DIR)/$@ $*.cc $*_test.cc $(addprefix $(OUT_DIR)/,$^) \ $(CXXFLAGS) +%_ctest: gtest_main.a $(SYSTEM_API_TEST_OBJS) clmctfy_only_api.a + $(create_bin) + $(CXX) -o $(OUT_DIR)/$@ $*.cc $*_ctest.cc $(addprefix $(OUT_DIR)/,$^) \ + $(CXXFLAGS) -fpermissive + %_proto: %.proto $(PROTOC) $^ --cpp_out=. + $(PROTOC_C) $^ --c_out=$(dir $^) --proto_path=$(dir $^):. %.pb.o: %_proto $(create_bin) $(CXX) -c $*.pb.cc -o $(OUT_DIR)/$@ $(CXXFLAGS) +%.pb-c.o: %_proto + $(create_bin) + $(CXX) -c $*.pb-c.c -o $(OUT_DIR)/$@ $(CXXFLAGS) -fpermissive + gen_protos: $(addsuffix _proto,$(INCLUDE_PROTOS) $(UTIL_PROTOS)) +%_ctest.o: gen_protos %_ctest.cc + $(create_bin) + $(CXX) -c $*.cc -o $(OUT_DIR)/$@ $(CXXFLAGS) -fpermissive + +%.o: gen_protos %.c + $(create_bin) + $(CC) -c $*.c -o $(OUT_DIR)/$@ $(CXXFLAGS) + %.o: gen_protos %.cc $(create_bin) $(CXX) -c $*.cc -o $(OUT_DIR)/$@ $(CXXFLAGS) +%.o: %.pb-c.c + $(create_bin) + $(CXX) -c $*.c -o $(OUT_DIR)/$@ $(CXXFLAGS) + # Rules for Building Google Test and Google Mock (based on gmock's example). # All Google Test headers. Usually you shouldn't change this definition. diff --git a/clmctfy/clmctfy_container.cc b/clmctfy/clmctfy_container.cc new file mode 100644 index 0000000..fa5ca56 --- /dev/null +++ b/clmctfy/clmctfy_container.cc @@ -0,0 +1,451 @@ +#include "clmctfy_container.h" + +#include +#include + +#include "util/task/statusor.h" +#include "base/callback.h" +#include "base/macros.h" + +#include "lmctfy.h" +#include "clmctfy_macros.h" +#include "clmctfy_container_struct.h" +#include "clmctfy_container_raw.h" +#include "clmctfy_event_callback_wrapper.h" + +using ::containers::lmctfy::Container; +using ::containers::lmctfy::ContainerApi; +using ::containers::lmctfy::ContainerSpec; +using ::containers::lmctfy::RunSpec; +using ::containers::lmctfy::EventSpec; +using ::containers::lmctfy::ContainerStats; +using ::util::internal::status_copy; +using ::util::internal::status_new; +using ::util::Status; +using ::util::StatusOr; +using ::util::error::INVALID_ARGUMENT; +using ::std::vector; +using ::std::unordered_map; + +#define STATUS_OK UTIL__ERROR__CODE__OK + +int lmctfy_container_run(struct container *container, + const int argc, + const char **argv, + const Containers__Lmctfy__RunSpec *spec, + pid_t *tid, + struct status *s) { + uint8_t *buf = NULL; + size_t sz = 0; + int ret = 0; + + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, spec); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + sz = containers__lmctfy__run_spec__get_packed_size(spec); + if (sz > 0) { + buf = new uint8_t[sz]; + containers__lmctfy__run_spec__pack(spec, buf); + } + ret = lmctfy_container_run_raw(container, argc, argv, buf, sz, tid, s); + if (buf != NULL) { + delete []buf; + } + return ret; +} + +int lmctfy_container_enter(struct container *container, + const pid_t *tids, + const int tids_size, + struct status *s) { + int ret = STATUS_OK; + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + + if (tids == NULL || tids_size <= 0) { + return ret; + } + + vector tids_v(tids_size); + int i = 0; + for (i = 0; i < tids_size; i++) { + tids_v[i] = tids[i]; + } + Status status = container->container_->Enter(tids_v); + return status_copy(s, status); +} + +int lmctfy_container_exec(struct container *container, + const int argc, + const char **argv, + struct status *s) { + int ret = STATUS_OK; + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + CHECK_POSITIVE_OR_RETURN(s, argc); + + vector cmds(argc); + int i = 0; + for (i = 0; i < argc; i++) { + cmds[i] = argv[i]; + } + + Status status = container->container_->Exec(cmds); + return status_copy(s, status); +} + +void lmctfy_delete_container(struct container *container) { + if (container != NULL) { + if (container->container_ != NULL) { + unordered_map::iterator iter; + for (iter = container->notif_map_.begin(); + iter != container->notif_map_.end(); + iter++) { + container->container_->UnregisterNotification(iter->first); + delete iter->second; + iter->second = NULL; + } + container->notif_map_.clear(); + delete container->container_; + } + delete container; + } +} + +int lmctfy_container_update(struct container *container, + int policy, + const Containers__Lmctfy__ContainerSpec *spec, + struct status *s) { + uint8_t *buf = NULL; + size_t sz = 0; + int ret = STATUS_OK; + + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, spec); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + sz = containers__lmctfy__container_spec__get_packed_size(spec); + if (sz > 0) { + buf = new uint8_t[sz]; + containers__lmctfy__container_spec__pack(spec, buf); + } + ret = lmctfy_container_update_raw(container, policy, buf, sz, s); + if (buf != NULL) { + delete []buf; + } + return ret; +} + +int lmctfy_container_spec(struct container *container, + Containers__Lmctfy__ContainerSpec **spec, + struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + CHECK_NOTNULL_OR_RETURN(s, spec); + + int ret = STATUS_OK; + uint8_t *buf = NULL; + StatusOr statusor_container_spec = container->container_->Spec(); + if (!statusor_container_spec.ok()) { + return status_copy(s, statusor_container_spec.status()); + } + + const ContainerSpec &container_spec = statusor_container_spec.ValueOrDie(); + int sz = container_spec.ByteSize(); + *spec = NULL; + if (sz > 0) { + buf = new uint8_t[sz]; + container_spec.SerializeToArray(buf, sz); + *spec = containers__lmctfy__container_spec__unpack(NULL, sz, buf); + delete []buf; + } + return ret; +} + +int lmctfy_container_list_subcontainers(struct container *c, + int list_policy, + struct container **subcontainers[], + int *subcontainers_size, + struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, c); + CHECK_NOTNULL_OR_RETURN(s, c->container_); + CHECK_NOTNULL_OR_RETURN(s, subcontainers); + CHECK_NOTNULL_OR_RETURN(s, subcontainers_size); + + *subcontainers_size = 0; + *subcontainers = NULL; + Container::ListPolicy policy; + + switch (list_policy) { + case CONTAINER_LIST_POLICY_SELF: + policy = Container::LIST_SELF; + break; + case CONTAINER_LIST_POLICY_RECURSIVE: + policy = Container::LIST_RECURSIVE; + break; + default: + return status_new(s, UTIL__ERROR__CODE__INVALID_ARGUMENT, "Unknown list policy: %d", list_policy); + } + + StatusOr< vector > statusor_subcontainers = + c->container_->ListSubcontainers(policy); + + if (!statusor_subcontainers.ok()) { + return status_copy(s, statusor_subcontainers.status()); + } + + const vector &subcontainers_vector = statusor_subcontainers.ValueOrDie(); + if (subcontainers_vector.size() == 0) { + return STATUS_OK; + } + struct container **subctnrs = (struct container **)malloc(sizeof(struct container *) * subcontainers_vector.size()); + if (subctnrs == NULL) { + return status_new(s, UTIL__ERROR__CODE__RESOURCE_EXHAUSTED, "out of memory"); + } + *subcontainers = subctnrs; + *subcontainers_size = subcontainers_vector.size(); + + vector::const_iterator container_iter = subcontainers_vector.begin(); + for (container_iter = subcontainers_vector.begin(); container_iter != subcontainers_vector.end(); container_iter++) { + struct container *ctnr = new container(); + ctnr->container_ = *container_iter; + *subctnrs = ctnr; + subctnrs++; + } + return STATUS_OK; +} + +int lmctfy_container_list_threads(struct container *container, + int list_policy, + pid_t *threads[], + int *threads_size, + struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + CHECK_NOTNULL_OR_RETURN(s, threads); + CHECK_POSITIVE_OR_RETURN(s, threads_size); + + *threads = NULL; + *threads_size = 0; + Container::ListPolicy policy; + switch (list_policy) { + case CONTAINER_LIST_POLICY_SELF: + policy = Container::LIST_SELF; + break; + case CONTAINER_LIST_POLICY_RECURSIVE: + policy = Container::LIST_RECURSIVE; + break; + default: + return status_new(s, UTIL__ERROR__CODE__INVALID_ARGUMENT, "Unknown list policy: %d", list_policy); + } + + StatusOr> statusor_pids = container->container_->ListThreads(policy); + if (!statusor_pids.ok()) { + return status_copy(s, statusor_pids.status()); + } + const vector &pids = statusor_pids.ValueOrDie(); + *threads_size = pids.size(); + if (*threads_size == 0) { + return STATUS_OK; + } + + pid_t *ptr = (pid_t *)malloc(sizeof(pid_t) * pids.size()); + *threads = ptr; + vector::const_iterator iter; + for (iter = pids.begin(); iter != pids.end(); iter++, ptr++) { + *ptr = *iter; + } + return STATUS_OK; +} + +int lmctfy_container_list_processes(struct container *c, + int list_policy, + pid_t *processes[], + int *processes_size, + struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, c); + CHECK_NOTNULL_OR_RETURN(s, c->container_); + CHECK_NOTNULL_OR_RETURN(s, processes); + CHECK_NOTNULL_OR_RETURN(s, processes_size); + + *processes = NULL; + *processes_size = 0; + Container::ListPolicy policy; + switch (list_policy) { + case CONTAINER_LIST_POLICY_SELF: + policy = Container::LIST_SELF; + break; + case CONTAINER_LIST_POLICY_RECURSIVE: + policy = Container::LIST_RECURSIVE; + break; + default: + return status_new(s, UTIL__ERROR__CODE__INVALID_ARGUMENT, "Unknown list policy: %d", list_policy); + } + + StatusOr> statusor_pids = c->container_->ListProcesses(policy); + if (!statusor_pids.ok()) { + return status_copy(s, statusor_pids.status()); + } + const vector &pids = statusor_pids.ValueOrDie(); + *processes_size = pids.size(); + if (*processes_size == 0) { + return STATUS_OK; + } + + pid_t *ptr = (pid_t *)malloc(sizeof(pid_t) * pids.size()); + *processes = ptr; + vector::const_iterator iter; + for (iter = pids.begin(); iter != pids.end(); iter++, ptr++) { + *ptr = *iter; + } + return STATUS_OK; +} + +int lmctfy_container_pause(struct container *container, struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + + Status status = container->container_->Pause(); + return status_copy(s, status); +} + +int lmctfy_container_resume(struct container *container, struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + + Status status = container->container_->Resume(); + return status_copy(s, status); +} + +int lmctfy_container_killall(struct container *container, struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + + Status status = container->container_->KillAll(); + return status_copy(s, status); +} + +int lmctfy_container_stats(struct container *container, + int stats_type, + Containers__Lmctfy__ContainerStats **stats, + struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + CHECK_NOTNULL_OR_RETURN(s, stats); + + int ret = STATUS_OK; + uint8_t *buf = NULL; + size_t sz; + ret = lmctfy_container_stats_raw(container, stats_type, (void **)&buf, &sz, s); + if (ret != STATUS_OK) { + return ret; + } + *stats = containers__lmctfy__container_stats__unpack(NULL, sz, buf); + free(buf); + return ret; + /* + Container::StatsType type; + switch (stats_type) { + case CONTAINER_STATS_TYPE_SUMMARY: + type = Container::STATS_SUMMARY; + break; + case CONTAINER_STATS_TYPE_FULL: + type = Container::STATS_FULL; + break; + default: + return status_new(s, UTIL__ERROR__CODE__INVALID_ARGUMENT, "Unknown stats type: %d", stats_type); + } + StatusOr statusor_container_stats = container->container_->Stats(type); + if (!statusor_container_stats.ok()) { + return status_copy(s, statusor_container_stats.status()); + } + + const ContainerStats &container_stats = statusor_container_stats.ValueOrDie(); + int sz = container_stats.ByteSize(); + *stats = NULL; + if (sz > 0) { + buf = new uint8_t[sz]; + container_stats.SerializeToArray(buf, sz); + *stats = containers__lmctfy__container_stats__unpack(NULL, sz, buf); + delete []buf; + } + return ret; + */ +} + +const char *lmctfy_container_name(struct container *container) { + if (container == NULL) { + return NULL; + } + if (container->container_ == NULL) { + return NULL; + } + return container->container_->name().c_str(); +} + +int lmctfy_container_register_notification(struct container *container, + lmctfy_event_callback_f callback, + void *user_data, + Containers__Lmctfy__EventSpec *spec, + notification_id_t *notif_id, + struct status *s) { + uint8_t *buf = NULL; + size_t sz = 0; + int ret = 0; + + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, spec); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + CHECK_NOTNULL_OR_RETURN(s, notif_id); + CHECK_NOTNULL_OR_RETURN(s, callback); + sz = containers__lmctfy__event_spec__get_packed_size(spec); + if (sz > 0) { + buf = new uint8_t[sz]; + containers__lmctfy__event_spec__pack(spec, buf); + } + ret = lmctfy_container_register_notification_raw(container, + callback, + user_data, + buf, + sz, + notif_id, + s); + if (buf != NULL) { + delete []buf; + } + return ret; +} + +int lmctfy_container_unregister_notification(struct container *container, + const notification_id_t notif_id, + struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + unordered_map::iterator iter = + container->notif_map_.find(notif_id); + if (iter == container->notif_map_.end()) { + return status_new(s, UTIL__ERROR__CODE__INVALID_ARGUMENT, "unknown notification id"); + } + Status status = + container->container_->UnregisterNotification(notif_id); + if (!status.ok()) { + return status_copy(s, status); + } + container->notif_map_.erase(iter); + delete iter->second; + return STATUS_OK; +} + diff --git a/clmctfy/clmctfy_container_api.cc b/clmctfy/clmctfy_container_api.cc new file mode 100644 index 0000000..d85a3b1 --- /dev/null +++ b/clmctfy/clmctfy_container_api.cc @@ -0,0 +1,147 @@ +#include "clmctfy_container_api.h" + +#include +#include + +#include "util/task/statusor.h" +#include "lmctfy.pb.h" +#include "util/task/codes.pb-c.h" +#include "lmctfy.h" + +#include "clmctfy_macros.h" +#include "clmctfy_container_struct.h" +#include "clmctfy_container_api_struct.h" +#include "clmctfy_container_api_raw.h" + +#define STATUS_OK UTIL__ERROR__CODE__OK + +using ::containers::lmctfy::Container; +using ::containers::lmctfy::ContainerApi; +using ::containers::lmctfy::InitSpec; +using ::containers::lmctfy::ContainerSpec; +using ::util::internal::status_copy; +using ::util::internal::status_new; +using ::util::Status; +using ::util::StatusOr; + +int lmctfy_init_machine(const Containers__Lmctfy__InitSpec *spec, struct status *s) { + uint8_t *buf = NULL; + size_t sz = 0; + int ret = 0; + + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, spec); + sz = containers__lmctfy__init_spec__get_packed_size(spec); + if (sz > 0) { + // TODO(monnand) Can we use alloca(3) here, so that we don't need to use heap? + buf = new uint8_t[sz]; + containers__lmctfy__init_spec__pack(spec, buf); + } + ret = lmctfy_init_machine_raw(buf, sz, s); + if (buf != NULL) { + delete []buf; + } + return ret; +} + +int lmctfy_new_container_api(struct container_api **api, struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, api); + *api = new container_api(); + (*api)->container_api_ = NULL; + StatusOr statusor_container_api = ContainerApi::New(); + RETURN_IF_ERROR_PTR(s, statusor_container_api, &((*api)->container_api_)); + return STATUS_OK; +} + +void lmctfy_delete_container_api(struct container_api *api) { + if (api != NULL) { + if (api->container_api_ != NULL) { + delete api->container_api_; + } + delete api; + } +} + +#define COPY_CONTAINER_STRUCTURE(ctnr_ptr, ctnr_struct) do { \ + if ((ctnr_ptr) != NULL) { \ + (*(ctnr_struct)) = new container(); \ + (*(ctnr_struct))->container_ = ctnr_ptr; \ + } \ +} while(0) + +int lmctfy_container_api_get_container(const struct container_api *api, + const char *container_name, + struct container **c, + struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, api); + CHECK_NOTNULL_OR_RETURN(s, api->container_api_); + CHECK_NOTNULL_OR_RETURN(s, c); + CHECK_NOTNULL_OR_RETURN(s, container_name); + CHECK_POSITIVE_OR_RETURN(s, strlen(container_name)); + Container *ctnr = NULL; + + StatusOr statusor = api->container_api_->Get(container_name); + RETURN_IF_ERROR_PTR(s, statusor, &ctnr); + COPY_CONTAINER_STRUCTURE(ctnr, c); + return STATUS_OK; +} + +int lmctfy_container_api_create_container(struct container_api *api, + const char *container_name, + const Containers__Lmctfy__ContainerSpec *spec, + struct container **c, + struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, api); + CHECK_NOTNULL_OR_RETURN(s, api->container_api_); + CHECK_NOTNULL_OR_RETURN(s, c); + CHECK_NOTNULL_OR_RETURN(s, spec); + CHECK_NOTNULL_OR_RETURN(s, container_name); + CHECK_POSITIVE_OR_RETURN(s, strlen(container_name)); + uint8_t *buf = NULL; + size_t sz = 0; + int ret = 0; + sz = containers__lmctfy__container_spec__get_packed_size(spec); + if (sz > 0) { + buf = new uint8_t[sz]; + } + containers__lmctfy__container_spec__pack(spec, buf); + ret = lmctfy_container_api_create_container_raw(api, container_name, buf, sz, c, s); + if (buf != NULL) { + delete []buf; + } + return ret; +} + +int lmctfy_container_api_destroy_container(struct container_api *api, + struct container *c, + struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, api); + CHECK_NOTNULL_OR_RETURN(s, api->container_api_); + int ret = STATUS_OK; + if (c != NULL && c->container_ != NULL) { + Status status = api->container_api_->Destroy(c->container_); + ret = status_copy(s, status); + } + return ret; +} + +int lmctfy_container_api_detect_container(struct container_api *api, + pid_t pid, + char **container_name, + struct status *s) { + int ret = STATUS_OK; + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, api); + CHECK_NOTNULL_OR_RETURN(s, api->container_api_); + CHECK_NOTNULL_OR_RETURN(s, container_name); + StatusOr statusor = api->container_api_->Detect(pid); + ret = status_copy(s, statusor.status()); + if (container_name != NULL && statusor.ok()) { + *container_name = strdup(statusor.ValueOrDie().c_str()); + } + return ret; +} diff --git a/clmctfy/clmctfy_container_api_ctest.cc b/clmctfy/clmctfy_container_api_ctest.cc new file mode 100644 index 0000000..a1bfc6c --- /dev/null +++ b/clmctfy/clmctfy_container_api_ctest.cc @@ -0,0 +1,193 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "clmctfy_container_api.h" + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "util/task/status.h" +#include "util/task/statusor.h" +#include "util/task/codes.pb-c.h" +#include "strings/stringpiece.h" + +#include "lmctfy_mock.h" +#include "lmctfy.h" +#include "clmctfy_test_macros.h" +#include "clmctfy_container_struct.h" +#include "clmctfy_container_api_struct.h" + +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::_; +using ::util::Status; +using ::util::StatusOr; + +namespace containers { +namespace lmctfy { + +StatusOr ContainerApi::New() { + return new StrictMockContainerApi(); +} + +Status ContainerApi::InitMachine(const InitSpec &spec) { + return Status::OK; +} + +class ClmctfyContainerApiTest : public ::testing::Test { + public: + virtual void SetUp() { + container_api_ = NULL; + container_ = NULL; + lmctfy_new_container_api(&container_api_, NULL); + } + + virtual void TearDown() { + lmctfy_delete_container_api(container_api_); + lmctfy_delete_container(container_); + } + protected: + struct container_api *container_api_; + struct container *container_; + StrictMockContainerApi *GetMockApi(); + StrictMockContainer *GetMockContainer(); +}; + +StrictMockContainerApi *ClmctfyContainerApiTest::GetMockApi() { + ContainerApi *capi = container_api_->container_api_; + StrictMockContainerApi *mock_api = dynamic_cast(capi); + return mock_api; +} + +StrictMockContainer *ClmctfyContainerApiTest::GetMockContainer() { + Container *ctnr = container_->container_; + StrictMockContainer *mock_container = dynamic_cast(ctnr); + return mock_container; +} + +TEST_F(ClmctfyContainerApiTest, GetContainer) { + StrictMockContainerApi *mock_api = GetMockApi(); + const char *container_name = "test"; + Container *ctnr = new StrictMockContainer(container_name); + StatusOr statusor_container(ctnr); + + string errmsg = "some error message"; + Status status = Status(::util::error::INTERNAL, errmsg); + StatusOr statusor(status); + + EXPECT_CALL(*mock_api, Get(StringPiece(container_name))) + .WillOnce(Return(statusor_container)) + .WillOnce(Return(statusor)); + + SHOULD_SUCCEED(lmctfy_container_api_get_container, container_api_, container_name, &container_); + Container *ctnr_2 = GetMockContainer(); + EXPECT_EQ(ctnr_2, ctnr); + struct container *tmp = container_; + container_ = NULL; + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_api_get_container, container_api_, container_name, &container_); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_api_get_container, container_api_, container_name, NULL); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_api_get_container, container_api_, NULL, &container_); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_api_get_container, container_api_, "", &container_); + WITH_NULL_CONTAINER_API_RUN(lmctfy_container_api_get_container, container_api_, container_name, &container_); + container_ = tmp; +} + +TEST_F(ClmctfyContainerApiTest, CreateContainer) { + StrictMockContainerApi *mock_api = GetMockApi(); + const char *container_name = "test"; + Container *ctnr = new StrictMockContainer(container_name); + StatusOr statusor_container(ctnr); + + string errmsg = "some error message"; + Status status = Status(::util::error::INTERNAL, errmsg); + StatusOr statusor(status); + + EXPECT_CALL(*mock_api, Create(StringPiece(container_name), _)) + .WillOnce(Return(statusor_container)) + .WillOnce(Return(statusor)); + + Containers__Lmctfy__ContainerSpec spec = CONTAINERS__LMCTFY__CONTAINER_SPEC__INIT; + + SHOULD_SUCCEED(lmctfy_container_api_create_container, container_api_, container_name, &spec, &container_); + Container *ctnr_2 = GetMockContainer(); + EXPECT_EQ(ctnr_2, ctnr); + struct container *tmp = container_; + container_ = NULL; + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_api_create_container, container_api_, container_name, &spec, &container_); + EXPECT_EQ(container_, (struct container *)NULL); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_api_create_container, container_api_, container_name, &spec, NULL); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_api_create_container, container_api_, container_name, NULL, &container_); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_api_create_container, container_api_, NULL, &spec, &container_); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_api_create_container, container_api_, "", &spec, &container_); + WITH_NULL_CONTAINER_API_RUN(lmctfy_container_api_get_container, container_api_, container_name, &container_); + container_ = tmp; +} + +TEST_F(ClmctfyContainerApiTest, DestroyContainer) { + StrictMockContainerApi *mock_api = GetMockApi(); + const char *container_name = "test"; + Container *ctnr = new StrictMockContainer(container_name); + StatusOr statusor_container = StatusOr(ctnr); + + string errmsg = "some error message"; + Status destroy_status(::util::error::INTERNAL, errmsg); + + EXPECT_CALL(*mock_api, Get(StringPiece(container_name))) + .WillOnce(Return(statusor_container)) + .WillOnce(Return(statusor_container)); + EXPECT_CALL(*mock_api, Destroy(ctnr)) + .WillOnce(Return(Status::OK)) + .WillOnce(Return(destroy_status)); + + SHOULD_SUCCEED(lmctfy_container_api_get_container, container_api_, container_name, &container_); + SHOULD_SUCCEED(lmctfy_container_api_destroy_container, container_api_, container_); + + SHOULD_SUCCEED(lmctfy_container_api_get_container, container_api_, container_name, &container_); + SHOULD_FAIL_WITH_ERROR(destroy_status, lmctfy_container_api_destroy_container, container_api_, container_); + + WITH_NULL_CONTAINER_API_RUN(lmctfy_container_api_destroy_container, container_api_, container_); + // XXX(monnand): Should succeed or not? + SHOULD_SUCCEED(lmctfy_container_api_destroy_container, container_api_, NULL); +} + +TEST_F(ClmctfyContainerApiTest, DetectContainer) { + StrictMockContainerApi *mock_api = GetMockApi(); + const char *container_name = "test"; + char *output_name = NULL; + pid_t pid = 10; + container_ = NULL; + StatusOr statusor(container_name); + + string errmsg = "some error message"; + Status status = Status(::util::error::INTERNAL, errmsg); + StatusOr statusor_fail = StatusOr(status); + + EXPECT_CALL(*mock_api, Detect(pid)) + .WillOnce(Return(statusor)) + .WillOnce(Return(statusor_fail)); + + SHOULD_SUCCEED(lmctfy_container_api_detect_container, container_api_, pid, &output_name); + EXPECT_EQ(string(container_name), string(output_name)); + free(output_name); + + output_name = NULL; + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_api_detect_container, container_api_, pid, &output_name); + EXPECT_EQ(output_name, NULL); + WITH_NULL_CONTAINER_API_RUN(lmctfy_container_api_detect_container, container_api_, pid, &output_name); +} + +} // namespace lmctfy +} // namespace containers + diff --git a/clmctfy/clmctfy_container_api_raw.cc b/clmctfy/clmctfy_container_api_raw.cc new file mode 100644 index 0000000..0785f71 --- /dev/null +++ b/clmctfy/clmctfy_container_api_raw.cc @@ -0,0 +1,63 @@ +#include "clmctfy_container_api_raw.h" + +#include "util/task/statusor.h" +#include "lmctfy.pb.h" +#include "util/task/codes.pb-c.h" +#include "lmctfy.h" + +#include "clmctfy_macros.h" +#include "clmctfy_container_struct.h" +#include "clmctfy_container_api_struct.h" + +#define STATUS_OK UTIL__ERROR__CODE__OK + +using ::containers::lmctfy::Container; +using ::containers::lmctfy::ContainerApi; +using ::containers::lmctfy::InitSpec; +using ::containers::lmctfy::ContainerSpec; +using ::util::internal::status_copy; +using ::util::internal::status_new; +using ::util::Status; +using ::util::StatusOr; + +int lmctfy_init_machine_raw(const void *spec, const size_t spec_size, struct status *s) { + InitSpec init_spec; + CHECK_NOTFAIL_OR_RETURN(s); + if (spec != NULL && spec_size > 0) { + // XXX should we consider this as an error? + init_spec.ParseFromArray(spec, spec_size); + } + Status v = ContainerApi::InitMachine(init_spec); + return status_copy(s, v); +} + +#define COPY_CONTAINER_STRUCTURE(ctnr_ptr, ctnr_struct) do { \ + if ((ctnr_ptr) != NULL) { \ + (*(ctnr_struct)) = new container(); \ + (*(ctnr_struct))->container_ = ctnr_ptr; \ + } \ +} while(0) + +int lmctfy_container_api_create_container_raw(struct container_api *api, + const char *container_name, + const void *spec, + const size_t spec_size, + struct container **c, + struct status *s) { + CHECK_NOTNULL_OR_RETURN(s, api); + CHECK_NOTNULL_OR_RETURN(s, api->container_api_); + CHECK_NOTNULL_OR_RETURN(s, c); + CHECK_NOTNULL_OR_RETURN(s, container_name); + CHECK_POSITIVE_OR_RETURN(s, strlen(container_name)); + ContainerSpec container_spec; + Container *ctnr = NULL; + if (spec != NULL && spec_size > 0) { + container_spec.ParseFromArray(spec, spec_size); + } + + StatusOr statusor = api->container_api_->Create(container_name, container_spec); + RETURN_IF_ERROR_PTR(s, statusor, &ctnr); + COPY_CONTAINER_STRUCTURE(ctnr, c); + return STATUS_OK; +} + diff --git a/clmctfy/clmctfy_container_api_struct.h b/clmctfy/clmctfy_container_api_struct.h new file mode 100644 index 0000000..ba8c073 --- /dev/null +++ b/clmctfy/clmctfy_container_api_struct.h @@ -0,0 +1,10 @@ +#ifndef LMCTFY_CLMCTFY_CLMCTFY_CONTAINER_API_STRUCT_H_ +#define LMCTFY_CLMCTFY_CLMCTFY_CONTAINER_API_STRUCT_H_ + +#include "lmctfy.h" + +struct container_api { + ::containers::lmctfy::ContainerApi *container_api_; +}; + +#endif // LMCTFY_CLMCTFY_CLMCTFY_CONTAINER_API_STRUCT_H_ diff --git a/clmctfy/clmctfy_container_ctest.cc b/clmctfy/clmctfy_container_ctest.cc new file mode 100644 index 0000000..c6b9ede --- /dev/null +++ b/clmctfy/clmctfy_container_ctest.cc @@ -0,0 +1,396 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "clmctfy_container.h" + +#include + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "util/task/statusor.h" +#include "strings/stringpiece.h" + +#include "lmctfy_mock.h" +#include "lmctfy.pb.h" +#include "clmctfy_test_macros.h" +#include "clmctfy_container_struct.h" +#include "clmctfy_container_api_struct.h" + +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::_; +using ::util::Status; +using ::util::StatusOr; +using ::std::vector; + +namespace containers { +namespace lmctfy { + +StatusOr ContainerApi::New() { + return new StrictMockContainerApi(); +} + +Status ContainerApi::InitMachine(const InitSpec &spec) { + return Status::OK; +} + +class ClmctfyContainerTest : public ::testing::Test { + public: + virtual void SetUp() { + container_api_ = NULL; + container_ = NULL; + const char *container_name = "/test"; + container_name_ = container_name; + lmctfy_new_container_api(&container_api_, NULL); + StrictMockContainerApi *mock_api = GetMockApi(); + Container *ctnr = new StrictMockContainer(container_name); + StatusOr statusor = StatusOr(ctnr); + EXPECT_CALL(*mock_api, Get(StringPiece(container_name))).WillOnce(Return(statusor)); + + lmctfy_container_api_get_container(container_api_, container_name, &container_, NULL); + } + + virtual void TearDown() { + lmctfy_delete_container_api(container_api_); + lmctfy_delete_container(container_); + } + protected: + struct container_api *container_api_; + struct container *container_; + StrictMockContainerApi *GetMockApi(); + StrictMockContainer *GetMockContainer(); + string container_name_; +}; + +StrictMockContainerApi *ClmctfyContainerTest::GetMockApi() { + ContainerApi *capi = container_api_->container_api_; + StrictMockContainerApi *mock_api = dynamic_cast(capi); + return mock_api; +} + +StrictMockContainer *ClmctfyContainerTest::GetMockContainer() { + Container *ctnr = container_->container_; + StrictMockContainer *mock_container = dynamic_cast(ctnr); + return mock_container; +} + +TEST_F(ClmctfyContainerTest, Exec) { + StrictMockContainer *mock_container = GetMockContainer(); + int argc = 2; + const char *argv[] = {"echo", "hello world"}; + vector cmds(argc); + string errmsg = "some error message"; + Status status(::util::error::INTERNAL, errmsg); + + for (int i = 0; i < argc; i++) { + cmds[i] = argv[i]; + } + + EXPECT_CALL(*mock_container, Exec(cmds)) + .WillOnce(Return(Status::OK)) + .WillOnce(Return(status)); + + SHOULD_SUCCEED(lmctfy_container_exec, container_, argc, argv); + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_exec, container_, argc, argv); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_exec, container_, 0, NULL); + WITH_NULL_CONTAINER_RUN(lmctfy_container_exec, container_, argc, argv); +} + +TEST_F(ClmctfyContainerTest, Update) { + StrictMockContainer *mock_container = GetMockContainer(); + Containers__Lmctfy__ContainerSpec spec = CONTAINERS__LMCTFY__CONTAINER_SPEC__INIT; + string errmsg = "some error message"; + Status status(::util::error::INTERNAL, errmsg); + + EXPECT_CALL(*mock_container, Update(_, Container::UPDATE_DIFF)) + .WillOnce(Return(Status::OK)) + .WillOnce(Return(status)); + + int policy = CONTAINER_UPDATE_POLICY_DIFF; + SHOULD_SUCCEED(lmctfy_container_update, container_, policy, &spec); + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_update, container_, policy, &spec); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_update, container_, -1, &spec); + WITH_NULL_CONTAINER_RUN(lmctfy_container_update, container_, -1, &spec); +} + +TEST_F(ClmctfyContainerTest, Run) { + StrictMockContainer *mock_container = GetMockContainer(); + string errmsg = "some error message"; + Status err_status(::util::error::INTERNAL, errmsg); + StatusOr statusor_success((pid_t)1); + StatusOr statusor_fail(err_status); + Containers__Lmctfy__RunSpec runspec = CONTAINERS__LMCTFY__RUN_SPEC__INIT; + pid_t tid; + const char *argv[] = {"/bin/echo", "hello world"}; + int argc = 2; + vector cmds(argc); + for (int i = 0; i < argc; i++) { + cmds[i] = argv[i]; + } + + EXPECT_CALL(*mock_container, Run(cmds, _)) + .WillOnce(Return(statusor_success)) + .WillOnce(Return(statusor_fail)); + + SHOULD_SUCCEED(lmctfy_container_run, container_, argc, argv, &runspec, &tid); + SHOULD_FAIL_WITH_ERROR(err_status, lmctfy_container_run, container_, argc, argv, &runspec, &tid); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_run, container_, 0, NULL, &runspec, &tid); + WITH_NULL_CONTAINER_RUN(lmctfy_container_run, container_, argc, argv, &runspec, &tid); +} + +TEST_F(ClmctfyContainerTest, Enter) { + StrictMockContainer *mock_container = GetMockContainer(); + string errmsg = "some error message"; + Status status(::util::error::INTERNAL, errmsg); + + EXPECT_CALL(*mock_container, Enter(_)) + .WillOnce(Return(Status::OK)) + .WillOnce(Return(status)); + + pid_t tids[] = {1, 2, 3, 4}; + int n = 4; + SHOULD_SUCCEED(lmctfy_container_enter, container_, tids, n); + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_enter, container_, tids, n); + // With 0 tid, it should success. + SHOULD_SUCCEED(lmctfy_container_enter, container_, NULL, 0); + WITH_NULL_CONTAINER_RUN(lmctfy_container_enter, container_, NULL, 0); +} + +TEST_F(ClmctfyContainerTest, Spec) { + StrictMockContainer *mock_container = GetMockContainer(); + Status status(::util::error::INTERNAL, "some error message"); + + ContainerSpec spec; + StatusOr statusor_spec(spec); + StatusOr statusor_fail(status); + + EXPECT_CALL(*mock_container, Spec()) + .WillOnce(Return(statusor_spec)) + .WillOnce(Return(statusor_fail)); + + Containers__Lmctfy__ContainerSpec *container_spec; + SHOULD_SUCCEED(lmctfy_container_spec, container_, &container_spec); + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_spec, container_, &container_spec); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_spec, container_, NULL); + WITH_NULL_CONTAINER_RUN(lmctfy_container_spec, container_, &container_spec); +} + +TEST_F(ClmctfyContainerTest, ListSubContainers) { + StrictMockContainer *mock_container = GetMockContainer(); + Status status(::util::error::INTERNAL, "some error message"); + + StrictMockContainer *ctnr1 = new StrictMockContainer("container1"); + StrictMockContainer *ctnr2 = new StrictMockContainer("container2"); + + vector subcontainers_vector(2); + subcontainers_vector[0] = ctnr1; + subcontainers_vector[1] = ctnr2; + StatusOr> statusor_subcontainers(subcontainers_vector); + Container::ListPolicy policy = Container::LIST_SELF; + + StatusOr> statusor_fail(status); + + EXPECT_CALL(*mock_container, ListSubcontainers(policy)) + .WillOnce(Return(statusor_subcontainers)) + .WillOnce(Return(statusor_fail)); + + struct container **subcontainers; + int nr_containers; + SHOULD_SUCCEED(lmctfy_container_list_subcontainers, container_, CONTAINER_LIST_POLICY_SELF, &subcontainers, &nr_containers); + EXPECT_EQ(nr_containers, subcontainers_vector.size()); + vector::iterator iter; + int i = 0; + for (i = 0, iter = subcontainers_vector.begin(); iter != subcontainers_vector.end(); iter++, i++) { + Container *ctnr = subcontainers[i]->container_; + EXPECT_EQ(*iter, ctnr); + lmctfy_delete_container(subcontainers[i]); + } + free(subcontainers); + + subcontainers = NULL; + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_list_subcontainers, container_, CONTAINER_LIST_POLICY_SELF, &subcontainers, &nr_containers); + EXPECT_EQ(nr_containers, 0); + EXPECT_EQ(subcontainers, (struct container **)NULL); + + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_list_subcontainers, container_, CONTAINER_LIST_POLICY_SELF, NULL, &nr_containers); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_list_subcontainers, container_, CONTAINER_LIST_POLICY_SELF, &subcontainers, NULL); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_list_subcontainers, container_, -1, &subcontainers, &nr_containers); + WITH_NULL_CONTAINER_RUN(lmctfy_container_list_subcontainers, container_, CONTAINER_LIST_POLICY_SELF, &subcontainers, &nr_containers); +} + +TEST_F(ClmctfyContainerTest, ListThreads) { + StrictMockContainer *mock_container = GetMockContainer(); + Status status(::util::error::INTERNAL, "some error message"); + + int N = 10; + vector pids_vector(N); + for (int i = 0; i < N; i++) { + pids_vector[i] = i + 1; + } + + StatusOr> statusor_pids(pids_vector); + StatusOr> statusor_fail(status); + Container::ListPolicy policy = Container::LIST_SELF; + + EXPECT_CALL(*mock_container, ListThreads(policy)) + .WillOnce(Return(statusor_pids)) + .WillOnce(Return(statusor_fail)); + + pid_t *pids; + int nr_threads; + SHOULD_SUCCEED(lmctfy_container_list_threads, container_, CONTAINER_LIST_POLICY_SELF, &pids, &nr_threads); + EXPECT_EQ(nr_threads, pids_vector.size()); + vector::const_iterator iter; + int i = 0; + for (i = 0, iter = pids_vector.begin(); iter != pids_vector.end(); iter++, i++) { + EXPECT_EQ(pids[i], *iter); + } + free(pids); + + pids = NULL; + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_list_threads, container_, CONTAINER_LIST_POLICY_SELF, &pids, &nr_threads); + EXPECT_EQ(nr_threads, 0); + EXPECT_EQ(pids, (pid_t *)NULL); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_list_threads, container_, CONTAINER_LIST_POLICY_SELF, NULL, &nr_threads); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_list_threads, container_, CONTAINER_LIST_POLICY_SELF, &pids, NULL); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_list_threads, container_, -1, &pids, &nr_threads); + WITH_NULL_CONTAINER_RUN(lmctfy_container_list_threads, container_, CONTAINER_LIST_POLICY_SELF, &pids, &nr_threads); +} + +TEST_F(ClmctfyContainerTest, ListProcesses) { + StrictMockContainer *mock_container = GetMockContainer(); + Status status(::util::error::INTERNAL, "some error message"); + + int N = 10; + vector pids_vector(N); + for (int i = 0; i < N; i++) { + pids_vector[i] = i + 1; + } + + StatusOr> statusor_pids(pids_vector); + StatusOr> statusor_fail(status); + Container::ListPolicy policy = Container::LIST_SELF; + + EXPECT_CALL(*mock_container, ListProcesses(policy)) + .WillOnce(Return(statusor_pids)) + .WillOnce(Return(statusor_fail)); + + pid_t *pids; + int nr_processes; + SHOULD_SUCCEED(lmctfy_container_list_processes, container_, CONTAINER_LIST_POLICY_SELF, &pids, &nr_processes); + EXPECT_EQ(nr_processes, pids_vector.size()); + vector::const_iterator iter; + int i = 0; + for (i = 0, iter = pids_vector.begin(); iter != pids_vector.end(); iter++, i++) { + EXPECT_EQ(pids[i], *iter); + } + free(pids); + + pids = NULL; + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_list_processes, container_, CONTAINER_LIST_POLICY_SELF, &pids, &nr_processes); + EXPECT_EQ(nr_processes, 0); + EXPECT_EQ(pids, (pid_t *)NULL); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_list_processes, container_, CONTAINER_LIST_POLICY_SELF, NULL, &nr_processes); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_list_processes, container_, CONTAINER_LIST_POLICY_SELF, &pids, NULL); + SHOULD_BE_INVALID_ARGUMENT(lmctfy_container_list_processes, container_, -1, &pids, &nr_processes); + WITH_NULL_CONTAINER_RUN(lmctfy_container_list_processes, container_, CONTAINER_LIST_POLICY_SELF, &pids, &nr_processes); +} + +TEST_F(ClmctfyContainerTest, Pause) { + StrictMockContainer *mock_container = GetMockContainer(); + Status status(::util::error::INTERNAL, "some error message"); + + EXPECT_CALL(*mock_container, Pause()) + .WillOnce(Return(Status::OK)) + .WillOnce(Return(status)); + + SHOULD_SUCCEED(lmctfy_container_pause, container_); + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_pause, container_); + WITH_NULL_CONTAINER_RUN(lmctfy_container_pause, container_); +} + +TEST_F(ClmctfyContainerTest, Resume) { + StrictMockContainer *mock_container = GetMockContainer(); + Status status(::util::error::INTERNAL, "some error message"); + + EXPECT_CALL(*mock_container, Resume()) + .WillOnce(Return(Status::OK)) + .WillOnce(Return(status)); + + SHOULD_SUCCEED(lmctfy_container_resume, container_); + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_resume, container_); + WITH_NULL_CONTAINER_RUN(lmctfy_container_resume, container_); +} + +TEST_F(ClmctfyContainerTest, KillAll) { + StrictMockContainer *mock_container = GetMockContainer(); + Status status(::util::error::INTERNAL, "some error message"); + + EXPECT_CALL(*mock_container, KillAll()) + .WillOnce(Return(Status::OK)) + .WillOnce(Return(status)); + + SHOULD_SUCCEED(lmctfy_container_killall, container_); + SHOULD_FAIL_WITH_ERROR(status, lmctfy_container_killall, container_); + WITH_NULL_CONTAINER_RUN(lmctfy_container_killall, container_); +} + +TEST_F(ClmctfyContainerTest, Name) { + StrictMockContainer *mock_container = GetMockContainer(); + string container_name = mock_container->name(); + const char *name = lmctfy_container_name(container_); + EXPECT_EQ(container_name, name); + name = lmctfy_container_name(NULL); + EXPECT_EQ(name, NULL); + Container *tmp = container_->container_; + container_->container_ = NULL; + name = lmctfy_container_name(container_); + EXPECT_EQ(name, NULL); + container_->container_ = tmp; +} + +static void event_callback_counter(struct container *container, + const struct status *s, + void *data) { + if (data != NULL) { + (*((int *)data))++; + } +} + +TEST_F(ClmctfyContainerTest, RegisterThenUnRegister) { + StrictMockContainer *mock_container = GetMockContainer(); + Containers__Lmctfy__EventSpec spec = CONTAINERS__LMCTFY__EVENT_SPEC__INIT; + notification_id_t notif_id = 0; + StatusOr statusor_success((Container::NotificationId)1); + int evt_counter = 0; + + EXPECT_CALL(*mock_container, RegisterNotification(_, _)) + .WillOnce(Return(statusor_success)); + + EXPECT_CALL(*mock_container, UnregisterNotification(1)) + .WillOnce(Return(Status::OK)); + + SHOULD_SUCCEED(lmctfy_container_register_notification, + container_, + event_callback_counter, + &evt_counter, + &spec, + ¬if_id); + EXPECT_EQ(notif_id, 1); +} + +} // namespace lmctfy +} // namespace containers diff --git a/clmctfy/clmctfy_container_raw.cc b/clmctfy/clmctfy_container_raw.cc new file mode 100644 index 0000000..b71b995 --- /dev/null +++ b/clmctfy/clmctfy_container_raw.cc @@ -0,0 +1,151 @@ +#include "clmctfy_container_raw.h" + +#include +#include +using ::std::vector; + +#include "util/task/codes.pb.h" + +#include "lmctfy.h" +#include "clmctfy_macros.h" +#include "clmctfy_container_struct.h" +#include "clmctfy_event_callback_wrapper.h" + +using ::containers::lmctfy::Container; +using ::containers::lmctfy::ContainerApi; +using ::containers::lmctfy::InitSpec; +using ::containers::lmctfy::ContainerSpec; +using ::containers::lmctfy::RunSpec; +using ::containers::lmctfy::EventSpec; +using ::containers::lmctfy::ContainerStats; +using ::util::Status; +using ::util::StatusOr; +using ::util::internal::status_copy; +using ::util::internal::status_new; + +#define STATUS_OK UTIL__ERROR__CODE__OK + +int lmctfy_container_run_raw(struct container *container, + const int argc, + const char **argv, + const void *spec, + const size_t spec_size, + pid_t *tid, + struct status *s) { + RunSpec run_spec; + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + CHECK_NOTNULL_OR_RETURN(s, tid); + CHECK_POSITIVE_OR_RETURN(s, argc); + if (spec != NULL && spec_size > 0) { + run_spec.ParseFromArray(spec, spec_size); + } + vector cmds(argc); + int i = 0; + for (i = 0; i < argc; i++) { + cmds[i] = argv[i]; + } + StatusOr statusor = container->container_->Run(cmds, run_spec); + RETURN_IF_ERROR_PTR(s, statusor, tid); + return STATUS_OK; +} + +int lmctfy_container_update_raw(struct container *container, + int policy, + const void *spec, + const size_t spec_size, + struct status *s) { + ContainerSpec container_spec; + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + if (spec != NULL && spec_size > 0) { + container_spec.ParseFromArray(spec, spec_size); + } + Container::UpdatePolicy p; + switch (policy) { + case CONTAINER_UPDATE_POLICY_DIFF: + p = Container::UPDATE_DIFF; + break; + case CONTAINER_UPDATE_POLICY_REPLACE: + p = Container::UPDATE_REPLACE; + break; + default: + return status_new(s, UTIL__ERROR__CODE__INVALID_ARGUMENT, + "Unknown update policy: %d", policy); + } + Status status = container->container_->Update(container_spec, p); + return status_copy(s, status); +} + +int lmctfy_container_register_notification_raw(struct container *container, + lmctfy_event_callback_f callback, + void *user_data, + const void *spec, + const size_t spec_size, + notification_id_t *notif_id, + struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + CHECK_NOTNULL_OR_RETURN(s, notif_id); + CHECK_NOTNULL_OR_RETURN(s, callback); + EventSpec event_spec; + if (spec != NULL && spec_size > 0) { + event_spec.ParseFromArray(spec, spec_size); + } + EventCallbackWrapper *cb = + new EventCallbackWrapper(container, callback, user_data); + + // Container object does not take the ownership of the callback. + StatusOr statusor_id = + container->container_->RegisterNotification(event_spec, cb); + + if (!statusor_id.ok()) { + delete cb; + return status_copy(s, statusor_id.status()); + } + notification_id_t nid = statusor_id.ValueOrDie(); + container->notif_map_[nid] = cb; + *notif_id = nid; + return STATUS_OK; +} + + +int lmctfy_container_stats_raw(struct container *container, + int stats_type, + void **stats, + size_t *sz, + struct status *s) { + CHECK_NOTFAIL_OR_RETURN(s); + CHECK_NOTNULL_OR_RETURN(s, container); + CHECK_NOTNULL_OR_RETURN(s, container->container_); + CHECK_NOTNULL_OR_RETURN(s, stats); + + int ret = STATUS_OK; + Container::StatsType type; + switch (stats_type) { + case CONTAINER_STATS_TYPE_SUMMARY: + type = Container::STATS_SUMMARY; + break; + case CONTAINER_STATS_TYPE_FULL: + type = Container::STATS_FULL; + break; + default: + return status_new(s, UTIL__ERROR__CODE__INVALID_ARGUMENT, "Unknown stats type: %d", stats_type); + } + StatusOr statusor_container_stats = container->container_->Stats(type); + if (!statusor_container_stats.ok()) { + return status_copy(s, statusor_container_stats.status()); + } + + const ContainerStats &container_stats = statusor_container_stats.ValueOrDie(); + *sz = container_stats.ByteSize(); + *stats = NULL; + if (sz > 0) { + *stats = malloc(*sz); + container_stats.SerializeToArray(*stats, *sz); + } + return ret; +} diff --git a/clmctfy/clmctfy_container_struct.h b/clmctfy/clmctfy_container_struct.h new file mode 100644 index 0000000..ba580ba --- /dev/null +++ b/clmctfy/clmctfy_container_struct.h @@ -0,0 +1,17 @@ +#ifndef LMCTFY_CLMCTFY_CLMCTFY_CONTAINER_STRUCT_H_ +#define LMCTFY_CLMCTFY_CLMCTFY_CONTAINER_STRUCT_H_ + +#include +#include "lmctfy.h" +#include "clmctfy_container.h" + +class EventCallbackWrapper; + +struct container { + ::containers::lmctfy::Container *container_; + // TODO(monnand): Make it thread-safe? + ::std::unordered_map notif_map_; +}; + + +#endif // LMCTFY_CLMCTFY_CLMCTFY_CONTAINER_STRUCT_H_ diff --git a/clmctfy/clmctfy_event_callback_wrapper.cc b/clmctfy/clmctfy_event_callback_wrapper.cc new file mode 100644 index 0000000..48a8560 --- /dev/null +++ b/clmctfy/clmctfy_event_callback_wrapper.cc @@ -0,0 +1,50 @@ +#include "clmctfy_event_callback_wrapper.h" + +#include "util/task/status.h" +#include "lmctfy.h" +#include "clmctfy_status_internal.h" +#include "clmctfy_container_struct.h" + +using ::containers::lmctfy::Container; +using ::util::Status; +using ::util::internal::status_copy; +using ::util::internal::status_new; + +void EventCallbackWrapper::Run(Container *c, Status s) { + if (callback_ == NULL) { + return; + } + struct status sts; + status_copy(&sts, s); + if (c == NULL) { + callback_(NULL, &sts, user_data_); + if (sts.message != NULL) { + free(sts.message); + } + return; + } + if (c != container_->container_) { + char *oldmsg = sts.message; + int olderrcode = sts.error_code; + struct container ctnr; + ctnr.container_ = c; + // This should never happen. + status_new(&sts, UTIL__ERROR__CODE__UNKNOWN, + "Unknown container passed to the callback. " + "(ErrorCode=%d, Message=\"%s\")", olderrcode, + (oldmsg == NULL ? "" : oldmsg)); + callback_(&ctnr, &sts, user_data_); + if (sts.message != NULL) { + free(sts.message); + } + if (oldmsg != NULL) { + free(oldmsg); + } + return; + } + callback_(container_, &sts, user_data_); + if (sts.message != NULL) { + free(sts.message); + } +} + diff --git a/clmctfy/clmctfy_event_callback_wrapper.h b/clmctfy/clmctfy_event_callback_wrapper.h new file mode 100644 index 0000000..cd953fc --- /dev/null +++ b/clmctfy/clmctfy_event_callback_wrapper.h @@ -0,0 +1,26 @@ +#ifndef LMCTFY_CLMCTFY_CLMCTFY_EVENT_CALLBACK_WRAPPER_H_ +#define LMCTFY_CLMCTFY_CLMCTFY_EVENT_CALLBACK_WRAPPER_H_ + +#include "lmctfy.h" +#include "clmctfy_container.h" + +class EventCallbackWrapper : public Callback2<::containers::lmctfy::Container *, ::util::Status> { + public: + EventCallbackWrapper(struct container *c, + lmctfy_event_callback_f cb, + void *user_data) + : container_(c), + callback_(cb), + user_data_(user_data) { } + virtual ~EventCallbackWrapper() {} + virtual bool IsRepeatable() const { return true; } + virtual void Run(::containers::lmctfy::Container *c, ::util::Status s); + private: + lmctfy_event_callback_f callback_; + void *user_data_; + struct container *container_; + ::containers::lmctfy::Container ::NotificationId notif_id_; + DISALLOW_COPY_AND_ASSIGN(EventCallbackWrapper); +}; + +#endif // LMCTFY_CLMCTFY_CLMCTFY_EVENT_CALLBACK_WRAPPER_H_ diff --git a/clmctfy/clmctfy_macros.h b/clmctfy/clmctfy_macros.h new file mode 100644 index 0000000..a730ef0 --- /dev/null +++ b/clmctfy/clmctfy_macros.h @@ -0,0 +1,47 @@ +#ifndef LMCTFY_CLMCTFY_CLMCTFY_MACROS_H_H +#define LMCTFY_CLMCTFY_CLMCTFY_MACROS_H_H + +#include "util/errors.h" +#include "util/task/status.h" +#include "util/task/statusor.h" +#include "util/task/codes.pb-c.h" + +#include "clmctfy_status.h" +#include "clmctfy_status_internal.h" + +#define RETURN_IF_ERROR_PTR(s, expr, out) \ + do { \ + auto _expr_result = (expr); \ + if (PREDICT_FALSE(!_expr_result.ok())) { \ + const ::util::Status _status = _expr_result.status(); \ + if (s != NULL) ::util::internal::status_copy(s, _status); \ + return (int)_status.error_code(); \ + } \ + (*(out)) = _expr_result.ValueOrDie(); \ + } while (0) + +#define CHECK_NOTFAIL_OR_RETURN(status) do { \ + if ((status) != NULL) { \ + if ((status)->error_code != UTIL__ERROR__CODE__OK) { \ + return (status)->error_code; \ + } \ + } \ +} while(0) + +#define CHECK_NOTNULL_OR_RETURN(status, ptr) do { \ + if ((ptr) == NULL) { \ + return ::util::internal::status_new(status, UTIL__ERROR__CODE__INVALID_ARGUMENT, \ + "In function %s: %s cannot be null", \ + __func__, #ptr); \ + } \ +} while (0) + +#define CHECK_POSITIVE_OR_RETURN(status, value) do { \ + if ((value) <= 0) { \ + return ::util::internal::status_new(status, UTIL__ERROR__CODE__INVALID_ARGUMENT, \ + "In function %s: %s=%d, but it should be positive", \ + __func__, #value, value); \ + } \ +} while (0) + +#endif // LMCTFY_CLMCTFY_CLMCTFY_MACROS_H_H diff --git a/clmctfy/clmctfy_status_internal.cc b/clmctfy/clmctfy_status_internal.cc new file mode 100644 index 0000000..0b21316 --- /dev/null +++ b/clmctfy/clmctfy_status_internal.cc @@ -0,0 +1,53 @@ +#include "clmctfy_status_internal.h" + +#include // to use strdup +#include + +#include "util/task/status.h" + +using ::util::Status; + +namespace util { +namespace internal { + +#define MAXLINE 4096 + +static int status_new_fmt(struct status *dst, int code, const char *fmt, va_list ap) { + char buf[MAXLINE]; + if (dst == NULL) { + return code; + } + dst->error_code = code; + + if (fmt == NULL || code == 0) { + return code; + } + memset(buf, 0, MAXLINE); + vsnprintf(buf, MAXLINE, fmt, ap); + dst->error_code = code; + dst->message = strdup(buf); + return code; +} + +int status_new(struct status *dst, int code, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + status_new_fmt(dst, code, fmt, ap); + va_end(ap); + return code; +} + +int status_copy(struct status *dst, const Status &src) { + if (dst == NULL) { + return (int)src.error_code(); + } + dst->error_code = src.error_code(); + if (!src.ok() && src.error_message() != "") { + dst->message = strdup(src.error_message().c_str()); + } + return (int)src.error_code(); +} + +} // namespace internal +} // namespace util + diff --git a/clmctfy/clmctfy_status_internal.h b/clmctfy/clmctfy_status_internal.h new file mode 100644 index 0000000..2997243 --- /dev/null +++ b/clmctfy/clmctfy_status_internal.h @@ -0,0 +1,16 @@ +#ifndef LMCTFY_CLMCTFY_CLMCTFY_STATUS_INTERNAL_H_ +#define LMCTFY_CLMCTFY_CLMCTFY_STATUS_INTERNAL_H_ + +#include "clmctfy_status.h" +#include "util/task/status.h" + +namespace util { +namespace internal { + +int status_new(struct status *dst, int code, const char *fmt, ...); +int status_copy(struct status *dst, const Status &src); + +} // namespace internal +} // namespace util + +#endif // LMCTFY_CLMCTFY_CLMCTFY_STATUS_INTERNAL_H_ diff --git a/clmctfy/clmctfy_test_macros.h b/clmctfy/clmctfy_test_macros.h new file mode 100644 index 0000000..b44beb9 --- /dev/null +++ b/clmctfy/clmctfy_test_macros.h @@ -0,0 +1,82 @@ +#ifndef LMCTFY_CLMCTFY_CLMCTFY_MACROS_CTEST_H_ +#define LMCTFY_CLMCTFY_CLMCTFY_MACROS_CTEST_H_ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "util/task/status.h" +#include "util/task/statusor.h" +#include "util/task/codes.pb-c.h" + +#include "clmctfy_status.h" +#include "clmctfy_container_api.h" +#include "clmctfy_container.h" +#include "clmctfy_container_struct.h" +#include "clmctfy_container_api_struct.h" + +// Some useful macros for writing test cases. + +#define WITH_NULL_CONTAINER_API_RUN(func, ...) do { \ + ContainerApi *tmp = container_api_->container_api_; \ + container_api_->container_api_ = NULL; \ + struct status s = {0, NULL}; \ + int ret = func(__VA_ARGS__, &s); \ + EXPECT_EQ(ret, s.error_code); \ + EXPECT_EQ(s.error_code, UTIL__ERROR__CODE__INVALID_ARGUMENT); \ + container_api_->container_api_ = tmp; \ + \ + struct container_api *tmp_api = container_api_; \ + container_api_ = NULL; \ + s = {0, NULL}; \ + ret = func(__VA_ARGS__, &s); \ + EXPECT_EQ(ret, s.error_code); \ + EXPECT_EQ(s.error_code, UTIL__ERROR__CODE__INVALID_ARGUMENT); \ + container_api_ = tmp_api; \ +} while(0) + +#define WITH_NULL_CONTAINER_RUN(func, ...) do { \ + Container *tmp = container_->container_; \ + container_->container_ = NULL; \ + struct status s = {0, NULL}; \ + int ret = func(__VA_ARGS__, &s); \ + EXPECT_EQ(ret, s.error_code); \ + EXPECT_EQ(s.error_code, UTIL__ERROR__CODE__INVALID_ARGUMENT); \ + container_->container_ = tmp; \ + \ + struct container *tmp_container = container_; \ + container_ = NULL; \ + s = {0, NULL}; \ + ret = func(__VA_ARGS__, &s); \ + EXPECT_EQ(ret, s.error_code); \ + EXPECT_EQ(s.error_code, UTIL__ERROR__CODE__INVALID_ARGUMENT); \ + container_ = tmp_container; \ +} while (0) + +#define SHOULD_SUCCEED(func, ...) do { \ + struct status s = {0, NULL}; \ + int ret = func(__VA_ARGS__, &s); \ + EXPECT_EQ(ret, 0); \ + EXPECT_EQ(s.error_code, 0); \ + EXPECT_EQ(s.message, NULL); \ +} while (0) + +#define SHOULD_FAIL_WITH_ERROR(st, func, ...) do { \ + struct status s = {0, NULL}; \ + int ret = func(__VA_ARGS__, &s); \ + EXPECT_EQ(ret, s.error_code); \ + EXPECT_EQ(s.error_code, (st).error_code()); \ + EXPECT_EQ((st).error_message(), s.message); \ + free(s.message); \ +} while (0) + +#define SHOULD_BE_INVALID_ARGUMENT(func, ...) do { \ + struct status s = {0, NULL}; \ + int ret = func(__VA_ARGS__, &s); \ + EXPECT_EQ(ret, s.error_code); \ + EXPECT_EQ(s.error_code, UTIL__ERROR__CODE__INVALID_ARGUMENT); \ + if (s.message != NULL) { \ + free(s.message); \ + } \ +} while (0) + +#endif // LMCTFY_CLMCTFY_CLMCTFY_MACROS_CTEST_H_ + diff --git a/examples/clmctfy_simple_existing.c b/examples/clmctfy_simple_existing.c new file mode 100644 index 0000000..6c8994f --- /dev/null +++ b/examples/clmctfy_simple_existing.c @@ -0,0 +1,66 @@ +// Simple example of the struct container_api usage for accessing +// information about an existing container. + +#include +#include +#include +#include + +#include "clmctfy.h" +#include "lmctfy.pb-c.h" + +int main() { + struct container_api *lmctfy = NULL; + struct container *container = NULL; + struct status s = {0, NULL}; + int err = 0; + char *container_name = NULL; + Containers__Lmctfy__ContainerStats *stats = NULL; + + err = lmctfy_new_container_api(&lmctfy, &s); + if (err != 0) { + printf("Failed to instantiate container_api: %s\n", s.message); + free(s.message); + lmctfy_delete_container_api(lmctfy); + return -1; + } + + // Get what container the current thread is in. + err = lmctfy_container_api_detect_container(lmctfy, 0, &container_name, &s); + if (err != 0) { + printf("Failed to detect the current container: %s\n", s.message); + free(s.message); + lmctfy_delete_container_api(lmctfy); + return -1; + } + printf("Current container: %s\n", container_name); + + err = lmctfy_container_api_get_container(lmctfy, ".", &container, &s); + if (err != 0) { + printf("Failed to get container: %s\n", s.message); + free(s.message); + free(container_name); + lmctfy_delete_container_api(lmctfy); + return -1; + } + + err = lmctfy_container_stats(container, CONTAINER_STATS_TYPE_SUMMARY, &stats, &s); + if (err != 0) { + printf("Failed to get container stats: %s\n", s.message); + free(s.message); + free(container_name); + lmctfy_delete_container(container); + lmctfy_delete_container_api(lmctfy); + return -1; + } + + printf("Memory usage: %ld\nWorking set: %ld\n", + stats->memory->usage, + stats->memory->working_set); + free(container_name); + free(stats); + lmctfy_delete_container(container); + lmctfy_delete_container_api(lmctfy); + + return 0; +} diff --git a/include/clmctfy-raw.h b/include/clmctfy-raw.h new file mode 100644 index 0000000..b765241 --- /dev/null +++ b/include/clmctfy-raw.h @@ -0,0 +1,8 @@ +#ifndef LMCTFY_C_BINDING_CLMCTFY_RAW_H_ +#define LMCTFY_C_BINDING_CLMCTFY_RAW_H_ + +#include "clmctfy_status.h" +#include "clmctfy_container_api_raw.h" +#include "clmctfy_container_raw.h" + +#endif // LMCTFY_C_BINDING_CLMCTFY_RAW_H_ diff --git a/include/clmctfy.h b/include/clmctfy.h new file mode 100644 index 0000000..96f3ee0 --- /dev/null +++ b/include/clmctfy.h @@ -0,0 +1,8 @@ +#ifndef LMCTFY_INCLUDE_CLMCTFY_H_ +#define LMCTFY_INCLUDE_CLMCTFY_H_ + +#include "clmctfy_status.h" +#include "clmctfy_container_api.h" +#include "clmctfy_container.h" + +#endif // LMCTFY_INCLUDE_CLMCTFY_H_ diff --git a/include/clmctfy_container.h b/include/clmctfy_container.h new file mode 100644 index 0000000..a164351 --- /dev/null +++ b/include/clmctfy_container.h @@ -0,0 +1,394 @@ +#ifndef CLMCTFY_INCLUDE_CLMCTFY_CONTAINER_H_ +#define CLMCTFY_INCLUDE_CLMCTFY_CONTAINER_H_ + +#include +#include + +#include "util/task/codes.pb-c.h" +#include "lmctfy.pb-c.h" +#include "clmctfy_status.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +struct container; +struct container_api; + +enum { + // Update only the specified fields. + CONTAINER_UPDATE_POLICY_DIFF, + + // Replace the existing container with the new specification. + CONTAINER_UPDATE_POLICY_REPLACE +}; + +enum { + // Only output the information of this container. + CONTAINER_LIST_POLICY_SELF, + + // Output the information of this container and all of its subcontainers and + // their subcontainers. + CONTAINER_LIST_POLICY_RECURSIVE +}; + +enum { + // A summary of the statistics (see each resource's definition of summary). + CONTAINER_STATS_TYPE_SUMMARY, + + // All available statistics. + CONTAINER_STATS_TYPE_FULL +}; + +// Callback used on an event notification. +// +// The container and status structure pointers are only valid within the +// callback. +// +// - container: The container that received the notification. It is an error +// to delete it. +// - status: The status of the notification. If OK, then the event registered +// occured. Otherwise, an error is reported in the status. Errors may +// be caused by container deletion or unexpected registration errors. +// it will be an error if the user call free(s->message); +// - user_data: +typedef void (*lmctfy_event_callback_f)(struct container *container, + const struct status *status, + void *user_data); + +typedef uint64_t notification_id_t; + +// Release the memory used by the container structure. The refered container +// will not be affected. +// +// Arguments: +// +// - container: The container. This pointer will be invalid after the call to +// this function +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +void lmctfy_delete_container(struct container *container); + +// Moves the specified threads into this container. Enter is atomic. +// +// Arguments: +// +// - container: The container. +// - tids: Array of thread IDs to move into the container. Caller takes the +// ownership. +// - tids_size: Number of thread IDs stored in tids. Caller takes the +// ownership. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_enter(struct container *container, + const pid_t *tids, + const int tids_size, + struct status *s); + +// Run the specified command inside the container. Multiple instances of run +// can be active simultaneously. Processes MUST be reaped by the caller. +// +// Arguments: +// +// - container: The container. +// - argc: number of arguments (including the binary file path). +// - argv: All arguments. The first element is the binary that will be executed +// and must be an absolute path. +// - spec: The specification of the runtime environment to use for the +// execution of the command. +// - tid: [output] On success, tid stores the PID of the command. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_run(struct container *container, + const int argc, + const char **argv, + const Containers__Lmctfy__RunSpec *spec, + pid_t *tid, + struct status *s); + +// Execute the specified command inside the container. This replaces the +// current process image with the specified command. The PATH environment +// variable is used, and the existing environment is passed to the new +// process image unchanged. +// +// Arguments: +// +// - container +// - argc: number of arguments (including the binary file path). +// - argv: All arguments. The first element is the binary that will be executed +// and must be an absolute path. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_exec(struct container *container, + const int argc, + const char **argv, + struct status *s); + +// Updates the container according to the specification. The set of resource +// types being isolated cannot change during an Update. This means that a +// CONTAINER_UPDATE_POLICY_REPLACE must specify all the resources being isolated +// and a CONTAINER_UPDATE_POLICY_DIFF cannot specify any resource that is not +// already being isolated. +// +// Arguments: +// +// - container +// - policy: Update policy. Can be either CONTAINER_UPDATE_POLICY_DIFF, or +// CONTAINER_UPDATE_POLICY_REPLACE. +// - spec: The specification of the desired updates. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_update(struct container *container, + int policy, + const Containers__Lmctfy__ContainerSpec *spec, + struct status *s); + +// Returns the resource isolation specification (ContainerSpec) of this +// container. +// +// Arguments: +// +// - container: The container +// - spec: [output] An address of a ContainerSpec pointer. *spec will points to +// the ContainerSpec of the container. Caller takes the ownership. *spec can +// be released by calling free(). +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_spec(struct container *container, + Containers__Lmctfy__ContainerSpec **spec, + struct status *s); + +// List all subcontainers. +// +// Arguments: +// +// - container: The container +// - list_policy: CONTAINER_LIST_POLICY_SELF or CONTAINER_LIST_POLICY_RECURSIVE +// - subcontainers: [output] The address of a pointer points to an array of +// struct container pointers. The caller takes the onwership. On success, the +// pointer will be assigned an address to an array of containers. The pointed +// array should be released with free(). The containers inside the array +// should be deleted/destroyed individually. +// - subcontainers_size: [output] The address of an integer used to store number +// of subcontainers in the array. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_list_subcontainers(struct container *container, + int list_policy, + struct container **subcontainers[], + int *subcontainers_size, + struct status *s); + +// List all TIDs in the container. +// +// Arguments: +// - container +// - list_policy: List policy. +// - threads: [output] The address of a pointer points to an array of pid_t. +// The caller takes the ownership. The array can be released with free(). +// - threads_size: [output] *threads_size is the number of TIDs stored in *threads. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_list_threads(struct container *container, + int list_policy, + pid_t *threads[], + int *threads_size, + struct status *s); + +// Get all PIDs in this container. +// +// Arguments: +// - container +// - list_policy: List policy. +// - processes: [output] The address of a pointer points to an array of pid_t. +// The caller takes the ownership. The array can be released with free(). +// - processes_size: [output] *processes_size is the number of PIDs stored in +// *processes. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_list_processes(struct container *container, + int list_policy, + pid_t *processes[], + int *processes_size, + struct status *s); + +// Atomically stops the execution of all threads inside the container and all +// subcontainers (recursively). All threads moved to a paused container will +// be paused as well (regardless of whether the PID is in the container). This +// guarantees to get all threads. +// +// Arguments: +// - container: The container. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_pause(struct container *container, + struct status *s); + +// Atomically resumes the execution of all threads inside the container and +// all subcontainers (recursively). All paused threads moved to a non-paused +// container will be resumed. +// +// Arguments: +// - container: The container. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_resume(struct container *container, + struct status *s); + +// Kills all processes running in the container. This operation is atomic and +// is synchronized with any mutable operations on this container. +// +// The operation sends a SIGKILL to all processes in the containers. Tourist +// threads are killed via SIGKILL after all processes have exited. +// +// Note that this operation can potentially take a long time (O(seconds)) if +// the processes in the container do not finish quickly. This operation also +// blocks all mutable container operations while it is in progress. +// +// Arguments: +// - container: The container. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_killall(struct container *container, + struct status *s); + +// Gets usage and state information for the container. Note that the snapshot +// is not atomic. +// +// Arguments: +// - container: The container. +// - stats_type: The type of statistics to output. +// - stats: [output] Used to store a pointer points to the container's +// statistics. The call takes the ownership of *stats, which should be +// free()'ed. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_stats(struct container *container, + int stats_type, + Containers__Lmctfy__ContainerStats **stats, + struct status *s); + +// Get the name of the container. +// +// Arguments: +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Return: +// The container name. The caller does not take the ownership. +const char *lmctfy_container_name(struct container *container); + +// Register a notification for a specified container event. All notifications +// are unregistered when the container is destroyed. +// +// Arguments: +// - container: The container. +// - callback: The callback to run when the event is triggered. The caller +// takes ownership of the callback which MUST be a repeatable callback. +// - user_data: The pointer which will be passed to the callback function as +// its last parameter. The caller takes the ownership. +// - spec: The specification for the event for which to register notifications. +// The caller takes the ownership. +// - notif_id: [output] The ID for the notification. The ID is unique within +// the current container_api instance. It will be used to unregister the +// notification +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_register_notification(struct container *container, + lmctfy_event_callback_f callback, + void *user_data, + Containers__Lmctfy__EventSpec *spec, + notification_id_t *notif_id, + struct status *s); + +// Unregister (stop) the specified notification from being received. +// +// Arguments: +// - container: The container. +// - notif_id: The unique notification ID for the container +// notification. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_unregister_notification(struct container *container, + const notification_id_t notif_id, + struct status *s); + + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // CLMCTFY_INCLUDE_CLMCTFY_CONTAINER_H_ diff --git a/include/clmctfy_container_api.h b/include/clmctfy_container_api.h new file mode 100644 index 0000000..6371166 --- /dev/null +++ b/include/clmctfy_container_api.h @@ -0,0 +1,138 @@ +#ifndef LMCTFY_INCLUDE_CLMCTFY_CONTAINER_API_H_ +#define LMCTFY_INCLUDE_CLMCTFY_CONTAINER_API_H_ + +#include + +#include "lmctfy.pb-c.h" +#include "clmctfy_status.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +struct container; +struct container_api; + +// Initializes the machine to start being able to create containers. +// +// Arguments: +// - spec: The specification. Caller owns the pointer. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_init_machine(const Containers__Lmctfy__InitSpec *spec, struct status *s); + +// Create a new container_api. +// +// Arguments: +// - api: [output] The address of a pointer to struct container_api. The +// pointer of the container api will be stored in this address. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_new_container_api(struct container_api **api, struct status *s); + +// Release the container api. +// +// Arguments: +// +// - api: The container api. The function takes the ownershp. +void lmctfy_delete_container_api(struct container_api *api); + +// Get a container +// +// Arguments: +// +// - api: A container api. +// - container_name: the container name. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// - container: [output] The address of a pointer to struct container. It will +// be used to store the pointer to the container. The caller takes the +// ownership. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_api_get_container( + const struct container_api *api, + const char *container_name, + struct container **container, + struct status *s); + +// Create a container +// +// Arguments: +// +// - api: A container api. +// - container_name: the container name. +// - spec: container specification. Caller owns the pointer. +// - container: [output] The address of a pointer to struct container. It will +// be used to store the newly created container. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_api_create_container( + struct container_api *api, + const char *container_name, + const Containers__Lmctfy__ContainerSpec *spec, + struct container **container, + struct status *s); + +// Destroy a container. The caller has to call lmctfy_delete_container after +// detroying a container. Otherwise, the memory occupied by the container +// structure will not be released. +// +// Arguments: +// +// - api: A container api. +// - container: The pointer to struct container. The pointer will become +// invalid after a successful destroy(). +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_api_destroy_container(struct container_api *api, + struct container *container, + struct status *s); + +// Detect what container the specified thread is in. +// +// Arguments: +// - api: The container api. +// - pid: The thread ID to check. 0 refers to self. +// - container_name: [output] Will be used to store the container name. +// It's the caller's responsibility to free() *container_name. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_api_detect_container(struct container_api *api, + pid_t pid, + char **container_name, + struct status *s); + + +#ifdef __cplusplus +} +#endif // __cplusplus +#endif // LMCTFY_INCLUDE_CLMCTFY_CONTAINER_API_H_ diff --git a/include/clmctfy_container_api_raw.h b/include/clmctfy_container_api_raw.h new file mode 100644 index 0000000..393f2c2 --- /dev/null +++ b/include/clmctfy_container_api_raw.h @@ -0,0 +1,58 @@ +#ifndef LMCTFY_INCLUDE_CLMCTFY_CONTAINER_API_RAW_H_ +#define LMCTFY_INCLUDE_CLMCTFY_CONTAINER_API_RAW_H_ + +#include +#include "clmctfy_status.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +struct container; +struct container_api; + +// Initializes the machine to start being able to create containers. +// +// Arguments: +// - spec: Serialized data (protobuf format) containing the specification. +// Caller takes the ownership. +// - spec_size: Size of the serialized data. +// - s: [output] s will be used as output. It contains the error code/message. +// +// Returns: +// Returns the error code. 0 on success. The return code is same as +// status_get_code(s). +int lmctfy_init_machine_raw(const void *spec, const size_t spec_size, struct status *s); + +// Create a container +// +// Arguments: +// +// - api: A container api. +// - container_name: the container name. +// - spec: Serialized data (protobuf format) containing the specification. +// Caller takes the ownership. +// - spec_size: Size of the serialized data. +// - container: [output] The address of a pointer to struct container. It will +// be used to store the newly created container. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_api_create_container_raw( + struct container_api *api, + const char *container_name, + const void *spec, + const size_t spec_size, + struct container **container, + struct status *s); + + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // LMCTFY_INCLUDE_CLMCTFY_CONTAINER_API_RAW_H_ diff --git a/include/clmctfy_container_raw.h b/include/clmctfy_container_raw.h new file mode 100644 index 0000000..588b2aa --- /dev/null +++ b/include/clmctfy_container_raw.h @@ -0,0 +1,126 @@ +#ifndef LMCTFY_INCLUDE_CLMCTFY_CONTAINER_RAW_H_ +#define LMCTFY_INCLUDE_CLMCTFY_CONTAINER_RAW_H_ + +#include +#include "clmctfy_status.h" +#include "clmctfy_container.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +struct container_api; +struct container; + +// Run the specified command inside the container. Multiple instances of run +// can be active simultaneously. Processes MUST be reaped by the caller. +// +// Arguments: +// +// - container: The container. +// - argc: number of arguments (including the binary file path). +// - argv: All arguments. The first element is the binary that will be executed +// and must be an absolute path. +// - spec: Serialized data (protobuf format) containing the specification. +// Caller takes the ownership. +// - spec_size: Size of the serialized data. +// - tid: [output] On success, tid stores the PID of the command. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_run_raw(struct container *container, + const int argc, + const char **argv, + const void *spec, + const size_t spec_size, + pid_t *tid, + struct status *s); + +// Updates the container according to the specification. The set of resource +// types being isolated cannot change during an Update. This means that a +// CONTAINER_UPDATE_POLICY_REPLACE must specify all the resources being isolated +// and a CONTAINER_UPDATE_POLICY_DIFF cannot specify any resource that is not +// already being isolated. +// +// Arguments: +// +// - container +// - policy: Update policy. Can be either CONTAINER_UPDATE_POLICY_DIFF, or +// CONTAINER_UPDATE_POLICY_REPLACE. +// - spec: Serialized data (protobuf format) containing the specification. +// Caller takes the ownership. +// - spec_size: Size of the serialized data. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_update_raw(struct container *container, + int policy, + const void *spec, + const size_t spec_size, + struct status *s); + +// Register a notification for a specified container event. All notifications +// are unregistered when the container is destroyed. +// +// Arguments: +// - container: The container. +// - callback: The callback to run when the event is triggered. The caller +// takes ownership of the callback which MUST be a repeatable callback. +// - user_data: The pointer which will be passed to the callback function as +// its last parameter. The caller takes the ownership. +// - spec: Serialized data (protobuf format) containing the specification. +// Caller takes the ownership. +// - spec_size: Size of the serialized data. +// - notif_id: [output] The ID for the notification. The ID is unique within +// the current container_api instance. It will be used to unregister the +// notification +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_register_notification_raw(struct container *container, + lmctfy_event_callback_f callback, + void *user_data, + const void *spec, + const size_t spec_size, + notification_id_t *notif_id, + struct status *s); + +// Gets usage and state information for the container. Note that the snapshot +// is not atomic. +// +// Arguments: +// - container: The container. +// - stats_type: The type of statistics to output. +// - spec: [output] Used to store a pointer points to the container's +// statistics, which is serialized. The call takes the ownership of +// *spec, which should be free()'ed. +// - stats_size: [output] The size of the serialized data. +// - s: [output] The status of the operations and an error message if the +// status is not OK. +// +// Returns: +// +// Returns the error code. 0 on success. When there's an error, the return code +// is same as s->error_code when s is not NULL. +int lmctfy_container_stats_raw(struct container *container, + int stats_type, + void **stats, + size_t *stats_size, + struct status *s); +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // LMCTFY_INCLUDE_CLMCTFY_CONTAINER_RAW_H_ diff --git a/include/clmctfy_status.h b/include/clmctfy_status.h new file mode 100644 index 0000000..91c8717 --- /dev/null +++ b/include/clmctfy_status.h @@ -0,0 +1,20 @@ +#ifndef LMCTFY_INCLUDE_CLMCTFY_STATUS_H_ +#define LMCTFY_INCLUDE_CLMCTFY_STATUS_H_ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#ifdef __cplusplus +} +#endif // __cplusplus + +struct status { + int error_code; + + // Null-terminated string allocated on heap. + // Needs to be free()'ed. + char *message; +}; + +#endif // LMCTFY_INCLUDE_CLMCTFY_STATUS_H_