Newer
Older
* @file llfile.cpp
* @author Michael Schlachter
* @date 2006-03-23
* @brief Implementation of cross-platform POSIX file buffer and c++
* stream classes.
*
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Copyright (C) 2010, Linden Research, Inc.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
Bryan O'Sullivan
committed
#if LL_WINDOWS
#include "llwin32headerslean.h"
#include <stdlib.h> // Windows errno
#include <vector>
#else
#include <errno.h>
Bryan O'Sullivan
committed
#endif
#include "linden_common.h"
#include "llfile.h"
#include "llstring.h"
#include "llerror.h"
#include "stringize.h"
static std::string empty;
// Many of the methods below use OS-level functions that mess with errno. Wrap
// variants of strerror() to report errors.
#if LL_WINDOWS
// On Windows, use strerror_s().
std::string strerr(int errn)
{
char buffer[256];
strerror_s(buffer, errn); // infers sizeof(buffer) -- love it!
return buffer;
}
Don Kjer
committed
typedef std::basic_ios<char,std::char_traits < char > > _Myios;
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#else
// On Posix we want to call strerror_r(), but alarmingly, there are two
// different variants. The one that returns int always populates the passed
// buffer (except in case of error), whereas the other one always returns a
// valid char* but might or might not populate the passed buffer. How do we
// know which one we're getting? Define adapters for each and let the compiler
// select the applicable adapter.
// strerror_r() returns char*
std::string message_from(int /*orig_errno*/, const char* /*buffer*/, size_t /*bufflen*/,
const char* strerror_ret)
{
return strerror_ret;
}
// strerror_r() returns int
std::string message_from(int orig_errno, const char* buffer, size_t bufflen,
int strerror_ret)
{
if (strerror_ret == 0)
{
return buffer;
}
// Here strerror_r() has set errno. Since strerror_r() has already failed,
// seems like a poor bet to call it again to diagnose its own error...
int stre_errno = errno;
if (stre_errno == ERANGE)
{
return STRINGIZE("strerror_r() can't explain errno " << orig_errno
<< " (" << bufflen << "-byte buffer too small)");
}
if (stre_errno == EINVAL)
{
return STRINGIZE("unknown errno " << orig_errno);
}
// Here we don't even understand the errno from strerror_r()!
return STRINGIZE("strerror_r() can't explain errno " << orig_errno
<< " (error " << stre_errno << ')');
}
std::string strerr(int errn)
{
char buffer[256];
// Select message_from() function matching the strerror_r() we have on hand.
return message_from(errn, buffer, sizeof(buffer),
strerror_r(errn, buffer, sizeof(buffer)));
}
#endif // ! LL_WINDOWS
// On either system, shorthand call just infers global 'errno'.
std::string strerr()
{
return strerr(errno);
}
int warnif(const std::string& desc, const std::string& filename, int rc, int accept=0)
{
if (rc < 0)
{
// Capture errno before we start emitting output
int errn = errno;
// For certain operations, a particular errno value might be
// acceptable -- e.g. stat() could permit ENOENT, mkdir() could permit
// EEXIST. Don't warn if caller explicitly says this errno is okay.
if (errn != accept)
{
LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename
<< "' (errno " << errn << "): " << strerr(errn) << LL_ENDL;
}
#if 0 && LL_WINDOWS // turn on to debug file-locking problems
// If the problem is "Permission denied," maybe it's because another
// process has the file open. Try to find out.
if (errn == EACCES) // *not* EPERM
{
// Only do any of this stuff (before LL_ENDL) if it will be logged.
LL_DEBUGS("LLFile") << empty;
// would be nice to use LLDir for this, but dependency goes the
// wrong way
const char* TEMP = LLFile::tmpdir();
if (! (TEMP && *TEMP))
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
{
LL_CONT << "No $TEMP, not running 'handle'";
}
else
{
std::string tf(TEMP);
tf += "\\handle.tmp";
// http://technet.microsoft.com/en-us/sysinternals/bb896655
std::string cmd(STRINGIZE("handle \"" << filename
// "openfiles /query /v | fgrep -i \"" << filename
<< "\" > \"" << tf << '"'));
LL_CONT << cmd;
if (system(cmd.c_str()) != 0)
{
LL_CONT << "\nDownload 'handle.exe' from http://technet.microsoft.com/en-us/sysinternals/bb896655";
}
else
{
std::ifstream inf(tf);
std::string line;
while (std::getline(inf, line))
{
LL_CONT << '\n' << line;
}
}
LLFile::remove(tf);
}
LL_CONT << LL_ENDL;
}
#endif // LL_WINDOWS hack to identify processes holding file open
}
return rc;
}
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
int warnif(const std::string& desc, const boost::filesystem::path& filename, int rc, int accept = 0)
{
if (rc < 0)
{
// Capture errno before we start emitting output
int errn = errno;
// For certain operations, a particular errno value might be
// acceptable -- e.g. stat() could permit ENOENT, mkdir() could permit
// EEXIST. Don't warn if caller explicitly says this errno is okay.
if (errn != accept)
{
#if LL_WINDOWS
LL_WARNS("LLFile") << "Couldn't " << desc << " '" << ll_convert_wide_to_string(filename.native())
<< "' (errno " << errn << "): " << strerr(errn) << LL_ENDL;
#else
LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename.native()
<< "' (errno " << errn << "): " << strerr(errn) << LL_ENDL;
#endif
}
#if 0 && LL_WINDOWS // turn on to debug file-locking problems
// If the problem is "Permission denied," maybe it's because another
// process has the file open. Try to find out.
if (errn == EACCES) // *not* EPERM
{
// Only do any of this stuff (before LL_ENDL) if it will be logged.
LL_DEBUGS("LLFile") << empty;
// would be nice to use LLDir for this, but dependency goes the
// wrong way
const char* TEMP = LLFile::tmpdir();
if (!(TEMP && *TEMP))
{
LL_CONT << "No $TEMP, not running 'handle'";
}
else
{
std::string tf(TEMP);
tf += "\\handle.tmp";
// http://technet.microsoft.com/en-us/sysinternals/bb896655
std::string cmd(STRINGIZE("handle \"" << filename
// "openfiles /query /v | fgrep -i \"" << filename
<< "\" > \"" << tf << '"'));
LL_CONT << cmd;
if (system(cmd.c_str()) != 0)
{
LL_CONT << "\nDownload 'handle.exe' from http://technet.microsoft.com/en-us/sysinternals/bb896655";
}
else
{
std::ifstream inf(tf);
std::string line;
while (std::getline(inf, line))
{
LL_CONT << '\n' << line;
}
}
LLFile::remove(tf);
}
LL_CONT << LL_ENDL;
}
#endif // LL_WINDOWS hack to identify processes holding file open
}
return rc;
}
int LLFile::mkdir(const std::string& dirname, int perms)
#if LL_WINDOWS
std::wstring utf16dirname = ll_convert_string_to_wide(dirname);
int rc = _wmkdir(utf16dirname.c_str());
int rc = ::mkdir(dirname.c_str(), (mode_t)perms);
// We often use mkdir() to ensure the existence of a directory that might
// already exist. There is no known case in which we want to call out as
// an error the requested directory already existing.
if (rc < 0 && errno == EEXIST)
{
// this is not the error you want, move along
return 0;
}
// anything else might be a problem
return warnif("mkdir", dirname, rc, EEXIST);
int LLFile::rmdir(const std::string& dirname)
#if LL_WINDOWS
// permissions are ignored on Windows
std::wstring utf16dirname = ll_convert_string_to_wide(dirname);
int rc = _wrmdir(utf16dirname.c_str());
int rc = ::rmdir(dirname.c_str());
return warnif("rmdir", dirname, rc);
LLFILE* LLFile::fopen(const std::string& filename, const char* mode) /* Flawfinder: ignore */
std::wstring utf16filename = ll_convert_string_to_wide(filename);
std::wstring utf16mode = ll_convert_string_to_wide(mode);
return _wfopen(utf16filename.c_str(),utf16mode.c_str());
#else
return ::fopen(filename.c_str(),mode); /* Flawfinder: ignore */
// static
LLFILE* LLFile::fopen(const boost::filesystem::path& filename, MODE_T accessmode) /* Flawfinder: ignore */
{
#if LL_WINDOWS
return _wfopen(filename.c_str(), accessmode);
#else
return ::fopen(filename.c_str(), accessmode); /* Flawfinder: ignore */
#endif
}
LLFILE* LLFile::_fsopen(const std::string& filename, const char* mode, int sharingFlag)
std::wstring utf16filename = ll_convert_string_to_wide(filename);
std::wstring utf16mode = ll_convert_string_to_wide(mode);
return _wfsopen(utf16filename.c_str(),utf16mode.c_str(),sharingFlag);
#else
llassert(0);//No corresponding function on non-windows
return NULL;
#endif
}
int LLFile::close(LLFILE * file)
{
int ret_value = 0;
if (file)
{
ret_value = fclose(file);
}
return ret_value;
}
int LLFile::remove(const std::string& filename, int supress_error)
std::wstring utf16filename = ll_convert_string_to_wide(filename);
int rc = _wremove(utf16filename.c_str());
int rc = ::remove(filename.c_str());
return warnif("remove", filename, rc, supress_error);
int LLFile::remove(const boost::filesystem::path& filename, int supress_error)
{
#if LL_WINDOWS
int rc = _wremove(filename.c_str());
#else
int rc = ::remove(filename.c_str());
#endif
return warnif("remove", filename, rc, supress_error);
}
andreykproductengine
committed
int LLFile::rename(const std::string& filename, const std::string& newname, int supress_error)
std::wstring utf16filename = ll_convert_string_to_wide(filename);
std::wstring utf16newname = ll_convert_string_to_wide(newname);
int rc = _wrename(utf16filename.c_str(),utf16newname.c_str());
int rc = ::rename(filename.c_str(),newname.c_str());
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// Note: This workaround generalises the solution previously applied in llxfer_file.
// In doing this we add more failure modes to the operation, the copy can fail, the unlink can fail, in fact the copy can fail for multiple reasons.
// "A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools." - Douglas Adams, Mostly harmless
if (rc)
{
S32 error_number = errno;
LL_INFOS("LLFile") << "Rename failure (" << error_number << ") - " << filename << " to " << newname << LL_ENDL;
if (EXDEV == error_number)
{
if (copy(filename, newname)==true) // sigh in their wisdom LL decided that copy returns bool true not 0 whjen no error. using == true to make that clear.
{
LL_INFOS("LLFile") << "Rename across mounts not supported; copying+unlinking the file instead." << LL_ENDL;
rc = LLFile::remove(filename);
if (rc)
{
LL_WARNS("LLFile") << "unlink failed during copy/unlink workaround. Please check for stray file: " << filename << LL_ENDL;
}
}
else
{
LL_WARNS("LLFile") << "Copy failure during rename workaround for rename " << filename << " to " << newname << " (check both filenames and maunally rectify)" << LL_ENDL;
}
rc=0; // We need to reset rc here to avoid the higher level function taking corrective action on what could be bad files.
}
else
{
LL_WARNS("LLFile") << "Rename fatally failed, no workaround attempted for errno="
<< errno << "." << LL_ENDL;
// rc will propogate alllowing corrective action above. Not entirely happy with this but it is what already happens so we're not making it worse.
}
}
andreykproductengine
committed
return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, supress_error);
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
int LLFile::rename(const boost::filesystem::path& filename, const boost::filesystem::path& newname, int supress_error)
{
#if LL_WINDOWS
int rc = _wrename(filename.c_str(), newname.c_str());
return warnif(STRINGIZE("rename to '" << ll_convert_wide_to_string(newname.native()) << "' from"), filename, rc, supress_error);
#else
int rc = ::rename(filename.c_str(), newname.c_str());
// Note: This workaround generalises the solution previously applied in llxfer_file.
// In doing this we add more failure modes to the operation, the copy can fail, the unlink can fail, in fact the copy can fail for multiple reasons.
// "A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools." - Douglas Adams, Mostly harmless
if (rc)
{
S32 error_number = errno;
LL_INFOS("LLFile") << "Rename failure (" << error_number << ") - " << filename << " to " << newname << LL_ENDL;
if (EXDEV == error_number)
{
if (copy(filename, newname) == true) // sigh in their wisdom LL decided that copy returns bool true not 0 whjen no error. using == true to make that clear.
{
LL_INFOS("LLFile") << "Rename across mounts not supported; copying+unlinking the file instead." << LL_ENDL;
rc = LLFile::remove(filename);
if (rc)
{
LL_WARNS("LLFile") << "unlink failed during copy/unlink workaround. Please check for stray file: " << filename << LL_ENDL;
}
}
else
{
LL_WARNS("LLFile") << "Copy failure during rename workaround for rename " << filename << " to " << newname << " (check both filenames and maunally rectify)" << LL_ENDL;
}
rc = 0; // We need to reset rc here to avoid the higher level function taking corrective action on what could be bad files.
}
else
{
LL_WARNS("LLFile") << "Rename fatally failed, no workaround attempted for errno="
<< errno << "." << LL_ENDL;
// rc will propogate alllowing corrective action above. Not entirely happy with this but it is what already happens so we're not making it worse.
}
}
return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, supress_error);
#endif
}
bool LLFile::copy(const std::string& from, const std::string& to)
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
{
bool copied = false;
LLFILE* in = LLFile::fopen(from, "rb"); /* Flawfinder: ignore */
if (in)
{
LLFILE* out = LLFile::fopen(to, "wb"); /* Flawfinder: ignore */
if (out)
{
char buf[16384]; /* Flawfinder: ignore */
size_t readbytes;
bool write_ok = true;
while(write_ok && (readbytes = fread(buf, 1, 16384, in))) /* Flawfinder: ignore */
{
if (fwrite(buf, 1, readbytes, out) != readbytes)
{
LL_WARNS("LLFile") << "Short write" << LL_ENDL;
write_ok = false;
}
}
if ( write_ok )
{
copied = true;
}
fclose(out);
}
fclose(in);
}
return copied;
}
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
bool LLFile::copy(const boost::filesystem::path& from, const boost::filesystem::path& to)
{
bool copied = false;
LLFILE* in = LLFile::fopen(from, TEXT("rb")); /* Flawfinder: ignore */
if (in)
{
LLFILE* out = LLFile::fopen(to, TEXT("wb")); /* Flawfinder: ignore */
if (out)
{
char buf[16384]; /* Flawfinder: ignore */
size_t readbytes;
bool write_ok = true;
while (write_ok && (readbytes = fread(buf, 1, 16384, in))) /* Flawfinder: ignore */
{
if (fwrite(buf, 1, readbytes, out) != readbytes)
{
LL_WARNS("LLFile") << "Short write" << LL_ENDL;
write_ok = false;
}
}
if (write_ok)
{
copied = true;
}
fclose(out);
}
fclose(in);
}
return copied;
}
int LLFile::stat(const std::string& filename, llstat* filestatus)
std::wstring utf16filename = ll_convert_string_to_wide(filename);
int rc = _wstat(utf16filename.c_str(),filestatus);
int rc = ::stat(filename.c_str(),filestatus);
// We use stat() to determine existence (see isfile(), isdir()).
// Don't spam the log if the subject pathname doesn't exist.
return warnif("stat", filename, rc, ENOENT);
int LLFile::stat(const boost::filesystem::path& filename, llstat* filestatus)
{
#if LL_WINDOWS
int rc = _wstat(filename.c_str(), filestatus);
#else
int rc = ::stat(filename.c_str(), filestatus);
#endif
// We use stat() to determine existence (see isfile(), isdir()).
// Don't spam the log if the subject pathname doesn't exist.
return warnif("stat", filename, rc, ENOENT);
}
bool LLFile::isdir(const std::string& filename)
Bryan O'Sullivan
committed
{
llstat st;
Bryan O'Sullivan
committed
return stat(filename, &st) == 0 && S_ISDIR(st.st_mode);
}
bool LLFile::isfile(const std::string& filename)
Bryan O'Sullivan
committed
{
llstat st;
Bryan O'Sullivan
committed
return stat(filename, &st) == 0 && S_ISREG(st.st_mode);
}
const char *LLFile::tmpdir()
{
static std::string utf8path;
if (utf8path.empty())
{
char sep;
#if LL_WINDOWS
sep = '\\';
std::vector<wchar_t> utf16path(MAX_PATH + 1);
GetTempPathW(utf16path.size(), &utf16path[0]);
utf8path = ll_convert_wide_to_string(&utf16path[0]);
Bryan O'Sullivan
committed
#else
sep = '/';
utf8path = LLStringUtil::getenv("TMPDIR", "/tmp/");
Bryan O'Sullivan
committed
#endif
if (utf8path[utf8path.size() - 1] != sep)
{
utf8path += sep;
}
}
return utf8path.c_str();
}
/***************** Modified file stream created to overcome the incorrect behaviour of posix fopen in windows *******************/
Don Kjer
committed
#if LL_WINDOWS
Don Kjer
committed
LLFILE * LLFile::_Fiopen(const std::string& filename,
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
{ // open a file
static const char *mods[] =
{ // fopen mode strings corresponding to valid[i]
"r", "w", "w", "a", "rb", "wb", "wb", "ab",
"r+", "w+", "a+", "r+b", "w+b", "a+b",
0};
static const int valid[] =
{ // valid combinations of open flags
ios_base::in,
ios_base::out,
ios_base::out | ios_base::trunc,
ios_base::out | ios_base::app,
ios_base::in | ios_base::binary,
ios_base::out | ios_base::binary,
ios_base::out | ios_base::trunc | ios_base::binary,
ios_base::out | ios_base::app | ios_base::binary,
ios_base::in | ios_base::out,
ios_base::in | ios_base::out | ios_base::trunc,
ios_base::in | ios_base::out | ios_base::app,
ios_base::in | ios_base::out | ios_base::binary,
ios_base::in | ios_base::out | ios_base::trunc
| ios_base::binary,
ios_base::in | ios_base::out | ios_base::app
| ios_base::binary,
0};
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
int n;
ios_base::openmode atendflag = mode & ios_base::ate;
ios_base::openmode norepflag = mode & ios_base::_Noreplace;
if (mode & ios_base::_Nocreate)
mode |= ios_base::in; // file must exist
mode &= ~(ios_base::ate | ios_base::_Nocreate | ios_base::_Noreplace);
for (n = 0; valid[n] != 0 && valid[n] != mode; ++n)
; // look for a valid mode
if (valid[n] == 0)
return (0); // no valid mode
else if (norepflag && mode & (ios_base::out || ios_base::app)
&& (fp = LLFile::fopen(filename, "r")) != 0) /* Flawfinder: ignore */
{ // file must not exist, close and fail
fclose(fp);
return (0);
}
else if (fp != 0 && fclose(fp) != 0)
return (0); // can't close after test open
// should open with protection here, if other than default
else if ((fp = LLFile::fopen(filename, mods[n])) == 0) /* Flawfinder: ignore */
return (0); // open failed
if (!atendflag || fseek(fp, 0, SEEK_END) == 0)
return (fp); // no need to seek to end, or seek succeeded
fclose(fp); // can't position at end
return (0);
}
Don Kjer
committed
#endif /* LL_WINDOWS */
Oz Linden
committed
#if LL_WINDOWS
/************** input file stream ********************************/
llifstream::llifstream() {}
Oz Linden
committed
// explicit
llifstream::llifstream(const std::string& _Filename, ios_base::openmode _Mode):
std::ifstream(ll_convert_string_to_wide( _Filename ).c_str(),
_Mode | ios_base::in)
Oz Linden
committed
{
}
void llifstream::open(const std::string& _Filename, ios_base::openmode _Mode)
Oz Linden
committed
{
std::ifstream::open(ll_convert_string_to_wide(_Filename).c_str(),
_Mode | ios_base::in);
Oz Linden
committed
}
/************** output file stream ********************************/
llofstream::llofstream() {}
Oz Linden
committed
// explicit
llofstream::llofstream(const std::string& _Filename, ios_base::openmode _Mode):
std::ofstream(ll_convert_string_to_wide( _Filename ).c_str(),
_Mode | ios_base::out)
Oz Linden
committed
{
}
void llofstream::open(const std::string& _Filename, ios_base::openmode _Mode)
Oz Linden
committed
{
std::ofstream::open(ll_convert_string_to_wide( _Filename ).c_str(),
_Mode | ios_base::out);
Oz Linden
committed
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
}
/************** helper functions ********************************/
std::streamsize llifstream_size(llifstream& ifstr)
{
if(!ifstr.is_open()) return 0;
std::streampos pos_old = ifstr.tellg();
ifstr.seekg(0, ios_base::beg);
std::streampos pos_beg = ifstr.tellg();
ifstr.seekg(0, ios_base::end);
std::streampos pos_end = ifstr.tellg();
ifstr.seekg(pos_old, ios_base::beg);
return pos_end - pos_beg;
}
std::streamsize llofstream_size(llofstream& ofstr)
{
if(!ofstr.is_open()) return 0;
std::streampos pos_old = ofstr.tellp();
ofstr.seekp(0, ios_base::beg);
std::streampos pos_beg = ofstr.tellp();
ofstr.seekp(0, ios_base::end);
std::streampos pos_end = ofstr.tellp();
ofstr.seekp(pos_old, ios_base::beg);
return pos_end - pos_beg;
}
#endif // LL_WINDOWS