Giới thiệu
Khi sử dụng thư viện C/C++ chưa được hộp cát, trình liên kết sẽ đảm bảo tất cả các hàm cần thiết đều có sẵn sau khi biên dịch, do đó, bạn không cần lo lắng liệu lệnh gọi API có thể không thành công trong thời gian chạy hay không.
Tuy nhiên, khi sử dụng Thư viện trong hộp cát, quá trình thực thi thư viện sẽ nằm trong một quy trình riêng biệt. Lỗi trong lệnh gọi API yêu cầu kiểm tra mọi loại vấn đề liên quan đến việc truyền lệnh gọi qua lớp RPC. Đôi khi, bạn có thể không quan tâm đến các lỗi lớp RPC, chẳng hạn như khi xử lý hàng loạt và hộp cát vừa được khởi động lại.
Tuy nhiên, vì những lý do nêu trên, bạn cần mở rộng quy trình kiểm tra lỗi thông thường đối với giá trị trả về của lệnh gọi API được cách ly để bao gồm cả việc kiểm tra xem có lỗi nào được trả về trên lớp RPC hay không. Đây là lý do khiến tất cả nguyên mẫu hàm thư viện đều trả về ::sapi::StatusOr<T>
thay vì T. Trong trường hợp lệnh gọi hàm thư viện không thành công (ví dụ: do vi phạm hộp cát), giá trị trả về sẽ chứa thông tin chi tiết về lỗi đã xảy ra.
Việc xử lý các lỗi lớp RPC có nghĩa là mỗi lệnh gọi đến Sandboxed
Library đều được theo dõi bằng một bước kiểm tra bổ sung lớp RPC của SAPI. Để xử lý những trường hợp đặc biệt đó, SAPI cung cấp mô-đun Giao dịch SAPI (transaction.h).
Mô-đun này chứa lớp ::sapi::Transaction
và đảm bảo rằng tất cả các lệnh gọi hàm đến Thư viện được cách ly đều đã hoàn tất mà không gặp bất kỳ vấn đề nào ở cấp RPC hoặc trả về một lỗi có liên quan.
Giao dịch SAPI
SAPI tách biệt Mã máy chủ khỏi Thư viện hộp cát và cho phép người gọi khởi động lại hoặc huỷ yêu cầu xử lý dữ liệu có vấn đề. SAPI Transaction tiến thêm một bước nữa và tự động lặp lại các quy trình không thành công.
Bạn có thể sử dụng Giao dịch SAPI theo hai cách: kế thừa trực tiếp từ ::sapi::Transaction
hoặc sử dụng con trỏ hàm được truyền đến ::sapi::BasicTransaction
.
Các giao dịch SAPI được xác định bằng cách ghi đè 3 hàm sau:
Phương thức giao dịch SAPI | |
---|---|
::sapi::Transaction::Init() |
Thao tác này tương tự như việc gọi một phương thức khởi tạo của thư viện C/C++ thông thường. Phương thức này chỉ được gọi một lần trong mỗi giao dịch với Thư viện có môi trường hộp cát, trừ phi giao dịch được khởi động lại. Trong trường hợp khởi động lại, phương thức này sẽ được gọi lại, bất kể trước đó đã có bao nhiêu lần khởi động lại. |
::sapi::Transaction::Main() |
Phương thức này được gọi cho mỗi lệnh gọi đến ::sapi::Transaction::Run() . |
::sapi::Transaction::Finish() |
Thao tác này tương tự như việc gọi một phương thức dọn dẹp của thư viện C/C++ thông thường. Phương thức này chỉ được gọi một lần trong quá trình huỷ đối tượng Giao dịch SAPI. |
Sử dụng thư viện thông thường
Trong một dự án không có thư viện được cách ly, mẫu thông thường khi xử lý các thư viện sẽ có dạng như sau:
LibInit();
while (data = NextDataToProcess()) {
result += LibProcessData(data);
}
LibClose();
Thư viện được khởi chạy, sau đó các hàm đã xuất của thư viện được dùng và cuối cùng, một hàm kết thúc/đóng được gọi để dọn dẹp môi trường.
Sử dụng thư viện trong môi trường hộp cát
Trong một dự án có các thư viện được đưa vào hộp cát, mã từ Normal Library Use (Sử dụng thư viện thông thường) sẽ chuyển thành đoạn mã sau khi sử dụng các giao dịch có lệnh gọi lại:
// Overwrite the Init method, passed to BasicTransaction initialization
::absl::Status Init(::sapi::Sandbox* sandbox) {
// Instantiate the SAPI Object
LibraryAPI lib(sandbox);
// Instantiate the Sandboxed Library
SAPI_RETURN_IF_ERROR(lib.LibInit());
return ::absl::OkStatus();
}
// Overwrite the Finish method, passed to BasicTransaction initialization
::absl::Status Finish(::sapi::Sandbox *sandbox) {
// Instantiate the SAPI Object
LibraryAPI lib(sandbox);
// Clean-up sandboxed library instance
SAPI_RETURN_IF_ERROR(lib.LibClose());
return ::absl::OkStatus();
}
// Wrapper function to process data, passed to Run method call
::absl::Status HandleData(::sapi::Sandbox *sandbox, Data data_to_process,
Result *out) {
// Instantiate the SAPI Object
LibraryAPI lib(sandbox);
// Call the sandboxed function LibProcessData
SAPI_ASSIGN_OR_RETURN(*out, lib.LibProcessData(data_to_process));
return ::absl::OkStatus();
}
void Handle() {
// Use SAPI Transactions by passing function pointers to ::sapi::BasicTransaction
::sapi::BasicTransaction transaction(Init, Finish);
while (data = NextDataToProcess()) {
::sandbox2::Result result;
// call the ::sapi::Transaction::Run() method
transaction.Run(HandleData, data, &result);
// ...
}
// ...
}
Lớp giao dịch đảm bảo khởi động lại thư viện trong trường hợp xảy ra lỗi trong quá trình gọi handle_data
– bạn có thể tìm hiểu thêm về vấn đề này trong phần sau.
Lần khởi động lại giao dịch
Nếu một lệnh gọi API thư viện được cách ly phát sinh lỗi trong quá trình thực thi các phương thức Giao dịch SAPI (xem bảng ở trên), thì giao dịch sẽ được khởi động lại. Số lần khởi động lại mặc định được xác định bằng kDefaultRetryCnt
trong transaction.h.
Sau đây là ví dụ về các lỗi phát sinh sẽ kích hoạt quá trình khởi động lại:
- Đã xảy ra lỗi vi phạm hộp cát
- Quy trình trong hộp cát gặp sự cố
- Một hàm được cách ly đã trả về mã lỗi do lỗi thư viện
Quy trình khởi động lại tuân theo quy trình Init()
và Main()
thông thường, đồng thời nếu các lệnh gọi lặp lại đến phương thức ::sapi::Transaction::Run()
trả về lỗi, thì toàn bộ phương thức sẽ trả về lỗi cho phương thức gọi
Xử lý lỗi RPC hoặc hộp cát
Giao diện Thư viện hộp cát được tạo tự động cố gắng gần với nguyên mẫu hàm thư viện C/C++ ban đầu nhất có thể. Tuy nhiên, Thư viện trong hộp cát cần có khả năng báo hiệu mọi lỗi hộp cát hoặc RPC.
Điều này đạt được bằng cách sử dụng các loại dữ liệu trả về ::sapi::StatusOr<T>
(hoặc ::sapi::Status
cho các hàm trả về void
), thay vì trả về trực tiếp giá trị trả về của các hàm được cách ly.
SAPI cung cấp thêm một số macro thuận tiện để kiểm tra và phản ứng với đối tượng Trạng thái SAPI. Các macro này được xác định trong tệp tiêu đề status_macro.h.
Đoạn mã sau đây là một đoạn trích từ ví dụ về tổng và minh hoạ cách sử dụng Trạng thái SAPI và các macro:
// Instead of void, use ::sapi::Status
::sapi::Status SumTransaction::Main() {
// Instantiate the SAPI Object
SumApi f(sandbox());
// ::sapi::StatusOr<int> sum(int a, int b)
SAPI_ASSIGN_OR_RETURN(int v, f.sum(1000, 337));
// ...
// ::sapi::Status sums(sapi::v::Ptr* params)
SumParams params;
params.mutable_data()->a = 1111;
params.mutable_data()->b = 222;
params.mutable_data()->ret = 0;
SAPI_RETURN_IF_ERROR(f.sums(params.PtrBoth()));
// ...
// Gets symbol address and prints its value
int *ssaddr;
SAPI_RETURN_IF_ERROR(sandbox()->Symbol(
"sumsymbol", reinterpret_cast<void**>(&ssaddr)));
::sapi::v::Int sumsymbol;
sumsymbol.SetRemote(ssaddr);
SAPI_RETURN_IF_ERROR(sandbox()->TransferFromSandboxee(&sumsymbol));
// ...
return ::sapi::OkStatus();
}
Khởi động lại hộp cát
Nhiều thư viện trong hộp cát xử lý dữ liệu đầu vào nhạy cảm của người dùng. Nếu thư viện được cách ly bị hỏng tại một thời điểm nào đó và lưu trữ dữ liệu giữa các lần chạy, thì dữ liệu nhạy cảm này sẽ gặp rủi ro. Ví dụ: nếu một phiên bản hộp cát của thư viện Imagemagick bắt đầu gửi ảnh của lần chạy trước.
Để tránh trường hợp như vậy, bạn không nên sử dụng lại hộp cát cho nhiều lần chạy. Để ngăn việc sử dụng lại hộp cát, Mã máy chủ có thể bắt đầu khởi động lại quy trình thư viện được cách ly bằng ::sapi::Sandbox::Restart()
hoặc ::sapi::Transaction::Restart()
khi sử dụng Giao dịch SAPI.
Việc khởi động lại sẽ làm mất hiệu lực mọi thông tin tham chiếu đến quy trình thư viện được cách ly. Điều này có nghĩa là các chỉ số mô tả tệp đã truyền hoặc bộ nhớ được phân bổ sẽ không còn tồn tại.