blob: 311cc28ae169b03b78614b8347334c00a188e9fa [file] [log] [blame]
Mike Frysinger8155d082012-04-06 15:23:18 -04001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Darin Petkov7a22d792010-11-08 14:10:00 -08002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "update_engine/full_update_generator.h"
6
7#include <inttypes.h>
8#include <fcntl.h>
9
10#include <tr1/memory>
11
12#include <base/string_util.h>
Mike Frysinger8155d082012-04-06 15:23:18 -040013#include <base/stringprintf.h>
Darin Petkov7a22d792010-11-08 14:10:00 -080014
15#include "update_engine/bzip.h"
16#include "update_engine/utils.h"
17
18using std::deque;
19using std::min;
20using std::max;
21using std::string;
22using std::tr1::shared_ptr;
23using std::vector;
24
25namespace chromeos_update_engine {
26
27namespace {
28
29// This class encapsulates a full update chunk processing thread. The processor
30// reads a chunk of data from the input file descriptor and compresses it. The
31// processor needs to be started through Start() then waited on through Wait().
32class ChunkProcessor {
33 public:
34 // Read a chunk of |size| bytes from |fd| starting at offset |offset|.
35 ChunkProcessor(int fd, off_t offset, size_t size)
36 : thread_(NULL),
37 fd_(fd),
38 offset_(offset),
39 buffer_in_(size) {}
40 ~ChunkProcessor() { Wait(); }
41
42 off_t offset() const { return offset_; }
43 const vector<char>& buffer_in() const { return buffer_in_; }
44 const vector<char>& buffer_compressed() const { return buffer_compressed_; }
45
46 // Starts the processor. Returns true on success, false on failure.
47 bool Start();
48
49 // Waits for the processor to complete. Returns true on success, false on
50 // failure.
51 bool Wait();
52
53 bool ShouldCompress() const {
54 return buffer_compressed_.size() < buffer_in_.size();
55 }
56
57 private:
58 // Reads the input data into |buffer_in_| and compresses it into
59 // |buffer_compressed_|. Returns true on success, false otherwise.
60 bool ReadAndCompress();
61 static gpointer ReadAndCompressThread(gpointer data);
62
63 GThread* thread_;
64 int fd_;
65 off_t offset_;
66 vector<char> buffer_in_;
67 vector<char> buffer_compressed_;
68
69 DISALLOW_COPY_AND_ASSIGN(ChunkProcessor);
70};
71
72bool ChunkProcessor::Start() {
73 thread_ = g_thread_create(ReadAndCompressThread, this, TRUE, NULL);
74 TEST_AND_RETURN_FALSE(thread_ != NULL);
75 return true;
76}
77
78bool ChunkProcessor::Wait() {
79 if (!thread_) {
80 return false;
81 }
82 gpointer result = g_thread_join(thread_);
83 thread_ = NULL;
84 TEST_AND_RETURN_FALSE(result == this);
85 return true;
86}
87
88gpointer ChunkProcessor::ReadAndCompressThread(gpointer data) {
89 return
90 reinterpret_cast<ChunkProcessor*>(data)->ReadAndCompress() ? data : NULL;
91}
92
93bool ChunkProcessor::ReadAndCompress() {
94 ssize_t bytes_read = -1;
95 TEST_AND_RETURN_FALSE(utils::PReadAll(fd_,
96 buffer_in_.data(),
97 buffer_in_.size(),
98 offset_,
99 &bytes_read));
100 TEST_AND_RETURN_FALSE(bytes_read == static_cast<ssize_t>(buffer_in_.size()));
101 TEST_AND_RETURN_FALSE(BzipCompress(buffer_in_, &buffer_compressed_));
102 return true;
103}
104
105} // namespace
106
107bool FullUpdateGenerator::Run(
108 Graph* graph,
109 const std::string& new_kernel_part,
110 const std::string& new_image,
111 off_t image_size,
112 int fd,
113 off_t* data_file_size,
114 off_t chunk_size,
115 off_t block_size,
116 vector<DeltaArchiveManifest_InstallOperation>* kernel_ops,
117 std::vector<Vertex::Index>* final_order) {
118 TEST_AND_RETURN_FALSE(chunk_size > 0);
119 TEST_AND_RETURN_FALSE((chunk_size % block_size) == 0);
120
121 size_t max_threads = max(sysconf(_SC_NPROCESSORS_ONLN), 4L);
122 LOG(INFO) << "Max threads: " << max_threads;
123
124 // Get the sizes early in the function, so we can fail fast if the user
125 // passed us bad paths.
126 TEST_AND_RETURN_FALSE(image_size >= 0 &&
127 image_size <= utils::FileSize(new_image));
128 const off_t kernel_size = utils::FileSize(new_kernel_part);
129 TEST_AND_RETURN_FALSE(kernel_size >= 0);
130
131 off_t part_sizes[] = { image_size, kernel_size };
132 string paths[] = { new_image, new_kernel_part };
133
134 for (int partition = 0; partition < 2; ++partition) {
135 const string& path = paths[partition];
136 LOG(INFO) << "compressing " << path;
Darin Petkov7a22d792010-11-08 14:10:00 -0800137 int in_fd = open(path.c_str(), O_RDONLY, 0);
138 TEST_AND_RETURN_FALSE(in_fd >= 0);
139 ScopedFdCloser in_fd_closer(&in_fd);
Darin Petkov7a22d792010-11-08 14:10:00 -0800140 deque<shared_ptr<ChunkProcessor> > threads;
Darin Petkov3375ff52010-11-08 16:20:54 -0800141 int last_progress_update = INT_MIN;
Darin Petkov7a22d792010-11-08 14:10:00 -0800142 off_t bytes_left = part_sizes[partition], counter = 0, offset = 0;
143 while (bytes_left > 0 || !threads.empty()) {
144 // Check and start new chunk processors if possible.
145 while (threads.size() < max_threads && bytes_left > 0) {
146 shared_ptr<ChunkProcessor> processor(
147 new ChunkProcessor(in_fd, offset, min(bytes_left, chunk_size)));
148 threads.push_back(processor);
149 TEST_AND_RETURN_FALSE(processor->Start());
150 bytes_left -= chunk_size;
151 offset += chunk_size;
152 }
153
154 // Need to wait for a chunk processor to complete and process its ouput
155 // before spawning new processors.
156 shared_ptr<ChunkProcessor> processor = threads.front();
157 threads.pop_front();
158 TEST_AND_RETURN_FALSE(processor->Wait());
159
160 DeltaArchiveManifest_InstallOperation* op = NULL;
161 if (partition == 0) {
162 graph->resize(graph->size() + 1);
163 graph->back().file_name =
164 StringPrintf("<rootfs-operation-%" PRIi64 ">", counter++);
165 op = &graph->back().op;
166 final_order->push_back(graph->size() - 1);
167 } else {
168 kernel_ops->resize(kernel_ops->size() + 1);
169 op = &kernel_ops->back();
170 }
171
172 const bool compress = processor->ShouldCompress();
173 const vector<char>& use_buf =
174 compress ? processor->buffer_compressed() : processor->buffer_in();
175 op->set_type(compress ?
176 DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ :
177 DeltaArchiveManifest_InstallOperation_Type_REPLACE);
178 op->set_data_offset(*data_file_size);
179 TEST_AND_RETURN_FALSE(utils::WriteAll(fd, &use_buf[0], use_buf.size()));
180 *data_file_size += use_buf.size();
181 op->set_data_length(use_buf.size());
182 Extent* dst_extent = op->add_dst_extents();
183 dst_extent->set_start_block(processor->offset() / block_size);
184 dst_extent->set_num_blocks(chunk_size / block_size);
185
Darin Petkov3375ff52010-11-08 16:20:54 -0800186 int progress = static_cast<int>(
187 (processor->offset() + processor->buffer_in().size()) * 100.0 /
188 part_sizes[partition]);
189 if (last_progress_update < progress &&
190 (last_progress_update + 10 <= progress || progress == 100)) {
191 LOG(INFO) << progress << "% complete (output size: "
192 << *data_file_size << ")";
193 last_progress_update = progress;
194 }
Darin Petkov7a22d792010-11-08 14:10:00 -0800195 }
196 }
197
198 return true;
199}
200
201} // namespace chromeos_update_engine