xref: /aosp_15_r20/external/tink/cc/util/file_output_stream.cc (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
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