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