Merge branch 'master' into gdal_sandbox

pull/64/head
happyCoder92 2020-10-19 10:05:44 +02:00 committed by GitHub
commit 2e3b118ec9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 5160 additions and 394 deletions

12
.gitmodules vendored
View File

@ -1,12 +1,18 @@
[submodule "oss-internship-2020/sapi_lodepng/lodepng"]
path = oss-internship-2020/lodepng/lodepng
url = https://github.com/lvandeve/lodepng
[submodule "oss-internship-2020/jsonnet/jsonnet"]
path = oss-internship-2020/jsonnet/jsonnet
url = https://github.com/google/jsonnet.git
[submodule "oss-internship-2020/openjpeg/openjpeg"]
path = oss-internship-2020/openjpeg/openjpeg
url = https://github.com/uclouvain/openjpeg.git
[submodule "oss-internship-2020/pffft/master"]
path = oss-internship-2020/pffft/master
url = https://bitbucket.org/jpommier/pffft/src/master/
[submodule "oss-internship-2020/curl/curl_wrapper/curl"]
path = oss-internship-2020/curl/curl_wrapper/curl
url = https://github.com/curl/curl
[submodule "oss-internship-2020/gdal/gdal"]
path = oss-internship-2020/gdal/gdal
url = https://github.com/OSGeo/gdal/
[submodule "oss-internship-2020/curl/curl_wrapper/curl"]
path = oss-internship-2020/curl/curl_wrapper/curl
url = git@github.com:curl/curl.git

View File

@ -12,15 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
cmake_minimum_required(VERSION 3.16)
cmake_minimum_required(VERSION 3.12)
project(libcurl_sandbox)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
option(CURL_SAPI_ENABLE_EXAMPLES "" ON)
option(CURL_SAPI_ENABLE_TESTS "" ON)
option(SAPI_CURL_ENABLE_EXAMPLES "" ON)
option(SAPI_CURL_ENABLE_TESTS "" ON)
# Add callbacks used by examples and tests
if (CURL_SAPI_ENABLE_EXAMPLES OR CURL_SAPI_ENABLE_TESTS)
@ -35,8 +35,8 @@ add_subdirectory(curl_wrapper)
# Setup Sandboxed API
set(SAPI_ROOT "" CACHE PATH "Path to the Sandboxed API source tree")
set(SAPI_ENABLE_EXAMPLES ${CURL_SAPI_ENABLE_EXAMPLES} CACHE BOOL "" FORCE)
set(SAPI_ENABLE_TESTS ${CURL_SAPI_ENABLE_TESTS} CACHE BOOL "" FORCE)
set(SAPI_ENABLE_EXAMPLES ${SAPI_CURL_ENABLE_EXAMPLES} CACHE BOOL "" FORCE)
set(SAPI_ENABLE_TESTS ${SAPI_CURL_ENABLE_TESTS} CACHE BOOL "" FORCE)
add_subdirectory(
"${SAPI_ROOT}"
"${CMAKE_BINARY_DIR}/sandboxed-api-build"
@ -125,7 +125,7 @@ add_sapi_library(curl_sapi
LIBRARY_NAME Curl
NAMESPACE ""
NAMESPACE curl
)
# Include generated SAPI header
@ -134,11 +134,11 @@ target_include_directories(curl_sapi INTERFACE
)
# Add examples
if (CURL_SAPI_ENABLE_EXAMPLES)
if (SAPI_CURL_ENABLE_EXAMPLES)
add_subdirectory(examples)
endif()
# Add tests
if (CURL_SAPI_ENABLE_TESTS)
if (SAPI_CURL_ENABLE_TESTS)
add_subdirectory(tests)
endif()

View File

@ -38,12 +38,15 @@ Variadic methods are currently not supported by Sandboxed API. To solve this,
these methods are defined with an additional explicit parameter in
`custom_curl.h`.
The methods are: - `curl_easy_setopt`. Use `curl_easy_setopt_ptr`,
`curl_easy_setopt_long` or `curl_easy_setopt_curl_off_t` instead. -
`curl_easy_getinfo`. Use `curl_easy_getinfo_ptr` instead. - `curl_multi_setopt`.
Use `curl_multi_setopt_ptr`, `curl_multi_setopt_long` or
`curl_multi_setopt_curl_off_t` instead. - `curl_share_setopt`. Use
`curl_share_setopt_ptr` or `curl_share_setopt_long` instead
The methods are:
- `curl_easy_setopt`. Use `curl_easy_setopt_ptr`, `curl_easy_setopt_long` or
`curl_easy_setopt_curl_off_t` instead.
- `curl_easy_getinfo`. Use `curl_easy_getinfo_ptr` instead.
- `curl_multi_setopt`. Use `curl_multi_setopt_ptr`, `curl_multi_setopt_long`
or `curl_multi_setopt_curl_off_t` instead.
- `curl_share_setopt`. Use `curl_share_setopt_ptr` or `curl_share_setopt_long`
instead
#### Methods with incomplete array arguments
@ -51,8 +54,10 @@ Incomplete array arguments are currently not supported by Sandboxed API. To
solve this, methods taking an incomplete array argument have a wrapper in
`custom_curl.h`, and take a pointer as the argument.
The methods are: - `curl_multi_poll`. Use `curl_multi_poll_sapi` instead. -
`curl_multi_wait`. Use `curl_multi_wait_sapi` instead.
The methods are:
- `curl_multi_poll`. Use `curl_multi_poll_sapi` instead.
- `curl_multi_wait`. Use `curl_multi_wait_sapi` instead.
#### Methods with conflicts on the generated header
@ -60,11 +65,15 @@ Some methods create conflicts on the generated header because of redefined
`#define` directives from files included by the header. To solve this, the
conflicting types and methods are redefined in `custom_curl.h`.
The types are: - `time_t`. Use `time_t_sapi` instead. - `fd_set`. Use
`fd_set_sapi` instead.
The types are:
The methods are: - `curl_getdate`. Use `curl_getdate_sapi` instead. -
`curl_multi_fdset`. Use `curl_multi_fdset_sapi` instead.
- `time_t`. Use `time_t_sapi` instead.
- `fd_set`. Use `fd_set_sapi` instead.
The methods are:
- `curl_getdate`. Use `curl_getdate_sapi` instead.
- `curl_multi_fdset`. Use `curl_multi_fdset_sapi` instead.
#### Function pointers

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef TESTS_CALLBACKS_H
#define TESTS_CALLBACKS_H
#ifndef CALLBACKS_H_
#define CALLBACKS_H_
#include <curl/curl.h>
@ -21,4 +21,4 @@
extern "C" size_t WriteToMemory(char* contents, size_t size, size_t num_bytes,
void* userp);
#endif // TESTS_CALLBACKS_H
#endif // CALLBACKS_H_

@ -1 +0,0 @@
Subproject commit cbe7fad20d969626a5c4eb0501a273dfe812bcd3

View File

@ -14,8 +14,8 @@
// Wrapper for curl library
#ifndef CURL_WRAPPER_H
#define CURL_WRAPPER_H
#ifndef CURL_WRAPPER_H_
#define CURL_WRAPPER_H_
#include <curl/curl.h>
@ -77,6 +77,7 @@ CURLSHcode curl_share_setopt_ptr(CURLSH* handle, CURLSHoption option,
// The wrapper method is needed to make the variadic argument explicit
CURLSHcode curl_share_setopt_long(CURLSH* handle, CURLSHoption option,
long parameter);
}
#endif // CURL_WRAPPER_H
} // extern "C"
#endif // CURL_WRAPPER_H_

View File

