2012-06-01 15:23:19 +00:00
|
|
|
// VFSTools.cpp - useful functions and misc stuff
|
|
|
|
// For conditions of distribution and use, see copyright notice in VFS.h
|
|
|
|
|
2014-04-07 02:16:15 +00:00
|
|
|
#include "VFSInternal.h"
|
2014-04-06 17:19:33 +00:00
|
|
|
#include "VFSTools.h"
|
2012-06-01 15:23:19 +00:00
|
|
|
|
|
|
|
#include <algorithm>
|
2014-04-07 02:16:15 +00:00
|
|
|
#include <ctype.h>
|
2012-06-01 15:23:19 +00:00
|
|
|
|
|
|
|
#if _WIN32
|
|
|
|
# define WIN32_LEAN_AND_MEAN
|
|
|
|
# include <windows.h>
|
2014-04-06 17:19:33 +00:00
|
|
|
# include <io.h>
|
2012-06-01 15:23:19 +00:00
|
|
|
#else
|
2012-09-23 03:31:29 +00:00
|
|
|
# ifdef __HAIKU__
|
2013-06-24 17:54:25 +00:00
|
|
|
# include <dirent.h>
|
2012-09-23 03:31:29 +00:00
|
|
|
# else
|
2013-06-24 17:54:25 +00:00
|
|
|
# include <sys/dir.h>
|
2012-09-23 03:31:29 +00:00
|
|
|
# endif
|
|
|
|
# include <unistd.h>
|
2012-06-01 15:23:19 +00:00
|
|
|
#endif
|
|
|
|
|
2012-06-14 15:54:40 +00:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
2012-06-01 15:23:19 +00:00
|
|
|
VFS_NAMESPACE_START
|
|
|
|
|
|
|
|
|
|
|
|
#if !_WIN32
|
2012-09-23 03:31:29 +00:00
|
|
|
#ifdef DT_DIR
|
2012-06-01 15:23:19 +00:00
|
|
|
static bool _IsFile(const char *path, dirent *dp)
|
|
|
|
{
|
|
|
|
switch(dp->d_type)
|
|
|
|
{
|
|
|
|
case DT_DIR:
|
|
|
|
return false;
|
|
|
|
case DT_LNK:
|
|
|
|
{
|
|
|
|
std::string fullname = path;
|
|
|
|
fullname += '/';
|
|
|
|
fullname += dp->d_name;
|
|
|
|
struct stat statbuf;
|
|
|
|
if(stat(fullname.c_str(), &statbuf))
|
|
|
|
return false; // error
|
|
|
|
return !S_ISDIR(statbuf.st_mode);
|
|
|
|
}
|
|
|
|
// TODO: for now, we consider other file types as regular files
|
|
|
|
default:
|
|
|
|
;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool _IsDir(const char *path, dirent *dp)
|
|
|
|
{
|
|
|
|
switch(dp->d_type)
|
|
|
|
{
|
|
|
|
case DT_DIR:
|
|
|
|
return true;
|
|
|
|
case DT_LNK:
|
|
|
|
{
|
|
|
|
std::string fullname = path;
|
|
|
|
fullname += '/';
|
|
|
|
fullname += dp->d_name;
|
|
|
|
struct stat statbuf;
|
|
|
|
if(stat(fullname.c_str(), &statbuf))
|
|
|
|
return false; // error
|
|
|
|
return S_ISDIR(statbuf.st_mode);
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2012-09-23 03:31:29 +00:00
|
|
|
|
|
|
|
#else // No DT_DIR, assume plain POSIX
|
|
|
|
|
|
|
|
static bool _IsDir(const char *path, dirent *dp)
|
|
|
|
{
|
|
|
|
const int len1 = strlen(path);
|
|
|
|
const int len2 = strlen(dp->d_name);
|
|
|
|
|
|
|
|
char *pathname = (char*)alloca(len1 + 1 + len2 + 1 + 13);
|
|
|
|
strcpy (pathname, path);
|
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
/* Avoid UNC-path "//name" on Cygwin. */
|
2012-09-23 03:31:29 +00:00
|
|
|
if (len1 > 0 && pathname[len1 - 1] != '/')
|
|
|
|
strcat (pathname, "/");
|
|
|
|
|
|
|
|
strcat (pathname, dp->d_name);
|
|
|
|
|
|
|
|
struct stat st;
|
|
|
|
if (stat (pathname, &st))
|
|
|
|
return false;
|
|
|
|
return S_ISDIR (st.st_mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool _IsFile(const char *path, dirent *dp)
|
|
|
|
{
|
|
|
|
return !_IsDir(path, dp);
|
|
|
|
}
|
2013-06-24 17:54:25 +00:00
|
|
|
#endif // DT_DIR
|
2012-09-23 03:31:29 +00:00
|
|
|
|
2013-06-24 17:54:25 +00:00
|
|
|
#endif // !_WIN32
|
2012-06-01 15:23:19 +00:00
|
|
|
|
|
|
|
// returns list of *plain* file names in given directory,
|
|
|
|
// without paths, and without anything else
|
2014-04-06 17:19:33 +00:00
|
|
|
bool GetFileList(const char *path, StringList& files)
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
|
|
|
#if !_WIN32
|
|
|
|
DIR * dirp;
|
|
|
|
struct dirent * dp;
|
|
|
|
dirp = opendir(path);
|
2014-04-06 17:19:33 +00:00
|
|
|
if(!dirp)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
while((dp=readdir(dirp)) != NULL)
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
if (_IsFile(path, dp)) // only add if it is not a directory
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
std::string s(dp->d_name);
|
|
|
|
files.push_back(s);
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
|
|
|
}
|
2014-04-06 17:19:33 +00:00
|
|
|
closedir(dirp);
|
|
|
|
return true;
|
2012-06-01 15:23:19 +00:00
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
#else
|
2012-06-01 15:23:19 +00:00
|
|
|
|
|
|
|
WIN32_FIND_DATA fil;
|
|
|
|
std::string search(path);
|
|
|
|
MakeSlashTerminated(search);
|
|
|
|
search += "*";
|
|
|
|
HANDLE hFil = FindFirstFile(search.c_str(),&fil);
|
2014-04-06 17:19:33 +00:00
|
|
|
if(hFil == INVALID_HANDLE_VALUE)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
do
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
if(!(fil.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
std::string s(fil.cFileName);
|
|
|
|
files.push_back(s);
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
|
|
|
}
|
2014-04-06 17:19:33 +00:00
|
|
|
while(FindNextFile(hFil, &fil));
|
2012-06-01 15:23:19 +00:00
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
FindClose(hFil);
|
|
|
|
return true;
|
|
|
|
|
|
|
|
#endif
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// returns a list of directory names in the given directory, *without* the source dir.
|
|
|
|
// if getting the dir list recursively, all paths are added, except *again* the top source dir beeing queried.
|
2014-04-06 17:19:33 +00:00
|
|
|
bool GetDirList(const char *path, StringList &dirs, int depth /* = 0 */)
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
|
|
|
#if !_WIN32
|
|
|
|
DIR * dirp;
|
|
|
|
struct dirent * dp;
|
|
|
|
dirp = opendir(path);
|
2014-04-06 17:19:33 +00:00
|
|
|
if(!dirp)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
std::string pathstr(path);
|
|
|
|
MakeSlashTerminated(pathstr);
|
|
|
|
while((dp = readdir(dirp))) // assignment is intentional
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
if (_IsDir(path, dp)) // only add if it is a directory
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
if(strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0)
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
dirs.push_back(dp->d_name);
|
|
|
|
if (depth) // needing a better way to do that
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
std::string d = dp->d_name;
|
|
|
|
std::string subdir = pathstr + d;
|
|
|
|
MakeSlashTerminated(d);
|
|
|
|
StringList newdirs;
|
|
|
|
GetDirList(subdir.c_str(), newdirs, depth - 1);
|
|
|
|
for(std::deque<std::string>::iterator it = newdirs.begin(); it != newdirs.end(); ++it)
|
|
|
|
dirs.push_back(d + *it);
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-04-06 17:19:33 +00:00
|
|
|
closedir(dirp);
|
|
|
|
return true;
|
2012-06-01 15:23:19 +00:00
|
|
|
|
|
|
|
#else
|
2014-04-06 17:19:33 +00:00
|
|
|
|
2013-06-24 17:54:25 +00:00
|
|
|
std::string pathstr(path);
|
|
|
|
MakeSlashTerminated(pathstr);
|
2012-06-01 15:23:19 +00:00
|
|
|
WIN32_FIND_DATA fil;
|
2013-06-24 17:54:25 +00:00
|
|
|
HANDLE hFil = FindFirstFile((pathstr + '*').c_str(),&fil);
|
2014-04-06 17:19:33 +00:00
|
|
|
if(hFil == INVALID_HANDLE_VALUE)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
do
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
if( fil.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
if (!strcmp(fil.cFileName, ".") || !strcmp(fil.cFileName, ".."))
|
|
|
|
continue;
|
2012-06-01 15:23:19 +00:00
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
dirs.push_back(fil.cFileName);
|
2012-06-01 15:23:19 +00:00
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
if (depth) // need a better way to do that
|
|
|
|
{
|
|
|
|
std::string d = fil.cFileName;
|
|
|
|
std::string subdir = pathstr + d;
|
|
|
|
MakeSlashTerminated(d);
|
|
|
|
StringList newdirs;
|
|
|
|
GetDirList(subdir.c_str(), newdirs, depth - 1);
|
|
|
|
for(std::deque<std::string>::iterator it = newdirs.begin(); it != newdirs.end(); ++it)
|
|
|
|
dirs.push_back(d + *it);
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-04-06 17:19:33 +00:00
|
|
|
while(FindNextFile(hFil, &fil));
|
|
|
|
|
|
|
|
FindClose(hFil);
|
|
|
|
return true;
|
2012-06-01 15:23:19 +00:00
|
|
|
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FileExists(const char *fn)
|
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
#if _WIN32
|
|
|
|
return _access(fn, 0) == 0;
|
2012-06-01 15:23:19 +00:00
|
|
|
#else
|
|
|
|
return access(fn, F_OK) == 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// must return true if creating the directory was successful, or already exists
|
|
|
|
bool CreateDir(const char *dir)
|
|
|
|
{
|
|
|
|
if(IsDirectory(dir)) // do not try to create if it already exists
|
|
|
|
return true;
|
|
|
|
bool result;
|
|
|
|
# if _WIN32
|
|
|
|
result = !!::CreateDirectory(dir, NULL);
|
|
|
|
# else
|
|
|
|
result = !mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
|
|
|
#endif
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CreateDirRec(const char *dir)
|
|
|
|
{
|
|
|
|
if(IsDirectory(dir))
|
|
|
|
return true;
|
|
|
|
bool result = true;
|
|
|
|
StringList li;
|
|
|
|
StrSplit(dir, "/\\", li, false);
|
|
|
|
std::string d;
|
2013-06-24 17:54:25 +00:00
|
|
|
d.reserve(strlen(dir) + 1);
|
|
|
|
if(*dir == '/')
|
|
|
|
d += '/';
|
|
|
|
bool last = false;
|
2012-06-01 15:23:19 +00:00
|
|
|
for(StringList::iterator it = li.begin(); it != li.end(); ++it)
|
|
|
|
{
|
|
|
|
d += *it;
|
|
|
|
last = CreateDir(d.c_str());
|
|
|
|
result = last && result;
|
|
|
|
d += '/';
|
|
|
|
}
|
|
|
|
return result || last;
|
|
|
|
}
|
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
bool GetFileSize(const char* fn, vfspos& size)
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
vfspos sz = 0;
|
2014-04-07 02:16:15 +00:00
|
|
|
#if defined(VFS_LARGEFILE_SUPPORT) && defined(_MSC_VER)
|
2012-06-14 15:54:40 +00:00
|
|
|
struct _stat64 st;
|
|
|
|
if(_stat64(fn, &st))
|
2014-04-06 17:19:33 +00:00
|
|
|
return false;
|
|
|
|
sz = st.st_size;
|
2014-04-07 02:16:15 +00:00
|
|
|
#else
|
2012-06-14 15:54:40 +00:00
|
|
|
struct stat st;
|
|
|
|
if(stat(fn, &st))
|
2014-04-06 17:19:33 +00:00
|
|
|
return false;
|
|
|
|
sz = st.st_size;
|
2012-06-14 15:54:40 +00:00
|
|
|
#endif
|
2014-04-06 17:19:33 +00:00
|
|
|
size = sz;
|
|
|
|
return true;
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
void FixSlashes(std::string& s)
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
|
|
|
char last = 0, cur;
|
2014-04-06 17:19:33 +00:00
|
|
|
size_t wpos = 0;
|
2012-06-01 15:23:19 +00:00
|
|
|
for(size_t i = 0; i < s.length(); ++i)
|
|
|
|
{
|
|
|
|
cur = s[i];
|
|
|
|
if(cur == '\\')
|
|
|
|
cur = '/';
|
|
|
|
if(last == '/' && cur == '/')
|
|
|
|
continue;
|
2014-04-06 17:19:33 +00:00
|
|
|
s[wpos++] = cur;
|
2012-06-01 15:23:19 +00:00
|
|
|
last = cur;
|
|
|
|
}
|
2014-04-06 17:19:33 +00:00
|
|
|
s.resize(wpos);
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
void FixPath(std::string& s)
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
|
|
|
if(s.empty())
|
2014-04-06 17:19:33 +00:00
|
|
|
return;
|
2012-06-01 15:23:19 +00:00
|
|
|
const char *p = s.c_str();
|
|
|
|
while(p[0] == '.' && (p[1] == '/' || p[1] == '\\'))
|
|
|
|
p += 2;
|
|
|
|
if(!*p)
|
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
s.clear();
|
|
|
|
return;
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
2014-04-06 17:19:33 +00:00
|
|
|
size_t len = s.length();
|
|
|
|
while(len)
|
|
|
|
{
|
|
|
|
char end = s[len - 1];
|
|
|
|
if(end == '/' || end == '\\') // strip trailing '/'
|
|
|
|
--len;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
s.resize(len);
|
|
|
|
FixSlashes(s);
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool IsDirectory(const char *s)
|
|
|
|
{
|
|
|
|
#if _WIN32
|
|
|
|
DWORD dwFileAttr = GetFileAttributes(s);
|
|
|
|
if(dwFileAttr == INVALID_FILE_ATTRIBUTES)
|
|
|
|
return false;
|
|
|
|
return !!(dwFileAttr & FILE_ATTRIBUTE_DIRECTORY);
|
|
|
|
#else
|
|
|
|
if ( access( s, 0 ) == 0 )
|
|
|
|
{
|
|
|
|
struct stat status;
|
|
|
|
stat( s, &status );
|
|
|
|
return status.st_mode & S_IFDIR; // FIXME: what about symlinks here?
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void MakeSlashTerminated(std::string& s)
|
|
|
|
{
|
|
|
|
if(s.length() && s[s.length() - 1] != '/')
|
|
|
|
s += '/';
|
|
|
|
}
|
|
|
|
|
|
|
|
// extracts the file name from a given path
|
2014-04-06 17:19:33 +00:00
|
|
|
const char *GetBaseNameFromPath(const char *str)
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
|
|
|
const char *p = strrchr(str, '/');
|
|
|
|
return p ? p+1 : str;
|
|
|
|
}
|
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
void StripFileExtension(std::string& s)
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
|
|
|
size_t pos = s.find_last_of('.');
|
|
|
|
size_t pos2 = s.find_last_of('/');
|
2014-04-06 17:19:33 +00:00
|
|
|
if(pos != std::string::npos && (pos2 == std::string::npos || pos2 < pos))
|
|
|
|
s.resize(pos+1);
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
void StripLastPath(std::string& s)
|
2012-06-01 15:23:19 +00:00
|
|
|
{
|
2014-04-06 17:19:33 +00:00
|
|
|
size_t len = s.length();
|
|
|
|
while(len)
|
|
|
|
{
|
|
|
|
char end = s[len - 1];
|
|
|
|
if(end == '/' || end == '\\') // strip trailing '/'
|
|
|
|
--len;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
s.resize(len);
|
2012-06-01 15:23:19 +00:00
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
if(s.empty())
|
|
|
|
return;
|
2012-06-01 15:23:19 +00:00
|
|
|
|
|
|
|
size_t pos = s.find_last_of('/');
|
|
|
|
if(pos == std::string::npos)
|
2014-04-06 17:19:33 +00:00
|
|
|
{
|
|
|
|
s.clear();
|
|
|
|
return; // nothing remains
|
|
|
|
}
|
2012-06-01 15:23:19 +00:00
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
s.resize(pos+1);
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
|
|
|
|
2014-04-06 17:19:33 +00:00
|
|
|
|
2012-06-01 15:23:19 +00:00
|
|
|
// from http://board.byuu.org/viewtopic.php?f=10&t=1089&start=15
|
|
|
|
bool WildcardMatch(const char *str, const char *pattern)
|
|
|
|
{
|
|
|
|
const char *cp = 0, *mp = 0;
|
|
|
|
while(*str && *pattern != '*')
|
|
|
|
{
|
|
|
|
if(*pattern != *str && *pattern != '?')
|
|
|
|
return false;
|
2013-06-24 17:54:25 +00:00
|
|
|
++pattern;
|
|
|
|
++str;
|
2012-06-01 15:23:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
while(*str)
|
|
|
|
{
|
|
|
|
if(*pattern == '*')
|
|
|
|
{
|
|
|
|
if(!*++pattern)
|
2013-06-24 17:54:25 +00:00
|
|
|
return true;
|
2012-06-01 15:23:19 +00:00
|
|
|
mp = pattern;
|
|
|
|
cp = str + 1;
|
|
|
|
}
|
|
|
|
else if(*pattern == *str || *pattern == '?')
|
|
|
|
{
|
|
|
|
++pattern;
|
|
|
|
++str;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pattern = mp;
|
|
|
|
str = cp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-24 17:54:25 +00:00
|
|
|
while(*pattern == '*')
|
|
|
|
++pattern;
|
2012-06-01 15:23:19 +00:00
|
|
|
|
|
|
|
return !*pattern;
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy strings, mangling newlines to system standard
|
|
|
|
// windows has 13+10
|
|
|
|
// *nix has 10
|
|
|
|
// exotic systems may have 10+13
|
|
|
|
size_t strnNLcpy(char *dst, const char *src, unsigned int n /* = -1 */)
|
|
|
|
{
|
|
|
|
char *olddst = dst;
|
|
|
|
bool had10 = false, had13 = false;
|
|
|
|
|
|
|
|
--n; // reserve 1 for \0 at end
|
|
|
|
|
|
|
|
while(*src && n)
|
|
|
|
{
|
|
|
|
if((had13 && *src == 10) || (had10 && *src == 13))
|
|
|
|
{
|
|
|
|
++src; // last was already mangled
|
|
|
|
had13 = had10 = false; // processed one CRLF pair
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
had10 = *src == 10;
|
|
|
|
had13 = *src == 13;
|
|
|
|
|
|
|
|
if(had10 || had13)
|
|
|
|
{
|
|
|
|
*dst++ = '\n';
|
|
|
|
++src;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
*dst++ = *src++;
|
|
|
|
|
|
|
|
--n;
|
|
|
|
}
|
|
|
|
|
|
|
|
*dst++ = 0;
|
|
|
|
|
|
|
|
return dst - olddst;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
VFS_NAMESPACE_END
|