1 // Copyright 2018 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ///////////////////////////////////////////////////////////////////////////////
16
17 #include "tink/util/file_output_stream.h"
18
19 #include <unistd.h>
20 #include <cstring>
21 #include <algorithm>
22
23 #include "absl/memory/memory.h"
24 #include "absl/status/status.h"
25 #include "tink/output_stream.h"
26 #include "tink/util/errors.h"
27 #include "tink/util/status.h"
28 #include "tink/util/statusor.h"
29
30 namespace crypto {
31 namespace tink {
32 namespace util {
33
34 namespace {
35
36 // Attempts to close file descriptor fd, while ignoring EINTR.
37 // (code borrowed from ZeroCopy-streams)
close_ignoring_eintr(int fd)38 int close_ignoring_eintr(int fd) {
39 int result;
40 do {
41 result = close(fd);
42 } while (result < 0 && errno == EINTR);
43 return result;
44 }
45
46
47 // Attempts to write 'count' bytes of data data from 'buf'
48 // to file descriptor fd, while ignoring EINTR.
write_ignoring_eintr(int fd,const void * buf,size_t count)49 int write_ignoring_eintr(int fd, const void *buf, size_t count) {
50 int result;
51 do {
52 result = write(fd, buf, count);
53 } while (result < 0 && errno == EINTR);
54 return result;
55 }
56
57 } // anonymous namespace
58
59
FileOutputStream(int file_descriptor,int buffer_size)60 FileOutputStream::FileOutputStream(int file_descriptor, int buffer_size) :
61 buffer_size_(buffer_size > 0 ? buffer_size : 128 * 1024) { // 128 KB
62 fd_ = file_descriptor;
63 count_in_buffer_ = 0;
64 count_backedup_ = 0;
65 buffer_ = nullptr;
66 position_ = 0;
67 buffer_offset_ = 0;
68 status_ = OkStatus();
69 }
70
Next(void ** data)71 crypto::tink::util::StatusOr<int> FileOutputStream::Next(void** data) {
72 if (!status_.ok()) return status_;
73
74 if (buffer_ == nullptr) { // possible only at the first call to Next()
75 buffer_ = absl::make_unique<uint8_t[]>(buffer_size_);
76 *data = buffer_.get();
77 count_in_buffer_ = buffer_size_;
78 position_ = buffer_size_;
79 return buffer_size_;
80 }
81
82 // If some space was backed up, return it first.
83 if (count_backedup_ > 0) {
84 position_ = position_ + count_backedup_;
85 buffer_offset_ = count_in_buffer_;
86 count_in_buffer_ = count_in_buffer_ + count_backedup_;
87 int backedup = count_backedup_;
88 count_backedup_ = 0;
89 *data = buffer_.get() + buffer_offset_;
90 return backedup;
91 }
92
93 // No space was backed up, so count_in_buffer_ == buffer_size_ holds here.
94 // Write the data from the buffer, and return available space in buffer_.
95 // The available space might not span the entire buffer_, as writing
96 // may succeed only for a prefix of buffer_ -- in this case the data still
97 // to be written is shifted in buffer_ and the remaining space is returned.
98 int write_result = write_ignoring_eintr(fd_, buffer_.get(), buffer_size_);
99 if (write_result <= 0) { // No data written or an I/O error occurred.
100 if (write_result == 0) {
101 return 0;
102 }
103 status_ = ToStatusF(absl::StatusCode::kInternal, "I/O error upon write: %d",
104 errno);
105 return status_;
106 }
107 // Some data was written, so we can return some portion of buffer_.
108 position_ = position_ + write_result;
109 count_in_buffer_ = buffer_size_;
110 count_backedup_ = 0;
111 buffer_offset_ = buffer_size_ - write_result;
112 *data = buffer_.get() + buffer_offset_;
113 if (write_result < buffer_size_) {
114 // Only part of the data was written, shift the remaining data in buffer_.
115 // Using memmove, as source and destination may overlap.
116 std::memmove(buffer_.get(), buffer_.get() + write_result, buffer_offset_);
117 }
118 return write_result;
119 }
120
BackUp(int count)121 void FileOutputStream::BackUp(int count) {
122 if (!status_.ok() || count < 1 || count_in_buffer_ == 0) return;
123 int curr_buffer_size = buffer_size_ - buffer_offset_;
124 int actual_count = std::min(count, curr_buffer_size - count_backedup_);
125 count_backedup_ += actual_count;
126 count_in_buffer_ -= actual_count;
127 position_ -= actual_count;
128 }
129
~FileOutputStream()130 FileOutputStream::~FileOutputStream() {
131 Close().IgnoreError();
132 }
133
Close()134 Status FileOutputStream::Close() {
135 if (!status_.ok()) return status_;
136 if (count_in_buffer_ > 0) {
137 // Try to write the remaining bytes.
138 int total_written = 0;
139 while (total_written < count_in_buffer_) {
140 int write_result = write_ignoring_eintr(
141 fd_, buffer_.get() + total_written, count_in_buffer_ - total_written);
142 if (write_result < 0) { // An I/O error occurred.
143 status_ = ToStatusF(absl::StatusCode::kInternal,
144 "I/O error upon write: %d", errno);
145 return status_;
146 } else if (write_result == 0) { // No progress, hence abort.
147 status_ =
148 ToStatusF(absl::StatusCode::kInternal,
149 "I/O error: failed to write %d bytes before closing.",
150 count_in_buffer_ - total_written);
151 return status_;
152 }
153 // Managed to write some bytes, hence continue.
154 total_written += write_result;
155 }
156 }
157 if (close_ignoring_eintr(fd_) == -1) {
158 status_ = ToStatusF(absl::StatusCode::kInternal, "I/O error upon close: %d",
159 errno);
160 return status_;
161 }
162 status_ = Status(absl::StatusCode::kFailedPrecondition, "Stream closed");
163 return OkStatus();
164 }
165
Position() const166 int64_t FileOutputStream::Position() const {
167 return position_;
168 }
169
170 } // namespace util
171 } // namespace tink
172 } // namespace crypto
173