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 void AfpConnectionLog(void *priv, enum loglevels loglevel, int logtype, const char *message)
44 CStdString msg = "LIBAFPCLIENT: " + CStdString(message);
49 CLog::Log(LOGWARNING, "%s", msg.c_str());
52 CLog::Log(LOGERROR, "%s", msg.c_str());
55 CLog::Log(LOGDEBUG, "%s", msg.c_str());
60 CAfpConnection::CAfpConnection()
61 : m_OpenConnections(0)
65 , m_pAfpUrl((struct afp_url*)malloc(sizeof(struct afp_url)))
66 , m_pAfpClient((struct libafpclient*)malloc(sizeof(struct libafpclient)))
67 , m_pLibAfp(new DllLibAfp())
72 CAfpConnection::~CAfpConnection()
77 if (m_pLibAfp->IsLoaded())
82 bool CAfpConnection::initLib()
86 if (m_pLibAfp->Load())
88 m_pAfpClient->unmount_volume = NULL;
89 m_pAfpClient->log_for_client = AfpConnectionLog;
90 m_pAfpClient->forced_ending_hook = NULL;
91 m_pAfpClient->scan_extra_fds = NULL;
92 m_pAfpClient->loop_started = NULL;
94 m_pLibAfp->libafpclient_register(m_pAfpClient);
95 m_pLibAfp->init_uams();
96 m_pLibAfp->afp_main_quick_startup(NULL);
97 CLog::Log(LOGDEBUG, "AFP: Supported UAMs: %s", m_pLibAfp->get_uam_names_list());
102 CLog::Log(LOGERROR, "AFP: Error loading afpclient lib.");
109 //only unmount here - afpclient lib is not
110 //stoppable (no afp_main_quick_shutdown as counter part
111 //for afp_main_quick_startup)
112 void CAfpConnection::Deinit()
114 if(m_pAfpVol && m_pLibAfp->IsLoaded())
118 m_pAfpUrl->servername[0] = '\0';
122 void CAfpConnection::Disconnect()
124 CSingleLock lock(*this);
128 void CAfpConnection::disconnectVolume()
132 // afp_unmount_volume(m_pAfpVol);
133 m_pLibAfp->afp_unmount_all_volumes(m_pAfpServer);
138 // taken from cmdline tool
139 bool CAfpConnection::connectVolume(const char *volumename, struct afp_volume *&pVolume)
142 if (strlen(volumename) != 0)
144 // Ah, we're not connected to a volume
145 unsigned int len = 0;
148 if ((pVolume = m_pLibAfp->find_volume_by_name(m_pAfpServer, volumename)) == NULL)
150 CLog::Log(LOGDEBUG, "AFP: Could not find a volume called %s\n", volumename);
154 pVolume->mapping = AFP_MAPPING_LOGINIDS;
155 pVolume->extra_flags |= VOLUME_EXTRA_FLAGS_NO_LOCKING;
157 if (m_pLibAfp->afp_connect_volume(pVolume, m_pAfpServer, mesg, &len, 1024 ))
159 CLog::Log(LOGDEBUG, "AFP: Could not access volume %s (error: %s)\n", pVolume->volume_name, mesg);
164 CLog::Log(LOGDEBUG, "AFP: Connected to volume %s\n", pVolume->volume_name_printable);
173 CStdString CAfpConnection::getAuthenticatedPath(const CURL &url)
177 CPasswordManager::GetInstance().AuthenticateURL(authURL);
182 CAfpConnection::afpConnnectError CAfpConnection::Connect(const CURL& url)
184 CSingleLock lock(*this);
185 struct afp_connection_request *conn_req = NULL;
186 struct afp_url tmpurl;
187 CURL nonConstUrl(getAuthenticatedPath(url)); // we need a editable copy of the url
188 bool serverChanged=false;
193 m_pLibAfp->afp_default_url(&tmpurl);
195 // if hostname has changed - assume server changed
196 if (!nonConstUrl.GetHostName().Equals(m_pAfpUrl->servername, false)|| (m_pAfpServer && m_pAfpServer->connect_state == 0))
198 serverChanged = true;
202 // if volume changed - also assume server changed (afpclient can't reuse old servobject it seems)
203 if (!nonConstUrl.GetShareName().Equals(m_pAfpUrl->volumename, false))
205 // no reusing of old server object possible with libafpclient it seems...
206 serverChanged = true;
210 // first, try to parse the URL
211 if (m_pLibAfp->afp_parse_url(&tmpurl, nonConstUrl.Get().c_str(), 0) != 0)
213 // Okay, this isn't a real URL
214 CLog::Log(LOGDEBUG, "AFP: Could not parse url: %s!\n", nonConstUrl.Get().c_str());
217 else // parsed successfully
219 // this is our current url object whe are connected to (at least we try)
223 // if no username and password is set - use no user authent uam
224 if (strlen(m_pAfpUrl->password) == 0 && strlen(m_pAfpUrl->username) == 0)
227 strncpy(m_pAfpUrl->uamname, "No User Authent", sizeof(m_pAfpUrl->uamname));
228 CLog::Log(LOGDEBUG, "AFP: Using anonymous authentication.");
230 else if ((nonConstUrl.GetPassWord().empty() || nonConstUrl.GetUserName().empty()) && serverChanged)
232 // this is our current url object whe are connected to (at least we try)
236 // we got a password in the url
237 if (!nonConstUrl.GetPassWord().empty())
239 // copy password because afp_parse_url just puts garbage into the password field :(
240 strncpy(m_pAfpUrl->password, nonConstUrl.GetPassWord().c_str(), 127);
243 // whe are not connected or we want to connect to another server
244 if (!m_pAfpServer || serverChanged)
246 // code from cmdline tool
247 conn_req = (struct afp_connection_request*)malloc(sizeof(struct afp_connection_request));
248 memset(conn_req, 0, sizeof(struct afp_connection_request));
250 conn_req->url = *m_pAfpUrl;
251 conn_req->url.requested_version = 31;
253 if (strlen(m_pAfpUrl->uamname)>0)
255 if ((conn_req->uam_mask = m_pLibAfp->find_uam_by_name(m_pAfpUrl->uamname)) == 0)
257 CLog::Log(LOGDEBUG, "AFP:I don't know about UAM %s\n", m_pAfpUrl->uamname);
258 m_pAfpUrl->volumename[0] = '\0';
259 m_pAfpUrl->servername[0] = '\0';
266 conn_req->uam_mask = m_pLibAfp->default_uams_mask();
271 if ((m_pAfpServer = m_pLibAfp->afp_wrap_server_full_connect(NULL, conn_req, NULL)) == NULL)
273 if ((m_pAfpServer = m_pLibAfp->afp_wrap_server_full_connect(NULL, conn_req)) == NULL)
276 m_pAfpUrl->volumename[0] = '\0';
277 m_pAfpUrl->servername[0] = '\0';
279 CLog::Log(LOGERROR, "AFP: Error connecting to %s", url.Get().c_str());
283 CLog::Log(LOGDEBUG, "AFP: Connected to server %s using UAM \"%s\"\n",
284 m_pAfpServer->server_name, m_pLibAfp->uam_bitmap_to_string(m_pAfpServer->using_uam));
285 // we don't need it after here ...
289 // if server changed reconnect volume
292 connectVolume(m_pAfpUrl->volumename, m_pAfpVol); // connect new volume
297 int CAfpConnection::stat(const CURL &url, struct stat *statbuff)
299 CSingleLock lock(*this);
300 CStdString strPath = gAfpConnection.GetPath(url);
301 struct afp_volume *pTmpVol = NULL;
302 struct afp_url tmpurl;
304 CURL nonConstUrl(getAuthenticatedPath(url)); // we need a editable copy of the url
306 if (!initLib() || !m_pAfpServer)
309 m_pLibAfp->afp_default_url(&tmpurl);
311 // first, try to parse the URL
312 if (m_pLibAfp->afp_parse_url(&tmpurl, nonConstUrl.Get().c_str(), 0) != 0)
314 // Okay, this isn't a real URL
315 CLog::Log(LOGDEBUG, "AFP: Could not parse url: %s!\n", nonConstUrl.Get().c_str());
319 // if no username and password is set - use no user authent uam
320 if (strlen(tmpurl.password) == 0 && strlen(tmpurl.username) == 0)
323 strncpy(tmpurl.uamname, "No User Authent", sizeof(tmpurl.uamname));
324 CLog::Log(LOGDEBUG, "AFP: Using anonymous authentication.");
326 else if ((nonConstUrl.GetPassWord().empty() || nonConstUrl.GetUserName().empty()))
328 // this is our current url object whe are connected to (at least we try)
332 // we got a password in the url
333 if (!nonConstUrl.GetPassWord().empty())
335 // copy password because afp_parse_url just puts garbage into the password field :(
336 strncpy(tmpurl.password, nonConstUrl.GetPassWord().c_str(), 127);
339 // connect new volume
340 if(connectVolume(tmpurl.volumename, pTmpVol) && pTmpVol)
342 iResult = m_pLibAfp->afp_wrap_getattr(pTmpVol, strPath.c_str(), statbuff);
343 //unmount single volume crashs
344 //we will get rid of the mounted volume
345 //once the context is changed in connect function
347 //m_pLibAfp->afp_unmount_volume(pTmpVol);
353 /* This is called from CApplication::ProcessSlow() and is used to tell if afp have been idle for too long */
354 void CAfpConnection::CheckIfIdle()
356 /* We check if there are open connections. This is done without a lock to not halt the mainthread. It should be thread safe as
357 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. */
358 if (m_OpenConnections == 0 && m_pAfpVol != NULL)
359 { /* I've set the the maxiumum IDLE time to be 1 min and 30 sec. */
360 CSingleLock lock(*this);
361 if (m_OpenConnections == 0 /* check again - when locked */)
363 if (m_IdleTimeout > 0)
369 CLog::Log(LOGNOTICE, "AFP is idle. Closing the remaining connections.");
370 gAfpConnection.Deinit();
376 /* The following two function is used to keep track on how many Opened files/directories there are.
377 needed for unloading the dylib*/
378 void CAfpConnection::AddActiveConnection()
380 CSingleLock lock(*this);
384 void CAfpConnection::AddIdleConnection()
386 CSingleLock lock(*this);
388 /* If we close a file we reset the idle timer so that we don't have any wierd behaviours if a user
389 leaves the movie paused for a long while and then press stop */
393 CStdString CAfpConnection::GetPath(const CURL &url)
395 struct afp_url tmpurl;
398 m_pLibAfp->afp_default_url(&tmpurl);
400 // First, try to parse the URL
401 if (m_pLibAfp->afp_parse_url(&tmpurl, url.Get().c_str(), 0) != 0 )
403 // Okay, this isn't a real URL
404 CLog::Log(LOGDEBUG, "AFP: Could not parse url.\n");
408 ret = CStdString(tmpurl.path);
413 CAfpConnection gAfpConnection;
421 gAfpConnection.AddActiveConnection();
424 CAFPFile::~CAFPFile()
426 gAfpConnection.AddIdleConnection();
430 int64_t CAFPFile::GetPosition()
432 if (m_pFp == NULL) return 0;
436 int64_t CAFPFile::GetLength()
438 if (m_pFp == NULL) return 0;
442 bool CAFPFile::Open(const CURL& url)
445 // we can't open files like afp://file.f or afp://server/file.f
446 // if a file matches the if below return false, it can't exist on a afp share.
447 if (!IsValidFile(url.GetFileName()))
449 CLog::Log(LOGNOTICE, "FileAfp: Bad URL : '%s'", url.GetFileName().c_str());
453 CSingleLock lock(gAfpConnection);
454 if (gAfpConnection.Connect(url) != CAfpConnection::AfpOk || !gAfpConnection.GetVolume())
456 m_pAfpVol = gAfpConnection.GetVolume();
458 CStdString strPath = gAfpConnection.GetPath(url);
460 if (gAfpConnection.GetImpl()->afp_wrap_open(m_pAfpVol, strPath.c_str(), O_RDONLY, &m_pFp))
462 if (gAfpConnection.GetImpl()->afp_wrap_open(m_pAfpVol, CURL::Encode(strPath.c_str()).c_str(), O_RDONLY, &m_pFp))
464 // write error to logfile
465 CLog::Log(LOGINFO, "CAFPFile::Open: Unable to open file : '%s'\nunix_err:'%x' error : '%s'", strPath.c_str(), errno, strerror(errno));
470 CLog::Log(LOGDEBUG,"CAFPFile::Open - opened %s, fd=%d",url.GetFileName().c_str(), m_pFp ? m_pFp->fileid:-1);
474 struct __stat64 tmpBuffer;
476 struct stat tmpBuffer;
485 m_fileSize = tmpBuffer.st_size;
487 // We've successfully opened the file!
492 bool CAFPFile::Exists(const CURL& url)
494 return Stat(url, NULL) == 0;
497 int CAFPFile::Stat(struct __stat64* buffer)
501 return Stat(m_url, buffer);
504 // TODO - maybe check returncode!
505 int CAFPFile::Stat(const CURL& url, struct __stat64* buffer)
507 CSingleLock lock(gAfpConnection);
508 if (gAfpConnection.Connect(url) != CAfpConnection::AfpOk || !gAfpConnection.GetVolume())
511 CStdString strPath = gAfpConnection.GetPath(url);
513 struct stat tmpBuffer = {0};
514 int iResult = gAfpConnection.GetImpl()->afp_wrap_getattr(gAfpConnection.GetVolume(), strPath.c_str(), &tmpBuffer);
518 memset(buffer, 0, sizeof(struct __stat64));
519 buffer->st_dev = tmpBuffer.st_dev;
520 buffer->st_ino = tmpBuffer.st_ino;
521 buffer->st_mode = tmpBuffer.st_mode;
522 buffer->st_nlink = tmpBuffer.st_nlink;
523 buffer->st_uid = tmpBuffer.st_uid;
524 buffer->st_gid = tmpBuffer.st_gid;
525 buffer->st_rdev = tmpBuffer.st_rdev;
526 buffer->st_size = tmpBuffer.st_size;
527 buffer->st_atime = tmpBuffer.st_atime;
528 buffer->st_mtime = tmpBuffer.st_mtime;
529 buffer->st_ctime = tmpBuffer.st_ctime;
535 unsigned int CAFPFile::Read(void *lpBuf, int64_t uiBufSize)
537 CSingleLock lock(gAfpConnection);
538 if (m_pFp == NULL || !m_pAfpVol)
541 if (uiBufSize > AFP_MAX_READ_SIZE)
542 uiBufSize = AFP_MAX_READ_SIZE;
545 char *name = m_pFp->basename;
547 char *name = m_pFp->name;
548 if (strlen(name) == 0)
549 name = m_pFp->basename;
553 int bytesRead = gAfpConnection.GetImpl()->afp_wrap_read(m_pAfpVol,
554 name, (char *)lpBuf,(size_t)uiBufSize, m_fileOffset, m_pFp, &eof);
556 m_fileOffset += bytesRead;
560 CLog::Log(LOGERROR, "%s - Error( %d, %d, %s )", __FUNCTION__, bytesRead, errno, strerror(errno));
564 return (unsigned int)bytesRead;
567 int64_t CAFPFile::Seek(int64_t iFilePosition, int iWhence)
569 off_t newOffset = m_fileOffset;
570 if (m_pFp == NULL) return -1;
575 newOffset = iFilePosition;
578 newOffset = m_fileSize+iFilePosition;
581 newOffset += iFilePosition;
585 if ( newOffset < 0 || newOffset > m_fileSize)
587 CLog::Log(LOGERROR, "%s - Error( %"PRId64")", __FUNCTION__, newOffset);
591 m_fileOffset = newOffset;
592 return (int64_t)m_fileOffset;
595 void CAFPFile::Close()
597 CSingleLock lock(gAfpConnection);
598 if (m_pFp != NULL && m_pAfpVol)
600 CLog::Log(LOGDEBUG, "CAFPFile::Close closing fd %d", m_pFp->fileid);
602 char *name = m_pFp->basename;
604 char *name = m_pFp->name;
605 if (strlen(name) == 0)
606 name = m_pFp->basename;
608 gAfpConnection.GetImpl()->afp_wrap_close(m_pAfpVol, name, m_pFp);
615 int CAFPFile::Write(const void* lpBuf, int64_t uiBufSize)
617 CSingleLock lock(gAfpConnection);
618 if (m_pFp == NULL || !m_pAfpVol)
621 int numberOfBytesWritten = 0;
625 // FIXME need a better way to get server's uid/gid
629 char *name = m_pFp->basename;
631 char *name = m_pFp->name;
632 if (strlen(name) == 0)
633 name = m_pFp->basename;
635 numberOfBytesWritten = gAfpConnection.GetImpl()->afp_wrap_write(m_pAfpVol,
636 name, (const char *)lpBuf, (size_t)uiBufSize, m_fileOffset, m_pFp, uid, gid);
638 return numberOfBytesWritten;
641 bool CAFPFile::Delete(const CURL& url)
643 CSingleLock lock(gAfpConnection);
644 if (gAfpConnection.Connect(url) != CAfpConnection::AfpOk || !gAfpConnection.GetVolume())
647 CStdString strPath = gAfpConnection.GetPath(url);
649 int result = gAfpConnection.GetImpl()->afp_wrap_unlink(gAfpConnection.GetVolume(), strPath.c_str());
652 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
654 return (result == 0);
657 bool CAFPFile::Rename(const CURL& url, const CURL& urlnew)
659 CSingleLock lock(gAfpConnection);
660 if (gAfpConnection.Connect(url) != CAfpConnection::AfpOk || !gAfpConnection.GetVolume())
663 CStdString strFile = gAfpConnection.GetPath(url);
664 CStdString strFileNew = gAfpConnection.GetPath(urlnew);
666 int result = gAfpConnection.GetImpl()->afp_wrap_rename(gAfpConnection.GetVolume(), strFile.c_str(), strFileNew.c_str());
669 CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
671 return (result == 0);
674 bool CAFPFile::OpenForWrite(const CURL& url, bool bOverWrite)
682 CSingleLock lock(gAfpConnection);
683 if (gAfpConnection.Connect(url) != CAfpConnection::AfpOk || !gAfpConnection.GetVolume())
686 // we can't open files like afp://file.f or afp://server/file.f
687 // if a file matches the if below return false, it can't exist on a afp share.
688 if (!IsValidFile(url.GetFileName()))
691 m_pAfpVol = gAfpConnection.GetVolume();
693 CStdString strPath = gAfpConnection.GetPath(url);
697 CLog::Log(LOGWARNING, "FileAFP::OpenForWrite() called with overwriting enabled! - %s", strPath.c_str());
698 ret = gAfpConnection.GetImpl()->afp_wrap_creat(m_pAfpVol, strPath.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
701 ret = gAfpConnection.GetImpl()->afp_wrap_open(m_pAfpVol, strPath.c_str(), O_RDWR, &m_pFp);
703 if (ret || m_pFp == NULL)
705 // write error to logfile
706 CLog::Log(LOGERROR, "CAFPFile::Open: Unable to open file : '%s'\nunix_err:'%x' error : '%s'", strPath.c_str(), errno, strerror(errno));
710 // We've successfully opened the file!
714 bool CAFPFile::IsValidFile(const CStdString& strFileName)
716 if (strFileName.find('/') == std::string::npos || // doesn't have sharename
717 StringUtils::EndsWith(strFileName, "/.") || // not current folder
718 StringUtils::EndsWith(strFileName, "/..")) // not parent folder
725 #endif // HAS_FILESYSTEM_AFP
726 #endif // TARGET_POSIX