summaryrefslogtreecommitdiff
path: root/indra/llcommon/llfile.cpp
blob: ddf239f3069afc62bf75d74100de6c2646e420e1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
138
139
140
141
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
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
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
380
381
382
383
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
426
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
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
488
489
490
491
492
493
494
/**
 * @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$
 * Second Life Viewer Source Code
 * 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
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "llfile.h"
#include "llstring.h"
#include "llerror.h"
#include "stringize.h"

#if LL_WINDOWS
#include "llwin32headerslean.h"
#include <stdlib.h>                 // Windows errno
#include <vector>
#else
#include <errno.h>
#endif

using namespace std;

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;
}

typedef std::basic_ios<char,std::char_traits < char > > _Myios;

#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))
            {
                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;
}

// static
int LLFile::mkdir(const std::string& dirname, int perms)
{
#if LL_WINDOWS
    // permissions are ignored on Windows
    std::string utf8dirname = dirname;
    llutf16string utf16dirname = utf8str_to_utf16str(utf8dirname);
    int rc = _wmkdir(utf16dirname.c_str());
#else
    int rc = ::mkdir(dirname.c_str(), (mode_t)perms);
#endif
    // 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);
}

// static
int LLFile::rmdir(const std::string& dirname)
{
#if LL_WINDOWS
    // permissions are ignored on Windows
    std::string utf8dirname = dirname;
    llutf16string utf16dirname = utf8str_to_utf16str(utf8dirname);
    int rc = _wrmdir(utf16dirname.c_str());
#else
    int rc = ::rmdir(dirname.c_str());
#endif
    return warnif("rmdir", dirname, rc);
}

// static
LLFILE* LLFile::fopen(const std::string& filename, const char* mode)    /* Flawfinder: ignore */
{
#if LL_WINDOWS
    std::string utf8filename = filename;
    std::string utf8mode = std::string(mode);
    llutf16string utf16filename = utf8str_to_utf16str(utf8filename);
    llutf16string utf16mode = utf8str_to_utf16str(utf8mode);
    return _wfopen(utf16filename.c_str(),utf16mode.c_str());
#else
    return ::fopen(filename.c_str(),mode);  /* Flawfinder: ignore */
#endif
}

LLFILE* LLFile::_fsopen(const std::string& filename, const char* mode, int sharingFlag)
{
#if LL_WINDOWS
    std::string utf8filename = filename;
    std::string utf8mode = std::string(mode);
    llutf16string utf16filename = utf8str_to_utf16str(utf8filename);
    llutf16string utf16mode = utf8str_to_utf16str(utf8mode);
    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)
{
#if LL_WINDOWS
    std::string utf8filename = filename;
    llutf16string utf16filename = utf8str_to_utf16str(utf8filename);
    int rc = _wremove(utf16filename.c_str());
#else
    int rc = ::remove(filename.c_str());
#endif
    return warnif("remove", filename, rc, supress_error);
}

int LLFile::rename(const std::string& filename, const std::string& newname, int supress_error)
{
#if LL_WINDOWS
    std::string utf8filename = filename;
    std::string utf8newname = newname;
    llutf16string utf16filename = utf8str_to_utf16str(utf8filename);
    llutf16string utf16newname = utf8str_to_utf16str(utf8newname);
    int rc = _wrename(utf16filename.c_str(),utf16newname.c_str());
#else
    int rc = ::rename(filename.c_str(),newname.c_str());
#endif
    return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, supress_error);
}

bool LLFile::copy(const std::string from, const std::string to)
{
    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;
}

int LLFile::stat(const std::string& filename, llstat* filestatus)
{
#if LL_WINDOWS
    std::string utf8filename = filename;
    llutf16string utf16filename = utf8str_to_utf16str(utf8filename);
    int rc = _wstat(utf16filename.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)
{
    llstat st;

    return stat(filename, &st) == 0 && S_ISDIR(st.st_mode);
}

bool LLFile::isfile(const std::string& filename)
{
    llstat st;

    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(static_cast<DWORD>(utf16path.size()), &utf16path[0]);
        utf8path = ll_convert_wide_to_string(&utf16path[0]);
#else
        sep = '/';

        utf8path = LLStringUtil::getenv("TMPDIR", "/tmp/");
#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 *******************/

#if LL_WINDOWS

LLFILE *    LLFile::_Fiopen(const std::string& filename,
        std::ios::openmode mode)
{   // 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};

    LLFILE *fp = 0;
    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);
}

#endif /* LL_WINDOWS */


#if LL_WINDOWS
/************** input file stream ********************************/

llifstream::llifstream() {}

// explicit
llifstream::llifstream(const std::string& _Filename, ios_base::openmode _Mode):
    std::ifstream(utf8str_to_utf16str( _Filename ).c_str(),
                  _Mode | ios_base::in)
{
}

void llifstream::open(const std::string& _Filename, ios_base::openmode _Mode)
{
    std::ifstream::open(utf8str_to_utf16str(_Filename).c_str(),
                        _Mode | ios_base::in);
}


/************** output file stream ********************************/


llofstream::llofstream() {}

// explicit
llofstream::llofstream(const std::string& _Filename, ios_base::openmode _Mode):
    std::ofstream(utf8str_to_utf16str( _Filename ).c_str(),
                  _Mode | ios_base::out)
{
}

void llofstream::open(const std::string& _Filename, ios_base::openmode _Mode)
{
    std::ofstream::open(utf8str_to_utf16str( _Filename ).c_str(),
                        _Mode | ios_base::out);
}

/************** 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