Coverage Report

Created: 2026-05-16 09:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/tools/source/stream/strmunx.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <stdio.h>
21
#include <fcntl.h>
22
#include <errno.h>
23
24
#include <tools/stream.hxx>
25
#include <map>
26
27
#include <mutex>
28
#include <osl/thread.h>
29
#include <sal/log.hxx>
30
31
#include <osl/file.hxx>
32
#include <osl/detail/file.h>
33
34
using namespace osl;
35
36
// InternalLock ----------------------------------------------------------------
37
38
namespace {
39
40
std::mutex& LockMutex()
41
495k
{
42
495k
    static std::mutex SINGLETON;
43
495k
    return SINGLETON;
44
495k
}
45
46
std::map<SvFileStream const *, osl::DirectoryItem> gLocks;
47
48
bool lockFile( const SvFileStream* pStream )
49
299k
{
50
299k
    osl::DirectoryItem aItem;
51
299k
    if (osl::DirectoryItem::get( pStream->GetFileName(), aItem) != osl::FileBase::E_None )
52
299k
    {
53
299k
        SAL_INFO("tools.stream", "Failed to lookup stream for locking");
54
299k
        return true;
55
299k
    }
56
57
0
    osl::FileStatus aStatus( osl_FileStatus_Mask_Type );
58
0
    if ( aItem.getFileStatus( aStatus ) != osl::FileBase::E_None )
59
0
    {
60
0
        SAL_INFO("tools.stream", "Failed to stat stream for locking");
61
0
        return true;
62
0
    }
63
0
    if( aStatus.getFileType() == osl::FileStatus::Directory )
64
0
        return true;
65
66
0
    std::unique_lock aGuard( LockMutex() );
67
0
    for( const auto& [rLockStream, rLockItem] : gLocks )
68
0
    {
69
0
        if( aItem.isIdenticalTo( rLockItem ) )
70
0
        {
71
0
            StreamMode nLockMode = rLockStream->GetStreamMode();
72
0
            StreamMode nNewMode = pStream->GetStreamMode();
73
0
            bool bDenyByOptions = (nLockMode & StreamMode::SHARE_DENYALL) ||
74
0
                ( (nLockMode & StreamMode::SHARE_DENYWRITE) && (nNewMode & StreamMode::WRITE) ) ||
75
0
                ( (nLockMode & StreamMode::SHARE_DENYREAD) && (nNewMode & StreamMode::READ) );
76
77
0
            if( bDenyByOptions )
78
0
            {
79
0
                return false; // file is already locked
80
0
            }
81
0
        }
82
0
    }
83
0
    gLocks[pStream] = aItem;
84
0
    return true;
85
0
}
86
87
void unlockFile( SvFileStream const * pStream )
88
495k
{
89
495k
    std::unique_lock aGuard( LockMutex() );
90
495k
    gLocks.erase(pStream);
91
495k
}
92
93
}
94
95
static ErrCode GetSvError( int nErrno )
96
0
{
97
0
    static struct { int nErr; ErrCode sv; } const errArr[] =
98
0
    {
99
0
        { 0,            ERRCODE_NONE },
100
0
        { EACCES,       SVSTREAM_ACCESS_DENIED },
101
0
        { EBADF,        SVSTREAM_INVALID_HANDLE },
102
#if defined(NETBSD) || \
103
    defined(FREEBSD) || defined(MACOSX) || defined(OPENBSD) || \
104
    defined(__FreeBSD_kernel__) || defined(DRAGONFLY) || \
105
    defined(IOS) || defined(HAIKU)
106
        { EDEADLK,      SVSTREAM_LOCKING_VIOLATION },
107
#else
108
0
        { EDEADLOCK,    SVSTREAM_LOCKING_VIOLATION },
109
0
#endif
110
0
        { EINVAL,       SVSTREAM_INVALID_PARAMETER },
111
0
        { EMFILE,       SVSTREAM_TOO_MANY_OPEN_FILES },
112
0
        { ENFILE,       SVSTREAM_TOO_MANY_OPEN_FILES },
113
0
        { ENOENT,       SVSTREAM_FILE_NOT_FOUND },
114
0
        { EPERM,        SVSTREAM_ACCESS_DENIED },
115
0
        { EROFS,        SVSTREAM_ACCESS_DENIED },
116
0
        { EAGAIN,       SVSTREAM_LOCKING_VIOLATION },
117
0
        { EISDIR,       SVSTREAM_PATH_NOT_FOUND },
118
0
        { ELOOP,        SVSTREAM_PATH_NOT_FOUND },
119
0
#if !defined(NETBSD) && !defined (FREEBSD) && \
120
0
    !defined(MACOSX) && !defined(OPENBSD) && !defined(__FreeBSD_kernel__) && \
121
0
    !defined(DRAGONFLY)
122
0
        { EMULTIHOP,    SVSTREAM_PATH_NOT_FOUND },
123
0
        { ENOLINK,      SVSTREAM_PATH_NOT_FOUND },
124
0
#endif
125
0
        { ENOTDIR,      SVSTREAM_PATH_NOT_FOUND },
126
0
        { ETXTBSY,      SVSTREAM_ACCESS_DENIED  },
127
0
        { EEXIST,       SVSTREAM_CANNOT_MAKE    },
128
0
        { ENOSPC,       SVSTREAM_DISK_FULL      },
129
0
        { int(0xFFFF),  SVSTREAM_GENERALERROR }
130
0
    };
131
132
0
    ErrCode nRetVal = SVSTREAM_GENERALERROR; // default error
133
0
    int i=0;
134
0
    do
135
0
    {
136
0
        if ( errArr[i].nErr == nErrno )
137
0
        {
138
0
            nRetVal = errArr[i].sv;
139
0
            break;
140
0
        }
141
0
        ++i;
142
0
    }
143
0
    while( errArr[i].nErr != 0xFFFF );
144
0
    return nRetVal;
145
0
}
146
147
static ErrCode GetSvError( oslFileError nErrno )
148
4
{
149
4
    static struct { oslFileError nErr; ErrCode sv; } const errArr[] =
150
4
    {
151
4
        { osl_File_E_None,        ERRCODE_NONE },
152
4
        { osl_File_E_ACCES,       SVSTREAM_ACCESS_DENIED },
153
4
        { osl_File_E_BADF,        SVSTREAM_INVALID_HANDLE },
154
4
        { osl_File_E_DEADLK,      SVSTREAM_LOCKING_VIOLATION },
155
4
        { osl_File_E_INVAL,       SVSTREAM_INVALID_PARAMETER },
156
4
        { osl_File_E_MFILE,       SVSTREAM_TOO_MANY_OPEN_FILES },
157
4
        { osl_File_E_NFILE,       SVSTREAM_TOO_MANY_OPEN_FILES },
158
4
        { osl_File_E_NOENT,       SVSTREAM_FILE_NOT_FOUND },
159
4
        { osl_File_E_PERM,        SVSTREAM_ACCESS_DENIED },
160
4
        { osl_File_E_ROFS,        SVSTREAM_ACCESS_DENIED },
161
4
        { osl_File_E_AGAIN,       SVSTREAM_LOCKING_VIOLATION },
162
4
        { osl_File_E_ISDIR,       SVSTREAM_PATH_NOT_FOUND },
163
4
        { osl_File_E_LOOP,        SVSTREAM_PATH_NOT_FOUND },
164
4
        { osl_File_E_MULTIHOP,    SVSTREAM_PATH_NOT_FOUND },
165
4
        { osl_File_E_NOLINK,      SVSTREAM_PATH_NOT_FOUND },
166
4
        { osl_File_E_NOTDIR,      SVSTREAM_PATH_NOT_FOUND },
167
4
        { osl_File_E_EXIST,       SVSTREAM_CANNOT_MAKE    },
168
4
        { osl_File_E_NOSPC,       SVSTREAM_DISK_FULL      },
169
4
        { oslFileError(0xFFFF),   SVSTREAM_GENERALERROR }
170
4
    };
171
172
4
    ErrCode nRetVal = SVSTREAM_GENERALERROR; // default error
173
4
    int i=0;
174
4
    do
175
29
    {
176
29
        if ( errArr[i].nErr == nErrno )
177
4
        {
178
4
            nRetVal = errArr[i].sv;
179
4
            break;
180
4
        }
181
25
        ++i;
182
25
    }
183
25
    while( errArr[i].nErr != oslFileError(0xFFFF) );
184
4
    return nRetVal;
185
4
}
186
187
SvFileStream::SvFileStream( const OUString& rFileName, StreamMode nOpenMode, std::optional<rtl_TextEncoding> oStreamEncoding )
188
495k
{
189
495k
    if (oStreamEncoding)
190
0
        SetStreamEncoding(*oStreamEncoding);
191
495k
    bIsOpen             = false;
192
495k
    m_isWritable        = false;
193
194
495k
    SetBufferSize( 1024 );
195
    // convert URL to SystemPath, if necessary
196
495k
    OUString aSystemFileName;
197
495k
    if( FileBase::getSystemPathFromFileURL( rFileName , aSystemFileName )
198
495k
        != FileBase::E_None )
199
1
    {
200
1
        aSystemFileName = rFileName;
201
1
    }
202
495k
    Open( aSystemFileName, nOpenMode );
203
495k
}
204
205
SvFileStream::SvFileStream()
206
7
{
207
7
    bIsOpen             = false;
208
7
    m_isWritable        = false;
209
7
    SetBufferSize( 1024 );
210
7
}
211
212
SvFileStream::~SvFileStream()
213
495k
{
214
495k
    Close();
215
495k
}
216
217
std::size_t SvFileStream::GetData( void* pData, std::size_t nSize )
218
42.5M
{
219
42.5M
    SAL_INFO("tools", OString::number(static_cast<sal_Int64>(nSize)) << " Bytes from " << aFilename);
220
221
42.5M
    sal_uInt64 nRead = 0;
222
42.5M
    if ( IsOpen() )
223
42.5M
    {
224
42.5M
        oslFileError rc = osl_readFile(mxFileHandle,pData,static_cast<sal_uInt64>(nSize),&nRead);
225
42.5M
        if ( rc != osl_File_E_None )
226
0
        {
227
0
            SetError( ::GetSvError( rc ));
228
0
            return -1;
229
0
        }
230
42.5M
    }
231
42.5M
    return static_cast<std::size_t>(nRead);
232
42.5M
}
233
234
std::size_t SvFileStream::PutData( const void* pData, std::size_t nSize )
235
1.11M
{
236
1.11M
    SAL_INFO("tools", OString::number(static_cast<sal_Int64>(nSize)) << " Bytes to " << aFilename);
237
238
1.11M
    sal_uInt64 nWrite = 0;
239
1.11M
    if ( IsOpen() )
240
1.11M
    {
241
1.11M
        oslFileError rc = osl_writeFile(mxFileHandle,pData,static_cast<sal_uInt64>(nSize),&nWrite);
242
1.11M
        if ( rc != osl_File_E_None )
243
0
        {
244
0
            SetError( ::GetSvError( rc ) );
245
0
            return -1;
246
0
        }
247
1.11M
        else if( !nWrite )
248
0
        {
249
0
            SetError( SVSTREAM_DISK_FULL );
250
0
            return -1;
251
0
        }
252
1.11M
    }
253
1.11M
    return static_cast<std::size_t>(nWrite);
254
1.11M
}
255
256
sal_uInt64 SvFileStream::SeekPos(sal_uInt64 const nPos)
257
71.8M
{
258
    // check if a truncated STREAM_SEEK_TO_END was passed
259
71.8M
    assert(nPos != sal_uInt64(sal_uInt32(STREAM_SEEK_TO_END)));
260
71.8M
    if ( IsOpen() )
261
71.8M
    {
262
71.8M
        oslFileError rc;
263
71.8M
        sal_uInt64 nNewPos;
264
71.8M
        if ( nPos != STREAM_SEEK_TO_END )
265
55.4M
            rc = osl_setFilePos( mxFileHandle, osl_Pos_Absolut, nPos );
266
16.3M
        else
267
16.3M
            rc = osl_setFilePos( mxFileHandle, osl_Pos_End, 0 );
268
269
71.8M
        if ( rc != osl_File_E_None )
270
0
        {
271
0
            SetError( SVSTREAM_SEEK_ERROR );
272
0
            return 0;
273
0
        }
274
71.8M
        if ( nPos != STREAM_SEEK_TO_END )
275
55.4M
            return nPos;
276
16.3M
        osl_getFilePos( mxFileHandle, &nNewPos );
277
16.3M
        return nNewPos;
278
71.8M
    }
279
0
    SetError( SVSTREAM_GENERALERROR );
280
0
    return 0;
281
71.8M
}
282
283
void SvFileStream::FlushData()
284
327k
{
285
327k
    auto rc = osl_syncFile(mxFileHandle);
286
327k
    if (rc != osl_File_E_None)
287
0
        SetError( ::GetSvError( rc ));
288
327k
 }
