Merge "Support dm-verity with verification based on root digest" into main
diff --git a/fs_mgr/libfstab/fstab.cpp b/fs_mgr/libfstab/fstab.cpp
index 60ec40f..6fa22fe 100644
--- a/fs_mgr/libfstab/fstab.cpp
+++ b/fs_mgr/libfstab/fstab.cpp
@@ -720,7 +720,7 @@
if (!ReadFstabFromFileCommon(path, fstab)) {
return false;
}
- if (path != kProcMountsPath) {
+ if (path != kProcMountsPath && !InRecovery()) {
if (!access(android::gsi::kGsiBootedIndicatorFile, F_OK)) {
std::string dsu_slot;
if (!android::gsi::GetActiveDsu(&dsu_slot)) {
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 6b34152..9401c66 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -107,7 +107,7 @@
static constexpr uint8_t kNumResumePoints = 4;
struct CowHeaderV3 : public CowHeader {
- // Number of sequence data stored (each of which is a 32 byte integer)
+ // Number of sequence data stored (each of which is a 32 bit integer)
uint64_t sequence_data_count;
// Number of currently written resume points &&
uint32_t resume_point_count;
@@ -311,6 +311,8 @@
std::ostream& operator<<(std::ostream& os, ResumePoint const& arg);
+std::ostream& operator<<(std::ostream& os, CowOperationType cow_type);
+
int64_t GetNextOpOffset(const CowOperationV2& op, uint32_t cluster_size);
int64_t GetNextDataOffset(const CowOperationV2& op, uint32_t cluster_size);
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
index b0eb723..8d1786c 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
@@ -52,6 +52,10 @@
}
}
+std::ostream& operator<<(std::ostream& os, CowOperationType cow_type) {
+ return EmitCowTypeString(os, cow_type);
+}
+
std::ostream& operator<<(std::ostream& os, CowOperationV2 const& op) {
os << "CowOperationV2(";
EmitCowTypeString(os, op.type) << ", ";
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
index ce68b39..036d335 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
@@ -114,6 +114,12 @@
for (auto op : *ops_) {
if (op.type() == kCowXorOp) {
xor_data_loc_->insert({op.new_block, data_pos});
+ } else if (op.type() == kCowReplaceOp) {
+ if (data_pos != op.source()) {
+ LOG(ERROR) << "Invalid data location for operation " << op
+ << ", expected: " << data_pos;
+ return false;
+ }
}
data_pos += op.data_length;
}
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
index 44b7344..3383a58 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
@@ -1,10 +1,3 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// 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,
@@ -15,6 +8,7 @@
#include <sys/stat.h>
#include <cstdio>
+#include <limits>
#include <memory>
#include <android-base/file.h>
@@ -513,19 +507,24 @@
TEST_F(CowTestV3, SequenceTest) {
CowOptions options;
- options.op_count_max = std::numeric_limits<uint32_t>::max();
+ constexpr int seq_len = std::numeric_limits<uint16_t>::max() / sizeof(uint32_t) + 1;
+ options.op_count_max = seq_len;
auto writer = CreateCowWriter(3, options, GetCowFd());
// sequence data. This just an arbitrary set of integers that specify the merge order. The
// actual calculation is done by update_engine and passed to writer. All we care about here is
// writing that data correctly
- const int seq_len = std::numeric_limits<uint16_t>::max() / sizeof(uint32_t) + 1;
uint32_t sequence[seq_len];
for (int i = 0; i < seq_len; i++) {
sequence[i] = i + 1;
}
ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence));
- ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len));
+ ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len - 1));
+ std::vector<uint8_t> data(writer->GetBlockSize());
+ for (size_t i = 0; i < data.size(); i++) {
+ data[i] = static_cast<uint8_t>(i & 0xFF);
+ }
+ ASSERT_TRUE(writer->AddRawBlocks(seq_len, data.data(), data.size()));
ASSERT_TRUE(writer->Finalize());
ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
@@ -539,6 +538,12 @@
const auto& op = iter->Get();
ASSERT_EQ(op->new_block, seq_len - i);
+ if (op->new_block == seq_len) {
+ std::vector<uint8_t> read_back(writer->GetBlockSize());
+ ASSERT_EQ(reader.ReadData(op, read_back.data(), read_back.size()),
+ static_cast<ssize_t>(read_back.size()));
+ ASSERT_EQ(read_back, data);
+ }
iter->Next();
}
@@ -683,5 +688,15 @@
}
}
+TEST_F(CowTestV3, CheckOpCount) {
+ CowOptions options;
+ options.op_count_max = 20;
+ options.batch_write = true;
+ options.cluster_ops = 200;
+ auto writer = CreateCowWriter(3, options, GetCowFd());
+ ASSERT_TRUE(writer->AddZeroBlocks(0, 19));
+ ASSERT_FALSE(writer->AddZeroBlocks(0, 19));
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index de097f5..d99e6e6 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -71,9 +71,6 @@
header_.block_size = options_.block_size;
header_.num_merge_ops = options_.num_merge_ops;
header_.cluster_ops = 0;
- if (batch_size_ > 1) {
- LOG(INFO) << "Batch writes enabled with batch size of " << batch_size_;
- }
if (options_.scratch_space) {
header_.buffer_size = BUFFER_REGION_DEFAULT_SIZE;
}
@@ -82,6 +79,7 @@
// WIP: not quite sure how some of these are calculated yet, assuming buffer_size is determined
// during COW size estimation
header_.sequence_data_count = 0;
+
header_.resume_point_count = 0;
header_.resume_point_max = kNumResumePoints;
header_.op_count = 0;
@@ -123,6 +121,19 @@
LOG(ERROR) << "Failed to create compressor for " << compression_.algorithm;
return false;
}
+ if (options_.cluster_ops &&
+ (android::base::GetBoolProperty("ro.virtual_ab.batch_writes", false) ||
+ options_.batch_write)) {
+ batch_size_ = std::max<size_t>(options_.cluster_ops, 1);
+ data_vec_.reserve(batch_size_);
+ cached_data_.reserve(batch_size_);
+ cached_ops_.reserve(batch_size_);
+ }
+ }
+ if (batch_size_ > 1) {
+ LOG(INFO) << "Batch writes: enabled with batch size " << batch_size_;
+ } else {
+ LOG(INFO) << "Batch writes: disabled";
}
return true;
}
@@ -216,30 +227,61 @@
return true;
}
+bool CowWriterV3::CheckOpCount(size_t op_count) {
+ if (IsEstimating()) {
+ return true;
+ }
+ if (header_.op_count + cached_ops_.size() + op_count > header_.op_count_max) {
+ LOG(ERROR) << "Current number of ops on disk: " << header_.op_count
+ << ", number of ops cached in memory: " << cached_ops_.size()
+ << ", number of ops attempting to write: " << op_count
+ << ", this will exceed max op count " << header_.op_count_max;
+ return false;
+ }
+ return true;
+}
+
bool CowWriterV3::EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) {
- std::vector<CowOperationV3> ops(num_blocks);
+ if (!CheckOpCount(num_blocks)) {
+ return false;
+ }
for (size_t i = 0; i < num_blocks; i++) {
- CowOperationV3& op = ops[i];
+ CowOperationV3& op = cached_ops_.emplace_back();
op.set_type(kCowCopyOp);
op.new_block = new_block + i;
op.set_source(old_block + i);
}
- if (!WriteOperation({ops.data(), ops.size()}, {})) {
- return false;
- }
+ if (NeedsFlush()) {
+ if (!FlushCacheOps()) {
+ return false;
+ }
+ }
return true;
}
bool CowWriterV3::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
+ if (!CheckOpCount(size / header_.block_size)) {
+ return false;
+ }
return EmitBlocks(new_block_start, data, size, 0, 0, kCowReplaceOp);
}
bool CowWriterV3::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
uint32_t old_block, uint16_t offset) {
+ if (!CheckOpCount(size / header_.block_size)) {
+ return false;
+ }
return EmitBlocks(new_block_start, data, size, old_block, offset, kCowXorOp);
}
+bool CowWriterV3::NeedsFlush() const {
+ // Allow bigger batch sizes for ops without data. A single CowOperationV3
+ // struct uses 14 bytes of memory, even if we cache 200 * 16 ops in memory,
+ // it's only ~44K.
+ return cached_data_.size() >= batch_size_ || cached_ops_.size() >= batch_size_ * 16;
+}
+
bool CowWriterV3::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
uint64_t old_block, uint16_t offset, CowOperationType type) {
if (compression_.algorithm != kCowCompressNone && compressor_ == nullptr) {
@@ -248,34 +290,18 @@
return false;
}
const size_t num_blocks = (size / header_.block_size);
- if (compression_.algorithm == kCowCompressNone) {
- std::vector<CowOperationV3> ops(num_blocks);
- for (size_t i = 0; i < num_blocks; i++) {
- CowOperation& op = ops[i];
- op.new_block = new_block_start + i;
- op.set_type(type);
- if (type == kCowXorOp) {
- op.set_source((old_block + i) * header_.block_size + offset);
- } else {
- op.set_source(next_data_pos_ + header_.block_size * i);
- }
- op.data_length = header_.block_size;
- }
- return WriteOperation({ops.data(), ops.size()}, data, size);
- }
-
- for (size_t i = 0; i < num_blocks; i += batch_size_) {
- const auto blocks_to_write = std::min<size_t>(batch_size_, num_blocks - i);
- std::vector<std::basic_string<uint8_t>> compressed_blocks(blocks_to_write);
- std::vector<CowOperationV3> ops(blocks_to_write);
- std::vector<struct iovec> vec(blocks_to_write);
+ for (size_t i = 0; i < num_blocks;) {
+ const auto blocks_to_write =
+ std::min<size_t>(batch_size_ - cached_data_.size(), num_blocks - i);
size_t compressed_bytes = 0;
for (size_t j = 0; j < blocks_to_write; j++) {
const uint8_t* const iter =
reinterpret_cast<const uint8_t*>(data) + (header_.block_size * (i + j));
- CowOperation& op = ops[j];
+ CowOperation& op = cached_ops_.emplace_back();
+ auto& vec = data_vec_.emplace_back();
+ auto& compressed_data = cached_data_.emplace_back();
op.new_block = new_block_start + i + j;
op.set_type(type);
@@ -284,43 +310,49 @@
} else {
op.set_source(next_data_pos_ + compressed_bytes);
}
-
- std::basic_string<uint8_t> compressed_data =
- compressor_->Compress(iter, header_.block_size);
- if (compressed_data.empty()) {
- LOG(ERROR) << "Compression failed during EmitBlocks(" << new_block_start << ", "
- << num_blocks << ");";
- return false;
+ if (compression_.algorithm == kCowCompressNone) {
+ compressed_data.resize(header_.block_size);
+ } else {
+ compressed_data = compressor_->Compress(iter, header_.block_size);
+ if (compressed_data.empty()) {
+ LOG(ERROR) << "Compression failed during EmitBlocks(" << new_block_start << ", "
+ << num_blocks << ");";
+ return false;
+ }
}
if (compressed_data.size() >= header_.block_size) {
compressed_data.resize(header_.block_size);
std::memcpy(compressed_data.data(), iter, header_.block_size);
}
- compressed_blocks[j] = std::move(compressed_data);
- vec[j] = {.iov_base = compressed_blocks[j].data(),
- .iov_len = compressed_blocks[j].size()};
- op.data_length = vec[j].iov_len;
+ vec = {.iov_base = compressed_data.data(), .iov_len = compressed_data.size()};
+ op.data_length = vec.iov_len;
compressed_bytes += op.data_length;
}
- if (!WriteOperation({ops.data(), ops.size()}, {vec.data(), vec.size()})) {
- PLOG(ERROR) << "AddRawBlocks with compression: write failed. new block: "
- << new_block_start << " compression: " << compression_.algorithm;
+ if (NeedsFlush() && !FlushCacheOps()) {
+ LOG(ERROR) << "EmitBlocks with compression: write failed. new block: "
+ << new_block_start << " compression: " << compression_.algorithm
+ << ", op type: " << type;
return false;
}
+ i += blocks_to_write;
}
return true;
}
bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, const uint64_t num_blocks) {
- std::vector<CowOperationV3> ops(num_blocks);
- for (uint64_t i = 0; i < ops.size(); i++) {
- auto& op = ops[i];
+ if (!CheckOpCount(num_blocks)) {
+ return false;
+ }
+ for (uint64_t i = 0; i < num_blocks; i++) {
+ auto& op = cached_ops_.emplace_back();
op.set_type(kCowZeroOp);
op.new_block = new_block_start + i;
}
- if (!WriteOperation({ops.data(), ops.size()})) {
- return false;
+ if (NeedsFlush()) {
+ if (!FlushCacheOps()) {
+ return false;
+ }
}
return true;
}
@@ -329,6 +361,10 @@
// remove all labels greater than this current one. we want to avoid the situation of adding
// in
// duplicate labels with differing op values
+ if (!FlushCacheOps()) {
+ LOG(ERROR) << "Failed to flush cached ops before emitting label " << label;
+ return false;
+ }
auto remove_if_callback = [&](const auto& resume_point) -> bool {
if (resume_point.label >= label) return true;
return false;
@@ -357,7 +393,15 @@
bool CowWriterV3::EmitSequenceData(size_t num_ops, const uint32_t* data) {
// TODO: size sequence buffer based on options
+ if (header_.op_count > 0 || !cached_ops_.empty()) {
+ LOG(ERROR) << "There's " << header_.op_count << " operations written to disk and "
+ << cached_ops_.size()
+ << " ops cached in memory. Writing sequence data is only allowed before all "
+ "operation writes.";
+ return false;
+ }
header_.sequence_data_count = num_ops;
+ next_data_pos_ = GetDataOffset(header_);
if (!android::base::WriteFullyAtOffset(fd_, data, sizeof(data[0]) * num_ops,
GetSequenceOffset(header_))) {
PLOG(ERROR) << "writing sequence buffer failed";
@@ -366,16 +410,32 @@
return true;
}
-bool CowWriterV3::WriteOperation(std::basic_string_view<CowOperationV3> op, const void* data,
- size_t size) {
- struct iovec vec {
- .iov_len = size
- };
- // Dear C++ god, this is effectively a const_cast. I had to do this because
- // pwritev()'s struct iovec requires a non-const pointer. The input data
- // will not be modified, as the iovec is only used for a write operation.
- std::memcpy(&vec.iov_base, &data, sizeof(data));
- return WriteOperation(op, {&vec, 1});
+bool CowWriterV3::FlushCacheOps() {
+ if (cached_ops_.empty()) {
+ if (!data_vec_.empty()) {
+ LOG(ERROR) << "Cached ops is empty, but data iovec has size: " << data_vec_.size()
+ << " this is definitely a bug.";
+ return false;
+ }
+ return true;
+ }
+ size_t bytes_written = 0;
+
+ for (auto& op : cached_ops_) {
+ if (op.type() == kCowReplaceOp) {
+ op.set_source(next_data_pos_ + bytes_written);
+ }
+ bytes_written += op.data_length;
+ }
+ if (!WriteOperation({cached_ops_.data(), cached_ops_.size()},
+ {data_vec_.data(), data_vec_.size()})) {
+ LOG(ERROR) << "Failed to flush " << cached_ops_.size() << " ops to disk";
+ return false;
+ }
+ cached_ops_.clear();
+ cached_data_.clear();
+ data_vec_.clear();
+ return true;
}
bool CowWriterV3::WriteOperation(std::basic_string_view<CowOperationV3> ops,
@@ -400,7 +460,6 @@
<< ops.size() << " ops will exceed the max of " << header_.op_count_max;
return false;
}
-
const off_t offset = GetOpOffset(header_.op_count, header_);
if (!android::base::WriteFullyAtOffset(fd_, ops.data(), ops.size() * sizeof(ops[0]), offset)) {
PLOG(ERROR) << "Write failed for " << ops.size() << " ops at " << offset;
@@ -420,13 +479,12 @@
return true;
}
-bool CowWriterV3::WriteOperation(const CowOperationV3& op, const void* data, size_t size) {
- return WriteOperation({&op, 1}, data, size);
-}
-
bool CowWriterV3::Finalize() {
CHECK_GE(header_.prefix.header_size, sizeof(CowHeaderV3));
CHECK_LE(header_.prefix.header_size, sizeof(header_));
+ if (!FlushCacheOps()) {
+ return false;
+ }
if (!android::base::WriteFullyAtOffset(fd_, &header_, header_.prefix.header_size, 0)) {
return false;
}
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index 93f1d24..3a7b877 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -16,6 +16,7 @@
#include <android-base/logging.h>
#include <string_view>
+#include <vector>
#include "writer_base.h"
@@ -42,19 +43,20 @@
private:
void SetupHeaders();
+ bool NeedsFlush() const;
bool ParseOptions();
bool OpenForWrite();
bool OpenForAppend(uint64_t label);
bool WriteOperation(std::basic_string_view<CowOperationV3> op,
std::basic_string_view<struct iovec> data);
- bool WriteOperation(std::basic_string_view<CowOperationV3> op, const void* data = nullptr,
- size_t size = 0);
- bool WriteOperation(const CowOperationV3& op, const void* data = nullptr, size_t size = 0);
bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
uint16_t offset, CowOperationType type);
bool CompressBlocks(size_t num_blocks, const void* data);
+ bool CheckOpCount(size_t op_count);
private:
+ bool ReadBackVerification();
+ bool FlushCacheOps();
CowHeaderV3 header_{};
CowCompression compression_;
// in the case that we are using one thread for compression, we can store and re-use the same
@@ -65,12 +67,14 @@
std::shared_ptr<std::vector<ResumePoint>> resume_points_;
uint64_t next_data_pos_ = 0;
- std::vector<std::basic_string<uint8_t>> compressed_buf_;
// in the case that we are using one thread for compression, we can store and re-use the same
// compressor
int num_compress_threads_ = 1;
- size_t batch_size_ = 0;
+ size_t batch_size_ = 1;
+ std::vector<CowOperationV3> cached_ops_;
+ std::vector<std::basic_string<uint8_t>> cached_data_;
+ std::vector<struct iovec> data_vec_;
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index c0c3eaf..e538d50 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -2864,15 +2864,23 @@
}
void KillSnapuserd() {
- auto status = android::base::GetProperty("init.svc.snapuserd", "stopped");
- if (status == "stopped") {
- return;
+ // Detach the daemon if it's alive
+ auto snapuserd_client = SnapuserdClient::TryConnect(kSnapuserdSocket, 5s);
+ if (snapuserd_client) {
+ snapuserd_client->DetachSnapuserd();
}
- auto snapuserd_client = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
- if (!snapuserd_client) {
- return;
+
+ // Now stop the service - Init will send a SIGKILL to the daemon. However,
+ // process state will move from "running" to "stopping". Only after the
+ // process is reaped by init, the service state is moved to "stopped".
+ //
+ // Since the tests involve starting the daemon immediately, wait for the
+ // process to completely stop (aka. wait until init reaps the terminated
+ // process).
+ android::base::SetProperty("ctl.stop", "snapuserd");
+ if (!android::base::WaitForProperty("init.svc.snapuserd", "stopped", 10s)) {
+ LOG(ERROR) << "Timed out waiting for snapuserd to stop.";
}
- snapuserd_client->DetachSnapuserd();
}
} // namespace snapshot
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 7deb173..7444f96 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -97,7 +97,7 @@
# create some directories (some are mount points) and symlinks
LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
dev proc sys system data data_mirror odm oem acct config storage mnt apex bootstrap-apex debug_ramdisk \
- linkerconfig second_stage_resources postinstall $(BOARD_ROOT_EXTRA_FOLDERS)); \
+ linkerconfig second_stage_resources postinstall tmp $(BOARD_ROOT_EXTRA_FOLDERS)); \
ln -sf /system/bin $(TARGET_ROOT_OUT)/bin; \
ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \
ln -sf /data/user_de/0/com.android.shell/files/bugreports $(TARGET_ROOT_OUT)/bugreports; \
diff --git a/rootdir/init.rc b/rootdir/init.rc
index fb64736..12c46eb 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -92,6 +92,12 @@
# checker programs.
mkdir /dev/fscklogs 0770 root system
+ # Create tmpfs for use by the shell user.
+ mount tmpfs tmpfs /tmp
+ restorecon /tmp
+ chown shell shell /tmp
+ chmod 0771 /tmp
+
on init
sysclktz 0
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index 2299481..8c8edb7 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -353,7 +353,6 @@
if (open_flags & O_CREAT) {
sync_parent(path, watcher);
}
- free(path);
/* at this point rc contains storage file fd */
msg->result = STORAGE_NO_ERROR;
@@ -361,6 +360,9 @@
ALOGV("%s: \"%s\": fd = %u: handle = %d\n",
__func__, path, rc, resp.handle);
+ free(path);
+ path = NULL;
+
/* a backing file has been opened, notify any waiting init steps */
if (!fs_ready_initialized) {
rc = property_set(FS_READY_PROPERTY, "1");