2 * Copyright (C) 2011-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 // FileAFP.cpp: implementation of the CAFPFile class.
23 //////////////////////////////////////////////////////////////////////
27 #if defined(HAS_FILESYSTEM_AFP)
29 #include "PasswordManager.h"
30 #include "AFPDirectory.h"
32 #include "settings/AdvancedSettings.h"
33 #include "threads/SingleLock.h"
34 #include "utils/log.h"
35 #include "utils/TimeUtils.h"
37 using namespace XFILE;
39 #define AFP_MAX_READ_SIZE 131072
41 CStdString URLEncode(const CStdString value)
43 CStdString encoded(value);
44 CURL::Encode(encoded);
48 void AfpConnectionLog(void *priv, enum loglevels loglevel, int logtype, const char *message)
51 CStdString msg = "LIBAFPCLIENT: " + CStdString(message);
56 CLog::Log(LOGWARNING, "%s", msg.c_str());
59 CLog::Log(LOGERROR, "%s", msg.c_str());
62 CLog::Log(LOGDEBUG, "%s", msg.c_str());
67 CAfpConnection::CAfpConnection()
68 : m_OpenConnections(0)
72 , m_pAfpUrl((struct afp_url*)malloc(sizeof(struct afp_url)))
73 , m_pAfpClient((struct libafpclient*)malloc(sizeof(struct libafpclient)))
74 , m_pLibAfp(new DllLibAfp())
79 CAfpConnection::~CAfpConnection()
84 if (m_pLibAfp->IsLoaded())
89 bool CAfpConnection::initLib()
93 if (m_pLibAfp->Load())
95 m_pAfpClient->unmount_volume = NULL;
96 m_pAfpClient->log_for_client = AfpConnectionLog;
97 m_pAfpClient->forced_ending_hook = NULL;
98 m_pAfpClient->scan_extra_fds = NULL;
99 m_pAfpClient->loop_started = NULL;
101 m_pLibAfp->libafpclient_register(m_pAfpClient);
102 m_pLibAfp->init_uams();
103 m_pLibAfp->afp_main_quick_startup(NULL);
104 CLog::Log(LOGDEBUG, "AFP: Supported UAMs: %s", m_pLibAfp->get_uam_names_list());
109 CLog::Log(LOGERROR, "AFP: Error loading afpclient lib.");
116 //only unmount here - afpclient lib is not
117 //stoppable (no afp_main_quick_shutdown as counter part
118 //for afp_main_quick_startup)
119 void CAfpConnection::Deinit()
121 if(m_pAfpVol && m_pLibAfp->IsLoaded())
125 m_pAfpUrl->servername[0] = '\0';
129 void CAfpConnection::Disconnect()
131 CSingleLock lock(*this);
135 void CAfpConnection::disconnectVolume()
139 // afp_unmount_volume(m_pAfpVol);
140 m_pLibAfp->afp_unmount_all_volumes(m_pAfpServer);
145 // taken from cmdline tool
146 bool CAfpConnection::connectVolume(const char *volumename, struct afp_volume *&pVolume)
149 if (strlen(volumename) != 0)
151 // Ah, we're not connected to a volume
152 unsigned int len = 0;
155 if ((pVolume = m_pLibAfp->find_volume_by_name(m_pAfpServer, volumename)) == NULL)
157 CLog::Log(LOGDEBUG, "AFP: Could not find a volume called %s\n", volumename);
161 pVolume->mapping = AFP_MAPPING_LOGINIDS;
162 pVolume->extra_flags |= VOLUME_EXTRA_FLAGS_NO_LOCKING;
164 if (m_pLibAfp->afp_connect_volume(pVolume, m_pAfpServer, mesg, &len, 1024 ))
166 CLog::Log(LOGDEBUG, "AFP: Could not access volume %s (error: %s)\n", pVolume->volume_name, mesg);
171 CLog::Log(LOGDEBUG, "AFP: Connected to volume %s\n", pVolume->volume_name_printable);
180 CStdString CAfpConnection::getAuthenticatedPath(const CURL &url)
184 CPasswordManager::GetInstance().AuthenticateURL(authURL);
189 CAfpConnection::afpConnnectError CAfpConnection::Connect(const CURL& url)
191 CSingleLock lock(*this);
192 struct afp_connection_request *conn_req = NULL;
193 struct afp_url tmpurl;
194 CURL nonConstUrl(getAuthenticatedPath(url)); // we need a editable copy of the url
195 bool serverChanged=false;
200 m_pLibAfp->afp_default_url(&tmpurl);
202 // if hostname has changed - assume server changed
203 if (!nonConstUrl.GetHostName().Equals(m_pAfpUrl->servername, false)|| (m_pAfpServer && m_pAfpServer->connect_state == 0))
205 serverChanged = true;
209 // if volume changed - also assume server changed (afpclient can't reuse old servobject it seems)
210 if (!nonConstUrl.GetShareName().Equals(m_pAfpUrl->volumename, false))
212 // no reusing of old server object possible with libafpclient it seems...
213 serverChanged = true;
217 // first, try to parse the URL
218 if (m_pLibAfp->afp_parse_url(&tmpurl, nonConstUrl.Get().c_str(), 0) != 0)
220 // Okay, this isn't a real URL
221 CLog::Log(LOGDEBUG, "AFP: Could not parse url: %s!\n", nonConstUrl.Get().c_str());
224 else // parsed sucessfull
226 // this is our current url object whe are connected to (at least we try)
230 // if no username and password is set - use no user authent uam
231 if (strlen(m_pAfpUrl->password) == 0 && strlen(m_pAfpUrl->username) == 0)
234 strncpy(m_pAfpUrl->uamname, "No User Authent", sizeof(m_pAfpUrl->uamname));
235 CLog::Log(LOGDEBUG, "AFP: Using anonymous authentication.");
237 else if ((nonConstUrl.GetPassWord().empty() || nonConstUrl.GetUserName().empty()) && serverChanged)
239 // this is our current url object whe are connected to (at least we try)
243 // we got a password in the url
244 if (!nonConstUrl.GetPassWord().empty())
246 // copy password because afp_parse_url just puts garbage into the password field :(
247 strncpy(m_pAfpUrl->password, nonConstUrl.GetPassWord().c_str(), 127);
250 // whe are not connected or we want to connect to another server
251 if (!m_pAfpServer || serverChanged)
253 // code from cmdline tool
254 conn_req = (struct afp_connection_request*)malloc(sizeof(struct afp_connection_request));
255 memset(conn_req, 0, sizeof(struct afp_connection_request));
257 conn_req->url = *m_pAfpUrl;
258 conn_req->url.requested_version = 31;
260 if (strlen(m_pAfpUrl->uamname)>0)
262 if ((conn_req->uam_mask = m_pLibAfp->find_uam_by_name(m_pAfpUrl->uamname)) == 0)
264 CLog::Log(LOGDEBUG, "AFP:I don't know about UAM %s\n", m_pAfpUrl->uamname);
265 m_pAfpUrl->volumename[0] = '\0';
266 m_pAfpUrl->servername[0] = '\0';
273 conn_req->uam_mask = m_pLibAfp->default_uams_mask();
278 if ((m_pAfpServer = m_pLibAfp->afp_wrap_server_full_connect(NULL, conn_req, NULL)) == NULL)
280 if ((m_pAfpServer = m_pLibAfp->afp_wrap_server_full_connect(NULL, conn_req)) == NULL)
283 m_pAfpUrl->volumename[0] = '\0';
284 m_pAfpUrl->servername[0] = '\0';
286 CLog::Log(LOGERROR, "AFP: Error connecting to %s", url.Get().c_str());
290 CLog::Log(LOGDEBUG, "AFP: Connected to server %s using UAM \"%s\"\n",
291 m_pAfpServer->server_name, m_pLibAfp->uam_bitmap_to_string(m_pAfpServer->using_uam));
292 // we don't need it after here ...
296 // if server changed reconnect volume
299 connectVolume(m_pAfpUrl->volumename, m_pAfpVol); // connect new volume
304 int CAfpConnection::stat(const CURL &url, struct stat *statbuff)
306 CSingleLock lock(*this);
307 CStdString strPath = gAfpConnection.GetPath(url);
308 struct afp_volume *pTmpVol = NULL;
309 struct afp_url tmpurl;
311 CURL nonConstUrl(getAuthenticatedPath(url)); // we need a editable copy of the url
313 if (!initLib() || !m_pAfpServer)
316 m_pLibAfp->afp_default_url(&tmpurl);
318 // first, try to parse the URL
319 if (m_pLibAfp->afp_parse_url(&tmpurl, nonConstUrl.Get().c_str(), 0) != 0)
321 // Okay, this isn't a real URL
322 CLog::Log(LOGDEBUG, "AFP: Could not parse url: %s!\n", nonConstUrl.Get().c_str());
326 // if no username and password is set - use no user authent uam
327 if (strlen(tmpurl.password) == 0 && strlen(tmpurl.username) == 0)
330 strncpy(tmpurl.uamname, "No User Authent", sizeof(tmpurl.uamname));
331 CLog::Log(LOGDEBUG, "AFP: Using anonymous authentication.");
333 else if ((nonConstUrl.GetPassWord().empty() || nonConstUrl.GetUserName().empty()))
335 // this is our current url object whe are connected to (at least we try)
339 // we got a password in the url
340 if (!nonConstUrl.GetPassWord().empty())
342 // copy password because afp_parse_url just puts garbage into the password field :(
343 strncpy(tmpurl.password, nonConstUrl.GetPassWord().c_str(), 127);
346 // connect new volume
347 if(connectVolume(tmpurl.volumename, pTmpVol) && pTmpVol)
349 iResult = m_pLibAfp->afp_wrap_getattr(pTmpVol, strPath.c_str(), statbuff);
350 //unmount single volume crashs
351 //we will get rid of the mounted volume
352 //once the context is changed in connect function
354 //m_pLibAfp->afp_unmount_volume(pTmpVol);
360 /* This is called from CApplication::ProcessSlow() and is used to tell if afp have been idle for too long */
361 void CAfpConnection::CheckIfIdle()
363 /* We check if there are open connections. This is done without a lock to not halt the mainthread. It should be thread safe as
364 worst case scenario is that m_OpenConnections could read 0 and then changed to 1 if this happens it will enter the if wich will lead to another check, wich is locked. */
365 if (m_OpenConnections == 0 && m_pAfpVol != NULL)
366 { /* I've set the the maxiumum IDLE time to be 1 min and 30 sec. */
367 CSingleLock lock(*this);
368 if (m_OpenConnections == 0 /* check again - when locked */)
370 if (m_IdleTimeout > 0)
376 CLog::Log(LOGNOTICE, "AFP is idle. Closing the remaining connections.");
377 gAfpConnection.Deinit();
383 /* The following two function is used to keep track on how many Opened files/directories there are.
384 needed for unloading the dylib*/
385 void CAfpConnection::AddActiveConnection()
387 CSingleLock lock(*this);
391 void CAfpConnection::AddIdleConnection()
393 CSingleLock lock(*this);
395 /* If we close a file we reset the idle timer so that we don't have any wierd behaviours if a user
396 leaves the movie paused for a long while and then press stop */
400 CStdString CAfpConnection::GetPath(const CURL &url)
402 struct afp_url tmpurl;
405 m_pLibAfp->afp_default_url(&tmpurl);
407 // First, try to parse the URL
408 if (m_pLibAfp->afp_parse_url(&tmpurl, url.Get().c_str(), 0) != 0 )
410 // Okay, this isn't a real URL
411 CLog::Log(LOGDEBUG, "AFP: Could not parse url.\n");
415 ret = CStdString(tmpurl.path);
420 CAfpConnection gAfpConnection;
428 gAfpConnection.AddActiveConnection();
431 CAFPFile::~CAFPFile()
433 gAfpConnection.AddIdleConnection();
437 int64_t CAFPFile::GetPosition()
439 if (m_pFp == NULL) return 0;
443 int64_t CAFPFile::GetLength()
445 if (m_pFp == NULL) return 0;
449 bool CAFPFile::Open(const CURL& url)
452 // we can't open files like afp://file.f or afp://server/file.f
453 // if a file matches the if below return false, it can't exist on a afp share.
454 if (!IsValidFile(url.GetFileName()))
456 CLog::Log(LOGNOTICE, "FileAfp: Bad URL : '%s'", url.GetFileName().c_str());
460 CSingleLock lock(gAfpConnection);
461 if (gAfpConnection.Connect(url) != CAfpConnection::AfpOk || !gAfpConnection.GetVolume())
463 m_pAfpVol = gAfpConnection.GetVolume();
465 CStdString strPath = gAfpConnection.GetPath(url);
467 if (gAfpConnection.GetImpl()->afp_wrap_open(m_pAfpVol, strPath.c_str(), O_RDONLY, &m_pFp))
469 if (gAfpConnection.GetImpl()->afp_wrap_open(m_pAfpVol, URLEncode(strPath.c_str()).c_str(), O_RDONLY, &m_pFp))
471 // write error to logfile
472 CLog::Log(LOGINFO, "CAFPFile::Open: Unable to open file : '%s'\nunix_err:'%x' error : '%s'", strPath.c_str(), errno, strerror(errno));
477 CLog::Log(LOGDEBUG,"CAFPFile::Open - opened %s, fd=%d",url.GetFileName().c_str(), m_pFp ? m_pFp->fileid:-1);
481 struct __stat64 tmpBuffer;
483 struct stat tmpBuffer;
492 m_fileSize = tmpBuffer.st_size;
494 // We've successfully opened the file!
499 bool CAFPFile::Exists(const CURL& url)
501 return Stat(url, NULL) == 0;
504 int CAFPFile::Stat(struct __stat64* buffer)
508 return Stat(m_url, buffer);
511 // TODO - maybe check returncode!
512 int CAFPFile::Stat(const CURL& url, struct __stat64* buffer)
514 CSingleLock lock(gAfpConnection);
515 if (gAfpConnection.Connect(url) != CAfpConnection::AfpOk || !gAfpConnection.GetVolume())
518 CStdString strPath = gAfpConnection.GetPath(url);
520 struct stat tmpBuffer = {0};
521 int iResult = gAfpConnection.GetImpl()->afp_wrap_getattr(gAfpConnection.GetVolume(), strPath.c_str(), &tmpBuffer);
525 memset(buffer, 0, sizeof(struct __stat64));
526 buffer->st_dev = tmpBuffer.st_dev;
527 buffer->st_ino = tmpBuffer.st_ino;
528 buffer->st_mode = tmpBuffer.st_mode;
529 buffer->st_nlink = tmpBuffer.st_nlink;
530 buffer->st_uid = tmpBuffer.st_uid;
531 buffer->st_gid = tmpBuffer.st_gid;
532 buffer->st_rdev = tmpBuffer.st_rdev;
533 buffer->st_size = tmpBuffer.st_size;
534 buffer->st_atime = tmpBuffer.st_atime;
535 buffer->st_mtime = tmpBuffer.st_mtime;
536 buffer->st_ctime = tmpBuffer.st_ctime;
542 unsigned int CAFPFile::Read(void *lpBuf, int64_t uiBufSize)
544 CSingleLock lock(gAfpConnection);
545 if (m_pFp == NULL || !m_pAfpVol)
548 if (uiBufSize > AFP_MAX_READ_SIZE)
549 uiBufSize = AFP_MAX_READ_SIZE;
552 char *name = m_pFp->basename;
554 char *name = m_pFp->name;
555 if (strlen(name) == 0)
556 name = m_pFp->basename;
560 int bytesRead = gAfpConnection.GetImpl()->afp_wrap_read(m_pAfpVol,
561 name, (char *)lpBuf,(size_t)uiBufSize, m_fileOffset, m_pFp, &eof);
563 m_fileOffset += bytesRead;
567 CLog::Log(LOGERROR, "%s - Error( %d, %d, %s )", __FUNCTION__, bytesRead, errno, strerror(errno));
571 return (unsigned int)bytesRead;
574 int64_t CAFPFile::Seek(int64_t iFilePosition, int iWhence)
576 off_t newOffset = m_fileOffset;
577 if (m_pFp == NULL) return -1;
582 newOffset = iFilePosition;
585 newOffset = m_fileSize+iFilePosition;
588 newOffset += iFilePosition;
592 if ( newOffset < 0 || newOffset > m_fileSize)
594 CLog::Log(LOGERROR, "%s - Error( %"PRId64")", __FUNCTION__, newOffset);
598 m_fileOffset = newOffset;
599 return (int64_t)m_fileOffset;
602 void CAFPFile::Close()
604 CSingleLock lock(gAfpConnection);
605 if (m_pFp != NULL && m_pAfpVol)
607 CLog::Log(LOGDEBUG, "CAFPFile::Close closing fd %d", m_pFp->fileid);
609 char *name = m_pFp->basename;
611 char *name = m_pFp->name;
612 if (strlen(name) == 0)
613 name = m_pFp->basename;
615 gAfpConnection.GetImpl()->afp_wrap_close(m_pAfpVol, name, m_pFp);
622 int CAFPFile::Write(const void* lpBuf, int64_t uiBufSize)
624 CSingleLock lock(gAfpConnection);
625 if (m_pFp == NULL || !m_pAfpVol)
628 int numberOfBytesWritten = 0;
632 // FIXME need a better way to get server's uid/gid
636 char *name = m_pFp->basename;
638 char *name = m_pFp->name;
639 if (strlen(name) == 0)
640 name = m_pFp->basename;
642 numberOfBytesWritten = gAfpConnection.GetImpl()->afp_wrap_write(m_pAfpVol,
643 name, (const char *)lpBuf, (size_t)uiBufSize, m_fileOffset, m_pFp, uid, gid);
645 return numberOfBytesWritten;
648 bool CAFPFile::Delete(const CURL& url)
650 CSingleLock lock(gAfpConnection);
651 if (gAfpConnection.Connect(url) != CAfpConnection::AfpOk || !gAfpConnection.GetVolume())
654 CStdString strPath = gAfpConnection.GetPath(url);
656 int result = gAfpConnection.GetImpl()->afp_wrap_unlink(gAfpConnection.GetVolume(), strPath.c_str());
659 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
661 return (result == 0);
664 bool CAFPFile::Rename(const CURL& url, const CURL& urlnew)
666 CSingleLock lock(gAfpConnection);
667 if (gAfpConnection.Connect(url) != CAfpConnection::AfpOk || !gAfpConnection.GetVolume())
670 CStdString strFile = gAfpConnection.GetPath(url);
671 CStdString strFileNew = gAfpConnection.GetPath(urlnew);
673 int result = gAfpConnection.GetImpl()->afp_wrap_rename(gAfpConnection.GetVolume(), strFile.c_str(), strFileNew.c_str());
676 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
678 return (result == 0);
681 bool CAFPFile::OpenForWrite(const CURL& url, bool bOverWrite)
689 CSingleLock lock(gAfpConnection);
690 if (gAfpConnection.Connect(url) != CAfpConnection::AfpOk || !gAfpConnection.GetVolume())
693 // we can't open files like afp://file.f or afp://server/file.f
694 // if a file matches the if below return false, it can't exist on a afp share.
695 if (!IsValidFile(url.GetFileName()))
698 m_pAfpVol = gAfpConnection.GetVolume();
700 CStdString strPath = gAfpConnection.GetPath(url);
704 CLog::Log(LOGWARNING, "FileAFP::OpenForWrite() called with overwriting enabled! - %s", strPath.c_str());
705 ret = gAfpConnection.GetImpl()->afp_wrap_creat(m_pAfpVol, strPath.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
708 ret = gAfpConnection.GetImpl()->afp_wrap_open(m_pAfpVol, strPath.c_str(), O_RDWR, &m_pFp);
710 if (ret || m_pFp == NULL)
712 // write error to logfile
713 CLog::Log(LOGERROR, "CAFPFile::Open: Unable to open file : '%s'\nunix_err:'%x' error : '%s'", strPath.c_str(), errno, strerror(errno));
717 // We've successfully opened the file!
721 bool CAFPFile::IsValidFile(const CStdString& strFileName)
723 if (strFileName.find('/') == std::string::npos || // doesn't have sharename
724 StringUtils::EndsWith(strFileName, "/.") || // not current folder
725 StringUtils::EndsWith(strFileName, "/..")) // not parent folder
732 #endif // HAS_FILESYSTEM_AFP
733 #endif // TARGET_POSIX