289
290
bool SvFileStream::LockFile()
291
495k
{
292
495k
    int nLockMode = 0;
293
294
495k
    if ( ! IsOpen() )
295
0
        return false;
296
297
495k
    if (m_eStreamMode & StreamMode::SHARE_DENYALL)
298
0
    {
299
0
        if (m_isWritable)
300
0
            nLockMode = F_WRLCK;
301
0
        else
302
0
            nLockMode = F_RDLCK;
303
0
    }
304
305
495k
    if (m_eStreamMode & StreamMode::SHARE_DENYREAD)
306
0
    {
307
0
        if (m_isWritable)
308
0
            nLockMode = F_WRLCK;
309
0
        else
310
0
        {
311
0
            SetError(SVSTREAM_LOCKING_VIOLATION);
312
0
            return false;
313
0
        }
314
0
    }
315
316
495k
    if (m_eStreamMode & StreamMode::SHARE_DENYWRITE)
317
299k
    {
318
299k
        if (m_isWritable)
319
299k
            nLockMode = F_WRLCK;
320
0
        else
321
0
            nLockMode = F_RDLCK;
322
299k
    }
323
324
495k
    if (!nLockMode)
325
196k
        return true;
326
327
299k
    if( !lockFile( this ) )
328
0
    {
329
0
        SAL_WARN("tools.stream", "InternalLock on " << aFilename << "failed");
330
0
        return false;
331
0
    }
332
333
299k
    return true;
334
299k
}
335
336
void SvFileStream::UnlockFile()
337
1.00M
{
338
1.00M
    if ( ! IsOpen() )
339
506k
        return;
340
341
495k
    unlockFile( this );
342
495k
}
343
344
void SvFileStream::Open( const OUString& rFilename, StreamMode nOpenMode )
345
495k
{
346
495k
    sal_uInt32 uFlags;
347
495k
    oslFileHandle nHandleTmp;
348
349
495k
    Close();
350
495k
    errno = 0;
351
495k
    m_eStreamMode = nOpenMode;
352
495k
    m_eStreamMode &= ~StreamMode::TRUNC; // don't truncate on reopen
353
354
495k
    aFilename = rFilename;
355
356
495k
    SAL_INFO("tools", aFilename);
357
358
495k
    OUString aFileURL;
359
495k
    osl::DirectoryItem aItem;
360
495k
    osl::FileStatus aStatus( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_LinkTargetURL );
361
362
    // FIXME: we really need to switch to a pure URL model ...
363
495k
    if ( osl::File::getFileURLFromSystemPath( aFilename, aFileURL ) != osl::FileBase::E_None )
364
0
        aFileURL = aFilename;
365
366
    // don't both stat()ing a temporary file, unnecessary
367
495k
    bool bStatValid = true;
368
495k
    if (!(nOpenMode & StreamMode::TEMPORARY))
369
10.5k
    {
370
10.5k
        bStatValid = ( osl::DirectoryItem::get( aFileURL, aItem) == osl::FileBase::E_None &&
371
10.5k
                        aItem.getFileStatus( aStatus ) == osl::FileBase::E_None );
372
373
        // SvFileStream can't open a directory
374
10.5k
        if( bStatValid && aStatus.getFileType() == osl::FileStatus::Directory )
375
0
        {
376
0
            SetError( ::GetSvError( EISDIR ) );
377
0
            return;
378
0
        }
379
10.5k
    }
380
381
495k
    if ( !( nOpenMode & StreamMode::WRITE ) )
382
4
        uFlags = osl_File_OpenFlag_Read;
383
495k
    else if ( !( nOpenMode & StreamMode::READ ) )
384
10.3k
        uFlags = osl_File_OpenFlag_Write;
385
485k
    else
386
485k
        uFlags = osl_File_OpenFlag_Read | osl_File_OpenFlag_Write;
387
388
    // Fix (MDA, 18.01.95): Don't open with O_CREAT upon RD_ONLY
389
    // Important for Read-Only-Filesystems (e.g,  CDROM)
390
495k
    if ( (!( nOpenMode & StreamMode::NOCREATE )) && ( uFlags != osl_File_OpenFlag_Read ) )
391
495k
        uFlags |= osl_File_OpenFlag_Create;
392
495k
    if ( nOpenMode & StreamMode::TRUNC )
393
0
        uFlags |= osl_File_OpenFlag_Trunc;
394
395
495k
    uFlags |= osl_File_OpenFlag_NoExcl | osl_File_OpenFlag_NoLock;
396
397
495k
    if ( nOpenMode & StreamMode::WRITE)
398
495k
    {
399
495k
        if ( nOpenMode & StreamMode::COPY_ON_SYMLINK )
400
0
        {
401
0
            if ( bStatValid && aStatus.getFileType() == osl::FileStatus::Link &&
402
0
                 aStatus.getLinkTargetURL().getLength() > 0 )
403
0
            {
404
                // delete the symbolic link, and replace it with the contents of the link
405
0
                if (osl::File::remove( aFileURL ) == osl::FileBase::E_None )
406
0
                {
407
0
                    File::copy( aStatus.getLinkTargetURL(), aFileURL );
408
0
                    SAL_INFO("tools.stream",
409
0
                        "Removing link and replacing with file contents (" <<
410
0
                        aStatus.getLinkTargetURL() << ") -> (" << aFileURL << ").");
411
0
                }
412
0
            }
413
0
        }
414
495k
    }
415
416
495k
    oslFileError rc = osl_openFile( aFileURL.pData, &nHandleTmp, uFlags );
417
495k
    if ( rc != osl_File_E_None )
418
4
    {
419
4
        if ( uFlags & osl_File_OpenFlag_Write )
420
0
        {
421
            // Change to read-only
422
0
            uFlags &= ~osl_File_OpenFlag_Write;
423
0
            rc = osl_openFile( aFileURL.pData, &nHandleTmp, uFlags );
424
0
        }
425
4
    }
426
495k
    if ( rc == osl_File_E_None )
427
495k
    {
428
495k
        mxFileHandle = nHandleTmp;
429
495k
        bIsOpen = true;
430
495k
        if ( uFlags & osl_File_OpenFlag_Write )
431
495k
            m_isWritable = true;
432
433
495k
        if ( !LockFile() ) // whole file
434
0
        {
435
0
            osl_closeFile( nHandleTmp );
436
0
            bIsOpen = false;
437
0
            m_isWritable = false;
438
0
            mxFileHandle = nullptr;
439
0
        }
440
495k
    }
441
4
    else
442
4
        SetError( ::GetSvError( rc ) );
443
495k
}
444
445
void SvFileStream::Close()
446
1.00M
{
447
1.00M
    UnlockFile();
448
449
1.00M
    if ( IsOpen() )
450
495k
    {
451
495k
        SAL_INFO("tools", "Closing " << aFilename);
452
495k
        FlushBuffer();
453
495k
        osl_closeFile( mxFileHandle );
454
495k
        mxFileHandle = nullptr;
455
495k
    }
456
457
1.00M
    bIsOpen     = false;
458
1.00M
    m_isWritable = false;
459
1.00M
    SvStream::ClearBuffer();
460
1.00M
    SvStream::ClearError();
461
1.00M
}
462
463
/// set filepointer to beginning of file
464
void SvFileStream::ResetError()
465
5.88k
{
466
5.88k
    SvStream::ClearError();
467
5.88k
}
468
469
void SvFileStream::SetSize (sal_uInt64 const nSize)
470
136
{
471
136
    if (IsOpen())
472
136
    {
473
136
        oslFileError rc = osl_setFileSize( mxFileHandle, nSize );
474
136
        if (rc != osl_File_E_None )
475
0
        {
476
0
            SetError ( ::GetSvError( rc ));
477
0
        }
478
136
    }
479
136
}
480
481
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */