/** * @file llxfer_file.cpp * @brief implementation of LLXfer_File class for a single xfer (file) * * $LicenseInfo:firstyear=2001&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" #if !LL_WINDOWS #include #include #endif #include "llxfer_file.h" #include "lluuid.h" #include "llerror.h" #include "llmath.h" #include "llstring.h" #include "lldir.h" // size of chunks read from/written to disk const U32 LL_MAX_XFER_FILE_BUFFER = 65536; // local function to copy a file S32 copy_file(const std::string& from, const std::string& to); /////////////////////////////////////////////////////////// LLXfer_File::LLXfer_File (S32 chunk_size) : LLXfer(chunk_size) { init(LLStringUtil::null, FALSE, chunk_size); } LLXfer_File::LLXfer_File (const std::string& local_filename, BOOL delete_local_on_completion, S32 chunk_size) : LLXfer(chunk_size) { init(local_filename, delete_local_on_completion, chunk_size); } /////////////////////////////////////////////////////////// LLXfer_File::~LLXfer_File () { cleanup(); } /////////////////////////////////////////////////////////// void LLXfer_File::init (const std::string& local_filename, BOOL delete_local_on_completion, S32 chunk_size) { mFp = NULL; mLocalFilename.clear(); mRemoteFilename.clear(); mRemotePath = LL_PATH_NONE; mTempFilename.clear(); mDeleteLocalOnCompletion = FALSE; mDeleteRemoteOnCompletion = FALSE; if (!local_filename.empty()) { mLocalFilename = local_filename.substr(0,LL_MAX_PATH-1); // You can only automatically delete .tmp file as a safeguard against nasty messages. std::string exten = mLocalFilename.substr(mLocalFilename.length()-4, 4); mDeleteLocalOnCompletion = (delete_local_on_completion && exten == ".tmp"); } } /////////////////////////////////////////////////////////// void LLXfer_File::cleanup () { if (mFp) { fclose(mFp); mFp = NULL; } LLFile::remove(mTempFilename, ENOENT); if (mDeleteLocalOnCompletion) { LL_DEBUGS("Xfer") << "Removing file: " << mLocalFilename << LL_ENDL; LLFile::remove(mLocalFilename, ENOENT); } else { LL_DEBUGS("Xfer") << "Keeping local file: " << mLocalFilename << LL_ENDL; } LLXfer::cleanup(); } /////////////////////////////////////////////////////////// S32 LLXfer_File::initializeRequest(U64 xfer_id, const std::string& local_filename, const std::string& remote_filename, ELLPath remote_path, const LLHost& remote_host, BOOL delete_remote_on_completion, void (*callback)(void**,S32,LLExtStat), void** user_data) { S32 retval = 0; // presume success mID = xfer_id; mLocalFilename = local_filename; mRemoteFilename = remote_filename; mRemotePath = remote_path; mRemoteHost = remote_host; mDeleteRemoteOnCompletion = delete_remote_on_completion; mTempFilename = gDirUtilp->getTempFilename(); mCallback = callback; mCallbackDataHandle = user_data; mCallbackResult = LL_ERR_NOERR; LL_INFOS("Xfer") << "Requesting xfer from " << remote_host << " for file: " << mLocalFilename << LL_ENDL; if (mBuffer) { delete(mBuffer); mBuffer = NULL; } mBuffer = new char[LL_MAX_XFER_FILE_BUFFER]; mBufferLength = 0; mPacketNum = 0; mStatus = e_LL_XFER_PENDING; return retval; } /////////////////////////////////////////////////////////// S32 LLXfer_File::startDownload() { S32 retval = 0; // presume success mFp = LLFile::fopen(mTempFilename,"w+b"); /* Flawfinder : ignore */ if (mFp) { fclose(mFp); mFp = NULL; // tbd - is it premature to send this message if the queue is backed up? gMessageSystem->newMessageFast(_PREHASH_RequestXfer); gMessageSystem->nextBlockFast(_PREHASH_XferID); gMessageSystem->addU64Fast(_PREHASH_ID, mID); gMessageSystem->addStringFast(_PREHASH_Filename, mRemoteFilename); gMessageSystem->addU8("FilePath", (U8) mRemotePath); gMessageSystem->addBOOL("DeleteOnCompletion", mDeleteRemoteOnCompletion); gMessageSystem->addBOOL("UseBigPackets", BOOL(mChunkSize == LL_XFER_LARGE_PAYLOAD)); gMessageSystem->addUUIDFast(_PREHASH_VFileID, LLUUID::null); gMessageSystem->addS16Fast(_PREHASH_VFileType, -1); gMessageSystem->sendReliable(mRemoteHost); mStatus = e_LL_XFER_IN_PROGRESS; } else { LL_WARNS("Xfer") << "Couldn't create file to be received!" << LL_ENDL; retval = -1; } return (retval); } /////////////////////////////////////////////////////////// S32 LLXfer_File::startSend (U64 xfer_id, const LLHost &remote_host) { S32 retval = LL_ERR_NOERR; // presume success mRemoteHost = remote_host; mID = xfer_id; mPacketNum = -1; // cout << "Sending file: " << mLocalFilename << endl; delete [] mBuffer; mBuffer = new char[LL_MAX_XFER_FILE_BUFFER]; mBufferLength = 0; mBufferStartOffset = 0; // We leave the file open, assuming we'll start reading and sending soon mFp = LLFile::fopen(mLocalFilename,"rb"); /* Flawfinder : ignore */ if (mFp) { fseek(mFp,0,SEEK_END); S32 file_size = ftell(mFp); if (file_size <= 0) { return LL_ERR_FILE_EMPTY; } setXferSize(file_size); fseek(mFp,0,SEEK_SET); } else { LL_INFOS("Xfer") << "Warning: " << mLocalFilename << " not found." << LL_ENDL; return (LL_ERR_FILE_NOT_FOUND); } mStatus = e_LL_XFER_PENDING; return (retval); } /////////////////////////////////////////////////////////// void LLXfer_File::closeFileHandle() { if (mFp) { fclose(mFp); mFp = NULL; } } /////////////////////////////////////////////////////////// S32 LLXfer_File::reopenFileHandle() { S32 retval = LL_ERR_NOERR; // presume success if (mFp == NULL) { mFp = LLFile::fopen(mLocalFilename,"rb"); /* Flawfinder : ignore */ if (mFp == NULL) { LL_INFOS("Xfer") << "Warning: " << mLocalFilename << " not found when re-opening file" << LL_ENDL; retval = LL_ERR_FILE_NOT_FOUND; } } return retval; } /////////////////////////////////////////////////////////// S32 LLXfer_File::getMaxBufferSize () { return(LL_MAX_XFER_FILE_BUFFER); } /////////////////////////////////////////////////////////// S32 LLXfer_File::suck(S32 start_position) { S32 retval = 0; if (mFp) { // grab a buffer from the right place in the file fseek (mFp,start_position,SEEK_SET); mBufferLength = (U32)fread(mBuffer,1,LL_MAX_XFER_FILE_BUFFER,mFp); mBufferStartOffset = start_position; if (feof(mFp)) { mBufferContainsEOF = TRUE; } else { mBufferContainsEOF = FALSE; } } else { retval = -1; } return (retval); } /////////////////////////////////////////////////////////// S32 LLXfer_File::flush() { S32 retval = 0; if (mBufferLength) { if (mFp) { LL_ERRS("Xfer") << "Overwriting open file pointer!" << LL_ENDL; } mFp = LLFile::fopen(mTempFilename,"a+b"); /* Flawfinder : ignore */ if (mFp) { S32 write_size = fwrite(mBuffer,1,mBufferLength,mFp); if (write_size != mBufferLength) { LL_WARNS("Xfer") << "Non-matching write size, requested " << mBufferLength << " but wrote " << write_size << LL_ENDL; } // LL_INFOS("Xfer") << "******* wrote " << mBufferLength << " bytes of file xfer" << LL_ENDL; fclose(mFp); mFp = NULL; mBufferLength = 0; } else { LL_WARNS("Xfer") << "LLXfer_File::flush() unable to open " << mTempFilename << " for writing!" << LL_ENDL; retval = LL_ERR_CANNOT_OPEN_FILE; } } return (retval); } /////////////////////////////////////////////////////////// S32 LLXfer_File::processEOF() { S32 retval = 0; mStatus = e_LL_XFER_COMPLETE; S32 flushval = flush(); // If we have no other errors, our error becomes the error generated by // flush. if (!mCallbackResult) { mCallbackResult = flushval; } LLFile::remove(mLocalFilename, ENOENT); if (!mCallbackResult) { if (LLFile::rename(mTempFilename,mLocalFilename)) { #if !LL_WINDOWS S32 error_number = errno; LL_INFOS("Xfer") << "Rename failure (" << error_number << ") - " << mTempFilename << " to " << mLocalFilename << LL_ENDL; if(EXDEV == error_number) { if(copy_file(mTempFilename, mLocalFilename) == 0) { LL_INFOS("Xfer") << "Rename across mounts; copying+unlinking the file instead." << LL_ENDL; unlink(mTempFilename.c_str()); } else { LL_WARNS("Xfer") << "Copy failure - " << mTempFilename << " to " << mLocalFilename << LL_ENDL; } } else { //LLFILE* fp = LLFile::fopen(mTempFilename, "r"); //LL_WARNS() << "File " << mTempFilename << " does " // << (!fp ? "not" : "" ) << " exit." << LL_ENDL; //if(fp) fclose(fp); //fp = LLFile::fopen(mLocalFilename, "r"); //LL_WARNS() << "File " << mLocalFilename << " does " // << (!fp ? "not" : "" ) << " exit." << LL_ENDL; //if(fp) fclose(fp); LL_WARNS("Xfer") << "Rename fatally failed, can only handle EXDEV (" << EXDEV << ")" << LL_ENDL; } #else LL_WARNS("Xfer") << "Rename failure - " << mTempFilename << " to " << mLocalFilename << LL_ENDL; #endif } } if (mFp) { fclose(mFp); mFp = NULL; } retval = LLXfer::processEOF(); return(retval); } /////////////////////////////////////////////////////////// bool LLXfer_File::matchesLocalFilename(const std::string& filename) { return (filename == mLocalFilename); } /////////////////////////////////////////////////////////// bool LLXfer_File::matchesRemoteFilename(const std::string& filename, ELLPath remote_path) { return ((filename == mRemoteFilename) && (remote_path == mRemotePath)); } /////////////////////////////////////////////////////////// std::string LLXfer_File::getFileName() { return mLocalFilename; } /////////////////////////////////////////////////////////// // hacky - doesn't matter what this is // as long as it's different from the other classes U32 LLXfer_File::getXferTypeTag() { return LLXfer::XFER_FILE; } /////////////////////////////////////////////////////////// #if !LL_WINDOWS // This is really close to, but not quite a general purpose copy // function. It does not really spam enough information, but is useful // for this cpp file, because this should never be called in a // production environment. S32 copy_file(const std::string& from, const std::string& to) { S32 rv = 0; LLFILE* in = LLFile::fopen(from, "rb"); /*Flawfinder: ignore*/ LLFILE* out = LLFile::fopen(to, "wb"); /*Flawfinder: ignore*/ if(in && out) { S32 read = 0; const S32 COPY_BUFFER_SIZE = 16384; U8 buffer[COPY_BUFFER_SIZE]; while(((read = fread(buffer, 1, sizeof(buffer), in)) > 0) && (fwrite(buffer, 1, read, out) == (U32)read)); /* Flawfinder : ignore */ if(ferror(in) || ferror(out)) rv = -2; } else { rv = -1; } if(in) fclose(in); if(out) fclose(out); return rv; } #endif