sfx2 store: avoid rename() for files with >1 hard link count

Rename would break the hard link; in practice e.g. Online's 'make run'
debug feature depends on not breaking hardlinks.

Change-Id: Id3ac19493b6b473274b7ed53a4ca06fc34bc1668
Reviewed-on: https://gerrit.libreoffice.org/51858
Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
Tested-by: Jenkins <ci@libreoffice.org>
diff --git a/sfx2/qa/cppunit/test_misc.cxx b/sfx2/qa/cppunit/test_misc.cxx
index a1ecc01..72f21fc 100644
--- a/sfx2/qa/cppunit/test_misc.cxx
+++ b/sfx2/qa/cppunit/test_misc.cxx
@@ -11,6 +11,7 @@

#ifndef _WIN32
#include <sys/stat.h>
#include <unistd.h>
#endif
#include <memory>

@@ -51,6 +52,7 @@ public:
    virtual void setUp() override;
    void testODFCustomMetadata();
    void testNoThumbnail();
    void testHardLinks();

    virtual void registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) override
    {
@@ -66,6 +68,7 @@ public:
    CPPUNIT_TEST_SUITE(MiscTest);
    CPPUNIT_TEST(testODFCustomMetadata);
    CPPUNIT_TEST(testNoThumbnail);
    CPPUNIT_TEST(testHardLinks);
    CPPUNIT_TEST_SUITE_END();

private:
@@ -147,6 +150,35 @@ void MiscTest::testNoThumbnail()
    xComponent->dispose();
}

void MiscTest::testHardLinks()
{
#ifndef _WIN32
    OUString aSourceDir = m_directories.getURLFromSrc("/sfx2/qa/cppunit/misc/");
    OUString aTargetDir = m_directories.getURLFromWorkdir("/CppunitTest/sfx2_misc.test.user/");
    const OUString aURL(aTargetDir + "hello.odt");
    osl::File::copy(aSourceDir + "hello.odt", aURL);
    OUString aTargetPath;
    osl::FileBase::getSystemPathFromFileURL(aURL, aTargetPath);
    OString aOld = aTargetPath.toUtf8();
    aTargetPath += ".2";
    OString aNew = aTargetPath.toUtf8();
    link(aOld.getStr(), aNew.getStr());

    uno::Reference<lang::XComponent> xComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument");
    CPPUNIT_ASSERT(xComponent.is());

    uno::Reference<frame::XStorable> xStorable(xComponent, uno::UNO_QUERY);
    xStorable->store();

    struct stat buf;
    stat(aOld.getStr(), &buf);
    // This failed: hard link count was 1, the hard link broke on store.
    CPPUNIT_ASSERT(buf.st_nlink > 1);

    xComponent->dispose();
#endif
}

CPPUNIT_TEST_SUITE_REGISTRATION(MiscTest);

}
diff --git a/sfx2/source/doc/docfile.cxx b/sfx2/source/doc/docfile.cxx
index 63ce058..d3f78d2 100644
--- a/sfx2/source/doc/docfile.cxx
+++ b/sfx2/source/doc/docfile.cxx
@@ -19,6 +19,10 @@

#include <config_features.h>

#ifdef UNX
#include <sys/stat.h>
#endif

#include <sfx2/docfile.hxx>
#include <sfx2/signaturestate.hxx>

@@ -190,6 +194,28 @@ sal_uInt64 GetDefaultFileAttributes(const OUString& rURL)
    return nRet;
}

/// Determines if rURL is a non-hard-linked file:// URL.
bool IsNotHardLinkedFile(const OUString& rURL)
{
    if (!comphelper::isFileUrl(rURL))
        return false;

#ifdef UNX
    OUString rPath;
    if (osl::FileBase::getSystemPathFromFileURL(rURL, rPath) != osl::FileBase::E_None)
        return false;

    struct stat buf;
    if (stat(rPath.toUtf8().getStr(), &buf) != 0)
        return false;

    if (buf.st_nlink > 1)
        return false;
#endif

    return true;
}

} // anonymous namespace

class SfxMedium_Impl
@@ -1812,7 +1838,7 @@ void SfxMedium::TransactedTransferForFS_Impl( const INetURLObject& aSource,
                OUString aDestMainURL = aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE);

                sal_uInt64 nAttributes = GetDefaultFileAttributes(aDestMainURL);
                if (comphelper::isFileUrl(aDestMainURL) && osl::File::move(aSourceMainURL, aDestMainURL) == osl::FileBase::E_None)
                if (IsNotHardLinkedFile(aDestMainURL) && osl::File::move(aSourceMainURL, aDestMainURL) == osl::FileBase::E_None)
                {
                    if (nAttributes)
                        // Adjust attributes, source might be created with