/*
* lftp - file transfer program
*
* Copyright (c) 1996-2016 by Alexander V. Lukyanov (lav@yars.free.net)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include "FileAccess.h"
#include
#include
#include
#include
#include
#include
#include
#include "ascii_ctype.h"
#include
#include "LsCache.h"
#include "log.h"
#include "url.h"
#include "misc.h"
#include "DummyProto.h"
#include "netrc.h"
#include "ArgV.h"
#include "ConnectionSlot.h"
#include "SignalHook.h"
#include "FileGlob.h"
#ifdef WITH_MODULES
# include "module.h"
#endif
xlist_head FileAccess::all_fa;
const FileAccessRef FileAccessRef::null;
void FileAccess::Init()
{
ClassInit();
pass_open=false;
default_cwd="~";
cwd.Set(default_cwd,false,0);
limit=FILE_END;
real_pos=UNKNOWN_POS;
pos=0;
mode=CLOSED;
retries=0;
max_retries=0;
opt_date=0;
opt_size=0;
fileset_for_info=0;
error_code=OK;
saved_errno=0;
mkdir_p=false;
rename_f=false;
ascii=false;
norest_manual=false;
entity_size=NO_SIZE;
entity_date=NO_DATE;
res_prefix=0;
chmod_mode=0644;
priority=last_priority=0;
all_fa.add(all_fa_node);
}
FileAccess::FileAccess(const FileAccess *fa)
: all_fa_node(this)
{
Init();
cwd=fa->cwd;
home=fa->home;
user.set(fa->user);
pass.set(fa->pass);
pass_open=fa->pass_open;
hostname.set(fa->hostname);
portname.set(fa->portname);
vproto.set(fa->vproto);
}
FileAccess::~FileAccess()
{
all_fa_node.remove();
}
void FileAccess::Open(const char *fn,int mode,off_t offs)
{
#ifdef OPEN_DEBUG
printf("%p->FA::Open(%s,%d)\n",this,fn?fn:"NULL",mode);
#endif
if(IsOpen())
Close();
Resume();
file.set(fn);
real_pos=UNKNOWN_POS;
pos=offs;
this->mode=mode;
mkdir_p=false;
rename_f=false;
Timeout(0);
switch((open_mode)mode)
{
case STORE:
case REMOVE:
case MAKE_DIR:
case CHANGE_MODE:
cache->FileChanged(this,file);
break;
case REMOVE_DIR:
cache->FileChanged(this,file);
cache->TreeChanged(this,file);
break;
default:
break;
}
}
const char *FileAccess::StrError(int err)
{
static xstring str;
// note to translators: several errors should not be displayed to user;
// so no need to translate them.
switch(err)
{
case(IN_PROGRESS):
return("Operation is in progress");
case(OK):
return("Error 0");
case(SEE_ERRNO):
if(error)
return str.vset(error.get(),": ",strerror(saved_errno),NULL);
return(strerror(saved_errno));
case(LOOKUP_ERROR):
return(error);
case(NOT_OPEN): // Actually this means an error in application
return("Class is not Open()ed");
case(NO_FILE):
if(error)
return str.vset(_("Access failed: "),error.get(),NULL);
return(_("File cannot be accessed"));
case(NO_HOST):
return(_("Not connected"));
case(FATAL):
if(error)
return str.vset(_("Fatal error"),": ",error.get(),NULL);
return(_("Fatal error"));
case(STORE_FAILED):
return(_("Store failed - you have to reput"));
case(LOGIN_FAILED):
if(error)
return str.vset(_("Login failed"),": ",error.get(),NULL);
return(_("Login failed"));
case(NOT_SUPP):
if(error)
return str.vset(_("Operation not supported"),": ",error.get(),NULL);
return(_("Operation not supported"));
case(FILE_MOVED):
if(error)
return str.vset(_("File moved"),": ",error.get(),NULL);
else
return str.vset(_("File moved to `"),location?location.get():"?","'",NULL);
}
return("");
}
void FileAccess::Close()
{
file.set(0);
file_url.set(0);
file1.set(0);
new_cwd=0;
mode=CLOSED;
opt_date=0;
opt_size=0;
fileset_for_info=0;
retries=0;
entity_size=NO_SIZE;
entity_date=NO_DATE;
ascii=false;
norest_manual=false;
location.set(0);
entity_content_type.set(0);
entity_charset.set(0);
ClearError();
}
void FileAccess::Open2(const char *f,const char *f1,open_mode o)
{
Close();
file1.set(f1);
Open(f,o);
cache->TreeChanged(this,file);
cache->FileChanged(this,file);
cache->FileChanged(this,file1);
}
void FileAccess::Rename(const char *rfile,const char *to,bool clobber)
{
Open2(rfile,to,RENAME);
rename_f=clobber;
}
void FileAccess::Mkdir(const char *fn,bool allp)
{
Open(fn,MAKE_DIR);
mkdir_p=allp;
}
StringSet *FileAccess::MkdirMakeSet() const
{
StringSet *set=new StringSet;
const char *sl=strchr(file,'/');
while(sl)
{
if(sl>file)
{
xstring& tmp=xstring::get_tmp(file,sl-file);
if(tmp.ne(".") && tmp.ne(".."))
set->Append(tmp);
}
sl=strchr(sl+1,'/');
}
return set;
}
bool FileAccess::SameLocationAs(const FileAccess *fa) const
{
return SameSiteAs(fa);
}
bool FileAccess::SameSiteAs(const FileAccess *fa) const
{
return SameProtoAs(fa);
}
const xstring& FileAccess::GetFileURL(const char *f,int flags) const
{
const char *proto=GetVisualProto();
if(proto[0]==0)
return xstring::get_tmp("");
ParsedURL u;
u.proto.set(proto);
if(!(flags&NO_USER))
u.user.set(user);
if((pass_open || (flags&WITH_PASSWORD)) && !(flags&NO_PASSWORD))
u.pass.set(pass);
u.host.set(hostname);
u.port.set(portname);
if(!(flags&NO_PATH))
{
if(cwd.url)
{
Path f_path(cwd);
if(f)
f_path.Change(f,true);
if(f_path.url)
{
int f_path_index=url::path_index(f_path.url);
return u.CombineTo(xstring::get_tmp(""),home)
.append(f_path.url+f_path_index);
}
}
bool is_dir=((!f || !*f) && !cwd.is_file);
if(!f || (f[0]!='/' && f[0]!='~'))
f=dir_file(cwd.path?cwd.path.get():"~",f);
u.path.set(f);
if(is_dir && url::dir_needs_trailing_slash(proto) && u.path.last_char()!='/')
u.path.append('/');
}
return u.CombineTo(xstring::get_tmp(""),home);
}
const xstring& FileAccess::GetConnectURL(int flags) const
{
return GetFileURL(0,flags);
}
void FileAccess::Connect(const char *host1,const char *port1)
{
Close();
hostname.set(host1);
portname.set(port1);
DontSleep();
ResetLocationData();
}
void FileAccess::Login(const char *user1,const char *pass1)
{
Close();
user.set(user1);
pass.set(pass1);
pass_open=false;
if(user && pass==0)
{
xlist_for_each(FileAccess,all_fa,node,o)
{
pass.set(o->pass);
if(SameSiteAs(o) && o->pass)
break;
}
if(!o)
pass.set(0);
if(pass==0 && hostname) // still no pass? Try .netrc
{
NetRC::Entry *nrc=NetRC::LookupHost(hostname,user);
if(nrc)
pass.set(nrc->pass);
}
}
ResetLocationData();
}
void FileAccess::ResetLocationData()
{
cwd.Set(default_cwd,false,0);
home.Set((char*)0);
}
void FileAccess::SetPasswordGlobal(const char *p)
{
pass.set(p);
xstring save_pass;
xlist_for_each(FileAccess,all_fa,node,o)
{
if(o==this)
continue;
save_pass.set(o->pass); // cheat SameSiteAs.
o->pass.set(pass);
if(!SameSiteAs(o))
o->pass.set(save_pass);
}
}
void FileAccess::GetInfoArray(FileSet *info)
{
Open(0,ARRAY_INFO);
fileset_for_info=info;
fileset_for_info->rewind();
}
static void expand_tilde(xstring &path, const char *home, int i=0)
{
if(!(path[i]=='~' && (path[i+1]==0 || path[i+1]=='/')))
return;
char prefix_len=(last_char(home)=='/' ? 2 : 1);
if(home[0]=='/' && i>0 && path[i-1]=='/')
home++;
path.set_substr(i,prefix_len,home);
}
void FileAccess::ExpandTildeInCWD()
{
if(home)
{
cwd.ExpandTilde(home);
if(new_cwd)
new_cwd->ExpandTilde(home);
if(real_cwd)
expand_tilde(real_cwd,home);
if(file)
expand_tilde(file,home);
if(file1)
expand_tilde(file1,home);
}
}
void FileAccess::set_home(const char *h)
{
home.Set(h);
ExpandTildeInCWD();
}
const char *FileAccess::ExpandTildeStatic(const char *s) const
{
if(!home || !(s[0]=='~' && (s[1]=='/' || s[1]==0)))
return s;
static xstring buf;
buf.set(s);
expand_tilde(buf,home);
return buf;
}
static inline
bool last_element_is_doubledot(const char *path,const char *end)
{
return((end==path+2 && !strncmp(path,"..",2))
|| (end>path+2 && !strncmp(end-3,"/..",3)));
}
int FileAccess::device_prefix_len(const char *path) const
{
ResValue dp=Query("device-prefix",hostname);
if(dp.is_nil() || !dp.to_bool())
return 0;
int i=0;
while(path[i] && (is_ascii_alnum(path[i]) || strchr("$_-",path[i])))
i++;
if(i>0 && path[i]==':')
return i+1+(path[i+1]=='/');
return 0;
}
void FileAccess::Path::Optimize(xstring& path,int device_prefix_len)
{
int prefix_size=0;
if(path[0]=='/' && path[1]=='~' && device_prefix_len==1)
{
prefix_size=2;
while(path[prefix_size]!='/' && path[prefix_size]!='\0')
prefix_size++;
}
else if(path[0]=='/')
{
prefix_size=1;
if(path[1]=='/' && (!path[2] || path[2]!='/'))
prefix_size=2;
}
else if(path[0]=='~')
{
prefix_size=1;
while(path[prefix_size]!='/' && path[prefix_size]!='\0')
prefix_size++;
}
else
{
// handle VMS and DOS devices.
prefix_size=device_prefix_len;
}
char *in;
char *out;
in=out=path.get_non_const()+prefix_size;
while((in[0]=='.' && (in[1]=='/' || in[1]==0))
|| (in>path && in[-1]=='/' && (in[0]=='/'
|| (in[0]=='.' && in[1]=='.' && (in[2]=='/' || in[2]==0)))))
{
if(in[0]=='.' && in[1]=='.')
in++;
in++;
if(*in=='/')
in++;
}
while(*in)
{
if(in[0]=='/')
{
// double slash
if(in[1]=='/')
{
in++;
continue;
}
if(in[1]=='.')
{
// . - cur dir
if(in[2]=='/' || in[2]=='\0')
{
in+=2;
continue;
}
// .. - prev dir
if(in[2]=='.' && (in[3]=='/' || in[3]=='\0'))
{
if(last_element_is_doubledot(path+prefix_size,out)
|| out==path
|| (out==path+prefix_size && out[-1]!='/'))
{
if(out>path && out[-1]!='/')
*out++='/';
*out++='.';
*out++='.';
}
else
{
while(out>path+prefix_size && *--out!='/')
;
}
in+=3;
continue;
}
}
// don't add slash after prefix with slash
if(out>path && out[-1]=='/')
{
in++;
continue;
}
}
*out++=*in++;
}
path.truncate(path.length()-(in-out));
}
void FileAccess::Chdir(const char *path,bool verify)
{
cwd.ExpandTilde(home);
Close();
new_cwd=new Path(&cwd);
new_cwd->Change(path,false);
if(verify)
Open(new_cwd->path,CHANGE_DIR);
else
{
cwd.Set(new_cwd);
new_cwd=0;
}
}
void FileAccess::PathVerify(const Path &p)
{
Close();
new_cwd=new Path(p);
Open(new_cwd->path,CHANGE_DIR);
}
void FileAccess::Chmod(const char *file,int m)
{
chmod_mode=m;
Open(file,CHANGE_MODE);
}
void FileAccess::SetError(int ec,const char *e)
{
if(ec==SEE_ERRNO && !saved_errno)
saved_errno=errno;
if(ec==NO_FILE && file && file[0] && !strstr(e,file))
error.vset(e," (",file.get(),")",NULL);
else
error.set(e);
error_code=ec;
}
void FileAccess::ClearError()
{
saved_errno=0;
error_code=OK;
error.set(0);
}
void FileAccess::Fatal(const char *e)
{
SetError(FATAL,e);
}
void FileAccess::SetSuggestedFileName(const char *fn)
{
suggested_filename.set(0);
if(fn==0)
return;
// don't allow subdirectories.
if(strchr(fn,'/') || strchr(fn,'\\') || strchr(fn,':'))
return;
for(int i=0; fn[i]; i++)
{
// don't allow control chars.
if(iscntrl((unsigned char)fn[i]))
return;
}
if(!*fn || *fn=='.')
return;
suggested_filename.set(fn);
}
void FileAccess::SetFileURL(const char *u)
{
file_url.set(u);
if(new_cwd && mode==CHANGE_DIR)
new_cwd->SetURL(u);
}
FileAccess *SessionPool::pool[pool_size];
void SessionPool::Reuse(FileAccess *f)
{
if(f==0)
return;
if(f->GetHostName()==0)
{
SMTask::Delete(f);
return;
}
f->Close();
f->SetPriority(0);
int i;
for(i=0; iIsBetterThan(pool[i]))
{
SMTask::Delete(pool[i]);
pool[i]=f;
return;
}
}
SMTask::Delete(f);
}
void SessionPool::Print(FILE *f)
{
int arr[pool_size];
int n=0;
int i;
for(i=0; iSameLocationAs(pool[i]))
break;
if(j==n)
arr[n++]=i;
}
// sort?
for(i=0; iGetConnectURL().get());
}
FileAccess *SessionPool::GetSession(int n)
{
if(n<0 || n>=pool_size)
return 0;
FileAccess *s=pool[n];
pool[n]=0;
return s;
}
FileAccess *SessionPool::Walk(int *n,const char *proto)
{
for( ; *nGetProto(),proto))
return pool[*n];
}
return 0;
}
void SessionPool::ClearAll()
{
int pass=0;
for(;;) {
int left=0;
for(int n=0; nDisconnect();
if(!pool[n]->IsConnected()) {
SMTask::Delete(pool[n]);
pool[n]=0;
} else {
left++;
}
}
if(left==0)
break;
SMTask::Schedule();
SMTask::Block();
pass++;
}
}
void FileAccess::SetTryTime(time_t t)
{
if(t)
reconnect_timer.Reset(Time(t));
else
reconnect_timer.Stop();
}
bool FileAccess::IsBetterThan(const FileAccess *fa) const
{
return(SameProtoAs(fa) && this->IsConnected() > fa->IsConnected());
}
void FileAccess::Reconfig(const char *) {}
void FileAccess::ConnectVerify() { mode=CONNECT_VERIFY; }
const char *FileAccess::CurrentStatus() { return ""; }
int FileAccess::Buffered() { return 0; }
bool FileAccess::IOReady() { return IsOpen(); }
int FileAccess::IsConnected() const { return 0; }
void FileAccess::UseCache(bool) {}
bool FileAccess::NeedSizeDateBeforehand() { return false; }
void FileAccess::Cleanup() {}
void FileAccess::CleanupThis() {}
ListInfo *FileAccess::MakeListInfo(const char *path) { return 0; }
Glob *FileAccess::MakeGlob(const char *pattern) { return new NoGlob(pattern); }
DirList *FileAccess::MakeDirList(ArgV *a) { delete a; return 0; }
void FileAccess::CleanupAll()
{
xlist_for_each(FileAccess,all_fa,node,o)
{
Enter(o);
o->CleanupThis();
Leave(o);
}
}
FileAccess *FileAccess::NextSameSite(FA *scan) const
{
if(scan==0)
scan=all_fa.first_obj();
else
scan=scan->all_fa_node.next_obj();
for( ; scan; scan=scan->all_fa_node.next_obj())
if(scan!=this && SameSiteAs(scan))
return scan;
return 0;
}
FileAccess *FileAccess::New(const char *proto,const char *host,const char *port)
{
ClassInit();
if(proto==0)
proto="file";
if(!strcmp(proto,"slot"))
{
const FA *session=ConnectionSlot::FindSession(host);
return session?session->Clone():0;
}
FA *session=Protocol::NewSession(proto);
if(!session)
return 0;
const char *n_proto=session->ProtocolSubstitution(host);
if(n_proto && strcmp(n_proto,proto))
{
FA *n_session=Protocol::NewSession(n_proto);
if(n_session)
{
Delete(session);
session=n_session;
session->SetVisualProto(proto);
}
}
if(host)
session->Connect(host,port);
return session;
}
FileAccess *FileAccess::New(const ParsedURL *u,bool dummy)
{
const char *proto=u->proto?u->proto.get():"file";
FileAccess *s=New(proto,u->host);
if(!s)
{
if(!dummy)
return 0;
return new DummyNoProto(proto);
}
if(strcmp(proto,"slot"))
s->Connect(u->host,u->port);
if(u->user)
s->Login(u->user,u->pass);
// path?
return s;
}
// FileAccess::Protocol implementation
xmap_p FileAccess::Protocol::proto_by_name;
FileAccess::Protocol::Protocol(const char *proto, SessionCreator *creator)
{
this->proto=proto;
this->New=creator;
proto_by_name.add(proto,this);
}
FileAccess::Protocol *FileAccess::Protocol::FindProto(const char *proto)
{
return proto_by_name.lookup(proto);
}
FileAccess *FileAccess::Protocol::NewSession(const char *proto)
{
Protocol *p;
p=FindProto(proto);
if(p)
return p->New();
#ifdef WITH_MODULES
#define PROTO_PREFIX "proto-"
const char *mod=xstring::cat(PROTO_PREFIX,proto,NULL);
void *map=module_load(mod,0,0);
if(map==0)
{
fprintf(stderr,"%s\n",module_error_message());
return 0;
}
p=FindProto(proto);
if(p)
return p->New();
#endif
return 0;
}
// FileAccessOperation implementation
void FileAccessOperation::SetError(const char *e)
{
error_text.set(e);
done=true;
}
void FileAccessOperation::SetErrorCached(const char *e)
{
SetError(e);
error_text.append(_(" [cached]"));
}
DirList::DirList(FileAccess *s,ArgV *a)
: FileAccessOperation(s), buf(new Buffer()), args(a), color(false)
{
}
DirList::~DirList()
{
}
// ListInfo implementation
ListInfo::ListInfo(FileAccess *s,const char *p)
: FileAccessOperation(s), exclude_prefix(0), exclude(0), need(0),
follow_symlinks(false), try_recursive(false), is_recursive(false)
{
if(session && p)
{
saved_cwd=session->GetCwd();
session->Chdir(p,false);
}
}
void ListInfo::PrepareToDie()
{
if(session)
session->Close();
if(session && saved_cwd)
session->SetCwd(saved_cwd);
}
ListInfo::~ListInfo() {}
// Path implementation
void FileAccess::Path::init()
{
device_prefix_len=0;
is_file=false;
}
FileAccess::Path::~Path()
{
}
void FileAccess::Path::Set(const char *new_path,bool new_is_file,const char *new_url,int new_device_prefix_len)
{
path.set(new_path);
is_file=new_is_file;
url.set(new_url);
device_prefix_len=new_device_prefix_len;
}
void FileAccess::Path::Set(const Path *o)
{
Set(o->path,o->is_file,o->url,o->device_prefix_len);
}
void FileAccess::Path::Change(const char *new_path,bool new_is_file,const char *new_path_enc,int new_device_prefix_len)
{
if(!new_path && new_path_enc)
new_path=url::decode(new_path_enc);
if(!new_path || !*new_path)
return;
const char *bn=basename_ptr(new_path);
if(!strcmp(bn,".") || !strcmp(bn,".."))
new_is_file=false;
int path_index=0;
if(url)
{
path_index=url::path_index(url);
xstring new_url_path(url+path_index);
if(is_file)
{
dirname_modify(new_url_path);
if(!new_url_path[0])
new_url_path.set("/~");
}
if(new_url_path.last_char()!='/')
new_url_path.append('/');
if(new_path[0]=='/' || new_path[0]=='~' || new_device_prefix_len!=0)
{
bool have_slash=((new_path_enc?new_path_enc:new_path)[0]=='/');
new_url_path.set(have_slash?"":"/");
}
if(new_path_enc)
new_url_path.append(new_path_enc);
else
new_url_path.append(url::encode(new_path,URL_PATH_UNSAFE));
if(!new_is_file && url::dir_needs_trailing_slash(url) && new_url_path.last_char()!='/')
new_url_path.append('/');
Optimize(new_url_path,!strncmp(new_url_path,"/~",2));
url.truncate(path_index);
url.append(new_url_path);
}
if(new_path[0]!='/' && new_path[0]!='~' && new_device_prefix_len==0
&& path && path[0])
{
if(is_file)
{
dirname_modify(path);
if(!path[0])
path.set("~");
}
if(last_char(path)=='/')
new_path=xstring::format("%s%s",path.get(),new_path);
else
new_path=xstring::format("%s/%s",path.get(),new_path);
}
path.set(new_path);
device_prefix_len=new_device_prefix_len;
Optimize();
strip_trailing_slashes(path);
is_file=new_is_file;
if(!strcmp(path,"/") || !strcmp(path,"//"))
is_file=false;
// sanity check
if(url)
{
ParsedURL u(url);
if(u.path.length()>1)
u.path.chomp('/');
if(!u.path.eq(path))
{
LogError(0,"URL mismatch %s [%s] vs %s, dropping URL\n",url.get(),u.path.get(),path.get());
url.set(0);
}
}
}
bool FileAccess::Path::operator==(const Path &p2) const
{
const Path &p1=*this;
if(p1.is_file!=p2.is_file)
return false;
if(xstrcmp(p1.path,p2.path))
return false;
if(xstrcmp(p1.url,p2.url))
return false;
return true;
}
void FileAccess::Path::ExpandTilde(const Path &home)
{
if(!home.path)
return;
if(path && path[0]=='~' && (path[1]=='/' || path[1]=='\0'))
{
device_prefix_len=home.device_prefix_len;
if(path[1]=='\0')
is_file=home.is_file;
}
if(url)
{
int pi=url::path_index(url);
if(url[pi]=='/' && url[pi+1]=='~')
pi++;
expand_tilde(url,home.url?home.url.get():url::encode(home.path,URL_PATH_UNSAFE).get(),pi);
}
expand_tilde(path,home.path);
}
#include "DirColors.h"
#include "LocalDir.h"
#include "FileCopy.h"
#include "modconfig.h"
#ifndef MODULE_PROTO_FTP
# include "ftpclass.h"
# define _ftp Ftp::ClassInit()
#else
# define _ftp
#endif
#ifndef MODULE_PROTO_FILE
# include "LocalAccess.h"
# define _file LocalAccess::ClassInit()
#else
# define _file
#endif
#ifndef MODULE_PROTO_HTTP
# include "Http.h"
# define _http Http::ClassInit()
#else
# define _http
#endif
#ifndef MODULE_PROTO_FISH
# include "Fish.h"
# define _fish Fish::ClassInit()
#else
# define _fish
#endif
#ifndef MODULE_PROTO_SFTP
# include "SFtp.h"
# define _sftp SFtp::ClassInit()
#else
# define _sftp
#endif
bool FileAccess::class_inited;
LsCache *FileAccess::cache;
void FileAccess::ClassInit()
{
if(class_inited)
return;
class_inited=true;
cache=new LsCache();
SignalHook::ClassInit();
ResMgr::ClassInit();
if(!Log::global)
Log::global=new Log("debug");
_ftp;
_file;
_http;
_fish;
_sftp;
// make it link in classes required by modules.
LocalDirectory ld;
}
void FileAccess::ClassCleanup()
{
Protocol::ClassCleanup();
call_dynamic_hook("lftp_network_cleanup");
DirColors::DeleteInstance();
delete cache;
cache=0;
FileCopy::fxp_create=0;
}
const FileAccessRef& FileAccessRef::operator=(FileAccess *p)
{
reuse();
ptr=SMTask::MakeRef(p);
return *this;
}
// hook-up gnulib...
CDECL_BEGIN
#include "md5.h"
#include "glob.h"
CDECL_END
void *_md5_hook=(void*)md5_init_ctx;
void *_glob_hook=(void*)glob;