@ -19,59 +19,67 @@
#include "../sandbox.h" // NOLINT(build/include)
namespace {
absl::Status Example1() {
// Initialize sandbox2 and sapi
curl::CurlSapiSandbox sandbox;
SAPI_RETURN_IF_ERROR(sandbox.Init());
curl::CurlApi api(&sandbox);
// Initialize the curl session
curl::CURL* curl_handle;
SAPI_ASSIGN_OR_RETURN(curl_handle, api.curl_easy_init());
sapi::v::RemotePtr curl(curl_handle);
if (!curl_handle) {
return absl::UnavailableError("curl_easy_init failed: curl is NULL");
}
int curl_code;
// Specify URL to get
sapi::v::ConstCStr url("http://example.com");
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_URL,
url.PtrBefore()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Set the library to follow a redirection
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_easy_setopt_long(
&curl, curl::CURLOPT_FOLLOWLOCATION, 1l));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_long failed: " + curl_code);
}
// Disable authentication of peer certificate
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_easy_setopt_long(
&curl, curl::CURLOPT_SSL_VERIFYPEER, 0l));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_long failed: " + curl_code);
}
// Perform the request
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_easy_perform(&curl));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_perform failed: " + curl_code);
}
// Cleanup curl
SAPI_RETURN_IF_ERROR(api.curl_easy_cleanup(&curl));
return absl::OkStatus();
}
} // namespace
int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
absl::Status status;
// Initialize sandbox2 and sapi
CurlSapiSandbox sandbox;
status = sandbox.Init();
if (!status.ok()) {
LOG(FATAL) << "Couldn't initialize Sandboxed API: " << status;
}
CurlApi api(&sandbox);
// Initialize the curl session
absl::StatusOr<CURL*> curl_handle = api.curl_easy_init();
if (!curl_handle.ok()) {
LOG(FATAL) << "curl_easy_init failed: " << curl_handle.status();
}
sapi::v::RemotePtr curl(curl_handle.value());
if (!curl.GetValue()) LOG(FATAL) << "curl_easy_init failed: curl is NULL";
absl::StatusOr<int> curl_code;
// Specify URL to get
sapi::v::ConstCStr url("http://example.com");
curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_URL, url.PtrBefore());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
}
// Set the library to follow a redirection
curl_code = api.curl_easy_setopt_long(&curl, CURLOPT_FOLLOWLOCATION, 1l);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_long failed: " << curl_code.status();
}
// Disable authentication of peer certificate
curl_code = api.curl_easy_setopt_long(&curl, CURLOPT_SSL_VERIFYPEER, 0l);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_long failed: " << curl_code.status();
}
// Perform the request
curl_code = api.curl_easy_perform(&curl);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_perform failed: " << curl_code.status();
}
// Cleanup curl
status = api.curl_easy_cleanup(&curl);
if (!status.ok()) {
LOG(FATAL) << "curl_easy_cleanup failed: " << status;
if (absl::Status status = Example1(); !status.ok()) {
LOG(ERROR) << "Example1 failed: " << status.ToString();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;

View File

@ -20,87 +20,89 @@
#include "../sandbox.h" // NOLINT(build/include)
int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
absl::Status status;
namespace {
absl::Status Example2() {
// Initialize sandbox2 and sapi
CurlSapiSandbox sandbox;
status = sandbox.Init();
if (!status.ok()) {
LOG(FATAL) << "Couldn't initialize Sandboxed API: " << status;
}
CurlApi api(&sandbox);
curl::CurlSapiSandbox sandbox;
SAPI_RETURN_IF_ERROR(sandbox.Init());
curl::CurlApi api(&sandbox);
// Generate pointer to WriteMemoryCallback function
void* function_ptr;
status = sandbox.rpc_channel()->Symbol("WriteToMemory", &function_ptr);
if (!status.ok()) {
LOG(FATAL) << "sapi::Sandbox::rpc_channel().Symbol failed: " << status;
}
sapi::v::RemotePtr remote_function_ptr(function_ptr);
SAPI_RETURN_IF_ERROR(
sandbox.rpc_channel()->Symbol("WriteToMemory", &function_ptr));
sapi::v::RemotePtr write_to_memory(function_ptr);
// Initialize the curl session
absl::StatusOr<CURL*> curl_handle = api.curl_easy_init();
if (!curl_handle.ok()) {
LOG(FATAL) << "curl_easy_init failed: " << curl_handle.status();
}
sapi::v::RemotePtr curl(curl_handle.value());
if (!curl.GetValue()) {
LOG(FATAL) << "curl_easy_init failed: curl is NULL";
curl::CURL* curl_handle;
SAPI_ASSIGN_OR_RETURN(curl_handle, api.curl_easy_init());
sapi::v::RemotePtr curl(curl_handle);
if (!curl_handle) {
return absl::UnavailableError("curl_easy_init failed: curl is NULL");
}
absl::StatusOr<int> curl_code;
int curl_code;
// Specify URL to get
sapi::v::ConstCStr url("http://example.com");
curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_URL, url.PtrBefore());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_URL,
url.PtrBefore()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Set WriteMemoryCallback as the write function
curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_WRITEFUNCTION,
&remote_function_ptr);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code,
api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_WRITEFUNCTION,
&write_to_memory));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Pass 'chunk' struct to the callback function
sapi::v::LenVal chunk(0);
curl_code =
api.curl_easy_setopt_ptr(&curl, CURLOPT_WRITEDATA, chunk.PtrBoth());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code,
api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_WRITEDATA,
chunk.PtrBoth()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Set a user agent
sapi::v::ConstCStr user_agent("libcurl-agent/1.0");
curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_USERAGENT,
user_agent.PtrBefore());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code,
api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_USERAGENT,
user_agent.PtrBefore()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Perform the request
curl_code = api.curl_easy_perform(&curl);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_perform failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_easy_perform(&curl));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_perform failed: " + curl_code);
}
// Retrieve memory size
status = sandbox.TransferFromSandboxee(&chunk);
if (!status.ok()) {
LOG(FATAL) << "sandbox.TransferFromSandboxee failed: " << status;
}
SAPI_RETURN_IF_ERROR(sandbox.TransferFromSandboxee(&chunk));
std::cout << "memory size: " << chunk.GetDataSize() << " bytes" << std::endl;
// Cleanup curl
status = api.curl_easy_cleanup(&curl);
if (!status.ok()) {
LOG(FATAL) << "curl_easy_cleanup failed: " << status;
SAPI_RETURN_IF_ERROR(api.curl_easy_cleanup(&curl));
return absl::OkStatus();
}
} // namespace
int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
if (absl::Status status = Example2(); !status.ok()) {
LOG(ERROR) << "Example2 failed: " << status.ToString();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;

View File

@ -19,7 +19,9 @@
#include "../sandbox.h" // NOLINT(build/include)
class CurlSapiSandboxEx3 : public CurlSapiSandbox {
namespace {
class CurlSapiSandboxEx3 : public curl::CurlSapiSandbox {
public:
CurlSapiSandboxEx3(std::string ssl_certificate, std::string ssl_key,
std::string ca_certificates)
@ -42,7 +44,7 @@ class CurlSapiSandboxEx3 : public CurlSapiSandbox {
.AddFile(ssl_key)
.AddFile(ca_certificates);
// Provide the new PolicyBuilder to ModifyPolicy in CurlSandbox
return CurlSapiSandbox::ModifyPolicy(policy_builder.get());
return curl::CurlSapiSandbox::ModifyPolicy(policy_builder.get());
}
std::string ssl_certificate;
@ -50,116 +52,121 @@ class CurlSapiSandboxEx3 : public CurlSapiSandbox {
std::string ca_certificates;
};
int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
absl::Status status;
// Get input parameters (should be absolute paths)
if (argc != 5) {
LOG(FATAL) << "wrong number of arguments (4 expected)";
}
std::string ssl_certificate = argv[1];
std::string ssl_key = argv[2];
std::string ssl_key_password = argv[3];
std::string ca_certificates = argv[4];
absl::Status Example3(std::string ssl_certificate, std::string ssl_key,
std::string ssl_key_password,
std::string ca_certificates) {
// Initialize sandbox2 and sapi
CurlSapiSandboxEx3 sandbox(ssl_certificate, ssl_key, ca_certificates);
status = sandbox.Init();
if (!status.ok()) {
LOG(FATAL) << "Couldn't initialize Sandboxed API: " << status;
}
CurlApi api(&sandbox);
SAPI_RETURN_IF_ERROR(sandbox.Init());
curl::CurlApi api(&sandbox);
absl::StatusOr<int> curl_code;
int curl_code;
// Initialize curl (CURL_GLOBAL_DEFAULT = 3)
curl_code = api.curl_global_init(3l);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_global_init failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_global_init(3l));
if (curl_code != 0) {
return absl::UnavailableError("curl_global_init failed: " + curl_code);
}
// Initialize curl easy handle
absl::StatusOr<CURL*> curl_handle = api.curl_easy_init();
if (!curl_handle.ok()) {
LOG(FATAL) << "curl_easy_init failed: " << curl_handle.status();
}
sapi::v::RemotePtr curl(curl_handle.value());
if (!curl.GetValue()) {
LOG(FATAL) << "curl_easy_init failed: curl is NULL";
curl::CURL* curl_handle;
SAPI_ASSIGN_OR_RETURN(curl_handle, api.curl_easy_init());
sapi::v::RemotePtr curl(curl_handle);
if (!curl_handle) {
return absl::UnavailableError("curl_easy_init failed: curl is NULL");
}
// Specify URL to get (using HTTPS)
sapi::v::ConstCStr url("https://example.com");
curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_URL, url.PtrBefore());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_URL,
url.PtrBefore()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Set the SSL certificate type to "PEM"
sapi::v::ConstCStr ssl_cert_type("PEM");
curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_SSLCERTTYPE,
ssl_cert_type.PtrBefore());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code,
api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_SSLCERTTYPE,
ssl_cert_type.PtrBefore()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Set the certificate for client authentication
sapi::v::ConstCStr sapi_ssl_certificate(ssl_certificate.c_str());
curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_SSLCERT,
sapi_ssl_certificate.PtrBefore());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code,
api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_SSLCERT,
sapi_ssl_certificate.PtrBefore()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Set the private key for client authentication
sapi::v::ConstCStr sapi_ssl_key(ssl_key.c_str());
curl_code =
api.curl_easy_setopt_ptr(&curl, CURLOPT_SSLKEY, sapi_ssl_key.PtrBefore());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code,
api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_SSLKEY,
sapi_ssl_key.PtrBefore()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Set the password used to protect the private key
sapi::v::ConstCStr sapi_ssl_key_password(ssl_key_password.c_str());
curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_KEYPASSWD,
sapi_ssl_key_password.PtrBefore());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code,
api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_KEYPASSWD,
sapi_ssl_key_password.PtrBefore()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Set the file with the certificates vaildating the server
sapi::v::ConstCStr sapi_ca_certificates(ca_certificates.c_str());
curl_code = api.curl_easy_setopt_ptr(&curl, CURLOPT_CAINFO,
sapi_ca_certificates.PtrBefore());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code,
api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_CAINFO,
sapi_ca_certificates.PtrBefore()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Verify the authenticity of the server
curl_code = api.curl_easy_setopt_long(&curl, CURLOPT_SSL_VERIFYPEER, 1L);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_long failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_easy_setopt_long(
&curl, curl::CURLOPT_SSL_VERIFYPEER, 1L));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_long failed: " + curl_code);
}
// Perform the request
curl_code = api.curl_easy_perform(&curl);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_perform failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_easy_perform(&curl));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_perform failed: " + curl_code);
}
// Cleanup curl easy handle
status = api.curl_easy_cleanup(&curl);
if (!status.ok()) {
LOG(FATAL) << "curl_easy_cleanup failed: " << status;
}
SAPI_RETURN_IF_ERROR(api.curl_easy_cleanup(&curl));
// Cleanup curl
status = api.curl_global_cleanup();
if (!status.ok()) {
LOG(FATAL) << "curl_global_cleanup failed: " << status;
SAPI_RETURN_IF_ERROR(api.curl_global_cleanup());
return absl::OkStatus();
}
} // namespace
int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
// Get input parameters (should be absolute paths)
if (argc != 5) {
LOG(ERROR) << "wrong number of arguments (4 expected)";
return EXIT_FAILURE;
}
if (absl::Status status = Example3(argv[1], argv[2], argv[3], argv[4]);
!status.ok()) {
LOG(ERROR) << "Example3 failed: " << status.ToString();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;

View File

@ -21,107 +21,113 @@
#include "curl_sapi.sapi.h" // NOLINT(build/include)
#include "sandboxed_api/util/flag.h"
int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
absl::Status status;
namespace {
absl::Status Example4() {
// Initialize sandbox2 and sapi
CurlSapiSandbox sandbox;
status = sandbox.Init();
if (!status.ok()) {
LOG(FATAL) << "Couldn't initialize Sandboxed API: " << status;
}
CurlApi api(&sandbox);
curl::CurlSapiSandbox sandbox;
SAPI_RETURN_IF_ERROR(sandbox.Init());
curl::CurlApi api(&sandbox);
// Number of running handles
sapi::v::Int still_running(1);
absl::StatusOr<int> curl_code;
int curl_code;
// Initialize curl (CURL_GLOBAL_DEFAULT = 3)
curl_code = api.curl_global_init(3l);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_global_init failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_global_init(3l));
if (curl_code != 0) {
return absl::UnavailableError("curl_global_init failed: " + curl_code);
}
// Initialize http_handle
absl::StatusOr<CURL*> curl_handle = api.curl_easy_init();
if (!curl_handle.ok()) {
LOG(FATAL) << "curl_easy_init failed: " << curl_handle.status();
}
sapi::v::RemotePtr http_handle(curl_handle.value());
if (!http_handle.GetValue()) {
LOG(FATAL) << "curl_easy_init failed: http_handle is NULL";
curl::CURL* curl_handle;
SAPI_ASSIGN_OR_RETURN(curl_handle, api.curl_easy_init());
sapi::v::RemotePtr http_handle(curl_handle);
if (!curl_handle) {
return absl::UnavailableError("curl_easy_init failed: curl is NULL");
}
// Specify URL to get
sapi::v::ConstCStr url("http://example.com");
curl_code =
api.curl_easy_setopt_ptr(&http_handle, CURLOPT_URL, url.PtrBefore());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code,
api.curl_easy_setopt_ptr(&http_handle, curl::CURLOPT_URL,
url.PtrBefore()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Initialize multi_handle
absl::StatusOr<CURLM*> curlm_handle = api.curl_multi_init();
if (!curlm_handle.ok()) {
LOG(FATAL) << "curl_multi_init failed: " << curlm_handle.status();
}
sapi::v::RemotePtr multi_handle(curlm_handle.value());
if (!multi_handle.GetValue()) {
LOG(FATAL) << "curl_multi_init failed: multi_handle is NULL";
curl::CURLM* curlm_handle;
SAPI_ASSIGN_OR_RETURN(curlm_handle, api.curl_multi_init());
sapi::v::RemotePtr multi_handle(curlm_handle);
if (!curlm_handle) {
return absl::UnavailableError(
"curl_multi_init failed: multi_handle is NULL");
}
// Add http_handle to the multi stack
curl_code = api.curl_multi_add_handle(&multi_handle, &http_handle);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_multi_add_handle failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code,
api.curl_multi_add_handle(&multi_handle, &http_handle));
if (curl_code != 0) {
return absl::UnavailableError("curl_multi_add_handle failed: " + curl_code);
}
while (still_running.GetValue()) {
sapi::v::Int numfds(0);
// Perform the request
curl_code = api.curl_multi_perform(&multi_handle, still_running.PtrBoth());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_mutli_perform failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_multi_perform(
&multi_handle, still_running.PtrBoth()));
if (curl_code != 0) {
return absl::UnavailableError("curl_mutli_perform failed: " + curl_code);
}
if (still_running.GetValue()) {
// Wait for an event or timeout
sapi::v::NullPtr null_ptr;
curl_code = api.curl_multi_poll_sapi(&multi_handle, &null_ptr, 0, 1000,
numfds.PtrBoth());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_multi_poll_sapi failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(
curl_code, api.curl_multi_poll_sapi(&multi_handle, &null_ptr, 0, 1000,
numfds.PtrBoth()));
if (curl_code != 0) {
return absl::UnavailableError("curl_multi_poll_sapi failed: " +
curl_code);
}
}
}
// Remove http_handle from the multi stack
curl_code = api.curl_multi_remove_handle(&multi_handle, &http_handle);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_multi_remove_handle failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code,
api.curl_multi_remove_handle(&multi_handle, &http_handle));
if (curl_code != 0) {
return absl::UnavailableError("curl_multi_remove_handle failed: " +
curl_code);
}
// Cleanup http_handle
status = api.curl_easy_cleanup(&http_handle);
if (!status.ok()) {
LOG(FATAL) << "curl_easy_cleanup failed: " << status;
}
SAPI_RETURN_IF_ERROR(api.curl_easy_cleanup(&http_handle));
// Cleanup multi_handle
curl_code = api.curl_multi_cleanup(&multi_handle);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_multi_cleanup failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_multi_cleanup(&multi_handle));
if (curl_code != 0) {
return absl::UnavailableError("curl_multi_cleanup failed: " + curl_code);
}
// Cleanup curl
status = api.curl_global_cleanup();
if (!status.ok()) {
LOG(FATAL) << "curl_global_cleanup failed: " << status;
SAPI_RETURN_IF_ERROR(api.curl_global_cleanup());
return absl::OkStatus();
}
} // namespace
int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
if (absl::Status status = Example4(); !status.ok()) {
LOG(ERROR) << "Example4 failed: " << status.ToString();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;

View File

@ -15,87 +15,90 @@
// Sandboxed version of multithread.c
// Multithreaded HTTP GET requests
#include <pthread.h>
#include <cstdlib>
#include <future> // NOLINT(build/c++11)
#include <thread> // NOLINT(build/c++11)
#include "../sandbox.h" // NOLINT(build/include)
void pull_one_url(const std::string& url, CurlApi& api) {
namespace {
absl::Status pull_one_url(const std::string& url, curl::CurlApi& api) {
// Initialize the curl session
absl::StatusOr<CURL*> curl_handle = api.curl_easy_init();
if (!curl_handle.ok()) {
LOG(FATAL) << "curl_easy_init failed: " << curl_handle.status();
}
sapi::v::RemotePtr curl(curl_handle.value());
if (!curl.GetValue()) {
LOG(FATAL) << "curl_easy_init failed: curl is NULL";
curl::CURL* curl_handle;
SAPI_ASSIGN_OR_RETURN(curl_handle, api.curl_easy_init());
sapi::v::RemotePtr curl(curl_handle);
if (!curl_handle) {
return absl::UnavailableError("curl_easy_init failed: curl is NULL");
}
absl::StatusOr<int> curl_code;
int curl_code;
// Specify URL to get
sapi::v::ConstCStr sapi_url(url.c_str());
curl_code =
api.curl_easy_setopt_ptr(&curl, CURLOPT_URL, sapi_url.PtrBefore());
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_setopt_ptr failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_URL,
sapi_url.PtrBefore()));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_setopt_ptr failed: " + curl_code);
}
// Perform the request
curl_code = api.curl_easy_perform(&curl);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_easy_perform failed: " << curl_code.status();
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_easy_perform(&curl));
if (curl_code != 0) {
return absl::UnavailableError("curl_easy_perform failed: " + curl_code);
}
// Cleanup curl
absl::Status status = api.curl_easy_cleanup(&curl);
if (!status.ok()) {
LOG(FATAL) << "curl_easy_cleanup failed: " << status;
}
// Cleanup curl easy handle
SAPI_RETURN_IF_ERROR(api.curl_easy_cleanup(&curl));
return absl::OkStatus();
}
const std::vector<std::string> urls = {
"http://example.com", "http://example.edu", "http://example.net",
"http://example.org"};
absl::Status Example5() {
// Initialize sandbox2 and sapi
curl::CurlSapiSandbox sandbox;
SAPI_RETURN_IF_ERROR(sandbox.Init());
curl::CurlApi api(&sandbox);
int curl_code;
// Initialize curl (CURL_GLOBAL_DEFAULT = 3)
SAPI_ASSIGN_OR_RETURN(curl_code, api.curl_global_init(3l));
if (curl_code != 0) {
return absl::UnavailableError("curl_global_init failed: " + curl_code);
}
// Create the threads (by using futures)
std::vector<std::future<absl::Status>> futures;
for (auto& url : urls) {
futures.emplace_back(
std::async(pull_one_url, std::ref(url), std::ref(api)));
}
// Join the threads and check for errors
for (auto& future : futures) {
SAPI_RETURN_IF_ERROR(future.get());
}
// Cleanup curl
SAPI_RETURN_IF_ERROR(api.curl_global_cleanup());
return absl::OkStatus();
}
} // namespace
int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
absl::Status status;
// Initialize sandbox2 and sapi
CurlSapiSandbox sandbox;
status = sandbox.Init();
if (!status.ok()) {
LOG(FATAL) << "Couldn't initialize Sandboxed API: " << status;
}
CurlApi api(&sandbox);
absl::StatusOr<int> curl_code;
// Initialize curl (CURL_GLOBAL_DEFAULT = 3)
curl_code = api.curl_global_init(3l);
if (!curl_code.ok() || curl_code.value() != CURLE_OK) {
LOG(FATAL) << "curl_global_init failed: " << curl_code.status();
}
// Create the threads
std::vector<std::thread> threads;
for (auto& url : urls) {
threads.emplace_back(pull_one_url, std::ref(url), std::ref(api));
}
// Join the threads
for (auto& thread : threads) {
thread.join();
}
// Cleanup curl
status = api.curl_global_cleanup();
if (!status.ok()) {
LOG(FATAL) << "curl_global_cleanup failed: " << status;
if (absl::Status status = Example5(); !status.ok()) {
LOG(ERROR) << "Example5 failed: " << status.ToString();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;

View File

@ -20,6 +20,8 @@
#include "../sandbox.h" // NOLINT(build/include)
#include "sandboxed_api/transaction.h"
namespace {
class CurlTransaction : public sapi::Transaction {
public:
explicit CurlTransaction(std::unique_ptr<sapi::Sandbox> sandbox)
@ -36,7 +38,7 @@ class CurlTransaction : public sapi::Transaction {
};
absl::Status CurlTransaction::Main() {
CurlApi api(sandbox());
curl::CurlApi api(sandbox());
// Initialize the curl session
SAPI_ASSIGN_OR_RETURN(void* curl_remote, api.curl_easy_init());
@ -47,13 +49,13 @@ absl::Status CurlTransaction::Main() {
sapi::v::ConstCStr url("http://example.com");
SAPI_ASSIGN_OR_RETURN(
int setopt_url_code,
api.curl_easy_setopt_ptr(&curl, CURLOPT_URL, url.PtrBefore()));
TRANSACTION_FAIL_IF_NOT(setopt_url_code == CURLE_OK,
api.curl_easy_setopt_ptr(&curl, curl::CURLOPT_URL, url.PtrBefore()));
TRANSACTION_FAIL_IF_NOT(setopt_url_code == curl::CURLE_OK,
"curl_easy_setopt_ptr failed");
// Perform the request
SAPI_ASSIGN_OR_RETURN(int perform_code, api.curl_easy_perform(&curl));
TRANSACTION_FAIL_IF_NOT(setopt_url_code == CURLE_OK,
TRANSACTION_FAIL_IF_NOT(setopt_url_code == curl::CURLE_OK,
"curl_easy_perform failed");
// Cleanup curl
@ -63,8 +65,10 @@ absl::Status CurlTransaction::Main() {
return absl::OkStatus();
}
} // namespace
int main(int argc, char* argv[]) {
CurlTransaction curl{std::make_unique<CurlSapiSandbox>()};
CurlTransaction curl{std::make_unique<curl::CurlSapiSandbox>()};
absl::Status status = curl.Run();
CHECK(status.ok()) << "CurlTransaction failed";

View File

@ -24,7 +24,9 @@
#include "curl_sapi.sapi.h" // NOLINT(build/include)
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
class CurlSapiSandbox : public CurlSandbox {
namespace curl {
class CurlSapiSandbox : public curl::CurlSandbox {
protected:
std::unique_ptr<sandbox2::Policy> ModifyPolicy(
sandbox2::PolicyBuilder* policy_builder) override {
@ -64,4 +66,6 @@ class CurlSapiSandbox : public CurlSandbox {
}
};
} // namespace curl
#endif // SANDBOX_H_

View File

@ -28,17 +28,17 @@
int CurlTestUtils::port_;
std::thread CurlTestUtils::server_thread_;
absl::Status CurlTestUtils::CurlTestSetUp() {
absl::Status curl::tests::CurlTestUtils::CurlTestSetUp() {
// Initialize sandbox2 and sapi
sandbox_ = std::make_unique<CurlSapiSandbox>();
sandbox_ = std::make_unique<curl::CurlSapiSandbox>();
absl::Status init = sandbox_->Init();
if (!init.ok()) {
return init;
}
api_ = std::make_unique<CurlApi>(sandbox_.get());
api_ = std::make_unique<curl::CurlApi>(sandbox_.get());
// Initialize curl
absl::StatusOr<CURL*> curl_handle = api_->curl_easy_init();
absl::StatusOr<curl::CURL*> curl_handle = api_->curl_easy_init();
if (!curl_handle.ok()) {
return curl_handle.status();
}
@ -51,23 +51,24 @@ absl::Status CurlTestUtils::CurlTestSetUp() {
// Specify request URL
sapi::v::ConstCStr sapi_url(kUrl.data());
curl_code = api_->curl_easy_setopt_ptr(curl_.get(), CURLOPT_URL,
curl_code = api_->curl_easy_setopt_ptr(curl_.get(), curl::CURLOPT_URL,
sapi_url.PtrBefore());
if (!curl_code.ok()) {
return curl_code.status();
}
if (curl_code.value() != CURLE_OK) {
if (curl_code.value() != curl::CURLE_OK) {
return absl::UnavailableError(
"curl_easy_setopt_ptr returned with the error code " +
curl_code.value());
}
// Set port
curl_code = api_->curl_easy_setopt_long(curl_.get(), CURLOPT_PORT, port_);
curl_code =
api_->curl_easy_setopt_long(curl_.get(), curl::CURLOPT_PORT, port_);
if (!curl_code.ok()) {
return curl_code.status();
}
if (curl_code.value() != CURLE_OK) {
if (curl_code.value() != curl::CURLE_OK) {
return absl::UnavailableError(
"curl_easy_setopt_long returned with the error code " +
curl_code.value());
@ -83,12 +84,12 @@ absl::Status CurlTestUtils::CurlTestSetUp() {
sapi::v::RemotePtr remote_function_ptr(function_ptr);
// Set WriteToMemory as the write function
curl_code = api_->curl_easy_setopt_ptr(curl_.get(), CURLOPT_WRITEFUNCTION,
&remote_function_ptr);
curl_code = api_->curl_easy_setopt_ptr(
curl_.get(), curl::CURLOPT_WRITEFUNCTION, &remote_function_ptr);
if (!curl_code.ok()) {
return curl_code.status();
}
if (curl_code.value() != CURLE_OK) {
if (curl_code.value() != curl::CURLE_OK) {
return absl::UnavailableError(
"curl_easy_setopt_ptr returned with the error code " +
curl_code.value());
@ -96,12 +97,12 @@ absl::Status CurlTestUtils::CurlTestSetUp() {
// Pass memory chunk object to the callback
chunk_ = std::make_unique<sapi::v::LenVal>(0);
curl_code = api_->curl_easy_setopt_ptr(curl_.get(), CURLOPT_WRITEDATA,
curl_code = api_->curl_easy_setopt_ptr(curl_.get(), curl::CURLOPT_WRITEDATA,
chunk_->PtrBoth());
if (!curl_code.ok()) {
return curl_code.status();
}
if (curl_code.value() != CURLE_OK) {
if (curl_code.value() != curl::CURLE_OK) {
return absl::UnavailableError(
"curl_easy_setopt_ptr returned with the error code " +
curl_code.value());
@ -110,18 +111,18 @@ absl::Status CurlTestUtils::CurlTestSetUp() {
return absl::OkStatus();
}
absl::Status CurlTestUtils::CurlTestTearDown() {
absl::Status curl::tests::CurlTestUtils::CurlTestTearDown() {
// Cleanup curl
return api_->curl_easy_cleanup(curl_.get());
}
absl::StatusOr<std::string> CurlTestUtils::PerformRequest() {
absl::StatusOr<std::string> curl::tests::CurlTestUtils::PerformRequest() {
// Perform the request
absl::StatusOr<int> curl_code = api_->curl_easy_perform(curl_.get());
if (!curl_code.ok()) {
return curl_code.status();
}
if (curl_code.value() != CURLE_OK) {
if (curl_code.value() != curl::CURLE_OK) {
return absl::UnavailableError(
"curl_easy_perform returned with the error code " + curl_code.value());
}
@ -267,7 +268,7 @@ void ServerLoop(int listening_socket, sockaddr_in socket_address) {
} // namespace
void CurlTestUtils::StartMockServer() {
void curl::tests::CurlTestUtils::StartMockServer() {
// Get the socket file descriptor
int listening_socket = socket(AF_INET, SOCK_STREAM, 0);

View File

@ -21,6 +21,8 @@
#include "sandboxed_api/util/flag.h"
#include "sandboxed_api/util/status_matchers.h"
namespace curl::tests {
// Helper class that can be used to test Curl Sandboxed
class CurlTestUtils {
protected:
@ -39,8 +41,8 @@ class CurlTestUtils {
// Responds with the POST request fields to a POST request
static void StartMockServer();
std::unique_ptr<CurlSapiSandbox> sandbox_;
std::unique_ptr<CurlApi> api_;
std::unique_ptr<curl::CurlSapiSandbox> sandbox_;
std::unique_ptr<curl::CurlApi> api_;
std::unique_ptr<sapi::v::RemotePtr> curl_;
static std::thread server_thread_;
@ -52,4 +54,6 @@ class CurlTestUtils {
std::unique_ptr<sapi::v::LenVal> chunk_;
};
} // namespace curl::tests
#endif // TESTS_H_

View File

@ -14,7 +14,9 @@
#include "test_utils.h" // NOLINT(build/include)
class CurlTest : public CurlTestUtils, public ::testing::Test {
namespace {
class CurlTest : public curl::tests::CurlTestUtils, public ::testing::Test {
protected:
void SetUp() override {
// Start mock server, get port number and check for any error
@ -38,9 +40,9 @@ TEST_F(CurlTest, EffectiveUrl) {
// Get effective URL
SAPI_ASSERT_OK_AND_ASSIGN(
int getinfo_code,
api_->curl_easy_getinfo_ptr(curl_.get(), CURLINFO_EFFECTIVE_URL,
api_->curl_easy_getinfo_ptr(curl_.get(), curl::CURLINFO_EFFECTIVE_URL,
effective_url_ptr.PtrBoth()));
ASSERT_EQ(getinfo_code, CURLE_OK);
ASSERT_EQ(getinfo_code, curl::CURLE_OK);
// Store effective URL in a string
SAPI_ASSERT_OK_AND_ASSIGN(std::string effective_url,
@ -74,9 +76,9 @@ TEST_F(CurlTest, ResponseCode) {
// Get response code
SAPI_ASSERT_OK_AND_ASSIGN(
int getinfo_code,
api_->curl_easy_getinfo_ptr(curl_.get(), CURLINFO_RESPONSE_CODE,
api_->curl_easy_getinfo_ptr(curl_.get(), curl::CURLINFO_RESPONSE_CODE,
response_code.PtrBoth()));
ASSERT_EQ(getinfo_code, CURLE_OK);
ASSERT_EQ(getinfo_code, curl::CURLE_OK);
// Check response code
ASSERT_EQ(response_code.GetValue(), 200);
@ -120,19 +122,21 @@ TEST_F(CurlTest, POSTResponse) {
// Set the size of the POST fields
SAPI_ASSERT_OK_AND_ASSIGN(
int setopt_post_fields_size,
api_->curl_easy_setopt_long(curl_.get(), CURLOPT_POSTFIELDSIZE,
api_->curl_easy_setopt_long(curl_.get(), curl::CURLOPT_POSTFIELDSIZE,
post_fields.GetSize()));
ASSERT_EQ(setopt_post_fields_size, CURLE_OK);
ASSERT_EQ(setopt_post_fields_size, curl::CURLE_OK);
// Set the POST fields
SAPI_ASSERT_OK_AND_ASSIGN(
int setopt_post_fields,
api_->curl_easy_setopt_ptr(curl_.get(), CURLOPT_POSTFIELDS,
api_->curl_easy_setopt_ptr(curl_.get(), curl::CURLOPT_POSTFIELDS,
post_fields.PtrBefore()));
ASSERT_EQ(setopt_post_fields, CURLE_OK);
ASSERT_EQ(setopt_post_fields, curl::CURLE_OK);
SAPI_ASSERT_OK_AND_ASSIGN(std::string response, PerformRequest());
// Compare response with expected response
ASSERT_EQ(std::string(post_fields.GetData()), response);
}
} // namespace

View File

@ -0,0 +1,58 @@
# Copyright 2020 Google LLC
#
# 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.
cmake_minimum_required(VERSION 3.10)
project(jsonnet-sapi C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
add_subdirectory(jsonnet)
add_subdirectory(examples)
set(SAPI_ROOT "../.." CACHE PATH "Path to the Sandboxed API source tree")
set(SAPI_ENABLE_EXAMPLES OFF CACHE BOOL "")
set(SAPI_ENABLE_TESTS OFF CACHE BOOL "")
add_subdirectory("${SAPI_ROOT}"
"${CMAKE_BINARY_DIR}/sandboxed-api-build"
EXCLUDE_FROM_ALL)
add_sapi_library(jsonnet_sapi
FUNCTIONS c_jsonnet_evaluate_snippet
c_jsonnet_make
c_jsonnet_destroy
c_read_input
c_write_output_file
c_jsonnet_realloc
c_jsonnet_evaluate_snippet_multi
c_write_multi_output_files
c_write_output_stream
c_jsonnet_evaluate_snippet_stream
c_jsonnet_fmt_snippet
INPUTS jsonnet_helper.h
LIBRARY jsonnet_helper
LIBRARY_NAME Jsonnet
NAMESPACE ""
)
target_include_directories(jsonnet_sapi INTERFACE
"${PROJECT_BINARY_DIR}"
)
target_link_libraries(jsonnet_sapi PUBLIC jsonnet_helper)
add_subdirectory(tests)

View File

@ -0,0 +1,84 @@
# Jsonnet Sandboxed API
This library provides sandboxed version of the
[Jsonnet](https://github.com/google/jsonnet) library.
## Examples
The `examples/` directory contains code to produce three command-line tools --
`jsonnet_sandboxed`, `jsonnet_yaml_stream_sandboxed` and
`jsonnet_multiple_files_sandboxed` to evaluate jsonnet code. The first one
enables the user to evaluate jsonnet code held in one file and writing to one
output file. The second evaluates one jsonnet file into one file, which can be
interepreted as YAML stream. The third one is for evaluating one jsonnet file
into multiple output files. All three tools are based on what can be found
[here](https://github.com/google/jsonnet/blob/master/cmd/jsonnet.cpp).
Apart from these, there is also a file producing `jsonnet_formatter_sandboxed`
executable. It is based on a tool found from
[here](https://github.com/google/jsonnet/blob/master/cmd/jsonnetfmt.cpp). It is
a jsonnet code formatter -- it changes poorly written jsonnet files into their
canonical form.
## Build
To build these examples, after cloning the whole Sandbox API project, you also
need to run
```
git submodule update --init --recursive
```
anywhere in the project tree in order to clone the `jsonnet` submodule.
Then in the `sandboxed-api/oss-internship-2020/jsonnet` run
```
mkdir build && cd build
cmake -G Ninja
ninja
```
To run `jsonnet_sandboxed` (or `jsonnet_yaml_stream_sandboxed` or
`jsonnet_formatter_sandboxed` in a similar way):
```
cd examples
./jsonnet_sandboxed \
absolute/path/to/the/input_file.jsonnet \
absolute/path/to/the/output_file
```
To run `jsonnet_mutiple_files_sandboxed`:
```
cd examples
./jsonnet_mutiple_files_sandboxed \
absolute/path/to/the/input_file.jsonnet \
absolute/path/to/the/output_directory
```
All three tools support evaluating one input file (possibly relying on multiple
other files, e.x. by jsonnet `import` command; the files must be held in the
same directory as input file) into one or more output files. Example jsonnet
codes to evaluate in a one-in-one-out manner can be found
[here](https://github.com/google/jsonnet/tree/master/examples). Example code
producing multiple output files or YAML stream files can be found in the
`examples/jsonnet_codes` directory (along with some other examples copied with
minimal changes from the library files), in files called
`multiple_files_example.jsonnet` and `yaml_stream_example.jsonnet`,
respectively. In the `examples/jsonnet_codes_expected_output` directory one can
found outputs the mentioned above files' evaluation should produce.
The formatter reads one input file and produces one output file as a result.
Example code for this tool can also be found in `examples/jsonnet_codes`
directory, in a file called `formatter_example.jsonnet`.
## Testing
A few tests prepared with a use of
[Google Test](https://github.com/google/googletest) framework can be found in
the `tests/` directory. To run them type:
```
cd tests
./tests
```

View File

@ -0,0 +1,75 @@
# Copyright 2020 Google LLC
#
# 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.
file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/gen_files")
file(COPY "${PROJECT_SOURCE_DIR}/jsonnet/cmd/jsonnet.cpp" DESTINATION "${PROJECT_BINARY_DIR}/gen_files/")
file(COPY "${PROJECT_SOURCE_DIR}/jsonnet.patch" DESTINATION "${PROJECT_BINARY_DIR}/gen_files/")
add_custom_command(
OUTPUT ${PROJECT_BINARY_DIR}/gen_files/write_helper.cc
COMMAND cd ${PROJECT_BINARY_DIR}/gen_files && patch < ${PROJECT_SOURCE_DIR}/jsonnet.patch > /dev/null
COMMAND mv ${PROJECT_BINARY_DIR}/gen_files/jsonnet.cpp ${PROJECT_BINARY_DIR}/gen_files/write_helper.cc
)
list(APPEND JSONNET_SAPI_HEADERS
${PROJECT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/headers
${PROJECT_BINARY_DIR}/gen_files
)
add_library(jsonnet_helper STATIC
${PROJECT_SOURCE_DIR}/jsonnet_helper.cc
${PROJECT_SOURCE_DIR}/jsonnet_helper.h
${PROJECT_SOURCE_DIR}/jsonnet/cmd/utils.h
${PROJECT_SOURCE_DIR}/jsonnet/cmd/utils.cpp
${PROJECT_BINARY_DIR}/gen_files/write_helper.cc
)
target_include_directories(jsonnet_helper PUBLIC
${JSONNET_SAPI_HEADERS}
)
target_link_libraries(jsonnet_helper
libjsonnet_for_binaries
)
foreach(exe base multiple_files yaml_stream formatter)
add_executable(jsonnet_${exe}_sandboxed
jsonnet_${exe}_example.cc
)
target_link_libraries(jsonnet_${exe}_sandboxed PRIVATE
libjsonnet
jsonnet_helper
jsonnet_sapi
sandbox2::file_base
sandbox2::fileops
sapi::sapi
)
target_include_directories(jsonnet_${exe}_sandboxed PUBLIC
${JSONNET_SAPI_HEADERS}
)
endforeach()
add_executable(jsonnet_base_transacted
jsonnet_base_transaction.cc
)
target_link_libraries(jsonnet_base_transacted PRIVATE
libjsonnet
jsonnet_helper
jsonnet_sapi
sapi::sapi
)

View File

@ -0,0 +1,95 @@
// Copyright 2020 Google LLC
//
// 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 <cstdlib>
#include <iostream>
#include "jsonnet_base_sandbox.h" // NOLINT(build/include)
#include "sandboxed_api/sandbox2/util/fileops.h"
#include "sandboxed_api/sandbox2/util/path.h"
absl::Status JsonnetMain(std::string in_file, std::string out_file) {
using sandbox2::file::JoinPath;
using sandbox2::file_util::fileops::Basename;
// Initialize sandbox.
JsonnetBaseSandbox sandbox(in_file, out_file);
SAPI_RETURN_IF_ERROR(sandbox.Init())
JsonnetApi api(&sandbox);
// Initialize library's main structure.
SAPI_ASSIGN_OR_RETURN(JsonnetVm * jsonnet_vm, api.c_jsonnet_make());
sapi::v::RemotePtr vm_pointer(jsonnet_vm);
// Read input file.
std::string in_file_in_sandboxee(JoinPath("/input", Basename(in_file)));
sapi::v::ConstCStr in_file_var(in_file_in_sandboxee.c_str());
SAPI_ASSIGN_OR_RETURN(char* input,
api.c_read_input(false, in_file_var.PtrBefore()));
// Process jsonnet data.
sapi::v::RemotePtr input_pointer(input);
sapi::v::Int error;
SAPI_ASSIGN_OR_RETURN(char* output, api.c_jsonnet_evaluate_snippet(
&vm_pointer, in_file_var.PtrBefore(),
&input_pointer, error.PtrAfter()));
CHECK(!error.GetValue()) << "Jsonnet code evaluation failed: "
<< error.GetValue() << "\n"
<< "Make sure all files used by your jsonnet file "
"are in the same directory as your file.";
// Write data to file.
std::string out_file_in_sandboxee(JoinPath("/output", Basename(out_file)));
sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str());
sapi::v::RemotePtr output_pointer(output);
SAPI_ASSIGN_OR_RETURN(
bool success,
api.c_write_output_file(&output_pointer, out_file_var.PtrBefore()));
CHECK(success) << "Writing to output file failed: " << success;
// Clean up.
SAPI_ASSIGN_OR_RETURN(char* result,
api.c_jsonnet_realloc(&vm_pointer, &output_pointer, 0));
SAPI_RETURN_IF_ERROR(api.c_jsonnet_destroy(&vm_pointer));
SAPI_RETURN_IF_ERROR(api.c_free_input(&input_pointer));
return absl::OkStatus();
}
int main(int argc, char* argv[]) {
using sandbox2::file_util::fileops::Basename;
google::InitGoogleLogging(argv[0]);
gflags::ParseCommandLineFlags(&argc, &argv, true);
if (!(argc == 3)) {
std::cerr << "Usage:\n"
<< Basename(argv[0])
<< " /absolute/path/to/INPUT.jsonnet /absolute/path/to/OUTPUT\n";
return EXIT_FAILURE;
}
std::string in_file(argv[1]);
std::string out_file(argv[2]);
absl::Status status = JsonnetMain(in_file, out_file);
if (!status.ok()) {
LOG(ERROR) << "Failed: " << status.ToString();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,88 @@
// Copyright 2020 Google LLC
//
// 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 "jsonnet_base_transaction.h" // NOLINT(build/include)
#include "sandboxed_api/sandbox2/util/fileops.h"
#include "sandboxed_api/sandbox2/util/path.h"
absl::Status JsonnetTransaction::Main() {
using sandbox2::file::JoinPath;
using sandbox2::file_util::fileops::Basename;
JsonnetApi api(sandbox());
// Initialize library's main structure.
SAPI_ASSIGN_OR_RETURN(JsonnetVm * jsonnet_vm, api.c_jsonnet_make());
sapi::v::RemotePtr vm_pointer(jsonnet_vm);
// Read input file.
std::string in_file_in_sandboxee(JoinPath("/input", Basename(in_file)));
sapi::v::ConstCStr in_file_var(in_file_in_sandboxee.c_str());
SAPI_ASSIGN_OR_RETURN(char* input,
api.c_read_input(false, in_file_var.PtrBefore()));
// Process jsonnet data.
sapi::v::RemotePtr input_pointer(input);
sapi::v::Int error;
SAPI_ASSIGN_OR_RETURN(char* output, api.c_jsonnet_evaluate_snippet(
&vm_pointer, in_file_var.PtrBefore(),
&input_pointer, error.PtrAfter()));
TRANSACTION_FAIL_IF_NOT(error.GetValue() == 0,
"Jsonnet code evaluation failed.");
// Write data to file.
std::string out_file_in_sandboxee(JoinPath("/output", Basename(out_file)));
sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str());
sapi::v::RemotePtr output_pointer(output);
SAPI_ASSIGN_OR_RETURN(
bool success,
api.c_write_output_file(&output_pointer, out_file_var.PtrBefore()));
TRANSACTION_FAIL_IF_NOT(success, "Writing to output file failed.");
// Clean up.
SAPI_ASSIGN_OR_RETURN(char* result,
api.c_jsonnet_realloc(&vm_pointer, &output_pointer, 0));
SAPI_RETURN_IF_ERROR(api.c_jsonnet_destroy(&vm_pointer));
SAPI_RETURN_IF_ERROR(api.c_free_input(&input_pointer));
return absl::OkStatus();
}
int main(int argc, char* argv[]) {
using sandbox2::file_util::fileops::Basename;
google::InitGoogleLogging(argv[0]);
gflags::ParseCommandLineFlags(&argc, &argv, true);
if (!(argc == 3)) {
std::cerr << "Usage:\n"
<< Basename(argv[0])
<< " /absolute/path/to/INPUT.jsonnet /absolute/path/to/OUTPUT\n";
return EXIT_FAILURE;
}
std::string in_file(argv[1]);
std::string out_file(argv[2]);
JsonnetTransaction jsonnet_transaction(in_file, out_file);
auto result = jsonnet_transaction.Run();
LOG(INFO) << "Transaction result: " << result.message();
CHECK(result.ok());
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,50 @@
/*
Copyright 2015 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.
*/
{
concat_array: [1, 2, 3] + [4],
concat_string: '123' + 4,
equality1: 1 == '1',
equality2: [{}, { x: 3 - 1 }]
== [{}, { x: 2 }],
ex1: 1 + 2 * 3 / (4 + 5),
// Bitwise operations first cast to int.
ex2: self.ex1 | 3,
// Modulo operator.
ex3: self.ex1 % 2,
// Boolean logic
ex4: (4 > 3) && (1 <= 3) || false,
// Mixing objects together
obj: { a: 1, b: 2 } + { b: 3, c: 4 },
// Test if a field is in an object
obj_member: 'foo' in { foo: 1 },
// String formatting
str1: 'The value of self.ex2 is '
+ self.ex2 + '.',
str2: 'The value of self.ex2 is %g.'
% self.ex2,
str3: 'ex1=%0.2f, ex2=%0.2f'
% [self.ex1, self.ex2],
// By passing self, we allow ex1 and ex2 to
// be extracted internally.
str4: 'ex1=%(ex1)0.2f, ex2=%(ex2)0.2f'
% self,
// Do textual templating of entire files:
str5: |||
ex1=%(ex1)0.2f
ex2=%(ex2)0.2f
||| % self,
}

View File

@ -0,0 +1,32 @@
// Copyright 2020 Google LLC
//
// 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.
// This is a poorly written jsonnet file. Given to the formatter executable will be changed into a canonical jsonnet file form.
local b = import "somefile.libsonnet"; # comment
local a = import "differentfile.libsonnet"; // another comment in different style
local SomeStuff = {bar: "foo"};
local funtion_to_do_addition(x,y)=x+y;
{
"this": ((3)) ,
"that that":
funtion_to_do_addition(4,2),
arrArr: [[
1, 2, 5
],
3, 10, 19
]
} + SomeStuff

View File

@ -0,0 +1,30 @@
/*
Copyright 2015 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.
*/
local martinis = import 'martinis.libsonnet';
{
'Vodka Martini': martinis['Vodka Martini'],
Manhattan: {
ingredients: [
{ kind: 'Rye', qty: 2.5 },
{ kind: 'Sweet Red Vermouth', qty: 1 },
{ kind: 'Angostura', qty: 'dash' },
],
garnish: importstr 'garnish.txt',
served: 'Straight Up',
},
}

View File

@ -0,0 +1,26 @@
// Copyright 2020 Google LLC
//
// 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.
// This is a jsonnet code which evaluates to mutliple output files.
{
"first_file.json": {
name: 'This is the first file created by the multiple-files example code.',
caption: 'The other one\'s name is -> ' + $["second_file.json"].name,
},
"second_file.json": {
name: 'And that is the other one.',
caption: 'If it was the first one, variable name would hold what\'s in <first_name> variable.',
first_name: $["first_file.json"].name,
},
}

View File

@ -0,0 +1,29 @@
/*
Copyright 2015 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.
*/
local utils = import 'utils.libsonnet';
{
Negroni: {
// Divide 3oz among the 3 ingredients.
ingredients: utils.equal_parts(3, [
'Farmers Gin',
'Sweet Red Vermouth',
'Campari',
]),
garnish: 'Orange Peel',
served: 'On The Rocks',
},
}

View File

@ -0,0 +1,26 @@
/*
Copyright 2015 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.
*/
{
equal_parts(size, ingredients)::
// Define a function-scoped variable.
local qty = size / std.length(ingredients);
// Return an array.
[
{ kind: i, qty: qty }
for i in ingredients
],
}

View File

@ -0,0 +1,26 @@
// Copyright 2020 Google LLC
//
// 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.
// This is a jsonnet code which evaluates to json file, which can be interpreted as YAML stream.
local
first_object = {
name: 'First object\'s name.',
age: 'Just created!',
},
second_object = {
name: 'Hi, my name is <second_object>.',
sibling: first_object.name
};
[first_object, second_object]

View File

@ -0,0 +1,26 @@
{
"concat_array": [
1,
2,
3,
4
],
"concat_string": "1234",
"equality1": false,
"equality2": true,
"ex1": 1.6666666666666665,
"ex2": 3,
"ex3": 1.6666666666666665,
"ex4": true,
"obj": {
"a": 1,
"b": 3,
"c": 4
},
"obj_member": true,
"str1": "The value of self.ex2 is 3.",
"str2": "The value of self.ex2 is 3.",
"str3": "ex1=1.67, ex2=3.00",
"str4": "ex1=1.67, ex2=3.00",
"str5": "ex1=1.67\nex2=3.00\n"
}

View File

@ -0,0 +1,4 @@
{
"caption": "The other one's name is -> And that is the other one.",
"name": "This is the first file created by the multiple-files example code."
}

View File

@ -0,0 +1,20 @@
{
"Negroni": {
"garnish": "Orange Peel",
"ingredients": [
{
"kind": "Farmers Gin",
"qty": 1
},
{
"kind": "Sweet Red Vermouth",
"qty": 1
},
{
"kind": "Campari",
"qty": 1
}
],
"served": "On The Rocks"
}
}

View File

@ -0,0 +1,5 @@
{
"caption": "If it was the first one, variable name would hold what's in <first_name> variable.",
"first_name": "This is the first file created by the multiple-files example code.",
"name": "And that is the other one."
}

View File

@ -0,0 +1,11 @@
---
{
"age": "Just created!",
"name": "First object's name."
}
---
{
"name": "Hi, my name is <second_object>.",
"sibling": "First object's name."
}
...

View File

@ -0,0 +1,128 @@
// Copyright 2020 Google LLC
//
// 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 <libgen.h>
#include <syscall.h>
#include <cstdlib>
#include <iostream>
#include "jsonnet_sapi.sapi.h" // NOLINT(build/include)
#include "sandboxed_api/util/flag.h"
#include "sandboxed_api/sandbox2/util/fileops.h"
#include "sandboxed_api/sandbox2/util/path.h"
class JsonnetSapiSandbox : public JsonnetSandbox {
public:
explicit JsonnetSapiSandbox(std::string in_file, std::string out_file)
: in_file_(std::move(in_file)), out_file_(std::move(out_file)) {}
// We need only the input file here, not the whole input directory
std::unique_ptr<sandbox2::Policy> ModifyPolicy(
sandbox2::PolicyBuilder*) override {
return sandbox2::PolicyBuilder()
.AllowStaticStartup()
.AllowOpen()
.AllowRead()
.AllowWrite()
.AllowStat()
.AllowSystemMalloc()
.AllowExit()
.AllowSyscalls({
__NR_futex,
__NR_close,
})
.AddDirectoryAt(dirname(&out_file_[0]), "/output", /*is_ro=*/false)
.AddFile(in_file_, true)
.BuildOrDie();
}
private:
std::string in_file_;
std::string out_file_;
};
absl::Status JsonnetMain(std::string in_file, std::string out_file) {
using sandbox2::file::JoinPath;
using sandbox2::file_util::fileops::Basename;
// Initialize sandbox.
JsonnetSapiSandbox sandbox(in_file, out_file);
SAPI_RETURN_IF_ERROR(sandbox.Init())
JsonnetApi api(&sandbox);
// Initialize library's main structure.
SAPI_ASSIGN_OR_RETURN(JsonnetVm * jsonnet_vm, api.c_jsonnet_make());
sapi::v::RemotePtr vm_pointer(jsonnet_vm);
// Read input file.
std::string in_file_in_sandboxee(JoinPath("/input", Basename(in_file)));
sapi::v::ConstCStr in_file_var(in_file_in_sandboxee.c_str());
SAPI_ASSIGN_OR_RETURN(char* input,
api.c_read_input(false, in_file_var.PtrBefore()));
// Process jsonnet data.
sapi::v::RemotePtr input_pointer(input);
sapi::v::Int error;
SAPI_ASSIGN_OR_RETURN(char* output, api.c_jsonnet_fmt_snippet(
&vm_pointer, in_file_var.PtrBefore(),
&input_pointer, error.PtrAfter()));
CHECK(!error.GetValue()) << "Jsonnet code evaluation failed: "
<< error.GetValue() << "\n";
// Write data to file.
std::string out_file_in_sandboxee(JoinPath("/output", Basename(out_file)));
sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str());
sapi::v::RemotePtr output_pointer(output);
SAPI_ASSIGN_OR_RETURN(
bool success,
api.c_write_output_file(&output_pointer, out_file_var.PtrBefore()));
CHECK(success) << "Writing to output file failed: " << success;
// Clean up.
SAPI_ASSIGN_OR_RETURN(char* result,
api.c_jsonnet_realloc(&vm_pointer, &output_pointer, 0));
SAPI_RETURN_IF_ERROR(api.c_jsonnet_destroy(&vm_pointer));
SAPI_RETURN_IF_ERROR(api.c_free_input(&input_pointer));
return absl::OkStatus();
}
int main(int argc, char* argv[]) {
using sandbox2::file_util::fileops::Basename;
google::InitGoogleLogging(argv[0]);
gflags::ParseCommandLineFlags(&argc, &argv, true);
if (!(argc == 3)) {
std::cerr << "Usage:\n"
<< Basename(argv[0])
<< " /absolute/path/to/INPUT.jsonnet /absolute/path/to/OUTPUT\n";
return EXIT_FAILURE;
}
std::string in_file(argv[1]);
std::string out_file(argv[2]);
absl::Status status = JsonnetMain(in_file, out_file);
if (!status.ok()) {
LOG(ERROR) << "Failed: " << status.ToString();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,132 @@
// Copyright 2020 Google LLC
//
// 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 <libgen.h>
#include <syscall.h>
#include <cstdlib>
#include <iostream>
#include "jsonnet_sapi.sapi.h" // NOLINT(build/include)
#include "sandboxed_api/util/flag.h"
#include "sandboxed_api/sandbox2/util/fileops.h"
#include "sandboxed_api/sandbox2/util/path.h"
class JsonnetSapiSandbox : public JsonnetSandbox {
public:
explicit JsonnetSapiSandbox(std::string in_file, std::string out_directory)
: in_file_(std::move(in_file)),
out_directory_(std::move(out_directory)) {}
// We need a slightly different policy than the default one
std::unique_ptr<sandbox2::Policy> ModifyPolicy(
sandbox2::PolicyBuilder*) override {
return sandbox2::PolicyBuilder()
.AllowStaticStartup()
.AllowOpen()
.AllowRead()
.AllowWrite()
.AllowStat()
.AllowSystemMalloc()
.AllowExit()
.AllowSyscalls({
__NR_futex,
__NR_close,
})
.AddDirectoryAt(sandbox2::file::CleanPath(&out_directory_[0]),
"/output",
/*is_ro=*/false)
.AddDirectoryAt(dirname(&in_file_[0]), "/input", true)
.BuildOrDie();
}
private:
std::string in_file_;
std::string out_directory_;
};
absl::Status JsonnetMain(std::string in_file, std::string out_file) {
using sandbox2::file::JoinPath;
using sandbox2::file_util::fileops::Basename;
// Initialize sandbox.
JsonnetSapiSandbox sandbox(in_file, out_file);
SAPI_RETURN_IF_ERROR(sandbox.Init())
JsonnetApi api(&sandbox);
// Initialize library's main structure.
SAPI_ASSIGN_OR_RETURN(JsonnetVm * jsonnet_vm, api.c_jsonnet_make());
sapi::v::RemotePtr vm_pointer(jsonnet_vm);
// Read input file.
std::string in_file_in_sandboxee(JoinPath("/input", Basename(in_file)));
sapi::v::ConstCStr in_file_var(in_file_in_sandboxee.c_str());
SAPI_ASSIGN_OR_RETURN(char* input,
api.c_read_input(false, in_file_var.PtrBefore()));
// Process jsonnet data.
sapi::v::RemotePtr input_pointer(input);
sapi::v::Int error;
SAPI_ASSIGN_OR_RETURN(char* output, api.c_jsonnet_evaluate_snippet_multi(
&vm_pointer, in_file_var.PtrBefore(),
&input_pointer, error.PtrAfter()));
CHECK(!error.GetValue()) << "Jsonnet code evaluation failed: "
<< error.GetValue() << "\n"
<< "Make sure all files used by your jsonnet file "
"are in the same directory as your file.";
// Write data to file.
std::string out_file_in_sandboxee(JoinPath("/output", Basename(out_file)));
sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str());
sapi::v::RemotePtr output_pointer(output);
SAPI_ASSIGN_OR_RETURN(
bool success,
api.c_write_output_file(&output_pointer, out_file_var.PtrBefore()));
CHECK(success) << "Writing to output file failed: " << success;
// Clean up.
SAPI_ASSIGN_OR_RETURN(char* result,
api.c_jsonnet_realloc(&vm_pointer, &output_pointer, 0));
SAPI_RETURN_IF_ERROR(api.c_jsonnet_destroy(&vm_pointer));
SAPI_RETURN_IF_ERROR(api.c_free_input(&input_pointer));
return absl::OkStatus();
}
int main(int argc, char* argv[]) {
using sandbox2::file_util::fileops::Basename;
google::InitGoogleLogging(argv[0]);
gflags::ParseCommandLineFlags(&argc, &argv, true);
if (!(argc == 3)) {
std::cerr << "Usage:\n"
<< Basename(argv[0])
<< " /absolute/path/to/INPUT.jsonnet /absolute/path/to/OUTPUT\n";
return EXIT_FAILURE;
}
std::string in_file(argv[1]);
std::string out_file(argv[2]);
absl::Status status = JsonnetMain(in_file, out_file);
if (!status.ok()) {
LOG(ERROR) << "Failed: " << status.ToString();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,95 @@
// Copyright 2020 Google LLC
//
// 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 <cstdlib>
#include <iostream>
#include "jsonnet_base_sandbox.h" // NOLINT(build/include)
#include "sandboxed_api/sandbox2/util/fileops.h"
#include "sandboxed_api/sandbox2/util/path.h"
absl::Status JsonnetMain(std::string in_file, std::string out_file) {
using sandbox2::file::JoinPath;
using sandbox2::file_util::fileops::Basename;
// Initialize sandbox.
JsonnetBaseSandbox sandbox(in_file, out_file);
SAPI_RETURN_IF_ERROR(sandbox.Init())
JsonnetApi api(&sandbox);
// Initialize library's main structure.
SAPI_ASSIGN_OR_RETURN(JsonnetVm * jsonnet_vm, api.c_jsonnet_make());
sapi::v::RemotePtr vm_pointer(jsonnet_vm);
// Read input file.
std::string in_file_in_sandboxee(JoinPath("/input", Basename(in_file)));
sapi::v::ConstCStr in_file_var(in_file_in_sandboxee.c_str());
SAPI_ASSIGN_OR_RETURN(char* input,
api.c_read_input(false, in_file_var.PtrBefore()));
// Process jsonnet data.
sapi::v::RemotePtr input_pointer(input);
sapi::v::Int error;
SAPI_ASSIGN_OR_RETURN(char* output, api.c_jsonnet_evaluate_snippet_stream(
&vm_pointer, in_file_var.PtrBefore(),
&input_pointer, error.PtrAfter()));
CHECK(!error.GetValue())
<< "Jsonnet code evaluation failed: " << error.GetValue() << "\n"
<< "Make sure all files used by your jsonnet file are in the same "
"directory as your file.";
// Write data to file.
std::string out_file_in_sandboxee(JoinPath("/output", Basename(out_file)));
sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str());
sapi::v::RemotePtr output_pointer(output);
SAPI_ASSIGN_OR_RETURN(
bool success,
api.c_write_output_file(&output_pointer, out_file_var.PtrBefore()));
CHECK(success) << "Writing to output file failed: " << success;
// Clean up.
SAPI_ASSIGN_OR_RETURN(char* result,
api.c_jsonnet_realloc(&vm_pointer, &output_pointer, 0));
SAPI_RETURN_IF_ERROR(api.c_jsonnet_destroy(&vm_pointer));
SAPI_RETURN_IF_ERROR(api.c_free_input(&input_pointer));
return absl::OkStatus();
}
int main(int argc, char* argv[]) {
using sandbox2::file_util::fileops::Basename;
google::InitGoogleLogging(argv[0]);
gflags::ParseCommandLineFlags(&argc, &argv, true);
if (!(argc == 3)) {
std::cerr << "Usage:\n"
<< Basename(argv[0])
<< " /absolute/path/to/INPUT.jsonnet /absolute/path/to/OUTPUT\n";
return EXIT_FAILURE;
}
std::string in_file(argv[1]);
std::string out_file(argv[2]);
absl::Status status = JsonnetMain(in_file, out_file);
if (!status.ok()) {
LOG(ERROR) << "Failed: " << status.ToString();
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,55 @@
// Copyright 2020 Google LLC
//
// 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.
#ifndef JSONNET_BASE_SANDBOX_H_
#define JSONNET_BASE_SANDBOX_H_
#include <libgen.h>
#include <syscall.h>
#include "jsonnet_sapi.sapi.h" // NOLINT(build/include)
#include "sandboxed_api/util/flag.h"
#include "sandboxed_api/transaction.h"
#include "sandboxed_api/vars.h"
class JsonnetBaseSandbox : public JsonnetSandbox {
public:
explicit JsonnetBaseSandbox(std::string in_file, std::string out_file)
: in_file_(in_file), out_file_(out_file) {}
std::unique_ptr<sandbox2::Policy> ModifyPolicy(
sandbox2::PolicyBuilder *) override {
return sandbox2::PolicyBuilder()
.AllowStaticStartup()
.AllowOpen()
.AllowRead()
.AllowWrite()
.AllowStat()
.AllowSystemMalloc()
.AllowExit()
.AllowSyscalls({
__NR_futex,
__NR_close,
})
.AddDirectoryAt(dirname(&out_file_[0]), "/output", /*is_ro=*/false)
.AddDirectoryAt(dirname(&in_file_[0]), "/input", true)
.BuildOrDie();
}
private:
std::string in_file_;
std::string out_file_;
};
#endif // JSONNET_BASE_SANDBOX_H_

View File

@ -0,0 +1,38 @@
// Copyright 2020 Google LLC
//
// 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.
#ifndef JSNONNET_BASE_TRANSACTION_H_
#define JSNONNET_BASE_TRANSACTION_H_
#include "jsonnet_base_sandbox.h" // NOLINT(build/include)
class JsonnetTransaction : public sapi::Transaction {
public:
JsonnetTransaction(std::string in_file, std::string out_file)
: sapi::Transaction(
std::make_unique<JsonnetBaseSandbox>(in_file, out_file)),
in_file_(in_file),
out_file_(out_file) {
sapi::Transaction::set_retry_count(0); // Try once, no retries
sapi::Transaction::SetTimeLimit(0); // Infinite time limit
}
private:
std::string in_file_;
std::string out_file_;
absl::Status Main() override;
};
#endif // JSNONNET_BASE_TRANSACTION_H_

View File

@ -0,0 +1,57 @@
// Copyright 2020 Google LLC
//
// 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.
#ifndef JSONNET_TESTS_H_
#define JSONNET_TESTS_H_
#include <unistd.h>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <streambuf>
#include <string>
#include "jsonnet_base_sandbox.h" // NOLINT(build/include)
#include "jsonnet_sapi.sapi.h" // NOLINT(build/include)
#include "gtest/gtest.h"
#include "sandboxed_api/util/flag.h"
#include "sandboxed_api/sandbox2/util/path.h"
#include "sandboxed_api/util/status_matchers.h"
class JsonnetTestHelper {
protected:
enum Evaluation { kBase, kMultipleFiles, kYamlStream };
void TestSetUp();
void TestTearDown();
void ReadInput(const char* filename);
void EvaluateJsonnetCode(Evaluation type, bool expected_correct);
void WriteOutput(const char* filename_or_directory, Evaluation type);
std::string ReadOutput(const char* filename);
std::unique_ptr<JsonnetBaseSandbox> sandbox_;
std::unique_ptr<JsonnetApi> api_;
std::unique_ptr<sapi::v::RemotePtr> input_;
std::unique_ptr<sapi::v::RemotePtr> output_;
std::unique_ptr<sapi::v::RemotePtr> vm_;
std::string input_filename_in_sandboxee_;
bool jsonnet_vm_was_used_;
bool input_was_read_;
};
#endif // JSONNET_TESTS_H_

@ -0,0 +1 @@
Subproject commit 3e25595d5c4acd32a1c3951a57471986b90d3bad

View File

@ -0,0 +1,664 @@
--- jsonnet.cpp 2020-09-09 12:15:33.687539042 +0000
+++ write_helper.cpp 2020-09-25 15:38:37.317147682 +0000
@@ -14,559 +14,125 @@
limitations under the License.
*/
-#include <cassert>
-#include <cstdlib>
-#include <cstring>
+// We need two functions defined in jsonnet.cpp file, used for writing output
+// (multiple files and yaml streams) -- with minor changes (e.x. return type).
-#include <exception>
#include <fstream>
#include <iostream>
-#include <list>
#include <map>
-#include <sstream>
-#include <string>
#include <vector>
-#include "utils.h"
+#include "jsonnet_helper.h" // NOLINT(build/include)
-extern "C" {
-#include <libjsonnet.h>
-}
-
-#ifdef _WIN32
-const char PATH_SEP = ';';
-#else
-const char PATH_SEP = ':';
-#endif
-
-void version(std::ostream &o)
-{
- o << "Jsonnet commandline interpreter " << jsonnet_version() << std::endl;
-}
-
-void usage(std::ostream &o)
-{
- version(o);
- o << "\n";
- o << "jsonnet {<option>} <filename>\n";
- o << "\n";
- o << "Available options:\n";
- o << " -h / --help This message\n";
- o << " -e / --exec Treat filename as code\n";
- o << " -J / --jpath <dir> Specify an additional library search dir (right-most wins)\n";
- o << " -o / --output-file <file> Write to the output file rather than stdout\n";
- o << " -m / --multi <dir> Write multiple files to the directory, list files on stdout\n";
- o << " -y / --yaml-stream Write output as a YAML stream of JSON documents\n";
- o << " -S / --string Expect a string, manifest as plain text\n";
- o << " -s / --max-stack <n> Number of allowed stack frames\n";
- o << " -t / --max-trace <n> Max length of stack trace before cropping\n";
- o << " --gc-min-objects <n> Do not run garbage collector until this many\n";
- o << " --gc-growth-trigger <n> Run garbage collector after this amount of object growth\n";
- o << " --version Print version\n";
- o << "Available options for specifying values of 'external' variables:\n";
- o << "Provide the value as a string:\n";
- o << " -V / --ext-str <var>[=<val>] If <val> is omitted, get from environment var <var>\n";
- o << " --ext-str-file <var>=<file> Read the string from the file\n";
- o << "Provide a value as Jsonnet code:\n";
- o << " --ext-code <var>[=<code>] If <code> is omitted, get from environment var <var>\n";
- o << " --ext-code-file <var>=<file> Read the code from the file\n";
- o << "Available options for specifying values of 'top-level arguments':\n";
- o << "Provide the value as a string:\n";
- o << " -A / --tla-str <var>[=<val>] If <val> is omitted, get from environment var <var>\n";
- o << " --tla-str-file <var>=<file> Read the string from the file\n";
- o << "Provide a value as Jsonnet code:\n";
- o << " --tla-code <var>[=<code>] If <code> is omitted, get from environment var <var>\n";
- o << " --tla-code-file <var>=<file> Read the code from the file\n";
- o << "Environment variables:\n";
- o << "JSONNET_PATH is a colon (semicolon on Windows) separated list of directories added\n";
- o << "in reverse order before the paths specified by --jpath (i.e. left-most wins)\n";
- o << "E.g. JSONNET_PATH=a:b jsonnet -J c -J d is equivalent to:\n";
- o << "JSONNET_PATH=d:c:a:b jsonnet\n";
- o << "jsonnet -J b -J a -J c -J d\n";
- o << "\n";
- o << "In all cases:\n";
- o << "<filename> can be - (stdin)\n";
- o << "Multichar options are expanded e.g. -abc becomes -a -b -c.\n";
- o << "The -- option suppresses option processing for subsequent arguments.\n";
- o << "Note that since filenames and jsonnet programs can begin with -, it is advised to\n";
- o << "use -- if the argument is unknown, e.g. jsonnet -- \"$FILENAME\".";
- o << std::endl;
-}
-
-/** Class for representing configuration read from command line flags. */
-struct JsonnetConfig {
- std::vector<std::string> inputFiles;
- std::string outputFile;
- bool filenameIsCode;
-
- // EVAL flags
- bool evalMulti;
- bool evalStream;
- std::string evalMultiOutputDir;
-
- JsonnetConfig()
- : filenameIsCode(false),
- evalMulti(false),
- evalStream(false)
- {
- }
-};
-
-bool get_var_val(const std::string &var_val, std::string &var, std::string &val)
-{
- size_t eq_pos = var_val.find_first_of('=', 0);
- if (eq_pos == std::string::npos) {
- var = var_val;
- const char *val_cstr = ::getenv(var.c_str());
- if (val_cstr == nullptr) {
- std::cerr << "ERROR: environment variable " << var << " was undefined." << std::endl;
- return false;
- }
- val = val_cstr;
- } else {
- var = var_val.substr(0, eq_pos);
- val = var_val.substr(eq_pos + 1, std::string::npos);
- }
- return true;
-}
-
-bool get_var_file(const std::string &var_file, const std::string &imp, std::string &var, std::string &val)
-{
- size_t eq_pos = var_file.find_first_of('=', 0);
- if (eq_pos == std::string::npos) {
- std::cerr << "ERROR: argument not in form <var>=<file> \"" << var_file << "\"."
- << std::endl;
- return false;
- }
- var = var_file.substr(0, eq_pos);
- const std::string path = var_file.substr(eq_pos + 1, std::string::npos);
-
- size_t b, e;
- val.erase().append(imp).append(" @'");
- // duplicate all the single quotes in @path to make a quoted string
- for (b = 0; (e = path.find("'", b)) != std::string::npos; b = e + 1) {
- val.append(path.substr(b, e - b + 1)).push_back('\'');
+/** Writes output files for multiple file output */
+bool write_multi_output_files(char *output, const std::string &output_dir, bool show_output_file_names) {
+ // If multiple file output is used, then iterate over each string from
+ // the sequence of strings returned by jsonnet_evaluate_snippet_multi,
+ // construct pairs of filename and content, and write each output file.
+ std::map<std::string, std::string> r;
+ for (const char *c = output; *c != '\0';) {
+ const char *filename = c;
+ const char *c2 = c;
+ while (*c2 != '\0') ++c2;
+ ++c2;
+ const char *json = c2;
+ while (*c2 != '\0') ++c2;
+ ++c2;
+ c = c2;
+ r[filename] = json;
+ }
+
+ std::ostream *o;
+ std::ofstream f;
+
+ o = &std::cout;
+
+ for (const auto &pair : r) {
+ const std::string &new_content = pair.second;
+ const std::string &filename = output_dir + pair.first;
+ if (show_output_file_names) {
+ (*o) << filename << std::endl;
}
- val.append(path.substr(b)).push_back('\'');
-
- return true;
-}
-
-enum ArgStatus {
- ARG_CONTINUE,
- ARG_SUCCESS,
- ARG_FAILURE,
-};
-
-/** Parse the command line arguments, configuring the Jsonnet VM context and
- * populating the JsonnetConfig.
- */
-static ArgStatus process_args(int argc, const char **argv, JsonnetConfig *config, JsonnetVm *vm)
-{
- auto args = simplify_args(argc, argv);
- std::vector<std::string> remaining_args;
-
- unsigned i = 0;
-
- for (; i < args.size(); ++i) {
- const std::string &arg = args[i];
- if (arg == "-h" || arg == "--help") {
- usage(std::cout);
- return ARG_SUCCESS;
- } else if (arg == "-v" || arg == "--version") {
- version(std::cout);
- return ARG_SUCCESS;
- } else if (arg == "-e" || arg == "--exec") {
- config->filenameIsCode = true;
- } else if (arg == "-o" || arg == "--output-file") {
- std::string output_file = next_arg(i, args);
- if (output_file.length() == 0) {
- std::cerr << "ERROR: -o argument was empty string" << std::endl;
- return ARG_FAILURE;
- }
- config->outputFile = output_file;
- } else if (arg == "--") {
- // All subsequent args are not options.
- while ((++i) < args.size())
- remaining_args.push_back(args[i]);
- break;
- } else if (arg == "-s" || arg == "--max-stack") {
- long l = strtol_check(next_arg(i, args));
- if (l < 1) {
- std::cerr << "ERROR: invalid --max-stack value: " << l << std::endl;
- return ARG_FAILURE;
- }
- jsonnet_max_stack(vm, l);
- } else if (arg == "-J" || arg == "--jpath") {
- std::string dir = next_arg(i, args);
- if (dir.length() == 0) {
- std::cerr << "ERROR: -J argument was empty string" << std::endl;
- return ARG_FAILURE;
- }
- if (dir[dir.length() - 1] != '/') {
- dir += '/';
- }
- jsonnet_jpath_add(vm, dir.c_str());
- } else if (arg == "-V" || arg == "--ext-str") {
- std::string var, val;
- if (!get_var_val(next_arg(i, args), var, val))
- return ARG_FAILURE;
- jsonnet_ext_var(vm, var.c_str(), val.c_str());
- } else if (arg == "-E" || arg == "--var" || arg == "--env") {
- // TODO(dcunnin): Delete this in a future release.
- std::cerr << "WARNING: jsonnet eval -E, --var and --env are deprecated,"
- << " please use -V or --ext-str." << std::endl;
- std::string var, val;
- if (!get_var_val(next_arg(i, args), var, val))
- return ARG_FAILURE;
- jsonnet_ext_var(vm, var.c_str(), val.c_str());
- } else if (arg == "--ext-str-file") {
- std::string var, val;
- if (!get_var_file(next_arg(i, args), "importstr", var, val))
- return ARG_FAILURE;
- jsonnet_ext_code(vm, var.c_str(), val.c_str());
- } else if (arg == "-F" || arg == "--file") {
- // TODO(dcunnin): Delete this in a future release.
- std::cerr << "WARNING: jsonnet eval -F and --file are deprecated,"
- << " please use --ext-str-file." << std::endl;
- std::string var, val;
- if (!get_var_file(next_arg(i, args), "importstr", var, val))
- return ARG_FAILURE;
- jsonnet_ext_code(vm, var.c_str(), val.c_str());
- } else if (arg == "--ext-code") {
- std::string var, val;
- if (!get_var_val(next_arg(i, args), var, val))
- return ARG_FAILURE;
- jsonnet_ext_code(vm, var.c_str(), val.c_str());
- } else if (arg == "--code-var" || arg == "--code-env") {
- // TODO(dcunnin): Delete this in a future release.
- std::cerr << "WARNING: jsonnet eval --code-var and --code-env are deprecated,"
- << " please use --ext-code." << std::endl;
- std::string var, val;
- if (!get_var_val(next_arg(i, args), var, val))
- return ARG_FAILURE;
- jsonnet_ext_code(vm, var.c_str(), val.c_str());
- } else if (arg == "--ext-code-file") {
- std::string var, val;
- if (!get_var_file(next_arg(i, args), "import", var, val))
- return ARG_FAILURE;
- jsonnet_ext_code(vm, var.c_str(), val.c_str());
- } else if (arg == "--code-file") {
- // TODO(dcunnin): Delete this in a future release.
- std::cerr << "WARNING: jsonnet eval --code-file is deprecated,"
- << " please use --ext-code-file." << std::endl;
- std::string var, val;
- if (!get_var_file(next_arg(i, args), "import", var, val))
- return ARG_FAILURE;
- jsonnet_ext_code(vm, var.c_str(), val.c_str());
- } else if (arg == "-A" || arg == "--tla-str") {
- std::string var, val;
- if (!get_var_val(next_arg(i, args), var, val))
- return ARG_FAILURE;
- jsonnet_tla_var(vm, var.c_str(), val.c_str());
- } else if (arg == "--tla-str-file") {
- std::string var, val;
- if (!get_var_file(next_arg(i, args), "importstr", var, val))
- return ARG_FAILURE;
- jsonnet_tla_code(vm, var.c_str(), val.c_str());
- } else if (arg == "--tla-code") {
- std::string var, val;
- if (!get_var_val(next_arg(i, args), var, val))
- return ARG_FAILURE;
- jsonnet_tla_code(vm, var.c_str(), val.c_str());
- } else if (arg == "--tla-code-file") {
- std::string var, val;
- if (!get_var_file(next_arg(i, args), "import", var, val))
- return ARG_FAILURE;
- jsonnet_tla_code(vm, var.c_str(), val.c_str());
-
- } else if (arg == "--gc-min-objects") {
- long l = strtol_check(next_arg(i, args));
- if (l < 0) {
- std::cerr << "ERROR: invalid --gc-min-objects value: " << l << std::endl;
- return ARG_FAILURE;
- }
- jsonnet_gc_min_objects(vm, l);
- } else if (arg == "-t" || arg == "--max-trace") {
- long l = strtol_check(next_arg(i, args));
- if (l < 0) {
- std::cerr << "ERROR: invalid --max-trace value: " << l << std::endl;
- return ARG_FAILURE;
- }
- jsonnet_max_trace(vm, l);
- } else if (arg == "--gc-growth-trigger") {
- std::string num = next_arg(i, args);
- char *ep;
- double v = std::strtod(num.c_str(), &ep);
- if (*ep != '\0' || num.length() == 0) {
- std::cerr << "ERROR: invalid number \"" << num << "\"" << std::endl;
- return ARG_FAILURE;
- }
- if (v < 0) {
- std::cerr << "ERROR: invalid --gc-growth-trigger \"" << num << "\""
- << std::endl;
- return ARG_FAILURE;
- }
- jsonnet_gc_growth_trigger(vm, v);
- } else if (arg == "-m" || arg == "--multi") {
- config->evalMulti = true;
- std::string output_dir = next_arg(i, args);
- if (output_dir.length() == 0) {
- std::cerr << "ERROR: -m argument was empty string" << std::endl;
- return ARG_FAILURE;
- }
- if (output_dir[output_dir.length() - 1] != '/') {
- output_dir += '/';
- }
- config->evalMultiOutputDir = output_dir;
- } else if (arg == "-y" || arg == "--yaml-stream") {
- config->evalStream = true;
- } else if (arg == "-S" || arg == "--string") {
- jsonnet_string_output(vm, 1);
- } else if (arg.length() > 1 && arg[0] == '-') {
- std::cerr << "ERROR: unrecognized argument: " << arg << std::endl;
- return ARG_FAILURE;
- } else {
- remaining_args.push_back(args[i]);
+ {
+ std::ifstream exists(filename.c_str());
+ if (exists.good()) {
+ std::string existing_content;
+ existing_content.assign(std::istreambuf_iterator<char>(exists),
+ std::istreambuf_iterator<char>());
+ if (existing_content == new_content) {
+ // Do not bump the timestamp on the file if its content is
+ // the same. This may trigger other tools (e.g. make) to do
+ // unnecessary work.
+ continue;
}
+ }
}
-
- const char *want = config->filenameIsCode ? "code" : "filename";
- if (remaining_args.size() == 0) {
- std::cerr << "ERROR: must give " << want << "\n" << std::endl;
- usage(std::cerr);
- return ARG_FAILURE;
- }
-
- if (remaining_args.size() > 1) {
- std::string filename = remaining_args[0];
- std::cerr << "ERROR: only one " << want << " is allowed\n" << std::endl;
- return ARG_FAILURE;
- }
- config->inputFiles = remaining_args;
- return ARG_CONTINUE;
-}
-
-/** Writes output files for multiple file output */
-static bool write_multi_output_files(JsonnetVm *vm, char *output, const std::string &output_dir,
- const std::string &output_file)
-{
- // If multiple file output is used, then iterate over each string from
- // the sequence of strings returned by jsonnet_evaluate_snippet_multi,
- // construct pairs of filename and content, and write each output file.
- std::map<std::string, std::string> r;
- for (const char *c = output; *c != '\0';) {
- const char *filename = c;
- const char *c2 = c;
- while (*c2 != '\0')
- ++c2;
- ++c2;
- const char *json = c2;
- while (*c2 != '\0')
- ++c2;
- ++c2;
- c = c2;
- r[filename] = json;
- }
- jsonnet_realloc(vm, output, 0);
-
- std::ostream *o;
std::ofstream f;
-
- if (output_file.empty()) {
- o = &std::cout;
- } else {
- f.open(output_file.c_str());
- if (!f.good()) {
- std::string msg = "Writing to output file: " + output_file;
- perror(msg.c_str());
- return false;
- }
- o = &f;
+ f.open(filename.c_str());
+ if (!f.good()) {
+ std::string msg = "Opening output file: " + filename;
+ perror(msg.c_str());
+ return false;
+ }
+ f << new_content;
+ f.close();
+ if (!f.good()) {
+ std::string msg = "Writing to output file: " + filename;
+ perror(msg.c_str());
+ return false;
}
+ }
- for (const auto &pair : r) {
- const std::string &new_content = pair.second;
- const std::string &filename = output_dir + pair.first;
- (*o) << filename << std::endl;
- {
- std::ifstream exists(filename.c_str());
- if (exists.good()) {
- std::string existing_content;
- existing_content.assign(std::istreambuf_iterator<char>(exists),
- std::istreambuf_iterator<char>());
- if (existing_content == new_content) {
- // Do not bump the timestamp on the file if its content is
- // the same. This may trigger other tools (e.g. make) to do
- // unnecessary work.
- continue;
- }
- }
- }
- std::ofstream f;
- f.open(filename.c_str());
- if (!f.good()) {
- std::string msg = "Opening output file: " + filename;
- perror(msg.c_str());
- return false;
- }
- f << new_content;
- f.close();
- if (!f.good()) {
- std::string msg = "Writing to output file: " + filename;
- perror(msg.c_str());
- return false;
- }
- }
+ std::cout.flush();
- if (output_file.empty()) {
- std::cout.flush();
- } else {
- f.close();
- if (!f.good()) {
- std::string msg = "Writing to output file: " + output_file;
- perror(msg.c_str());
- return false;
- }
- }
- return true;
+ return true;
}
/** Writes output files for YAML stream output */
-static bool write_output_stream(JsonnetVm *vm, char *output, const std::string &output_file)
-{
- std::ostream *o;
- std::ofstream f;
-
- if (output_file.empty()) {
- o = &std::cout;
- } else {
- f.open(output_file.c_str());
- if (!f.good()) {
- std::string msg = "Writing to output file: " + output_file;
- perror(msg.c_str());
- return false;
- }
- o = &f;
- }
-
- // If YAML stream output is used, then iterate over each string from
- // the sequence of strings returned by jsonnet_evaluate_snippet_stream,
- // and add the --- and ... as defined by the YAML spec.
- std::vector<std::string> r;
- for (const char *c = output; *c != '\0';) {
- const char *json = c;
- while (*c != '\0')
- ++c;
- ++c;
- r.emplace_back(json);
- }
- jsonnet_realloc(vm, output, 0);
- for (const auto &str : r) {
- (*o) << "---\n";
- (*o) << str;
- }
- if (r.size() > 0)
- (*o) << "...\n";
- o->flush();
-
- if (output_file.empty()) {
- std::cout.flush();
- } else {
- f.close();
- if (!f.good()) {
- std::string msg = "Writing to output file: " + output_file;
- perror(msg.c_str());
- return false;
- }
+bool write_output_stream(char *output, const std::string &output_file) {
+ std::ostream *o;
+ std::ofstream f;
+
+ if (output_file.empty()) {
+ o = &std::cout;
+ } else {
+ f.open(output_file.c_str());
+ if (!f.good()) {
+ std::string msg = "Writing to output file: " + output_file;
+ perror(msg.c_str());
+ return false;
+ }
+ o = &f;
+ }
+
+ // If YAML stream output is used, then iterate over each string from
+ // the sequence of strings returned by jsonnet_evaluate_snippet_stream,
+ // and add the --- and ... as defined by the YAML spec.
+ std::vector<std::string> r;
+ for (const char *c = output; *c != '\0';) {
+ const char *json = c;
+ while (*c != '\0') ++c;
+ ++c;
+ r.emplace_back(json);
+ }
+
+ for (const auto &str : r) {
+ (*o) << "---\n";
+ (*o) << str;
+ }
+ if (r.size() > 0) (*o) << "...\n";
+ o->flush();
+
+ if (output_file.empty()) {
+ std::cout.flush();
+ } else {
+ f.close();
+ if (!f.good()) {
+ std::string msg = "Writing to output file: " + output_file;
+ perror(msg.c_str());
+ return false;
}
+ }
- return true;
-}
-
-int main(int argc, const char **argv)
-{
- try {
- JsonnetVm *vm = jsonnet_make();
- JsonnetConfig config;
- if (const char *jsonnet_path_env = getenv("JSONNET_PATH")) {
- std::list<std::string> jpath;
- std::istringstream iss(jsonnet_path_env);
- std::string path;
- while (std::getline(iss, path, PATH_SEP)) {
- jpath.push_front(path);
- }
- for (const std::string &path : jpath) {
- jsonnet_jpath_add(vm, path.c_str());
- }
- }
- ArgStatus arg_status = process_args(argc, argv, &config, vm);
- if (arg_status != ARG_CONTINUE) {
- jsonnet_destroy(vm);
- return arg_status == ARG_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE;
- }
-
- // Evaluate input Jsonnet and handle any errors from Jsonnet VM.
- int error;
- char *output;
- assert(config.inputFiles.size() == 1);
-
- // Read input file.
- std::string input;
- if (!read_input(config.filenameIsCode, &config.inputFiles[0], &input)) {
- jsonnet_destroy(vm);
- return EXIT_FAILURE;
- }
-
- if (config.evalMulti) {
- output = jsonnet_evaluate_snippet_multi(
- vm, config.inputFiles[0].c_str(), input.c_str(), &error);
- } else if (config.evalStream) {
- output = jsonnet_evaluate_snippet_stream(
- vm, config.inputFiles[0].c_str(), input.c_str(), &error);
- } else {
- output = jsonnet_evaluate_snippet(
- vm, config.inputFiles[0].c_str(), input.c_str(), &error);
- }
-
- if (error) {
- std::cerr << output;
- jsonnet_realloc(vm, output, 0);
- jsonnet_destroy(vm);
- return EXIT_FAILURE;
- }
-
- // Write output JSON.
- if (config.evalMulti) {
- if (!write_multi_output_files(
- vm, output, config.evalMultiOutputDir, config.outputFile)) {
- jsonnet_destroy(vm);
- return EXIT_FAILURE;
- }
- } else if (config.evalStream) {
- if (!write_output_stream(vm, output, config.outputFile)) {
- jsonnet_destroy(vm);
- return EXIT_FAILURE;
- }
- } else {
- bool successful = write_output_file(output, config.outputFile);
- jsonnet_realloc(vm, output, 0);
- if (!successful) {
- jsonnet_destroy(vm);
- return EXIT_FAILURE;
- }
- }
-
- jsonnet_destroy(vm);
- return EXIT_SUCCESS;
-
- } catch (const std::bad_alloc &) {
- // Avoid further allocation attempts
- fputs("Internal out-of-memory error (please report this)\n", stderr);
- } catch (const std::exception &e) {
- std::cerr << "Internal error (please report this): " << e.what() << std::endl;
- } catch (...) {
- std::cerr << "An unknown exception occurred (please report this)." << std::endl;
- }
- return EXIT_FAILURE;
+ return true;
}

View File

@ -0,0 +1,76 @@
// Copyright 2020 Google LLC
//
// 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 "jsonnet_helper.h" // NOLINT(build/include)
#include <cstring>
struct JsonnetVm* c_jsonnet_make(void) {
return jsonnet_make();
}
void c_jsonnet_destroy(struct JsonnetVm* vm) { return jsonnet_destroy(vm); }
char* c_jsonnet_evaluate_snippet(struct JsonnetVm* vm, const char* filename,
char* snippet, int* error) {
return jsonnet_evaluate_snippet(vm, filename, snippet, error);
}
char* c_jsonnet_evaluate_snippet_multi(struct JsonnetVm* vm,
const char* filename,
const char* snippet, int* error) {
return jsonnet_evaluate_snippet_multi(vm, filename, snippet, error);
}
char* c_jsonnet_evaluate_snippet_stream(struct JsonnetVm* vm,
const char* filename,
const char* snippet, int* error) {
return jsonnet_evaluate_snippet_stream(vm, filename, snippet, error);
}
char* c_read_input(bool filename_is_code, const char* filename) {
std::string s_filename(filename);
std::string s_input;
bool check = read_input(filename_is_code, &s_filename, &s_input);
char* c_input = strdup(s_input.c_str());
if (check) return c_input;
return nullptr;
}
void c_free_input(char* input) { free(input); }
bool c_write_output_file(const char* output, const char* output_file) {
std::string s_output_file(output_file);
return write_output_file(output, s_output_file);
}
bool c_write_multi_output_files(char* output, char* output_dir,
bool show_output_file_names) {
std::string s_output_dir(output_dir);
return write_multi_output_files(output, s_output_dir, show_output_file_names);
}
bool c_write_output_stream(char* output, char* output_file) {
std::string s_output_file(output_file);
return write_output_stream(output, s_output_file);
}
char* c_jsonnet_realloc(struct JsonnetVm* vm, char* str, size_t sz) {
return jsonnet_realloc(vm, str, sz);
}
char* c_jsonnet_fmt_snippet(struct JsonnetVm* vm, const char* filename,
const char* snippet, int* error) {
return jsonnet_fmt_snippet(vm, filename, snippet, error);
}

View File

@ -0,0 +1,66 @@
// Copyright 2020 Google LLC
//
// 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.
#ifndef JSONNET_HELPER_H_
#define JSONNET_HELPER_H_
extern "C" {
#include <libjsonnet.h> // NOLINT(build/include)
#include <libjsonnet_fmt.h> // NOLINT(build/include)
}
#include "jsonnet/cmd/utils.h" // NOLINT(build/include)
extern "C" struct JsonnetVm* c_jsonnet_make(void);
extern "C" void c_jsonnet_destroy(struct JsonnetVm* vm);
extern "C" char* c_jsonnet_evaluate_snippet(struct JsonnetVm* vm,
const char* filename, char* snippet,
int* error);
extern "C" char* c_jsonnet_evaluate_snippet_multi(struct JsonnetVm* vm,
const char* filename,
const char* snippet,
int* error);
extern "C" char* c_jsonnet_evaluate_snippet_stream(struct JsonnetVm* vm,
const char* filename,
const char* snippet,
int* error);
extern "C" char* c_read_input(bool filename_is_code, const char* filename);
extern "C" void c_free_input(char* input);
extern "C" bool c_write_output_file(const char* output,
const char* output_file);
extern "C" char* c_jsonnet_realloc(struct JsonnetVm* vm, char* str, size_t sz);
extern "C" bool c_write_multi_output_files(char* output, char* output_dir,
bool show_output_file_names);
bool write_multi_output_files(char* output, const std::string& output_dir,
bool show_output_file_names);
extern "C" bool c_write_output_stream(char* output, char* output_file);
bool write_output_stream(char* output, const std::string& output_file);
extern "C" char* c_jsonnet_fmt_snippet(struct JsonnetVm* vm,
const char* filename,
const char* snippet, int* error);
#endif // JSONNET_HELPER_H_

View File

@ -0,0 +1,42 @@
# Copyright 2020 Google LLC
#
# 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(GoogleTest)
# We need to prepare convenient directories so the tests will be able to access them
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/tests/tests_input)
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/tests/tests_output)
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/tests/tests_expected_output)
add_custom_target(test_preparation ALL
COMMAND cp ${PROJECT_SOURCE_DIR}/examples/jsonnet_codes/* ${PROJECT_BINARY_DIR}/tests/tests_input
COMMAND cp ${PROJECT_SOURCE_DIR}/examples/jsonnet_codes_expected_output/* ${PROJECT_BINARY_DIR}/tests/tests_expected_output
)
add_executable(tests
${PROJECT_SOURCE_DIR}/headers/jsonnet_tests.h
jsonnet_tests.cc
jsonnet_tests_utils.cc
)
target_include_directories(tests PUBLIC
${PROJECT_SOURCE_DIR}/headers
)
target_link_libraries(tests
jsonnet_sapi sapi::sapi
gtest gmock gtest_main
)
gtest_discover_tests(tests)

View File

@ -0,0 +1,113 @@
// Copyright 2020 Google LLC
//
// 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 "jsonnet_tests.h" // NOLINT(build/include)
namespace {
class JsonnetTest : public JsonnetTestHelper, public testing::Test {
protected:
void SetUp() override { TestSetUp(); }
void TearDown() override { TestTearDown(); }
};
// Basic test
TEST_F(JsonnetTest, SetUp_TearDown) {
ASSERT_FALSE(jsonnet_vm_was_used_);
ASSERT_FALSE(input_was_read_);
}
// One file evaluation to one file
TEST_F(JsonnetTest, One_file_no_dependencies) {
constexpr char kInputFile[] = "arith.jsonnet";
constexpr char kOutputFile[] = "arith_output";
constexpr char kOutputToRead[] = "tests_output/arith_output";
constexpr char kOutputToExpect[] = "tests_expected_output/arith.golden";
Read_input(kInputFile);
EvaluateJsonnetCode(kBase, true);
WriteOutput(kOutputFile, kBase);
std::string produced_output = ReadOutput(kOutputToRead);
std::string expected_output = ReadOutput(kOutputToExpect);
ASSERT_STREQ(produced_output.c_str(), expected_output.c_str());
}
// One file evaluating to one file, dependent on some other files
TEST_F(JsonnetTest, One_file_some_dependencies) {
constexpr char kInputFile[] = "negroni.jsonnet";
constexpr char kOutputFile[] = "negroni_output";
constexpr char kOutputToRead[] = "tests_output/negroni_output";
constexpr char kOutputToExpect[] = "tests_expected_output/negroni.golden";
ReadInput(kInputFile);
EvaluateJsonnetCode(kBase, true);
WriteOutput(kOutputFile, kBase);
const std::string produced_output = Read_output(kOutputToRead);
const std::string expected_output = Read_output(kOutputToExpect);
ASSERT_STREQ(produced_output.c_str(), expected_output.c_str());
}
// One file evaluating to two files
TEST_F(JsonnetTest, Multiple_files) {
constexpr char kInputFile[] = "multiple_files_example.jsonnet";
constexpr char kOutputFile[] = "";
constexpr char kOutputToRead1[] = "tests_output/first_file.json";
constexpr char kOutputToRead2[] = "tests_output/second_file.json";
constexpr char kOutputToExpect1[] = "tests_expected_output/first_file.json";
constexpr char kOutputToExpect2[] = "tests_expected_output/second_file.json";
ReadInput(kInputFile);
EvaluateJsonnetCode(kMultipleFiles, true);
WriteOutput(kOutputFile, kMultipleFiles);
const std::string produced_output_1 = ReadOutput(kOutputToRead1);
const std::string produced_output_2 = ReadOutput(kOutputToRead2);
const std::string expected_output_1 = ReadOutput(kOutputToExpect1);
const std::string expected_output_2 = ReadOutput(kOutputToExpect2);
ASSERT_STREQ(produced_output_1.c_str(), expected_output_1.c_str());
ASSERT_STREQ(produced_output_2.c_str(), expected_output_2.c_str());
}
// One file evaluating to yaml stream format
TEST_F(JsonnetTest, Yaml_stream) {
constexpr char kInputFile[] = "yaml_stream_example.jsonnet";
constexpr char kOutputFile[] = "yaml_stream_example.yaml";
constexpr char kOutputToRead[] = "tests_output/yaml_stream_example.yaml";
constexpr char kOutputToExpect[] =
"tests_expected_output/yaml_stream_example.yaml";
ReadInput(kInputFile);
EvaluateJsonnetCode(kYamlStream, true);
WriteOutput(kOutputFile, kYamlStream);
const std::string produced_output = ReadOutput(kOutputToRead);
const std::string expected_output = ReadOutput(kOutputToExpect);
ASSERT_STREQ(produced_output.c_str(), expected_output.c_str());
}
// One file depended on some other files not accessible by the sandbox
TEST_F(JsonnetTest, Bad_evaluation) {
constexpr char kInputFile[] = "imports.jsonnet";
ReadInput(kInputFile);
EvaluateJsonnetCode(kBase, false);
}
} // namespace

View File

@ -0,0 +1,160 @@
// Copyright 2020 Google LLC
//
// 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 "jsonnet_tests.h" // NOLINT(build/include)
// Prepares what is needed to perform a test.
void JsonnetTestHelper::TestSetUp() {
// Get paths to where input and output is stored.
char buffer[256];
int error = readlink("/proc/self/exe", buffer, 256);
ASSERT_GE(error, 0);
std::pair<absl::string_view, absl::string_view> parts_of_path =
sandbox2::file::SplitPath(buffer);
absl::string_view binary_path = parts_of_path.first;
std::string input_path =
sandbox2::file::JoinPath(binary_path, "tests_input", "dummy_input");
std::string output_path =
sandbox2::file::JoinPath(binary_path, "tests_output", "dummy_input");
// Set up sandbox and api.
sandbox_ = absl::make_unique<JsonnetBaseSandbox>(input_path, output_path);
ASSERT_THAT(sandbox_->Init(), sapi::IsOk());
api_ = absl::make_unique<JsonnetApi>(sandbox_.get());
// Initialize library's main structure.
SAPI_ASSERT_OK_AND_ASSIGN(JsonnetVm * vm_ptr, api_->c_jsonnet_make());
vm_ = absl::make_unique<sapi::v::RemotePtr>(vm_ptr);
jsonnet_vm_was_used_ = false;
input_was_read_ = false;
}
// Cleans up after a test.
void JsonnetTestHelper::TestTearDown() {
if (jsonnet_vm_was_used_) {
SAPI_ASSERT_OK_AND_ASSIGN(char* result,
api_->c_jsonnet_realloc(vm_.get(), output_.get(), 0));
}
ASSERT_THAT(api_->c_jsonnet_destroy(vm_.get()), sapi::IsOk());
if (input_was_read_) {
ASSERT_THAT(api_->c_free_input(input_.get()), sapi::IsOk());
}
}
// Reads input from file.
void JsonnetTestHelper::ReadInput(const char* filename) {
std::string in_file_in_sandboxee(std::string("/input/") +
basename(const_cast<char*>(&filename[0])));
input_filename_in_sandboxee_ = std::move(in_file_in_sandboxee);
sapi::v::ConstCStr in_file_var(input_filename_in_sandboxee_.c_str());
SAPI_ASSERT_OK_AND_ASSIGN(char* input_ptr,
api_->c_read_input(0, in_file_var.PtrBefore()));
input_ = absl::make_unique<sapi::v::RemotePtr>(input_ptr);
input_was_read_ = true;
}
// Evaluates jsonnet code.
void JsonnetTestHelper::Evaluate_jsonnet_code(Evaluation type,
bool expected_correct) {
sapi::v::ConstCStr in_file_var(input_filename_in_sandboxee_.c_str());
sapi::v::Int error;
char* output_ptr;
switch (type) {
case kBase: {
SAPI_ASSERT_OK_AND_ASSIGN(output_ptr, api_->c_jsonnet_evaluate_snippet(
vm_.get(), in_file_var.PtrBefore(),
input_.get(), error.PtrAfter()));
break;
}
case kMultipleFiles: {
SAPI_ASSERT_OK_AND_ASSIGN(output_ptr, api_->c_jsonnet_evaluate_snippet_multi(
vm_.get(), in_file_var.PtrBefore(),
input_.get(), error.PtrAfter()));
break;
}
case kYamlStream: {
SAPI_ASSERT_OK_AND_ASSIGN(output_ptr, api_->c_jsonnet_evaluate_snippet_stream(
vm_.get(), in_file_var.PtrBefore(),
input_.get(), error.PtrAfter()));
break;
}
}
if (expected_correct) {
ASSERT_THAT(error.GetValue(), testing::Eq(0));
} else {
ASSERT_THAT(error.GetValue(), testing::Eq(1));
}
output_ = absl::make_unique<sapi::v::RemotePtr>(output_ptr);
jsonnet_vm_was_used_ = true;
}
// Writes output to file.
void JsonnetTestHelper::WriteOutput(const char* filename_or_directory,
Evaluation type) {
bool success;
switch (type) {
case kBase: {
std::string out_file_in_sandboxee(
std::string("/output/") +
basename(const_cast<char*>(&filename_or_directory[0])));
sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str());
SAPI_ASSERT_OK_AND_ASSIGN(
success,
api_->c_write_output_file(output_.get(), out_file_var.PtrBefore()));
break;
}
case kMultipleFiles: {
std::string out_file_in_sandboxee(std::string("/output/"));
sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str());
SAPI_ASSERT_OK_AND_ASSIGN(success,
api_->c_write_multi_output_files(
output_.get(), out_file_var.PtrBefore(), false));
break;
}
case kYamlStream: {
std::string out_file_in_sandboxee(
std::string("/output/") +
basename(const_cast<char*>(&filename_or_directory[0])));
sapi::v::ConstCStr out_file_var(out_file_in_sandboxee.c_str());
SAPI_ASSERT_OK_AND_ASSIGN(
success,
api_->c_write_output_stream(output_.get(), out_file_var.PtrBefore()));
break;
}
}
ASSERT_THAT(success, testing::Eq(true));
}
// Reads the output written to a file by library function / expected output
std::string JsonnetTestHelper::ReadOutput(const char* filename) {
std::ifstream input_stream(filename);
std::string contents((std::istreambuf_iterator<char>(input_stream)),
std::istreambuf_iterator<char>());
return contents;
}

View File

@ -0,0 +1,3 @@
build/
.cache
.vscode

View File

@ -0,0 +1,61 @@
# Copyright 2020 Google LLC
#
# 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.
cmake_minimum_required(VERSION 3.16)
project(libarchive_sapi CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED 17)
# Build SAPI library
#set(SAPI_ROOT "" CACHE PATH "Path to the Sandboxed API source tree")
set(SAPI_ROOT "/usr/local/google/home/amedar/internship/sandboxed-api" CACHE PATH "Path to the Sandboxed API source tree")
include(FetchContent)
FetchContent_Declare(
libarchive
GIT_REPOSITORY https://github.com/libarchive/libarchive
PATCH_COMMAND cd libarchive && patch < ${CMAKE_SOURCE_DIR}/patches/header.patch && patch < ${CMAKE_SOURCE_DIR}/patches/archive_virtual.patch
)
FetchContent_MakeAvailable(libarchive)
add_subdirectory("${SAPI_ROOT}"
"${CMAKE_BINARY_DIR}/sandboxed-api-build"
EXCLUDE_FROM_ALL
)
file(STRINGS functions_to_sandbox.txt FUNCTIONS_LIST)
add_sapi_library(
libarchive_sapi
FUNCTIONS ${FUNCTIONS_LIST}
INPUTS
${CMAKE_BINARY_DIR}/_deps/libarchive-src/libarchive/archive.h
${CMAKE_BINARY_DIR}/_deps/libarchive-src/libarchive/archive_entry.h
LIBRARY archive_static
LIBRARY_NAME Libarchive
NAMESPACE ""
)
target_include_directories(libarchive_sapi INTERFACE
"${PROJECT_BINARY_DIR}" # To find the generated SAPI header
)
add_subdirectory(examples)
add_subdirectory(test)

View File

@ -0,0 +1,48 @@
# libarchive Sandboxed API
Sandboxed version of the [libarchive](https://www.libarchive.org/) minitar [example](https://github.com/libarchive/libarchive/blob/master/examples/minitar/minitar.c) using [Sandboxed API](https://github.com/google/sandboxed-api).
## Build
```
mkdir -p build && cd build
cmake .. -G Ninja
cmake --build .
```
The example binary file can be found at **build/examples/sapi_minitar** and the unit tests at **build/test/sapi_minitar_test**.
## Patches
The original libarchive code required patching since one of the custom types produced errors with libclang Python byndings. The patches are applied automatically during the build step and they do not modify the functionality of the library. The repository is also fetched automatically.
## Examples
In this project, the minitar example is sandboxed.
The code is found in the **examples** directory and is structured as follows:
- **sapi_minitar_main.cc** - ***main*** function of the minitar tool. This is mostly similar to the original example.
- **sapi_minitar.h** and **sapi_minitar.cc** - The two main functions (***CreateArchive*** and ***ExtractArchive***) and other helper functions.
- **sandbox.h** - Custom security policies, depending on the whether the user creates or extracts an archive.
On top of that, unit tests can be found in the **test/minitar_test.cc** file.
## Usage
The unit tests can be executed with `./build/test/sapi_minitar_test`.
The **sapi_minitar** command line tool can be used in the same way as the original example. It is also similar to the [tar](https://man7.org/linux/man-pages/man1/tar.1.html) command, only with fewer options:
`./build/examples/sapi_minitar -[options] [-f file] [files]`
The available options are:
- *c* - Create archive.
- *x* - Extract archive.
- *t* - Extract archive but only print entries.
- *p* - Preserve.
- *v* - Verbose.
- *j* or *y* - Compress with BZIP2.
- *Z* - Default compression.
- *z* - Compress with GZIP.
If no compression method is chosen (in the case of archive creation) the files will only be stored.

View File

@ -0,0 +1,43 @@
# Copyright 2020 Google LLC
#
# 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.
add_library(sapi_minitar_lib STATIC
sapi_minitar.cc
sapi_minitar.h
sandbox.h
)
target_link_libraries(sapi_minitar_lib PUBLIC
glog::glog
libarchive_sapi
sandbox2::executor
sandbox2::fileops
sandbox2::file_base
sandbox2::util
sandbox2::temp_file
sapi::sapi
)
target_include_directories(sapi_minitar_lib INTERFACE
"${PROJECT_SOURCE_DIR}/examples"
)
add_executable(sapi_minitar
sapi_minitar_main.cc
)
target_link_libraries(sapi_minitar PRIVATE
sapi_minitar_lib
)

View File

@ -0,0 +1,147 @@
// Copyright 2020 Google LLC
//
// 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.
#ifndef SAPI_LIBARCHIVE_EXAMPLES_SANDBOX_H
#define SAPI_LIBARCHIVE_EXAMPLES_SANDBOX_H
#include <asm/unistd_64.h>
#include <linux/fs.h>
#include "libarchive_sapi.sapi.h" // NOLINT(build/include)
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
#include "sandboxed_api/sandbox2/util/fileops.h"
// When creating an archive, we need read permissions on each of the
// file/directory added in the archive. Also, in order to create the archive, we
// map "/output" with the basename of the archive. This way, the program can
// create the file without having access to anything else.
class SapiLibarchiveSandboxCreate : public LibarchiveSandbox {
public:
SapiLibarchiveSandboxCreate(const std::vector<std::string>& files,
absl::string_view archive_path)
: files_(files), archive_path_(archive_path) {}
private:
std::unique_ptr<sandbox2::Policy> ModifyPolicy(
sandbox2::PolicyBuilder*) override {
sandbox2::PolicyBuilder policy =
sandbox2::PolicyBuilder()
.AddDirectoryAt(archive_path_, "/output", false)
.AllowRead()
.AllowWrite()
.AllowOpen()
.AllowSystemMalloc()
.AllowGetIDs()
.AllowSafeFcntl()
.AllowStat()
.AllowExit()
.AllowSyscalls({
__NR_futex,
__NR_lseek,
__NR_close,
__NR_gettid,
__NR_umask,
__NR_utimensat,
__NR_unlink,
__NR_mkdir,
__NR_fstatfs,
__NR_socket,
__NR_connect,
__NR_flistxattr,
__NR_recvmsg,
__NR_getdents64,
})
// Allow ioctl only for FS_IOC_GETFLAGS.
.AddPolicyOnSyscall(__NR_ioctl,
{ARG(1), JEQ(FS_IOC_GETFLAGS, ALLOW)});
// We check whether the entry is a file or a directory.
for (const auto& i : files_) {
struct stat s;
CHECK(stat(i.c_str(), &s) == 0) << "Could not stat " << i;
if (S_ISDIR(s.st_mode)) {
policy = policy.AddDirectory(i);
} else {
policy = policy.AddFile(i);
}
}
return policy.BuildOrDie();
}
const std::vector<std::string> files_;
absl::string_view archive_path_;
};
// When an archive is extracted, the generated files/directories will be placed
// relative to the current working directory. In order to add permissions to
// this we create a temporary directory at every extraction. Then, we change the
// directory of the sandboxed process to that directory and map it to the
// current "real" working directory. This way the contents of the archived will
// pe placed correctly without offering additional permission.
class SapiLibarchiveSandboxExtract : public LibarchiveSandbox {
public:
SapiLibarchiveSandboxExtract(absl::string_view archive_path, int do_extract,
absl::string_view tmp_dir)
: archive_path_(archive_path),
do_extract_(do_extract),
tmp_dir_(tmp_dir) {}
private:
void ModifyExecutor(sandbox2::Executor* executor) override {
// If the user only wants to list the entries in the archive, we do
// not need to worry about changing directories;
if (do_extract_) {
executor->set_cwd(std::string(tmp_dir_));
}
}
std::unique_ptr<sandbox2::Policy> ModifyPolicy(
sandbox2::PolicyBuilder*) override {
sandbox2::PolicyBuilder policy = sandbox2::PolicyBuilder()
.AllowRead()
.AllowWrite()
.AllowOpen()
.AllowSystemMalloc()
.AllowGetIDs()
.AllowSafeFcntl()
.AllowStat()
.AllowExit()
.AllowSyscalls({
__NR_futex,
__NR_lseek,
__NR_close,
__NR_gettid,
__NR_umask,
__NR_utimensat,
__NR_unlink,
__NR_mkdir,
})
.AddFile(archive_path_);
if (do_extract_) {
// Get the real cwd and map it to the temporary directory in which
// the sandboxed process takes place().
std::string cwd = sandbox2::file_util::fileops::GetCWD();
policy = policy.AddDirectoryAt(cwd, tmp_dir_, false);
}
return policy.BuildOrDie();
}
absl::string_view archive_path_;
absl::string_view tmp_dir_;
const int do_extract_;
};
#endif // SAPI_LIBARCHIVE_EXAMPLES_SANDBOX_H

View File

@ -0,0 +1,540 @@
// Copyright 2020 Google LLC
//
// 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 "sapi_minitar.h" // NOLINT(build/include)
#include "absl/status/status.h"
#include "sandboxed_api/sandbox2/util/path.h"
#include "sandboxed_api/util/status_macros.h"
absl::Status CreateArchive(const char* initial_filename, int compress,
const char** argv, bool verbose) {
// We split the filename path into dirname and filename. To the filename we
// prepend "/output/"" so that it will work with the security policy.
std::string abs_path = MakeAbsolutePathAtCWD(std::string(initial_filename));
auto [archive_path, filename_tmp] =
std::move(sandbox2::file::SplitPath(abs_path));
std::string filename = sandbox2::file::JoinPath("/output/", filename_tmp);
std::vector<std::string> absolute_paths;
sandbox2::util::CharPtrArrToVecString(const_cast<char* const*>(argv),
&absolute_paths);
std::vector<std::string> relative_paths = absolute_paths;
std::transform(absolute_paths.begin(), absolute_paths.end(),
absolute_paths.begin(), MakeAbsolutePathAtCWD);
std::transform(relative_paths.begin(), relative_paths.end(),
relative_paths.begin(), sandbox2::file::CleanPath);
// At this point, we have the relative and absolute paths (cleaned) saved
// in vectors.
// Initialize sandbox and api objects.
SapiLibarchiveSandboxCreate sandbox(absolute_paths, archive_path);
SAPI_RETURN_IF_ERROR(sandbox.Init());
LibarchiveApi api(&sandbox);
SAPI_ASSIGN_OR_RETURN(archive * ret_archive, api.archive_write_new());
if (ret_archive == nullptr) {
return absl::FailedPreconditionError("Failed to create write archive");
}
// Treat the pointer as remote. There is no need to copy the data
// to the client process.
sapi::v::RemotePtr a(ret_archive);
int rc;
std::string msg;
// switch (compress) {
// case 'j':
// case 'y':
if (compress == 'j' || compress == 'y') {
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_bzip2(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_add_filter_bzip2 call");
}
// break;
} else if (compress == 'Z') {
// case 'Z':
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_compress(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_add_filter_compress call");
}
// break;
} else if (compress == 'z') {
// case 'z':
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_gzip(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_add_filter_gzip call");
}
// break;
} else {
// default:
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_add_filter_none(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_add_filter_none call");
}
// break;
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_set_format_ustar(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_set_format_ustar call");
}
const char* filename_ptr = filename.data();
if (filename_ptr != nullptr && strcmp(filename_ptr, "-") == 0) {
filename_ptr = nullptr;
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_open_filename(
&a, sapi::v::ConstCStr(filename_ptr).PtrBefore()));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_open_filename call");
}
int file_idx = 0;
// We can directly use the vectors defined before.
for (int file_idx = 0; file_idx < absolute_paths.size(); ++file_idx) {
SAPI_ASSIGN_OR_RETURN(ret_archive, api.archive_read_disk_new());
if (ret_archive == nullptr) {
return absl::FailedPreconditionError(
"Failed to create read_disk archive");
}
sapi::v::RemotePtr disk(ret_archive);
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_disk_set_standard_lookup(&disk));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from read_disk_set_standard_lookup call");
}
// We use the absolute path first.
SAPI_ASSIGN_OR_RETURN(
rc,
api.archive_read_disk_open(
&disk,
sapi::v::ConstCStr(absolute_paths[file_idx].c_str()).PtrBefore()));
if (rc != ARCHIVE_OK) {
SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString(
api.archive_error_string(&disk), sandbox));
return absl::FailedPreconditionError(msg);
}
while (true) {
archive_entry* ret_archive_entry;
SAPI_ASSIGN_OR_RETURN(ret_archive_entry, api.archive_entry_new());
if (ret_archive_entry == nullptr) {
return absl::FailedPreconditionError("Failed to create archive_entry");
}
sapi::v::RemotePtr entry(ret_archive_entry);
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_next_header2(&disk, &entry));
if (rc == ARCHIVE_EOF) {
break;
}
if (rc != ARCHIVE_OK) {
SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString(
api.archive_error_string(&disk), sandbox));
return absl::FailedPreconditionError(msg);
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_disk_descend(&disk));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError("read_disk_descend call failed");
}
// After using the absolute path before, we now need to add the pathname
// to the archive entry. This would help store the files by their relative
// paths(similar to the usual tar command).
// However, in the case where a directory is added to the archive,
// all of the files inside of it are added as well so we replace the
// absolute path prefix with the relative one.
// Example:
// we add the folder "test_files" which becomes
// "/absolute/path/test_files" and the files inside of it will become
// similar to "/absolute/path/test_files/file1"
// which we then change to "test_files/file1" so that it is relative.
SAPI_ASSIGN_OR_RETURN(
std::string path_name,
CheckStatusAndGetString(api.archive_entry_pathname(&entry), sandbox));
path_name.replace(path_name.begin(),
path_name.begin() + absolute_paths[file_idx].length(),
relative_paths[file_idx]);
// On top of those changes, we need to remove leading '/' characters
// and also remove everything up to the last occurrence of '../'.
size_t found = path_name.find_first_not_of("/");
if (found != std::string::npos) {
path_name.erase(path_name.begin(), path_name.begin() + found);
}
// Search either for the last '/../' or check if
// the path has '../' in the beginning.
found = path_name.rfind("/../");
if (found != std::string::npos) {
path_name = path_name.substr(found + 4);
} else if (path_name.substr(0, 3) == "../") {
path_name = path_name.substr(3);
}
SAPI_RETURN_IF_ERROR(api.archive_entry_set_pathname(
&entry, sapi::v::ConstCStr(path_name.c_str()).PtrBefore()));
if (verbose) {
SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString(
api.archive_entry_pathname(&entry), sandbox));
std::cout << msg << std::endl;
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_header(&a, &entry));
if (rc < ARCHIVE_OK) {
SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString(
api.archive_error_string(&a), sandbox));
std::cout << msg << std::endl;
}
if (rc == ARCHIVE_FATAL) {
return absl::FailedPreconditionError(
"Unexpected result from write_header call");
}
// In the following section, the calls (read, archive_write_data) are done
// on the sandboxed process since we do not need to transfer the data in
// the client process.
if (rc > ARCHIVE_FAILED) {
SAPI_ASSIGN_OR_RETURN(
msg, CheckStatusAndGetString(api.archive_entry_sourcepath(&entry),
sandbox));
int fd = open(msg.c_str(), O_RDONLY);
if (fd < 0) {
return absl::FailedPreconditionError("Could not open file");
}
sapi::v::Fd sapi_fd(fd);
sapi::v::Int read_ret;
sapi::v::Array<char> buff(kBuffSize);
sapi::v::UInt ssize(kBuffSize);
// We allocate the buffer remotely and then we can simply use the
// remote pointer(with PtrNone).
// This allows us to keep the data in the remote process without always
// transferring the memory.
SAPI_RETURN_IF_ERROR(sandbox.Allocate(&buff, true));
// We can use sapi methods that help us with file descriptors.
SAPI_RETURN_IF_ERROR(sandbox.TransferToSandboxee(&sapi_fd));
SAPI_RETURN_IF_ERROR(
sandbox.Call("read", &read_ret, &sapi_fd, buff.PtrNone(), &ssize));
while (read_ret.GetValue() > 0) {
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_data(&a, buff.PtrNone(),
read_ret.GetValue()));
SAPI_RETURN_IF_ERROR(sandbox.Call("read", &read_ret, &sapi_fd,
buff.PtrNone(), &ssize));
}
// sapi_fd variable goes out of scope here so both the local and the
// remote file descriptors are closed.
}
SAPI_RETURN_IF_ERROR(api.archive_entry_free(&entry));
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_close(&disk));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from read_close call");
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_free(&disk));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from read_free call");
}
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_close(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_close call");
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_free(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_free call");
}
return absl::OkStatus();
}
absl::Status ExtractArchive(const char* filename, int do_extract, int flags,
bool verbose) {
std::string tmp_dir;
if (do_extract) {
SAPI_ASSIGN_OR_RETURN(tmp_dir, CreateTempDirAtCWD());
}
// We can use a struct like this in order to delete the temporary
// directory that was created earlier whenever the function ends.
struct ExtractTempDirectoryCleanup {
ExtractTempDirectoryCleanup(const std::string& dir) : dir_(dir) {}
~ExtractTempDirectoryCleanup() {
sandbox2::file_util::fileops::DeleteRecursively(dir_);
}
private:
std::string dir_;
};
// We should only delete it if the do_extract flag is true which
// means that this struct is instantiated only in that case.
auto cleanup_ptr =
do_extract ? absl::make_unique<ExtractTempDirectoryCleanup>(tmp_dir)
: nullptr;
std::string filename_absolute = MakeAbsolutePathAtCWD(filename);
// Initialize sandbox and api objects.
SapiLibarchiveSandboxExtract sandbox(filename_absolute, do_extract, tmp_dir);
SAPI_RETURN_IF_ERROR(sandbox.Init());
LibarchiveApi api(&sandbox);
SAPI_ASSIGN_OR_RETURN(archive * ret_archive, api.archive_read_new());
if (ret_archive == nullptr) {
return absl::FailedPreconditionError("Failed to create read archive");
}
sapi::v::RemotePtr a(ret_archive);
SAPI_ASSIGN_OR_RETURN(ret_archive, api.archive_write_disk_new());
if (ret_archive == nullptr) {
return absl::FailedPreconditionError("Failed to create write disk archive");
}
sapi::v::RemotePtr ext(ret_archive);
int rc;
std::string msg;
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_disk_set_options(&ext, flags));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_disk_set_options call");
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_filter_bzip2(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from read_support_filter_bzip2 call");
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_filter_gzip(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from read_suppport_filter_gzip call");
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_filter_compress(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from read_support_filter_compress call");
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_format_tar(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result fromread_support_format_tar call");
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_support_format_cpio(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from read_support_format_tar call");
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_disk_set_standard_lookup(&ext));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_disk_set_standard_lookup call");
}
const char* filename_ptr = filename_absolute.c_str();
if (filename_ptr != nullptr && strcmp(filename_ptr, "-") == 0) {
filename_ptr = nullptr;
}
// The entries are saved with a relative path so they are all created
// relative to the current working directory.
SAPI_ASSIGN_OR_RETURN(
rc, api.archive_read_open_filename(
&a, sapi::v::ConstCStr(filename_ptr).PtrBefore(), kBlockSize));
if (rc != ARCHIVE_OK) {
SAPI_ASSIGN_OR_RETURN(
msg, CheckStatusAndGetString(api.archive_error_string(&a), sandbox));
return absl::FailedPreconditionError(msg);
}
while (true) {
sapi::v::IntBase<archive_entry*> entry_ptr_tmp(0);
SAPI_ASSIGN_OR_RETURN(
rc, api.archive_read_next_header(&a, entry_ptr_tmp.PtrAfter()));
if (rc == ARCHIVE_EOF) {
break;
}
if (rc != ARCHIVE_OK) {
SAPI_ASSIGN_OR_RETURN(
msg, CheckStatusAndGetString(api.archive_error_string(&a), sandbox));
return absl::FailedPreconditionError(msg);
}
sapi::v::RemotePtr entry(entry_ptr_tmp.GetValue());
if (verbose && do_extract) {
std::cout << "x ";
}
if (verbose || !do_extract) {
SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString(
api.archive_entry_pathname(&entry), sandbox));
std::cout << msg << std::endl;
}
if (do_extract) {
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_header(&ext, &entry));
if (rc != ARCHIVE_OK) {
SAPI_ASSIGN_OR_RETURN(msg, CheckStatusAndGetString(
api.archive_error_string(&a), sandbox));
std::cout << msg << std::endl;
} else {
SAPI_ASSIGN_OR_RETURN(rc, CopyData(&a, &ext, api, sandbox));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Failed to copy data between archive structs.");
}
}
}
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_close(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected value from read_close call");
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_read_free(&a));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from read_free call");
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_close(&ext));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_close call");
}
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_free(&ext));
if (rc != ARCHIVE_OK) {
return absl::FailedPreconditionError(
"Unexpected result from write_free call");
}
return absl::OkStatus();
}
absl::StatusOr<int> CopyData(sapi::v::RemotePtr* ar, sapi::v::RemotePtr* aw,
LibarchiveApi& api,
SapiLibarchiveSandboxExtract& sandbox) {
int rc;
std::string msg;
sapi::v::IntBase<archive_entry*> buff_ptr_tmp(0);
sapi::v::ULLong size;
sapi::v::SLLong offset;
while (true) {
SAPI_ASSIGN_OR_RETURN(
rc, api.archive_read_data_block(ar, buff_ptr_tmp.PtrAfter(),
size.PtrAfter(), offset.PtrAfter()));
if (rc == ARCHIVE_EOF) {
return ARCHIVE_OK;
}
if (rc != ARCHIVE_OK) {
SAPI_ASSIGN_OR_RETURN(
msg, CheckStatusAndGetString(api.archive_error_string(ar), sandbox));
std::cout << msg << std::endl;
return rc;
}
sapi::v::RemotePtr buff(buff_ptr_tmp.GetValue());
SAPI_ASSIGN_OR_RETURN(rc, api.archive_write_data_block(
aw, &buff, size.GetValue(), offset.GetValue()));
if (rc != ARCHIVE_OK) {
SAPI_ASSIGN_OR_RETURN(
msg, CheckStatusAndGetString(api.archive_error_string(ar), sandbox));
std::cout << msg << std::endl;
return rc;
}
}
}
std::string MakeAbsolutePathAtCWD(const std::string& path) {
std::string result = sandbox2::file_util::fileops::MakeAbsolute(
path, sandbox2::file_util::fileops::GetCWD());
CHECK(result != "") << "Could not create absolute path for: " << path;
return sandbox2::file::CleanPath(result);
}
absl::StatusOr<std::string> CheckStatusAndGetString(
const absl::StatusOr<char*>& status, LibarchiveSandbox& sandbox) {
SAPI_ASSIGN_OR_RETURN(char* str, status);
if (str == nullptr) {
return absl::FailedPreconditionError("Could not get string from archive");
}
return sandbox.GetCString(sapi::v::RemotePtr(str));
}
absl::StatusOr<std::string> CreateTempDirAtCWD() {
std::string cwd = sandbox2::file_util::fileops::GetCWD();
CHECK(!cwd.empty()) << "Could not get current working directory";
cwd.append("/");
SAPI_ASSIGN_OR_RETURN(std::string result, sandbox2::CreateTempDir(cwd));
return result;
}

View File

@ -0,0 +1,63 @@
// Copyright 2020 Google LLC
//
// 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.
#ifndef SAPI_LIBARCHIVE_EXAMPLES_MINITAR_H
#define SAPI_LIBARCHIVE_EXAMPLES_MINITAR_H
#include <archive.h>
#include <archive_entry.h>
#include <fcntl.h>
#include "libarchive_sapi.sapi.h" // NOLINT(build/include)
#include "sandbox.h" // NOLINT(build/include)
#include "sandboxed_api/sandbox2/util.h"
#include "sandboxed_api/sandbox2/util/path.h"
#include "sandboxed_api/sandbox2/util/temp_file.h"
// Creates an archive file at the given filename.
absl::Status CreateArchive(const char* filename, int compress,
const char** argv, bool verbose = true);
// Extracts an archive file. If do_extract is true, the files will
// be created relative to the current working directory. If do_extract
// is false then the function will just print the entries of the archive.
absl::Status ExtractArchive(const char* filename, int do_extract, int flags,
bool verbose = true);
// This function is only called from the "extract function". It is still
// isolated in order to not modify the code structure as much.
absl::StatusOr<int> CopyData(sapi::v::RemotePtr* ar, sapi::v::RemotePtr* aw,
LibarchiveApi& api,
SapiLibarchiveSandboxExtract& sandbox);
inline constexpr size_t kBlockSize = 10240;
inline constexpr size_t kBuffSize = 16384;
// Converts one string to an absolute path by prepending the current
// working directory to the relative path.
// The path is also cleaned at the end.
std::string MakeAbsolutePathAtCWD(const std::string& path);
// This function takes a status as argument and after checking the status
// it transfers the string. This is used mostly with archive_error_string
// and other library functions that return a char*.
absl::StatusOr<std::string> CheckStatusAndGetString(
const absl::StatusOr<char*>& status, LibarchiveSandbox& sandbox);
// Creates a temporary directory in the current working directory and
// returns the path. This is used in the extract function where the sandboxed
// process changes the current working directory to this temporary directory.
absl::StatusOr<std::string> CreateTempDirAtCWD();
#endif // SAPI_LIBARCHIVE_EXAMPLES_MINITAR_H

View File

@ -0,0 +1,130 @@
// Copyright 2020 Google LLC
//
// 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.
// This file contains the main function from the original minitar example:
// https://github.com/libarchive/libarchive/blob/master/examples/minitar/minitar.c
// Most of the logic is the same, it was only simplified a bit since this is
// only used for the command line tool.
// No sandboxing takes place in this function.
#include <iostream>
#include "sapi_minitar.h" // NOLINT(build/include)
static void PrintUsage() {
/* Many program options depend on compile options. */
const char* m =
"Usage: minitar [-"
"c"
"j"
"tvx"
"y"
"Z"
"z"
"] [-f file] [file]\n";
std::cout << m << std::endl;
exit(EXIT_FAILURE);
}
int main(int unused_argc, const char** argv) {
google::InitGoogleLogging(argv[0]);
const char* filename = nullptr;
int compress;
int flags;
int mode;
int opt;
mode = 'x';
int verbose = 0;
compress = '\0';
flags = ARCHIVE_EXTRACT_TIME;
while (*++argv != nullptr && **argv == '-') {
const char* p = *argv + 1;
while ((opt = *p++) != '\0') {
switch (opt) {
case 'c':
mode = opt;
break;
case 'f':
if (*p != '\0')
filename = p;
else
filename = *++argv;
p += strlen(p);
break;
case 'j':
compress = opt;
break;
case 'p':
flags |= ARCHIVE_EXTRACT_PERM;
flags |= ARCHIVE_EXTRACT_ACL;
flags |= ARCHIVE_EXTRACT_FFLAGS;
break;
case 't':
mode = opt;
break;
case 'v':
verbose++;
break;
case 'x':
mode = opt;
break;
case 'y':
compress = opt;
break;
case 'Z':
compress = opt;
break;
case 'z':
compress = opt;
break;
default:
PrintUsage();
}
}
}
absl::Status status;
switch (mode) {
case 'c':
status = CreateArchive(filename, compress, argv, verbose);
if (!status.ok()) {
LOG(ERROR) << "Archive creation failed with message: "
<< status.message();
return EXIT_FAILURE;
}
break;
case 't':
status = ExtractArchive(filename, 0, flags, verbose);
if (!status.ok()) {
LOG(ERROR) << "Archive extraction failed with message: "
<< status.message();
return EXIT_FAILURE;
}
break;
case 'x':
status = ExtractArchive(filename, 1, flags, verbose);
if (!status.ok()) {
LOG(ERROR) << "Archive extraction failed with message: "
<< status.message();
return EXIT_FAILURE;
}
break;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,37 @@
archive_entry_free
archive_entry_new
archive_entry_pathname
archive_entry_sourcepath
archive_error_string
archive_read_close
archive_read_data_block
archive_read_disk_descend
archive_read_disk_new
archive_read_disk_open
archive_read_disk_set_standard_lookup
archive_read_free
archive_read_new
archive_read_next_header
archive_read_next_header2
archive_read_open_filename
archive_read_support_filter_bzip2
archive_read_support_filter_compress
archive_read_support_filter_gzip
archive_read_support_format_cpio
archive_read_support_format_tar
archive_write_add_filter_bzip2
archive_write_add_filter_compress
archive_write_add_filter_gzip
archive_write_add_filter_none
archive_write_close
archive_write_data
archive_write_data_block
archive_write_disk_new
archive_write_disk_set_options
archive_write_disk_set_standard_lookup
archive_write_free
archive_write_header
archive_write_new
archive_write_open_filename
archive_write_set_format_ustar
archive_entry_set_pathname

View File

@ -0,0 +1,18 @@
--- archive_virtual.c 2020-09-11 16:39:07.158014139 +0000
+++ archive_virtual2.c 2020-09-11 16:39:50.842107856 +0000
@@ -124,13 +124,13 @@
return ((a->vtable->archive_write_finish_entry)(a));
}
-la_ssize_t
+int
archive_write_data(struct archive *a, const void *buff, size_t s)
{
return ((a->vtable->archive_write_data)(a, buff, s));
}
-la_ssize_t
+int
archive_write_data_block(struct archive *a, const void *buff, size_t s,
la_int64_t o)
{

View File

@ -0,0 +1,16 @@
--- archive.h 2020-09-11 14:23:21.758842500 +0000
+++ archive2.h 2020-09-11 14:20:27.310494460 +0000
@@ -840,11 +840,11 @@
*/
__LA_DECL int archive_write_header(struct archive *,
struct archive_entry *);
-__LA_DECL la_ssize_t archive_write_data(struct archive *,
+__LA_DECL int archive_write_data(struct archive *,
const void *, size_t);
/* This interface is currently only available for archive_write_disk handles. */
-__LA_DECL la_ssize_t archive_write_data_block(struct archive *,
+__LA_DECL int archive_write_data_block(struct archive *,
const void *, size_t, la_int64_t);
__LA_DECL int archive_write_finish_entry(struct archive *);

View File

@ -0,0 +1,29 @@
# Copyright 2020 Google LLC
#
# 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(GoogleTest)
enable_testing()
add_executable(sapi_minitar_test
minitar_test.cc
)
target_link_libraries(sapi_minitar_test PRIVATE
sapi_minitar_lib
gtest
sapi::test_main
)
gtest_discover_tests(sapi_minitar_test)

View File

@ -0,0 +1,309 @@
// Copyright 2020 Google LLC
//
// 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 <fstream>
#include "sapi_minitar.h" // NOLINT(build/include)
#include "gtest/gtest.h"
#include "sandboxed_api/sandbox2/util/path.h"
#include "sandboxed_api/util/status_matchers.h"
using ::sandbox2::file::JoinPath;
using ::sapi::IsOk;
using ::testing::Eq;
using ::testing::IsTrue;
using ::testing::StrEq;
using ::sandbox2::file_util::fileops::Exists;
using ::sandbox2::util::VecStringToCharPtrArr;
namespace {
// We will use a fixture class for testing which allows us to override the
// SetUp and TearDown functions. Also, data that needs to be initialized
// or destroyed only once (the test files and directories) will be handled
// in the SetUpTestSuite and TearDownTestSuite functions which are executed
// only once.
// All of the testing data will be placed in a temporary directory and each
// test will have it's own temporary directory. At the end of each test
// and all of the tests, the temporary data is deleted.
class MiniTarTest : public ::testing::Test {
protected:
// Before running the tests, we create a temporary directory which will
// store generated files and directories used for testing.
// The directory will look as follows:
// -file1
// -dir1 - file2
// - dir2 - file3
static void SetUpTestSuite() {
absl::StatusOr<std::string> tmp_status = CreateTempDirAtCWD();
ASSERT_THAT(tmp_status, IsOk());
data_dir_ = new std::string(std::move(tmp_status).value());
init_wd_ = new std::string(sandbox2::file_util::fileops::GetCWD());
ASSERT_THAT(Exists(data_dir_, false), IsTrue())
<< "Test data directory was not created";
ASSERT_THAT(chdir(data_dir_.data()), Eq(0))
<< "Could not chdir into test data directory";
CreateAndWriteToFile(kFile1);
ASSERT_THAT(mkdir(kDir1.data(), 0755), Eq(0)) << "Could not create dir1";
CreateAndWriteToFile(kFile2);
ASSERT_THAT(mkdir(kDir2.data(), 0755), Eq(0)) << "Could not create dir2";
CreateAndWriteToFile(kFile3);
test_count_ = 0;
}
static void TearDownTestSuite() {
// The tests have the data directory as their working directory at the end
// so we move to the initial working directory in order to not delete the
// directory that we are inside of.
ASSERT_THAT(chdir(init_wd_->data()), Eq(0))
<< "Could not chdir into initial working directory";
EXPECT_THAT(sandbox2::file_util::fileops::DeleteRecursively(*data_dir_),
IsTrue)
<< "Error during test data deletion";
delete init_wd_;
delete data_dir_;
}
void SetUp() override {
// We use a unique id based on test count to make sure that files created
// during tests do not overlap.
id_ = "test" + std::to_string(test_count_);
absl::StatusOr<std::string> tmp_status = CreateTempDirAtCWD();
ASSERT_THAT(tmp_status, IsOk());
tmp_dir_ = tmp_status.value();
ASSERT_THAT(Exists(tmp_dir_, false), IsTrue)
<< "Could not create test specific temporary directory";
ASSERT_THAT(chdir(data_dir_->data()), Eq(0))
<< "Could not chdir into test data directory";
}
void TearDown() override {
// Move to another directory before deleting the temporary folder.
ASSERT_THAT(chdir(data_dir_->data()), Eq(0))
<< "Could not chdir into test data directory";
EXPECT_THAT(sandbox2::file_util::fileops::DeleteRecursively(tmp_dir_),
IsTrue)
<< "Error during test temporary directory deletion";
++test_count_;
}
// Creates the file specified and writes the same filename.
// This is done in order to not have completely empty files for the
// archiving step.
static void CreateAndWriteToFile(absl::string_view file) {
std::ofstream fin(file.data());
ASSERT_THAT(fin.is_open(), IsTrue()) << "Could not create" << file;
fin << file;
fin.close();
}
// Checks if the files exists and if the contents are correct.
// In these tests, each file contains the relative path from the test
// directory.
// Example: dir1/dir2/file3 will contain dir1/dir2/file3.
// What the files contain does not matter as much, the only important thing
// is that they are not empty so we can check if the contents are preserved.
static void CheckFile(const std::string& file) {
ASSERT_THAT(Exists(file, false), IsTrue()) << "Could not find " << file;
std::ifstream fin(file);
ASSERT_THAT(fin.is_open(), IsTrue()) << "Error when opening " << file;
std::string file_contents((std::istreambuf_iterator<char>(fin)),
std::istreambuf_iterator<char>());
EXPECT_THAT(file_contents, StrEq(file))
<< "Contents of " << file << " are different after extraction";
fin.close();
}
static int test_count_;
static std::string* data_dir_;
static std::string* init_wd_;
std::string tmp_dir_;
std::string id_;
static constexpr absl::string_view kFile1 = "file1";
static constexpr absl::string_view kFile2 = "dir1/file2";
static constexpr absl::string_view kFile3 = "dir1/dir2/file3";
static constexpr absl::string_view kDir1 = "dir1";
static constexpr absl::string_view kDir2 = "dir1/dir2";
};
int MiniTarTest::test_count_;
std::string* MiniTarTest::data_dir_;
std::string* MiniTarTest::init_wd_;
// The tests have the following pattern:
// 1) From inside the test data directory, call the create function with
// different arguments.
// 2) Move to the test specific temporary directory created during the
// set up phase.
// 3) Extract the archive created at step 1.
// 4) Check that the files in the archive have been extracted correctly
// by first checking if they exist and then checking if the content is the
// same as in the original file.
TEST_F(MiniTarTest, TestFileSimple) {
std::vector<std::string> v = {kFile1.data()};
ASSERT_THAT(CreateArchive(id_.data(), 0, VecStringToCharPtrArr(v), false),
IsOk());
ASSERT_THAT(chdir(tmp_dir_.data()), Eq(0))
<< "Could not chdir into test data directory";
ASSERT_THAT(ExtractArchive(JoinPath(*data_dir_, id_).data(), 1, 0, false),
IsOk());
CheckFile(std::string(kFile1));
}
TEST_F(MiniTarTest, TestMultipleFiles) {
std::vector<std::string> v = {kFile1.data(), kFile2.data(), kFile3.data()};
ASSERT_THAT(CreateArchive(id_.data(), 0, VecStringToCharPtrArr(v), false),
IsOk());
ASSERT_THAT(Exists(id_.data(), false), IsTrue())
<< "Archive file was not created";
ASSERT_THAT(chdir(tmp_dir_.data()), Eq(0))
<< "Could not chdir into test data directory";
ASSERT_THAT(ExtractArchive(JoinPath(*data_dir_, id_).data(), 1, 0, false),
IsOk());
CheckFile(std::string(kFile1));
CheckFile(std::string(kFile2));
CheckFile(std::string(kFile3));
}
TEST_F(MiniTarTest, TestDirectorySimple) {
std::vector<std::string> v = {kDir2.data()};
ASSERT_THAT(CreateArchive(id_.data(), 0, VecStringToCharPtrArr(v), false),
IsOk());
ASSERT_THAT(chdir(tmp_dir_.data()), Eq(0))
<< "Could not chdir into test data directory";
ASSERT_THAT(ExtractArchive(JoinPath(*data_dir_, id_).data(), 1, 0, false),
IsOk());
CheckFile(std::string(kFile3));
}
TEST_F(MiniTarTest, TestDirectoryNested) {
std::vector<std::string> v = {kDir1.data()};
ASSERT_THAT(CreateArchive(id_.data(), 0, VecStringToCharPtrArr(v), false),
IsOk());
ASSERT_THAT(chdir(tmp_dir_.data()), Eq(0))
<< "Could not chdir into test data directory";
ASSERT_THAT(ExtractArchive(JoinPath(*data_dir_, id_).data(), 1, 0, false),
IsOk());
CheckFile(std::string(kFile2));
CheckFile(std::string(kFile3));
}
TEST_F(MiniTarTest, TestComplex) {
std::vector<std::string> v = {kFile1.data(), kDir1.data()};
ASSERT_THAT(CreateArchive(id_.data(), 0, VecStringToCharPtrArr(v), false),
IsOk());
ASSERT_THAT(chdir(tmp_dir_.data()), Eq(0))
<< "Could not chdir into test data directory";
ASSERT_THAT(ExtractArchive(JoinPath(*data_dir_, id_).data(), 1, 0, false),
IsOk());
CheckFile(std::string(kFile1));
CheckFile(std::string(kFile2));
CheckFile(std::string(kFile3));
}
TEST_F(MiniTarTest, TestCompress) {
std::vector<std::string> v = {kFile1.data(), kDir1.data()};
int compress = 'Z';
ASSERT_THAT(
CreateArchive(id_.data(), compress, VecStringToCharPtrArr(v), false),
IsOk());
ASSERT_THAT(chdir(tmp_dir_.data()), Eq(0))
<< "Could not chdir into test data directory";
ASSERT_THAT(ExtractArchive(JoinPath(*data_dir_, id_).data(), 1, 0, false),
IsOk());
CheckFile(std::string(kFile1));
CheckFile(std::string(kFile2));
CheckFile(std::string(kFile3));
}
TEST_F(MiniTarTest, TestGZIP) {
std::vector<std::string> v = {kFile1.data(), kDir1.data()};
int compress = 'z';
ASSERT_THAT(
CreateArchive(id_.data(), compress, VecStringToCharPtrArr(v), false),
IsOk());
ASSERT_THAT(chdir(tmp_dir_.data()), Eq(0))
<< "Could not chdir into test data directory";
ASSERT_THAT(ExtractArchive(JoinPath(*data_dir_, id_).data(), 1, 0, false),
IsOk());
CheckFile(std::string(kFile1));
CheckFile(std::string(kFile2));
CheckFile(std::string(kFile3));
}
TEST_F(MiniTarTest, TestBZIP2) {
std::vector<std::string> v = {kFile1.data(), kDir1.data()};
int compress = 'j';
ASSERT_THAT(
CreateArchive(id_.data(), compress, VecStringToCharPtrArr(v), false),
IsOk());
ASSERT_THAT(chdir(tmp_dir_.data()), Eq(0))
<< "Could not chdir into test data directory";
ASSERT_THAT(ExtractArchive(JoinPath(*data_dir_, id_).data(), 1, 0, false),
IsOk());
CheckFile(std::string(kFile1));
CheckFile(std::string(kFile2));
CheckFile(std::string(kFile3));
}
TEST_F(MiniTarTest, TestPaths) {
// These should be equivalent to kFile1 and kDir1 after cleaning.
std::vector<std::string> v = {JoinPath("a/b/../../c/../", kFile1).data(),
JoinPath("d/../e/././///../", kDir1).data()};
ASSERT_THAT(CreateArchive(id_.data(), 0, VecStringToCharPtrArr(v), false),
IsOk());
ASSERT_THAT(chdir(tmp_dir_.data()), Eq(0))
<< "Could not chdir into test data directory";
ASSERT_THAT(ExtractArchive(JoinPath(*data_dir_, id_).data(), 1, 0, false),
IsOk());
CheckFile(std::string(kFile1));
CheckFile(std::string(kFile2));
CheckFile(std::string(kFile3));
}
} // namespace

View File

@ -0,0 +1,3 @@
build/
.clang-format
.cache

View File

@ -0,0 +1,80 @@
# Copyright 2020 Google LLC
#
# 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.
cmake_minimum_required(VERSION 3.16)
project(lodepng_sapi CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED 17)
# Apply the patches to the header file.
add_custom_command(
OUTPUT "${PROJECT_BINARY_DIR}/lodepng/lodepng.h" "${PROJECT_BINARY_DIR}/lodepng/lodepng.cpp"
COMMENT "Applying patch to header file."
COMMAND cp -r "${PROJECT_SOURCE_DIR}/lodepng" "${PROJECT_BINARY_DIR}/"
COMMAND cp "${PROJECT_SOURCE_DIR}/patches/header.patch" "${PROJECT_BINARY_DIR}/lodepng/"
COMMAND cd "${PROJECT_BINARY_DIR}/lodepng" && patch < header.patch
)
# Build static library.
add_library(lodepng STATIC
"${PROJECT_BINARY_DIR}/lodepng/lodepng.cpp"
"${PROJECT_BINARY_DIR}/lodepng/lodepng.h"
)
target_include_directories(lodepng PUBLIC "${PROJECT_BINARY_DIR}/lodepng")
# Build SAPI library
set(SAPI_ROOT "" CACHE PATH "Path to the Sandboxed API source tree")
add_subdirectory("${SAPI_ROOT}"
"${CMAKE_BINARY_DIR}/sandboxed-api-build"
EXCLUDE_FROM_ALL
)
add_sapi_library(
lodepng_sapi
FUNCTIONS
lodepng_decode_memory
lodepng_decode32
lodepng_decode24
lodepng_decode_file
lodepng_decode32_file
lodepng_decode24_file
lodepng_encode_memory
lodepng_encode32
lodepng_encode24
lodepng_encode_file
lodepng_encode32_file
lodepng_encode24_file
lodepng_save_file
lodepng_load_file
INPUTS "${PROJECT_BINARY_DIR}/lodepng/lodepng.h"
LIBRARY lodepng
LIBRARY_NAME Lodepng
NAMESPACE ""
)
target_include_directories(lodepng_sapi INTERFACE
"${PROJECT_BINARY_DIR}" # To find the generated SAPI header
)
add_subdirectory(examples)

View File

@ -0,0 +1,42 @@
# LodePNG Sandboxed API
Sandboxed version of the [LodePNG](https://github.com/lvandeve/lodepng) library, using [Sandboxed API](https://github.com/google/sandboxed-api)
## Details
With Sandboxed API, many of the library's functions can be sandboxed. However, they need the `extern "C"` keyword defined so that name mangling does not happen, which is why a patch that adds it is used. The only differences are found in the header file. An alternative to this is to define another library that wraps every needed function, specifying the required keyword.
Even if many of the functions from the library can be sandboxed, there are some that are not supported (those which have `std::vector` parameters, overloaded functions etc.). If you really need these functions, a solution is to implement a custom library that wraps around these functions in order to make them compatible.
## Patches
In the **patches** folder there is a patch file that adds `extern "C"` to the required functions in the header file in order to sandbox them. This patch is applied automatically during the build phase.
## Build
First, run `git submodule update --init --recursive` to update submodules.
After this, run the following commands:
`mkdir -p build && cd build`
`cmake .. -G Ninja`
`cmake --build .`
The example binary files can be found in `build/examples`.
## Examples
The code found in the **examples** folder features a basic use case of the library. An image is generated, encoded into a file and then decoded to check that the values are the same. The encoding part was based on [this example](https://github.com/lvandeve/lodepng/blob/master/examples/example_encode.c) while decoding was based on [this](https://github.com/lvandeve/lodepng/blob/master/examples/example_decode.c).
This example code is structured as:
- `main_unsandboxed.cc` - unsandboxed example
- `main_sandboxed.cc` - sandboxed version of the example
- `main_unit_test.cc` - tests(using [Google Test](https://github.com/google/googletest)).
On top of those files, there are other files used by all three of the examples:
- `sandbox.h` - custom sandbox policy
- `helpers.h` and `helpers.cc` - constants and functions used in the main files.
The executables generated from these files will create a temporary directory in the current working path. Inside that directory the two generated **png** files will be created. At the end, the directory is deleted. If those programs do not stop midway or return a failure code, then everything works fine.

View File

@ -0,0 +1,70 @@
# Copyright 2020 Google LLC
#
# 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.
# Build the unsandboxed main
add_executable(lodepng_unsandboxed
main_unsandboxed.cc
helpers.cc
)
target_link_libraries(lodepng_unsandboxed PRIVATE
lodepng
sapi::sapi
sandbox2::temp_file
sandbox2::file_base
sandbox2::fileops
glog::glog
)
# Build the sandboxed main
add_executable(lodepng_sandboxed
main_sandboxed.cc
sandbox.h
helpers.cc
)
target_link_libraries(lodepng_sandboxed PRIVATE
lodepng_sapi
sapi::sapi
sandbox2::temp_file
sandbox2::fileops
sapi::vars
sapi::status
glog::glog
)
# Build the unit tests
include(GoogleTest)
enable_testing()
add_executable(main_unit_test
main_unit_test.cc
helpers.cc
)
target_link_libraries(main_unit_test PRIVATE
lodepng_sapi
absl::memory
absl::strings
absl::time
glog::glog
sapi::flags
sapi::sapi
sandbox2::temp_file
sandbox2::fileops
sapi::status
sapi::test_main
sapi::vars
)
gtest_discover_tests(main_unit_test)

View File

@ -0,0 +1,43 @@
// Copyright 2020 Google LLC
//
// 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 "helpers.h" // NOLINT(build/include)
#include "sandboxed_api/sandbox2/util/temp_file.h"
std::vector<uint8_t> GenerateValues() {
std::vector<uint8_t> image;
image.reserve(kImgLen);
for (int y = 0; y < kHeight; ++y) {
for (int x = 0; x < kWidth; ++x) {
image.push_back(255 * !(x & y));
image.push_back(x ^ y);
image.push_back(x | y);
image.push_back(255);
}
}
return image;
}
std::string CreateTempDirAtCWD() {
std::string cwd = sandbox2::file_util::fileops::GetCWD();
CHECK(!cwd.empty()) << "Could not get current working directory";
cwd.append("/");
absl::StatusOr<std::string> result = sandbox2::CreateTempDir(cwd);
CHECK(result.ok()) << "Could not create temporary directory";
return result.value();
}

View File

@ -0,0 +1,39 @@
// Copyright 2020 Google LLC
//
// 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.
#ifndef LODEPNG_EXAMPLES_HELPERS_H_
#define LODEPNG_EXAMPLES_HELPERS_H_
#include <cstddef>
#include <cstdint>
#include <glog/logging.h>
#include "sandboxed_api/sandbox2/util/fileops.h"
#include "sandboxed_api/sandbox2/util/temp_file.h"
inline constexpr size_t kWidth = 512;
inline constexpr size_t kHeight = 512;
inline constexpr size_t kImgLen = kWidth * kHeight * 4;
// Returns a vector that contains values used for testing.
// This part of code is taken from
// https://github.com/lvandeve/lodepng/blob/master/examples/example_encode.c#L96-L104.
// The generated image contains square fractals.
std::vector<uint8_t> GenerateValues();
// Creates a temporary directory in the current working directory and returns
// the path.
std::string CreateTempDirAtCWD();
#endif // LODEPNG_EXAMPLES_HELPERS_H_

View File

@ -0,0 +1,193 @@
// Copyright 2020 Google LLC
//
// 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 <iostream>
#include <glog/logging.h>
#include "helpers.h" // NOLINT(build/include)
#include "sandbox.h" // NOLINT(build/include)
void EncodeDecodeOneStep(SapiLodepngSandbox& sandbox, LodepngApi& api) {
// Generate the values.
std::vector<uint8_t> image = GenerateValues();
// Encode the image.
sapi::v::Array<uint8_t> sapi_image(kImgLen);
CHECK(std::copy(image.begin(), image.end(), sapi_image.GetData()))
<< "Could not copy values";
sapi::v::ConstCStr sapi_filename("/output/out_generated1.png");
absl::StatusOr<unsigned int> result = api.lodepng_encode32_file(
sapi_filename.PtrBefore(), sapi_image.PtrBefore(), kWidth, kHeight);
CHECK(result.ok()) << "encode32_file call failed";
CHECK(!result.value()) << "Unexpected result from encode32_file call";
// After the image has been encoded, decode it to check that the
// pixel values are the same.
sapi::v::UInt sapi_width, sapi_height;
sapi::v::IntBase<uint8_t*> sapi_image_ptr(0);
result = api.lodepng_decode32_file(
sapi_image_ptr.PtrBoth(), sapi_width.PtrBoth(), sapi_height.PtrBoth(),
sapi_filename.PtrBefore());
CHECK(result.ok()) << "decode32_file call failes";
CHECK(!result.value()) << "Unexpected result from decode32_file call";
CHECK(sapi_width.GetValue() == kWidth) << "Widths differ";
CHECK(sapi_height.GetValue() == kHeight) << "Heights differ";
// The pixels have been allocated inside the sandboxed process
// memory, so we need to transfer them to this process.
// Transferring the memory has the following steps:
// 1) define an array with the required length.
// 2) set the remote pointer for the array to specify where the memory
// that will be transferred is located.
// 3) transfer the memory to this process (this step is why we need
// the pointer and the length).
sapi::v::Array<uint8_t> sapi_pixels(kImgLen);
sapi_pixels.SetRemote(sapi_image_ptr.GetValue());
CHECK(sandbox.TransferFromSandboxee(&sapi_pixels).ok())
<< "Error during transfer from sandboxee";
// Now, we can compare the values.
CHECK(absl::equal(image.begin(), image.end(), sapi_pixels.GetData(),
sapi_pixels.GetData() + kImgLen))
<< "Values differ";
// Free the memory allocated inside the sandbox.
CHECK(sandbox.GetRpcChannel()->Free(sapi_image_ptr.GetValue()).ok())
<< "Could not free memory inside sandboxed process";
}
void EncodeDecodeTwoSteps(SapiLodepngSandbox& sandbox, LodepngApi& api) {
// Generate the values.
std::vector<uint8_t> image = GenerateValues();
// Encode the image into memory first.
sapi::v::Array<uint8_t> sapi_image(kImgLen);
CHECK(std::copy(image.begin(), image.end(), sapi_image.GetData()))
<< "Could not copy values";
sapi::v::ConstCStr sapi_filename("/output/out_generated2.png");
sapi::v::ULLong sapi_pngsize;
sapi::v::IntBase<uint8_t*> sapi_png_ptr(0);
// Encode it into memory.
absl::StatusOr<unsigned int> result =
api.lodepng_encode32(sapi_png_ptr.PtrBoth(), sapi_pngsize.PtrBoth(),
sapi_image.PtrBefore(), kWidth, kHeight);
CHECK(result.ok()) << "encode32 call failed";
CHECK(!result.value()) << "Unexpected result from encode32 call";
// The new array (pointed to by sapi_png_ptr) is allocated
// inside the sandboxed process so we need to transfer it to this
// process.
sapi::v::Array<uint8_t> sapi_png_array(sapi_pngsize.GetValue());
sapi_png_array.SetRemote(sapi_png_ptr.GetValue());
CHECK(sandbox.TransferFromSandboxee(&sapi_png_array).ok())
<< "Error during transfer from sandboxee";
// Write the image into the file (from memory).
result =
api.lodepng_save_file(sapi_png_array.PtrBefore(), sapi_pngsize.GetValue(),
sapi_filename.PtrBefore());
CHECK(result.ok()) << "save_file call failed";
CHECK(!result.value()) << "Unexpected result from save_file call";
// Now, decode the image using the 2 steps in order to compare the values.
sapi::v::UInt sapi_width, sapi_height;
sapi::v::IntBase<uint8_t*> sapi_png_ptr2(0);
sapi::v::ULLong sapi_pngsize2;
// Load the file in memory.
result =
api.lodepng_load_file(sapi_png_ptr2.PtrBoth(), sapi_pngsize2.PtrBoth(),
sapi_filename.PtrBefore());
CHECK(result.ok()) << "load_file call failed";
CHECK(!result.value()) << "Unexpected result from load_file call";
CHECK(sapi_pngsize.GetValue() == sapi_pngsize2.GetValue())
<< "Png sizes differ";
// Transfer the png array.
sapi::v::Array<uint8_t> sapi_png_array2(sapi_pngsize2.GetValue());
sapi_png_array2.SetRemote(sapi_png_ptr2.GetValue());
CHECK(sandbox.TransferFromSandboxee(&sapi_png_array2).ok())
<< "Error during transfer from sandboxee";
// After the file is loaded, decode it so we have access to the values
// directly.
sapi::v::IntBase<uint8_t*> sapi_png_ptr3(0);
result = api.lodepng_decode32(
sapi_png_ptr3.PtrBoth(), sapi_width.PtrBoth(), sapi_height.PtrBoth(),
sapi_png_array2.PtrBefore(), sapi_pngsize2.GetValue());
CHECK(result.ok()) << "decode32 call failed";
CHECK(!result.value()) << "Unexpected result from decode32 call";
CHECK(sapi_width.GetValue() == kWidth) << "Widths differ";
CHECK(sapi_height.GetValue() == kHeight) << "Heights differ";
// Transfer the pixels so they can be used here.
sapi::v::Array<uint8_t> sapi_pixels(kImgLen);
sapi_pixels.SetRemote(sapi_png_ptr3.GetValue());
CHECK(sandbox.TransferFromSandboxee(&sapi_pixels).ok())
<< "Error during transfer from sandboxee";
// Compare the values.
CHECK(absl::equal(image.begin(), image.end(), sapi_pixels.GetData(),
sapi_pixels.GetData() + kImgLen))
<< "Values differ";
// Free the memory allocated inside the sandbox.
CHECK(sandbox.GetRpcChannel()->Free(sapi_png_ptr.GetValue()).ok())
<< "Could not free memory inside sandboxed process";
CHECK(sandbox.GetRpcChannel()->Free(sapi_png_ptr2.GetValue()).ok())
<< "Could not free memory inside sandboxed process";
CHECK(sandbox.GetRpcChannel()->Free(sapi_png_ptr3.GetValue()).ok())
<< "Could not free memory inside sandboxed process";
}
int main(int argc, char* argv[]) {
google::InitGoogleLogging(argv[0]);
const std::string images_path = CreateTempDirAtCWD();
CHECK(sandbox2::file_util::fileops::Exists(images_path, false))
<< "Temporary directory does not exist";
SapiLodepngSandbox sandbox(images_path);
CHECK(sandbox.Init().ok()) << "Error during sandbox initialization";
LodepngApi api(&sandbox);
EncodeDecodeOneStep(sandbox, api);
EncodeDecodeTwoSteps(sandbox, api);
if (sandbox2::file_util::fileops::DeleteRecursively(images_path)) {
LOG(WARNING) << "Temporary folder could not be deleted";
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,213 @@
// Copyright 2020 Google LLC
//
// 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 "helpers.h" // NOLINT(build/include)
#include "sandbox.h" // NOLINT(build/include)
#include "gtest/gtest.h"
#include "sandboxed_api/util/status_matchers.h"
using ::sapi::IsOk;
using ::testing::Eq;
using ::testing::IsTrue;
using ::testing::NotNull;
namespace {
TEST(HelpersTest, CreateTempDirAtCWD) {
const std::string images_path = CreateTempDirAtCWD();
ASSERT_THAT(sandbox2::file_util::fileops::Exists(images_path, false),
IsTrue())
<< "Temporary directory does not exist";
EXPECT_THAT(sandbox2::file_util::fileops::DeleteRecursively(images_path),
IsTrue())
<< "Temporary directory could not be deleted";
}
TEST(HelpersTest, GenerateValues) {
EXPECT_THAT(GenerateValues().size(), Eq(kImgLen));
}
TEST(LodePngTest, Init) {
const std::string images_path = CreateTempDirAtCWD();
ASSERT_THAT(sandbox2::file_util::fileops::Exists(images_path, false),
IsTrue())
<< "Temporary directory does not exist";
SapiLodepngSandbox sandbox(images_path);
ASSERT_THAT(sandbox.Init(), IsOk()) << "Error during sandbox init";
EXPECT_THAT(sandbox2::file_util::fileops::DeleteRecursively(images_path),
IsTrue())
<< "Temporary directory could not be deleted";
}
// Generate an image, encode it, decode it and compare the pixels with the
// initial values.
TEST(LodePngTest, EncodeDecodeOneStep) {
const std::string images_path = CreateTempDirAtCWD();
ASSERT_THAT(sandbox2::file_util::fileops::Exists(images_path, false),
IsTrue())
<< "Temporary directory does not exist";
SapiLodepngSandbox sandbox(images_path);
ASSERT_THAT(sandbox.Init(), IsOk()) << "Error during sandbox initialization";
LodepngApi api(&sandbox);
std::vector<uint8_t> image = GenerateValues();
sapi::v::Array<uint8_t> sapi_image(kImgLen);
EXPECT_THAT(std::copy(image.begin(), image.end(), sapi_image.GetData()),
IsTrue())
<< "Could not copy values";
sapi::v::ConstCStr sapi_filename("/output/out_generated1.png");
SAPI_ASSERT_OK_AND_ASSIGN(
unsigned int result,
api.lodepng_encode32_file(sapi_filename.PtrBefore(),
sapi_image.PtrBefore(), kWidth, kHeight));
ASSERT_THAT(result, Eq(0)) << "Unexpected result from encode32_file call";
sapi::v::UInt sapi_width, sapi_height;
sapi::v::IntBase<uint8_t*> sapi_image_ptr(0);
SAPI_ASSERT_OK_AND_ASSIGN(result,
api.lodepng_decode32_file(
sapi_image_ptr.PtrBoth(), sapi_width.PtrBoth(),
sapi_height.PtrBoth(), sapi_filename.PtrBefore()));
ASSERT_THAT(result, Eq(0)) << "Unexpected result from decode32_file call";
EXPECT_THAT(sapi_width.GetValue(), Eq(kWidth)) << "Widths differ";
EXPECT_THAT(sapi_height.GetValue(), Eq(kHeight)) << "Heights differ";
sapi::v::Array<uint8_t> sapi_pixels(kImgLen);
sapi_pixels.SetRemote(sapi_image_ptr.GetValue());
ASSERT_THAT(sandbox.TransferFromSandboxee(&sapi_pixels), IsOk())
<< "Error during transfer from sandboxee";
EXPECT_THAT(absl::equal(image.begin(), image.end(), sapi_pixels.GetData(),
sapi_pixels.GetData() + kImgLen),
IsTrue())
<< "Values differ";
EXPECT_THAT(sandbox.GetRpcChannel()->Free(sapi_image_ptr.GetValue()), IsOk())
<< "Could not free memory inside sandboxed process";
EXPECT_THAT(sandbox2::file_util::fileops::DeleteRecursively(images_path),
IsTrue())
<< "Temporary directory could not be deleted";
}
// Similar to the previous test, only that we use encoding by saving the data in
// memory and then writing it to the file and decoding by first decoding in
// memory and then getting the actual pixel values.
TEST(LodePngTest, EncodeDecodeTwoSteps) {
const std::string images_path = CreateTempDirAtCWD();
ASSERT_THAT(sandbox2::file_util::fileops::Exists(images_path, false),
IsTrue())
<< "Temporary directory does not exist";
SapiLodepngSandbox sandbox(images_path);
ASSERT_THAT(sandbox.Init(), IsOk()) << "Error during sandbox init";
LodepngApi api(&sandbox);
std::vector<uint8_t> image = GenerateValues();
sapi::v::Array<uint8_t> sapi_image(kImgLen);
EXPECT_THAT(std::copy(image.begin(), image.end(), sapi_image.GetData()),
IsTrue())
<< "Could not copy values";
sapi::v::ConstCStr sapi_filename("/output/out_generated2.png");
sapi::v::ULLong sapi_pngsize;
sapi::v::IntBase<uint8_t*> sapi_png_ptr(0);
SAPI_ASSERT_OK_AND_ASSIGN(
unsigned int result,
api.lodepng_encode32(sapi_png_ptr.PtrBoth(), sapi_pngsize.PtrBoth(),
sapi_image.PtrBefore(), kWidth, kHeight));
ASSERT_THAT(result, Eq(0)) << "Unexpected result from encode32 call";
sapi::v::Array<uint8_t> sapi_png_array(sapi_pngsize.GetValue());
sapi_png_array.SetRemote(sapi_png_ptr.GetValue());
ASSERT_THAT(sandbox.TransferFromSandboxee(&sapi_png_array), IsOk())
<< "Error during transfer from sandboxee";
SAPI_ASSERT_OK_AND_ASSIGN(
result,
api.lodepng_save_file(sapi_png_array.PtrBefore(), sapi_pngsize.GetValue(),
sapi_filename.PtrBefore()));
ASSERT_THAT(result, Eq(0)) << "Unexpected result from save_file call";
sapi::v::UInt sapi_width, sapi_height;
sapi::v::IntBase<uint8_t*> sapi_png_ptr2(0);
sapi::v::ULLong sapi_pngsize2;
SAPI_ASSERT_OK_AND_ASSIGN(
result,
api.lodepng_load_file(sapi_png_ptr2.PtrBoth(), sapi_pngsize2.PtrBoth(),
sapi_filename.PtrBefore()));
ASSERT_THAT(result, Eq(0)) << "Unexpected result from load_file call";
EXPECT_THAT(sapi_pngsize.GetValue(), Eq(sapi_pngsize2.GetValue()))
<< "Png sizes differ";
sapi::v::Array<uint8_t> sapi_png_array2(sapi_pngsize2.GetValue());
sapi_png_array2.SetRemote(sapi_png_ptr2.GetValue());
ASSERT_THAT(sandbox.TransferFromSandboxee(&sapi_png_array2), IsOk())
<< "Error during transfer from sandboxee";
sapi::v::IntBase<uint8_t*> sapi_png_ptr3(0);
SAPI_ASSERT_OK_AND_ASSIGN(
result,
api.lodepng_decode32(sapi_png_ptr3.PtrBoth(), sapi_width.PtrBoth(),
sapi_height.PtrBoth(), sapi_png_array2.PtrBefore(),
sapi_pngsize2.GetValue()));
ASSERT_THAT(result, Eq(0)) << "Unexpected result from decode32 call";
EXPECT_THAT(sapi_width.GetValue(), Eq(kWidth)) << "Widths differ";
EXPECT_THAT(sapi_height.GetValue(), Eq(kHeight)) << "Heights differ";
sapi::v::Array<uint8_t> sapi_pixels(kImgLen);
sapi_pixels.SetRemote(sapi_png_ptr3.GetValue());
ASSERT_THAT(sandbox.TransferFromSandboxee(&sapi_pixels), IsOk())
<< "Error during transfer from sandboxee";
EXPECT_THAT(absl::equal(image.begin(), image.end(), sapi_pixels.GetData(),
sapi_pixels.GetData() + kImgLen),
IsTrue())
<< "Values differ";
EXPECT_THAT(sandbox.GetRpcChannel()->Free(sapi_png_ptr.GetValue()), IsOk());
EXPECT_THAT(sandbox.GetRpcChannel()->Free(sapi_png_ptr2.GetValue()), IsOk());
EXPECT_THAT(sandbox.GetRpcChannel()->Free(sapi_png_ptr3.GetValue()), IsOk());
EXPECT_THAT(sandbox2::file_util::fileops::DeleteRecursively(images_path),
IsTrue())
<< "Temporary directory could not be deleted";
}
} // namespace

View File

@ -0,0 +1,116 @@
// Copyright 2020 Google LLC
//
// 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 <iostream>
#include <glog/logging.h>
#include "helpers.h" // NOLINT(build/include)
#include "lodepng.h" // NOLINT(build/include)
#include "sandboxed_api/sandbox2/util/fileops.h"
#include "sandboxed_api/sandbox2/util/path.h"
void EncodeDecodeOneStep(const std::string& images_path) {
// Generate the values.
std::vector<uint8_t> image = GenerateValues();
// Encode the image.
const std::string filename =
sandbox2::file::JoinPath(images_path, "/out_generated1.png");
unsigned int result =
lodepng_encode32_file(filename.c_str(), image.data(), kWidth, kHeight);
CHECK(!result) << "Unexpected result from encode32_file call";
// After the image has been encoded, decode it to check that the
// pixel values are the same.
unsigned int width, height;
uint8_t* image2 = 0;
result = lodepng_decode32_file(&image2, &width, &height, filename.c_str());
CHECK(!result) << "Unexpected result from decode32_file call";
CHECK(width == kWidth) << "Widths differ";
CHECK(height == kHeight) << "Heights differ";
// Now, we can compare the values.
CHECK(absl::equal(image.begin(), image.end(), image2, image2 + kImgLen))
<< "Values differ";
free(image2);
}
void EncodeDecodeTwoSteps(const std::string& images_path) {
// Generate the values.
std::vector<uint8_t> image = GenerateValues();
// Encode the image into memory first.
const std::string filename =
sandbox2::file::JoinPath(images_path, "/out_generated2.png");
uint8_t* png;
size_t pngsize;
unsigned int result =
lodepng_encode32(&png, &pngsize, image.data(), kWidth, kHeight);
CHECK(!result) << "Unexpected result from encode32 call";
// Write the image into the file (from memory).
result = lodepng_save_file(png, pngsize, filename.c_str());
CHECK(!result) << "Unexpected result from save_file call";
// Now, decode the image using the 2 steps in order to compare the values.
unsigned int width, height;
uint8_t* png2;
size_t pngsize2;
// Load the file in memory.
result = lodepng_load_file(&png2, &pngsize2, filename.c_str());
CHECK(!result) << "Unexpected result from load_file call";
CHECK(pngsize == pngsize2) << "Png sizes differ";
uint8_t* image2;
result = lodepng_decode32(&image2, &width, &height, png2, pngsize2);
CHECK(!result) << "Unexpected result from decode32 call";
CHECK(width == kWidth) << "Widths differ";
CHECK(height == kHeight) << "Heights differ";
// Compare the values.
CHECK(absl::equal(image.begin(), image.end(), image2, image2 + kImgLen))
<< "Values differ";
free(png);
free(png2);
free(image2);
}
int main(int argc, char* argv[]) {
google::InitGoogleLogging(argv[0]);
const std::string images_path = CreateTempDirAtCWD();
CHECK(sandbox2::file_util::fileops::Exists(images_path, false))
<< "Temporary directory does not exist";
EncodeDecodeOneStep(images_path);
EncodeDecodeTwoSteps(images_path);
if (sandbox2::file_util::fileops::DeleteRecursively(images_path)) {
LOG(WARNING) << "Temporary folder could not be deleted";
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,49 @@
// Copyright 2020 Google LLC
//
// 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.
#ifndef LODEPNG_EXAMPLES_SANDBOX_H_
#define LODEPNG_EXAMPLES_SANDBOX_H_
#include <syscall.h>
#include "lodepng_sapi.sapi.h" // NOLINT(build/include)
class SapiLodepngSandbox : public LodepngSandbox {
public:
explicit SapiLodepngSandbox(const std::string& images_path)
: images_path_(images_path) {}
private:
std::unique_ptr<sandbox2::Policy> ModifyPolicy(
sandbox2::PolicyBuilder*) override {
return sandbox2::PolicyBuilder()
.AllowRead()
.AllowWrite()
.AllowOpen()
.AllowSystemMalloc()
.AllowExit()
.AllowStat()
.AddDirectoryAt(images_path_, "/output/", /*is_ro=*/false)
.AllowSyscalls({
__NR_futex,
__NR_lseek,
__NR_close,
})
.BuildOrDie();
}
const std::string images_path_;
};
#endif // LODEPNG_EXAMPLES_SANDBOX__

View File

@ -0,0 +1,86 @@
--- lodepng.h 2020-09-11 08:41:22.280259945 +0000
+++ lodepng2.h 2020-09-11 08:45:17.134266042 +0000
@@ -89,6 +89,8 @@
#endif
#endif
+
+
#ifdef LODEPNG_COMPILE_CPP
#include <vector>
#include <string>
@@ -126,6 +128,9 @@
bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types.
Return value: LodePNG error code (0 means no error).
*/
+
+extern "C" {
+
unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h,
const unsigned char* in, size_t insize,
LodePNGColorType colortype, unsigned bitdepth);
@@ -154,10 +159,12 @@
/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image.*/
unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h,
const char* filename);
+
#endif /*LODEPNG_COMPILE_DISK*/
#endif /*LODEPNG_COMPILE_DECODER*/
+
#ifdef LODEPNG_COMPILE_ENCODER
/*
Converts raw pixel data into a PNG image in memory. The colortype and bitdepth
@@ -204,6 +211,9 @@
/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image.*/
unsigned lodepng_encode24_file(const char* filename,
const unsigned char* image, unsigned w, unsigned h);
+
+}
+
#endif /*LODEPNG_COMPILE_DISK*/
#endif /*LODEPNG_COMPILE_ENCODER*/
@@ -219,6 +229,8 @@
unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h,
const std::vector<unsigned char>& in,
LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8);
+
+
#ifdef LODEPNG_COMPILE_DISK
/*
Converts PNG file from disk to raw pixel data in memory.
@@ -251,6 +263,7 @@
unsigned encode(const std::string& filename,
const std::vector<unsigned char>& in, unsigned w, unsigned h,
LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8);
+
#endif /* LODEPNG_COMPILE_DISK */
#endif /* LODEPNG_COMPILE_ENCODER */
} /* namespace lodepng */
@@ -318,6 +331,7 @@
extern const LodePNGCompressSettings lodepng_default_compress_settings;
void lodepng_compress_settings_init(LodePNGCompressSettings* settings);
+
#endif /*LODEPNG_COMPILE_ENCODER*/
#ifdef LODEPNG_COMPILE_PNG
@@ -943,6 +957,8 @@
#endif /*LODEPNG_COMPILE_ZLIB*/
#ifdef LODEPNG_COMPILE_DISK
+
+extern "C" {
/*
Load a file from disk into buffer. The function allocates the out buffer, and
after usage you should free it.
@@ -962,6 +978,7 @@
return value: error code (0 means ok)
*/
unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename);
+}
#endif /*LODEPNG_COMPILE_DISK*/
#ifdef LODEPNG_COMPILE_CPP

@ -1 +0,0 @@
Subproject commit 65c8f577d2f057e80040e98958eae80ca76c6b94

@ -1 +0,0 @@
Subproject commit 74d7261be17cf659d5930d4830609406bd7553e3

View File

@ -201,7 +201,7 @@ absl::Status Sandbox::Init() {
ModifyExecutor(executor.get());
s2_ = absl::make_unique<sandbox2::Sandbox2>(std::move(executor),
std::move(s2p));
std::move(s2p), CreateNotifier());
auto res = s2_->RunAsync();
comms_ = s2_->comms();

View File

@ -141,6 +141,9 @@ class Sandbox {
// Modifies the Executor object if needed.
virtual void ModifyExecutor(sandbox2::Executor* executor) {}
// Provides a custom notifier for sandboxee events. May return nullptr.
virtual std::unique_ptr<sandbox2::Notify> CreateNotifier() { return nullptr; }
// Exits the sandboxee.
void Exit() const;

View File

@ -54,7 +54,7 @@ absl::StatusOr<std::unique_ptr<Buffer>> Buffer::CreateFromFd(int fd) {
// Creates a new Buffer of the specified size, backed by a temporary file that
// will be immediately deleted.
absl::StatusOr<std::unique_ptr<Buffer>> Buffer::CreateWithSize(int64_t size) {
absl::StatusOr<std::unique_ptr<Buffer>> Buffer::CreateWithSize(size_t size) {
int fd;
if (!util::CreateMemFd(&fd)) {
return absl::InternalError("Could not create buffer temp file");

View File

@ -41,7 +41,7 @@ class Buffer final {
// Creates a new Buffer of the specified size, backed by a temporary file that
// will be immediately deleted.
static absl::StatusOr<std::unique_ptr<Buffer>> CreateWithSize(int64_t size);
static absl::StatusOr<std::unique_ptr<Buffer>> CreateWithSize(size_t size);
// Returns a pointer to the buffer, which is read/write.
uint8_t* data() const { return buf_; }

View File

@ -20,8 +20,6 @@
namespace sandbox2 {
const char kForkServerDisableEnv[] = "SANDBOX2_NOFORKSERVER";
pid_t ForkClient::SendRequest(const ForkRequest& request, int exec_fd,
int comms_fd, int user_ns_fd, pid_t* init_pid) {
// Acquire the channel ownership for this request (transaction).

View File

@ -17,34 +17,35 @@
#include <sys/types.h>
#include "absl/base/attributes.h"
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/mutex.h"
namespace sandbox2 {
// Envvar indicating that this process should not start the fork-server.
ABSL_CONST_INIT extern const char kForkServerDisableEnv[];
constexpr inline char kForkServerDisableEnv[] = "SANDBOX2_NOFORKSERVER";
class Comms;
class ForkRequest;
class ForkClient {
public:
explicit ForkClient(Comms* comms) : comms_(comms) {}
ForkClient(const ForkClient&) = delete;
ForkClient& operator=(const ForkClient&) = delete;
explicit ForkClient(Comms* comms) : comms_(comms) {}
// Sends the fork request over the supplied Comms channel.
pid_t SendRequest(const ForkRequest& request, int exec_fd, int comms_fd,
int user_ns_fd = -1, pid_t* init_pid = nullptr);
private:
// Comms channel connecting with the ForkServer. Not owned by the object.
Comms* comms_;
Comms* comms_ ABSL_GUARDED_BY(comms_mutex_);
// Mutex locking transactions (requests) over the Comms channel.
absl::Mutex comms_mutex_;
};
} // namespace sandbox2
#endif // SANDBOXED_API_SANDBOX2_FORK_CLIENT_H_

View File

@ -33,6 +33,7 @@
#include <memory>
#include "absl/base/attributes.h"
#include "absl/status/statusor.h"
#include "absl/strings/escaping.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"

View File

@ -21,6 +21,7 @@
#include "absl/base/internal/endian.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "sandboxed_api/sandbox2/util.h"

View File

@ -59,9 +59,9 @@ bool GeneratorASTVisitor::VisitFunctionDecl(clang::FunctionDecl* decl) {
options_.function_names.count(ToStringView(decl->getName())) > 0)) {
functions_.push_back(decl);
CollectRelatedTypes(decl->getDeclaredReturnType(), &types_);
collector_.CollectRelatedTypes(decl->getDeclaredReturnType());
for (const clang::ParmVarDecl* param : decl->parameters()) {
CollectRelatedTypes(param->getType(), &types_);
collector_.CollectRelatedTypes(param->getType());
}
}
return true;
@ -75,7 +75,7 @@ void GeneratorASTConsumer::HandleTranslationUnit(clang::ASTContext& context) {
"AST traversal exited early");
}
for (clang::QualType qual : visitor_.types_) {
for (clang::QualType qual : visitor_.collector_.collected()) {
emitter_.CollectType(qual);
}
for (clang::FunctionDecl* func : visitor_.functions_) {

View File

@ -65,8 +65,9 @@ class GeneratorASTVisitor
private:
friend class GeneratorASTConsumer;
TypeCollector collector_;
std::vector<clang::FunctionDecl*> functions_;
QualTypeSet types_;
const GeneratorOptions& options_;
};

View File

@ -30,10 +30,15 @@ bool IsFunctionReferenceType(clang::QualType qual) {
} // namespace
void CollectRelatedTypes(clang::QualType qual, QualTypeSet* types) {
void TypeCollector::CollectRelatedTypes(clang::QualType qual) {
if (seen_.contains(qual)) {
return;
}
seen_.insert(qual);
if (const auto* typedef_type = qual->getAs<clang::TypedefType>()) {
CollectRelatedTypes(typedef_type->getDecl()->getUnderlyingType(), types);
types->insert(qual);
CollectRelatedTypes(typedef_type->getDecl()->getUnderlyingType());
collected_.insert(qual);
return;
}
@ -43,26 +48,26 @@ void CollectRelatedTypes(clang::QualType qual, QualTypeSet* types) {
->getAs<clang::FunctionProtoType>()) {
// Note: Do not add the function type itself, as this will always be a
// pointer argument. We only need to collect all its related types.
CollectRelatedTypes(function_type->getReturnType(), types);
CollectRelatedTypes(function_type->getReturnType());
for (const clang::QualType& param : function_type->getParamTypes()) {
CollectRelatedTypes(param, types);
CollectRelatedTypes(param);
}
return;
}
}
if (IsPointerOrReference(qual)) {
clang::QualType pointee = qual->getPointeeType();
while (IsPointerOrReference(pointee)) {
clang::QualType pointee = qual;
do {
pointee = pointee->getPointeeType();
}
CollectRelatedTypes(pointee, types);
} while (IsPointerOrReference(pointee));
CollectRelatedTypes(pointee);
return;
}
// C array with specified constant size (i.e. int a[42])?
if (const clang::ArrayType* array_type = qual->getAsArrayTypeUnsafe()) {
CollectRelatedTypes(array_type->getElementType(), types);
CollectRelatedTypes(array_type->getElementType());
return;
}
@ -71,19 +76,19 @@ void CollectRelatedTypes(clang::QualType qual, QualTypeSet* types) {
// Collect the underlying integer type of enum classes as well, as it may
// be a typedef.
if (const clang::EnumDecl* decl = enum_type->getDecl(); decl->isFixed()) {
CollectRelatedTypes(decl->getIntegerType(), types);
CollectRelatedTypes(decl->getIntegerType());
}
}
types->insert(qual);
collected_.insert(qual);
return;
}
if (const auto* record_type = qual->getAs<clang::RecordType>()) {
const clang::RecordDecl* decl = record_type->getDecl();
for (const clang::FieldDecl* field : decl->fields()) {
CollectRelatedTypes(field->getType(), types);
CollectRelatedTypes(field->getType());
}
types->insert(qual);
collected_.insert(qual);
return;
}
}

View File

@ -41,20 +41,29 @@ inline bool IsPointerOrReference(clang::QualType qual) {
qual->isLValueReferenceType() || qual->isRValueReferenceType();
}
// Computes the transitive closure of all types that a type depends on. Those
// are types that need to be declared before a declaration of the type denoted
// by the qual parameter is valid. For example, given
// struct SubStruct { bool truth_value; };
// struct AggregateStruct {
// int int_member;
// SubStruct struct_member;
// };
//
// calling this function on the type "AggregateStruct" yields these types:
// int
// SubStruct
// bool
void CollectRelatedTypes(clang::QualType qual, QualTypeSet* types);
class TypeCollector {
public:
// Computes the transitive closure of all types that a type depends on. Those
// are types that need to be declared before a declaration of the type denoted
// by the qual parameter is valid. For example, given
// struct SubStruct { bool truth_value; };
// struct AggregateStruct {
// int int_member;
// SubStruct struct_member;
// };
//
// calling this function on the type "AggregateStruct" yields these types:
// int
// SubStruct
// bool
void CollectRelatedTypes(clang::QualType qual);
QualTypeSet& collected() { return collected_; }
private:
QualTypeSet collected_;
QualTypeSet seen_;
};
// Maps a qualified type to a fully qualified SAPI-compatible type name. This
// is used for the generated code that invokes the actual function call RPC.

View File

@ -492,7 +492,6 @@ class Function(object):
self._tu = tu
self.cursor = cursor # type: cindex.Index
self.name = cursor.spelling # type: Text
self.mangled_name = cursor.mangled_name # type: Text
self.result = ReturnType(self, cursor.result_type)
self.original_definition = '{} {}'.format(
cursor.result_type.spelling, self.cursor.displayname) # type: Text
@ -542,7 +541,7 @@ class Function(object):
def is_mangled(self):
# type: () -> bool
return self.name != self.mangled_name
return self.cursor.mangled_name != self.cursor.spelling
def __hash__(self):
# type: () -> int
@ -550,7 +549,7 @@ class Function(object):
def __eq__(self, other):
# type: (Function) -> bool
return self.mangled_name == other.mangled_name
return self.cursor.mangled_name == other.cursor.mangled_name
class _TranslationUnit(object):

View File

@ -832,7 +832,7 @@ class CodeAnalysisTest(parameterized.TestCase):
functions = tu.get_functions()
self.assertLen(functions, 2)
mangled_names = [f.mangled_name for f in functions]
mangled_names = [f.cursor.mangled_name for f in functions]
self.assertSameElements(mangled_names, ['sum', '_Z3sumif'])

View File

@ -23,6 +23,7 @@
#include "absl/base/macros.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "sandboxed_api/rpcchannel.h"
#include "sandboxed_api/var_abstract.h"
#include "sandboxed_api/var_pointable.h"
@ -143,8 +144,9 @@ class Array : public Var, public Pointable {
// buffer is owned by the class, and is mutable.
class CStr : public Array<char> {
public:
explicit CStr(char* cstr) : Array<char>(strlen(cstr) + 1) {
std::copy(cstr, cstr + GetNElem(), GetData());
explicit CStr(absl::string_view cstr) : Array<char>(cstr.size() + 1) {
std::copy(cstr.begin(), cstr.end(), GetData());
GetData()[cstr.size()] = '\0';
}
std::string ToString() const final {