1 /*
2  * Copyright (C) 2009-2016 Codership Oy <info@codership.com>
3  *
4  * $Id$
5  */
6 
7 #include "gu_fdesc.hpp"
8 
9 #include "gu_logger.hpp"
10 #include "gu_throw.hpp"
11 
12 extern "C" {
13 #include "gu_limits.h"
14 }
15 
16 #if !defined(_XOPEN_SOURCE) && !defined(__APPLE__) && !defined(__DragonFly__)
17 #define _XOPEN_SOURCE 600
18 #endif
19 
20 #include <cerrno>
21 #include <limits>
22 #include <sys/stat.h>
23 #include <sys/statvfs.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 
27 #ifndef O_CLOEXEC // CentOS < 6.0 does not have it
28 #define O_CLOEXEC 0
29 #endif
30 
31 #ifndef O_NOATIME
32 #define O_NOATIME 0
33 #endif
34 
35 namespace gu
36 {
37     static int const OPEN_FLAGS   = O_RDWR | O_NOATIME | O_CLOEXEC;
38     static int const CREATE_FLAGS = OPEN_FLAGS | O_CREAT /*| O_TRUNC*/;
39 
40     /* respect user umask by allowing all bits by default */
41     static mode_t const CREATE_MODE =
42         S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ;
43 
FileDescriptor(const std::string & fname,bool const sync)44     FileDescriptor::FileDescriptor (const std::string& fname,
45                                     bool const         sync)
46         : name_(fname),
47           fd_  (open (name_.c_str(), OPEN_FLAGS)),
48           size_(fd_ < 0 ? 0 : lseek (fd_, 0, SEEK_END)),
49           sync_(sync)
50     {
51         constructor_common();
52     }
53 
54     static unsigned long long
available_storage(const std::string & name,size_t size)55     available_storage(const std::string& name, size_t size)
56     {
57         static size_t const reserve(1 << 20); // reserve 1M free space
58         struct statvfs stat;
59         int const err(statvfs(name.c_str(), &stat));
60 
61         if (0 == err)
62         {
63             unsigned long long  const free_size(stat.f_bavail * stat.f_bsize);
64 
65             if (reserve < free_size)
66             {
67                 return free_size - reserve;
68             }
69             else
70             {
71                 return 0;
72             }
73         }
74         else
75         {
76             int const errn(errno);
77             log_warn << "statvfs() failed on '" << name << "' partition: "
78                      << errn << " (" << strerror(errn) <<"). Proceeding anyway.";
79             return std::numeric_limits<unsigned long long>::max();
80         }
81     }
82 
FileDescriptor(const std::string & fname,size_t const size,bool const allocate,bool const sync)83     FileDescriptor::FileDescriptor (const std::string& fname,
84                                     size_t const       size,
85                                     bool   const       allocate,
86                                     bool   const       sync)
87         : name_(fname),
88           fd_  (open (fname.c_str(), CREATE_FLAGS, CREATE_MODE)),
89           size_(size),
90           sync_(sync)
91     {
92         constructor_common();
93 
94         off_t const current_size(lseek (fd_, 0, SEEK_END));
95 
96         if (current_size < size_)
97         {
98             unsigned long long const available(available_storage(name_, size_));
99 
100             if (size_t(size_) > available)
101             {
102                 ::close(fd_);
103                 ::unlink(name_.c_str());
104                 gu_throw_error(ENOSPC) << "Requested size " << size_ << " for '"
105                                        << name_
106                                        << "' exceeds available storage space "
107                                        << available;
108             }
109 
110             if (allocate)
111             {
112                 // reserve space that hasn't been reserved
113                 prealloc (current_size);
114             }
115             else
116             {
117                 // reserve size or bus error follows mmap()
118                 write_byte (size_ - 1);
119             }
120         }
121         else if (current_size > size_)
122         {
123             log_debug << "Truncating '" << name_<< "' to " << size_<< " bytes.";
124 
125             if (ftruncate(fd_, size_))
126             {
127                 gu_throw_error(errno) << "Failed to truncate '" << name_
128                                       << "' to " << size_ << " bytes.";
129             }
130         }
131         else
132         {
133             log_debug << "Reusing existing '" << name_ << "'.";
134         }
135     }
136 
137     void
constructor_common()138     FileDescriptor::constructor_common()
139     {
140         if (fd_ < 0) {
141             gu_throw_error(errno) << "Failed to open file '" + name_ + '\'';
142         }
143 #if !defined(__APPLE__) /* Darwin does not have posix_fadvise */
144 /* benefits are questionable
145         int err(posix_fadvise (value, 0, size, POSIX_FADV_SEQUENTIAL));
146 
147         if (err != 0)
148         {
149             log_warn << "Failed to set POSIX_FADV_SEQUENTIAL on "
150                      << name << ": " << err << " (" << strerror(err) << ")";
151         }
152 */
153 #endif
154         log_debug << "Opened file '" << name_ << "', size: " << size_;
155         log_debug << "File descriptor: " << fd_;
156     }
157 
~FileDescriptor()158     FileDescriptor::~FileDescriptor ()
159     {
160         if (sync_)
161         {
162             try { sync(); } catch (Exception& e) { log_error << e.what(); }
163         }
164 
165         if (close(fd_) != 0)
166         {
167             int const err(errno);
168             log_error << "Failed to close file '" << name_ << "': "
169                       << err << " (" << strerror(err) << '\'';
170         }
171         else
172         {
173             log_debug << "Closed  file '" << name_ << "'";
174         }
175     }
176 
177     void
sync() const178     FileDescriptor::sync () const
179     {
180         log_debug << "Flushing file '" << name_ << "'";
181 
182         if (fsync (fd_) < 0) {
183             gu_throw_error(errno) << "fsync() failed on '" + name_ + '\'';
184         }
185 
186         log_debug << "Flushed file '" << name_ << "'";
187     }
188 
189     bool
write_byte(off_t offset)190     FileDescriptor::write_byte (off_t offset)
191     {
192         byte_t const byte (0);
193 
194         if (lseek (fd_, offset, SEEK_SET) != offset)
195             gu_throw_error(errno) << "lseek() failed on '" << name_ << '\'';
196 
197         if (write (fd_, &byte, sizeof(byte)) != sizeof(byte))
198             gu_throw_error(errno) << "write() failed on '" << name_ << '\'';
199 
200         return true;
201     }
202 
203     /*! prealloc() fallback */
204     void
write_file(off_t const start)205     FileDescriptor::write_file (off_t const start)
206     {
207         // last byte of the start page
208         off_t offset = (start / GU_PAGE_SIZE + 1) * GU_PAGE_SIZE - 1;
209 
210         log_info << "Preallocating " << (size_ - start) << '/' << size_
211                  << " bytes in '" << name_ << "'...";
212 
213         while (offset < size_ && write_byte (offset))
214         {
215             offset += GU_PAGE_SIZE;
216         }
217 
218         if (offset >= size_ && write_byte (size_ - 1))
219         {
220             sync();
221             return;
222         }
223 
224         gu_throw_error (errno) << "File preallocation failed";
225     }
226 
227     void
prealloc(off_t const start)228     FileDescriptor::prealloc(off_t const start)
229     {
230         off_t const diff (size_ - start);
231 
232         log_debug << "Preallocating " << diff << '/' << size_ << " bytes in '"
233                   << name_ << "'...";
234 
235 #if defined(__APPLE__)
236         if (-1 == fcntl (fd_, F_SETSIZE, size_) && -1 == ftruncate (fd_, size_))
237         {
238 #else
239         int const ret = posix_fallocate (fd_, start, diff);
240         if (0 != ret)
241         {
242             errno = ret;
243 #endif
244             if ((EINVAL == errno || ENOSYS == errno) && start >= 0 && diff > 0)
245             {
246                 // FS does not support the operation, try physical write
247                 write_file (start);
248             }
249             else
250             {
251                 gu_throw_error (errno) << "File preallocation failed";
252             }
253         }
254     }
255 }
256