Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
file.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#include "ortools/base/file.h"
15
16#include <sys/stat.h>
17#include <sys/types.h>
18
19#include <cerrno>
20#include <cstdint>
21
22#if defined(_MSC_VER)
23#include <io.h>
24#define access _access
25#define F_OK 0
26#else
27#include <unistd.h>
28#endif
29
30#include <cstdio>
31#include <cstdlib>
32#include <cstring>
33#include <memory>
34#include <string>
35
36#include "absl/log/check.h"
37#include "absl/log/log.h"
38#include "absl/status/status.h"
39#include "absl/strings/str_cat.h"
40#include "absl/strings/string_view.h"
41#include "bzlib.h"
42#include "google/protobuf/io/tokenizer.h"
43#include "google/protobuf/message.h"
44#include "google/protobuf/text_format.h"
45#include "zlib.h"
46
47namespace {
48enum class Format { NORMAL_FILE, GZIP_FILE, BZIP2_FILE };
49
50static Format GetFormatFromName(absl::string_view name) {
51 const int size = name.size();
52 if (size > 4 && name.substr(size - 3) == ".gz") {
53 return Format::GZIP_FILE;
54 } else if (size > 5 && name.substr(size - 4) == ".bz2") {
55 return Format::BZIP2_FILE;
56 } else {
57 return Format::NORMAL_FILE;
58 }
59}
60
61class CFile : public File {
62 public:
63 CFile(FILE* c_file, absl::string_view name) : File(name), f_(c_file) {}
64 ~CFile() override = default;
65
66 // Reads "size" bytes to buf from file, buf should be pre-allocated.
67 size_t Read(void* buf, size_t size) override {
68 return fread(buf, 1, size, f_);
69 }
70
71 // Writes "size" bytes of buf to file, buf should be pre-allocated.
72 size_t Write(const void* buf, size_t size) override {
73 return fwrite(buf, 1, size, f_);
74 }
75
76 // Closes the file and delete the underlying FILE* descriptor.
77 absl::Status Close(int /*flags*/) override {
78 absl::Status status;
79 if (f_ == nullptr) {
80 return status;
81 }
82 if (fclose(f_) == 0) {
83 f_ = nullptr;
84 } else {
85 status.Update(
86 absl::Status(absl::StatusCode::kInvalidArgument,
87 absl::StrCat("Could not close file '", name_, "'")));
88 }
89 delete this;
90 return status;
91 }
92
93 // Flushes buffer.
94 bool Flush() override { return fflush(f_) == 0; }
95
96 // Returns file size.
97 size_t Size() override {
98 struct stat f_stat;
99 stat(name_.c_str(), &f_stat);
100 return f_stat.st_size;
101 }
102
103 bool Open() const override { return f_ != nullptr; }
104
105 private:
106 FILE* f_;
107};
108
109class GzFile : public File {
110 public:
111 GzFile(gzFile gz_file, absl::string_view name) : File(name), f_(gz_file) {}
112 ~GzFile() override = default;
113
114 // Reads "size" bytes to buf from file, buf should be pre-allocated.
115 size_t Read(void* buf, size_t size) override { return gzread(f_, buf, size); }
116
117 // Writes "size" bytes of buf to file, buf should be pre-allocated.
118 size_t Write(const void* buf, size_t size) override {
119 return gzwrite(f_, buf, size);
120 }
121
122 // Closes the file and delete the underlying FILE* descriptor.
123 absl::Status Close(int /*flags*/) override {
124 absl::Status status;
125 if (f_ == nullptr) {
126 return status;
127 }
128 if (gzclose(f_) == 0) {
129 f_ = nullptr;
130 } else {
131 status.Update(
132 absl::Status(absl::StatusCode::kInvalidArgument,
133 absl::StrCat("Could not close file '", name_, "'")));
134 }
135 delete this;
136 return status;
137 }
138
139 // Flushes buffer.
140 bool Flush() override { return gzflush(f_, Z_FINISH) == Z_OK; }
141
142 // Returns file size.
143 size_t Size() override {
144 gzFile file;
145 std::string null_terminated_name = std::string(name_);
146#if defined(_MSC_VER)
147 file = gzopen(null_terminated_name.c_str(), "rb");
148#else
149 file = gzopen(null_terminated_name.c_str(), "r");
150#endif
151 if (!file) {
152 LOG(FATAL) << "Cannot get the size of '" << name_
153 << "': " << strerror(errno);
154 }
155
156 const int kLength = 5 * 1024;
157 unsigned char buffer[kLength];
158 size_t uncompressed_size = 0;
159 while (true) {
160 int err;
161 int bytes_read;
162 bytes_read = gzread(file, buffer, kLength - 1);
163 uncompressed_size += bytes_read;
164 if (bytes_read < kLength - 1) {
165 if (gzeof(file)) {
166 break;
167 } else {
168 const char* error_string;
169 error_string = gzerror(file, &err);
170 if (err) {
171 LOG(FATAL) << "Error " << error_string;
172 }
173 }
174 }
175 }
176 gzclose(file);
177 return uncompressed_size;
178 }
179
180 bool Open() const override { return f_ != nullptr; }
181
182 private:
183 gzFile f_;
184};
185
186class Bz2File : public File {
187 public:
188 Bz2File(BZFILE* bz_file, absl::string_view name) : File(name), f_(bz_file) {}
189 ~Bz2File() override = default;
190
191 // Reads "size" bytes to buf from file, buf should be pre-allocated.
192 size_t Read(void* buf, size_t size) override {
193 return BZ2_bzread(f_, buf, size);
194 }
195
196 // Writes "size" bytes of buf to file, buf should be pre-allocated.
197 size_t Write(const void* buf, size_t size) override {
198 return BZ2_bzwrite(f_, const_cast<void*>(buf), size);
199 }
200
201 // Closes the file and delete the underlying FILE* descriptor.
202 absl::Status Close(int /*flags*/) override {
203 absl::Status status;
204 if (f_ == nullptr) {
205 return absl::OkStatus();
206 }
207 BZ2_bzclose(f_);
208 f_ = nullptr;
209 delete this;
210 return absl::OkStatus();
211 }
212
213 // Flushes buffer.
214 bool Flush() override { return BZ2_bzflush(f_) == 0; }
215
216 // Returns file size.
217 size_t Size() override {
218 BZFILE* file;
219 std::string null_terminated_name = std::string(name_);
220#if defined(_MSC_VER)
221 file = BZ2_bzopen(null_terminated_name.c_str(), "rb");
222#else
223 file = BZ2_bzopen(null_terminated_name.c_str(), "r");
224#endif
225 if (!file) {
226 LOG(FATAL) << "Cannot get the size of '" << name_
227 << "': " << strerror(errno);
228 }
229
230 const int kLength = 5 * 1024;
231 unsigned char buffer[kLength];
232 size_t uncompressed_size = 0;
233 while (true) {
234 int bytes_read;
235 bytes_read = BZ2_bzread(file, buffer, kLength - 1);
236 uncompressed_size += bytes_read;
237 if (bytes_read < kLength - 1) break;
238 }
239 BZ2_bzclose(file);
240 return uncompressed_size;
241 }
242
243 bool Open() const override { return f_ != nullptr; }
244
245 private:
246 BZFILE* f_;
247};
248
249} // namespace
250
251File::File(absl::string_view name) : name_(name) {}
252
253File* File::OpenOrDie(absl::string_view file_name, absl::string_view mode) {
254 File* f = File::Open(file_name, mode);
255 CHECK(f != nullptr) << absl::StrCat("Could not open '", file_name, "'");
256 return f;
257}
258
259File* File::Open(absl::string_view file_name, absl::string_view mode) {
260 std::string null_terminated_name = std::string(file_name);
261 std::string null_terminated_mode = std::string(mode);
262#if defined(_MSC_VER)
263 if (null_terminated_mode == "r") {
264 null_terminated_mode = "rb";
265 } else if (null_terminated_mode == "w") {
266 null_terminated_mode = "wb";
267 }
268#endif
269 const Format format = GetFormatFromName(file_name);
270 switch (format) {
271 case Format::NORMAL_FILE: {
272 FILE* c_file =
273 fopen(null_terminated_name.c_str(), null_terminated_mode.c_str());
274 if (c_file == nullptr) return nullptr;
275 return new CFile(c_file, file_name);
276 }
277 case Format::GZIP_FILE: {
278 gzFile gz_file =
279 gzopen(null_terminated_name.c_str(), null_terminated_mode.c_str());
280 if (!gz_file) return nullptr;
281 return new GzFile(gz_file, file_name);
282 }
283 case Format::BZIP2_FILE: {
284 BZFILE* bz_file = BZ2_bzopen(null_terminated_name.c_str(),
285 null_terminated_mode.c_str());
286 if (!bz_file) return nullptr;
287 return new Bz2File(bz_file, file_name);
288 }
289 }
290 // never reach
291 return nullptr;
292}
293
294int64_t File::ReadToString(std::string* line, uint64_t max_length) {
295 CHECK(line != nullptr);
296 line->clear();
297
298 if (max_length == 0) return 0;
299
300 int64_t needed = max_length;
301 int bufsize = (needed < (2 << 20) ? needed : (2 << 20));
302
303 std::unique_ptr<char[]> buf(new char[bufsize]);
304
305 int64_t nread = 0;
306 while (needed > 0) {
307 nread = Read(buf.get(), (bufsize < needed ? bufsize : needed));
308 if (nread > 0) {
309 line->append(buf.get(), nread);
310 needed -= nread;
311 } else {
312 break;
313 }
314 }
315 return (nread >= 0 ? static_cast<int64_t>(line->size()) : -1);
316}
317
318size_t File::WriteString(absl::string_view str) {
319 return Write(str.data(), str.size());
320}
321
322absl::string_view File::filename() const { return name_; }
323
324void File::Init() {}
325
326namespace file {
327absl::Status Open(absl::string_view file_name, absl::string_view mode, File** f,
328 Options options) {
329 if (options == Defaults()) {
330 *f = File::Open(file_name, mode);
331 if (*f != nullptr) {
332 return absl::OkStatus();
333 }
334 }
335 return absl::Status(absl::StatusCode::kInvalidArgument,
336 absl::StrCat("Could not open '", file_name, "'"));
337}
338
339File* OpenOrDie(absl::string_view file_name, absl::string_view mode,
340 Options options) {
341 File* f;
342 CHECK_EQ(options, Defaults());
343 f = File::Open(file_name, mode);
344 CHECK(f != nullptr) << absl::StrCat("Could not open '", file_name, "'");
345 return f;
346}
347
348absl::StatusOr<std::string> GetContents(absl::string_view path,
349 Options options) {
350 std::string contents;
351 absl::Status status = GetContents(path, &contents, options);
352 if (!status.ok()) {
353 return status;
354 }
355 return contents;
356}
357
358absl::Status GetContents(absl::string_view file_name, std::string* output,
359 Options options) {
360 File* file;
361 // For windows, the "b" is added in file::Open.
362 auto status = file::Open(file_name, "r", &file, options);
363 if (!status.ok()) return status;
364
365 const int64_t size = file->Size();
366 if (file->ReadToString(output, size) == size) {
367 status.Update(file->Close(options));
368 return status;
369 }
370
371 file->Close(options).IgnoreError(); // Even if ReadToString() fails!
372
373 return absl::Status(absl::StatusCode::kInvalidArgument,
374 absl::StrCat("Could not read from '", file_name, "'."));
375}
376
377absl::Status WriteString(File* file, absl::string_view contents,
378 Options options) {
379 if (options == Defaults() && file != nullptr &&
380 file->Write(contents.data(), contents.size()) == contents.size()) {
381 return absl::OkStatus();
382 }
383 return absl::Status(
384 absl::StatusCode::kInvalidArgument,
385 absl::StrCat("Could not write ", contents.size(), " bytes"));
386}
387
388absl::Status SetContents(absl::string_view file_name,
389 absl::string_view contents, Options options) {
390 File* file;
391 // For windows, the "b" is added in file::Open.
392 auto status = file::Open(file_name, "w", &file, options);
393 if (!status.ok()) return status;
394 status = file::WriteString(file, contents, options);
395 status.Update(file->Close(options)); // Even if WriteString() fails!
396 return status;
397}
398
399namespace {
400class NoOpErrorCollector : public google::protobuf::io::ErrorCollector {
401 public:
402 ~NoOpErrorCollector() override = default;
403 void RecordError(int /*line*/, int /*column*/,
404 absl::string_view /*message*/) override {}
405};
406} // namespace
407
408absl::Status GetTextProto(absl::string_view file_name,
409 google::protobuf::Message* proto, Options options) {
410 if (options == Defaults()) {
411 std::string str;
412 if (!GetContents(file_name, &str, file::Defaults()).ok()) {
413 VLOG(1) << "Could not read '" << file_name << "'";
414 return absl::Status(
415 absl::StatusCode::kInvalidArgument,
416 absl::StrCat("Could not read proto from '", file_name, "'."));
417 }
418
419 // Attempt to decode ASCII before deciding binary. Do it in this order
420 // because it is much harder for a binary encoding to happen to be a valid
421 // ASCII encoding than the other way around. For instance "index: 1\n" is a
422 // valid (but nonsensical) binary encoding. We want to avoid printing errors
423 // for valid binary encodings if the ASCII parsing fails, and so specify a
424 // no-op error collector.
425 NoOpErrorCollector error_collector;
426 google::protobuf::TextFormat::Parser parser;
427 parser.RecordErrorsTo(&error_collector);
428
429 if (parser.ParseFromString(str, proto)) { // Text format.
430 return absl::OkStatus();
431 }
432
433 if (proto->ParseFromString(str)) { // Binary format.
434 return absl::OkStatus();
435 }
436
437 // Re-parse the ASCII, just to show the diagnostics (we could also get them
438 // out of the ErrorCollector but this way is easier).
439 google::protobuf::TextFormat::ParseFromString(str, proto);
440 VLOG(1) << "Could not parse contents of '" << file_name << "'";
441 }
442 return absl::Status(
443 absl::StatusCode::kInvalidArgument,
444 absl::StrCat("Could not read proto from '", file_name, "'."));
445}
446
447absl::Status SetTextProto(absl::string_view file_name,
448 const google::protobuf::Message& proto,
449 Options options) {
450 if (options == Defaults()) {
451 std::string proto_string;
452 if (google::protobuf::TextFormat::PrintToString(proto, &proto_string) &&
453 file::SetContents(file_name, proto_string, file::Defaults()).ok()) {
454 return absl::OkStatus();
455 }
456 }
457 return absl::Status(
458 absl::StatusCode::kInvalidArgument,
459 absl::StrCat("Could not write proto to '", file_name, "'."));
460}
461
462absl::Status GetBinaryProto(const absl::string_view file_name,
463 google::protobuf::Message* proto, Options options) {
464 std::string str;
465 if (options == Defaults() &&
466 GetContents(file_name, &str, file::Defaults()).ok() &&
467 proto->ParseFromString(str)) {
468 return absl::OkStatus();
469 }
470 return absl::Status(
471 absl::StatusCode::kInvalidArgument,
472 absl::StrCat("Could not read proto from '", file_name, "'."));
473}
474
475absl::Status SetBinaryProto(absl::string_view file_name,
476 const google::protobuf::Message& proto,
477 Options options) {
478 if (options == Defaults()) {
479 std::string proto_string;
480 if (proto.AppendToString(&proto_string) &&
481 file::SetContents(file_name, proto_string, file::Defaults()).ok()) {
482 return absl::OkStatus();
483 }
484 }
485 return absl::Status(
486 absl::StatusCode::kInvalidArgument,
487 absl::StrCat("Could not write proto to '", file_name, "'."));
488}
489
490absl::Status Delete(absl::string_view path, Options options) {
491 if (options == Defaults()) {
492 std::string null_terminated_path = std::string(path);
493 if (remove(null_terminated_path.c_str()) == 0) return absl::OkStatus();
494 }
495 return absl::Status(absl::StatusCode::kInvalidArgument,
496 absl::StrCat("Could not delete '", path, "'."));
497}
498
499absl::Status Exists(absl::string_view path, Options options) {
500 if (options == Defaults()) {
501 std::string null_terminated_path = std::string(path);
502 if (access(null_terminated_path.c_str(), F_OK) == 0) {
503 return absl::OkStatus();
504 }
505 }
506 return absl::Status(absl::StatusCode::kInvalidArgument,
507 absl::StrCat("File '", path, "' does not exist."));
508}
509} // namespace file
Definition file.h:29
absl::string_view filename() const
Returns the file name.
Definition file.cc:322
virtual size_t Write(const void *buf, size_t size)=0
Writes "size" bytes of buf to file, buff should be pre-allocated.
virtual size_t Read(void *buf, size_t size)=0
Reads "size" bytes to buf from file, buff should be pre-allocated.
static void Init()
Inits internal data structures.
Definition file.cc:324
static File * OpenOrDie(absl::string_view file_name, absl::string_view mode)
Definition file.cc:253
std::string name_
Definition file.h:78
size_t WriteString(absl::string_view str)
Writes a string to file.
Definition file.cc:318
File(absl::string_view name)
Definition file.cc:251
virtual bool Open() const =0
Returns whether the file is currently open.
int64_t ReadToString(std::string *line, uint64_t max_length)
Definition file.cc:294
Definition file.cc:326
int Options
Definition file.h:83
absl::StatusOr< std::string > GetContents(absl::string_view path, Options options)
-— Content API -—
Definition file.cc:348
absl::Status Exists(absl::string_view path, Options options)
Definition file.cc:499
absl::Status SetBinaryProto(absl::string_view file_name, const google::protobuf::Message &proto, Options options)
Definition file.cc:475
absl::Status SetTextProto(absl::string_view file_name, const google::protobuf::Message &proto, Options options)
Definition file.cc:447
absl::Status GetTextProto(absl::string_view file_name, google::protobuf::Message *proto, Options options)
-— Protobuf API -—
Definition file.cc:408
File * OpenOrDie(absl::string_view file_name, absl::string_view mode, Options options)
Definition file.cc:339
absl::Status WriteString(File *file, absl::string_view contents, Options options)
Definition file.cc:377
absl::Status GetBinaryProto(const absl::string_view file_name, google::protobuf::Message *proto, Options options)
Definition file.cc:462
Options Defaults()
Definition file.h:85
absl::Status Delete(absl::string_view path, Options options)
Definition file.cc:490
absl::Status Open(absl::string_view file_name, absl::string_view mode, File **f, Options options)
As of 2016-01, these methods can only be used with flags = file::Defaults().
Definition file.cc:327
absl::Status SetContents(absl::string_view file_name, absl::string_view contents, Options options)
Definition file.cc:388