12 #include <sys/types.h>
13 #include <sys/statvfs.h>
26 std::unique_ptr<ioHandle::attrDataType> attrData,
27 std::unique_ptr<acl::list> aclData) {
28 return std::unique_ptr<base::writer>(
new writerPosixFile(path, mightAppend,
29 sourceSize, readBlockSize,
38 std::unique_ptr<ioHandle::attrDataType> attrData,
39 std::unique_ptr<acl::list> aclData) {
48 "required fsid of output file system", 0);
50 "bit mask for required fsid of output file system", 0);
52 "duration after which file operations are considered slow",
53 std::chrono::seconds(1));
56 const std::string& srcPath,
58 std::vector<std::remove_reference<decltype(path)>::type::value_type> disposable_buffer(path.c_str(), path.c_str() + path.size() + 1);
59 std::string dir(dirname(disposable_buffer.data()));
62 if (stat(dir.c_str(), &statbuf)) {
63 if (errno == ENOENT || errno == ENOTDIR) {
67 auto result = mkdir(dir.c_str(), 0777u);
70 if (result != 0 && errno == EEXIST) {
76 if (
gid != -1 ||
uid != -1) {
81 }
else if (! S_ISDIR(statbuf.st_mode)) {
83 dir,
"ensure parents",
84 "is not a directory (st_mode is ", statbuf.st_mode,
") but should be");
87 struct statvfs fsstat;
101 auto retval = lstat(path.c_str(), &dsttat);
103 if (retval && errno == ENOENT) {
108 if (S_ISDIR(dsttat.st_mode)) {
110 auto retval = rmdir(path.c_str());
119 path,
"remove",
"directory not empty but should be removed");
130 auto retval = unlink(path.c_str());
132 if (retval && errno == ENOENT) {
142 const std::string& toPath) {
144 auto retval = std::rename(fromPath.c_str(), toPath.c_str());
145 if (retval && (errno == ENOENT || errno == ENOTDIR)) {
148 throwcall::good0(retval,
"can't rename '", fromPath,
"' to '", toPath,
"'");
154 const std::unique_ptr<const genericStat>& readInitialStat,
155 const std::string& toPath,
157 struct stat fromPathStatBuf;
160 auto statRetVal = stat(fromPath.c_str(), &fromPathStatBuf);
162 if (statRetVal && (errno == ENOENT || errno == ENOTDIR)) {
163 if (readInitialStat && readInitialStat->
isDir()) {
169 }
else if (statRetVal) {
173 genericStat fromPathStat(fromPathStatBuf, std::chrono::nanoseconds(1));
174 if (!readInitialStat || (! readInitialStat->
isDir() &&
175 (fromPathStat.
size != readInitialStat->
size
178 if (readInitialStat) {
179 std::string initialTime;
180 std::string finalTime;
181 readInitialStat->
getMtime(initialTime);
185 "file (", toPath,
") changed unexpectedly ("
186 , initialTime,
" -> ", finalTime,
", "
187 , readInitialStat->
size,
" -> ", fromPathStat.
size
188 ,
", doing fresh copy");
192 "file (", toPath,
") has no initial stat, doing fresh copy");
197 auto retval = std::rename(fromPath.c_str(), toPath.c_str());
199 if (retval && (errno == ENOENT || errno == ENOTDIR)) {
200 if (readInitialStat->
isDir()) {
206 "vanished unexpectedly, doing fresh copy");
210 throwcall::good0(retval,
"can't rename '", fromPath,
"' to '", toPath,
"'");
218 const std::string& path,
221 auto retval = symlink(target.data(), path.c_str());
222 if (retval != 0 && errno != EEXIST) {
225 if (
static_cast<std::make_signed<decltype(
gid)
>::type>(
gid) != -1
226 ||
static_cast<std::make_signed<decltype(
uid)
>::type>(
uid) != -1) {
234 size_t readBlockSize,
236 bool noWrite, std::unique_ptr<ioHandle::attrDataType> aAttrData,
237 std::unique_ptr<acl::list> aAclData):
239 attrData(std::move(aAttrData)),
240 aclData(std::move(aAclData)) {
241 bool haveWriteStat =
false;
246 auto openMode = O_CREAT | O_TRUNC | O_WRONLY;
251 if (errno != ENOENT) {
255 haveWriteStat =
true;
257 sourceSize > readBlockSize) {
265 -1,
"can't open ",
path,
" for writing");
266 if (!haveWriteStat) {
271 "can't advise ",
path,
" as sequential");
273 "can't advise ",
path,
" as use only once");
278 std::unique_ptr<ioHandle::attrDataType> aAttrData,
279 std::unique_ptr<acl::list> aAclData,
282 attrData(std::move(aAttrData)),
283 aclData(std::move(aAclData)) {
288 static const std::string pattern(
"XXXXXX.tmp");
289 std::vector<char> tmpName;
290 tmpName.reserve(
path.size() + pattern.size() + 1);
291 tmpName.insert(tmpName.end(),
path.cbegin(),
path.cend());
293 tmpName.insert(tmpName.end(), pattern.cbegin(), pattern.cend());
294 tmpName.push_back(
'\0');
297 -1,
"can't open ", tmpName.data(),
" for writing");
298 aPath.replace(0,
path.size(), tmpName.data(), tmpName.size());
302 "can't advise ",
path,
" as sequential");
304 "can't advise ",
path,
" as use only once");
308 if (close(fd) != 0) {
310 path,
"close during unwind ",
311 std::system_category().default_error_condition(errno).message());
314 path,
"unlink failed copy",
"due to exception");
315 auto retval = unlink(path.c_str());
316 if (retval && errno != ENOENT) {
318 path,
"can't remove bad copy ",
319 std::system_category().default_error_condition(errno).message());
326 closeAndRemoveBadCopy();
332 }
catch (
const std::exception& e) {
334 path,
"set attr at close ",
336 closeAndRemoveBadCopy();
349 -1,
"can't seek ", path,
" to ", position);
352 return writeInitialStat.st_size == 0;
355 return writeInitialStat.st_size;
362 auto currentPosition =
throwcall::badval(lseek(fd, 0, SEEK_CUR), -1,
"lseek to determine position on", path);
364 auto holeEnd = currentPosition + b.
size();
368 "fruncate ", path,
" to ", holeEnd);
372 "can't seek to hole end (", holeEnd,
") in ", path);
376 size_t bytes_writen_so_far = 0;
378 while (bytes_writen_so_far < b.
size()) {
379 auto count = b.
size() - bytes_writen_so_far;
381 if (count > blockSize) {
387 -1,
"write failed on ", path);
389 bytes_writen_so_far += bytes_writen;
395 size_t bytes_writen_so_far = 0;
397 while (bytes_writen_so_far < b.
size()) {
398 auto count = b.
size() - bytes_writen_so_far;
400 if (count > blockSize) {
405 -1,
"write failed on ", path);
406 bytes_writen_so_far += bytes_writen;
412 struct timespec times[2];
423 auto oldmask = umask(0);
429 if (
gid != -1 ||
uid != -1) {
435 "can't set owner/group (", readInitialStat.
ownerUid,
",", readInitialStat.
ownerGid,
") of ",
443 struct timespec times[2];
444 times[0].tv_nsec = UTIME_OMIT;
447 throwcall::good0(utimensat(0, path.c_str(), times, 0),
"can't set mtime on ", path);
453 auto oldmask = umask(0);
459 if (
gid != -1 ||
uid != -1) {
473 return throwcall::badval(pathconf(dirPath.c_str(), _PC_NAME_MAX), -1,
"can't get max name lenght on", dirPath);