#include "safeio.h" #include "socket.h" #include "stringf.h" #include #include #include #include #include #include #include // https://stackoverflow.com/a/8615450/2995591 #include // glob(),globfree() #include // memset() #include #include static std::vector cxxglob(const std::string pattern) { glob_t glob_result; memset(&glob_result,0,sizeof(glob_result)); int return_value=glob(pattern.c_str(),GLOB_TILDE,NULL,&glob_result); if (return_value) fatal("glob() failed with return_value %s",return_value); vector filenames; filenames.reserve(glob_result.gl_pathc); for (size_t i = 0; i < glob_result.gl_pathc; ++i) filenames.push_back(string(glob_result.gl_pathv[i])); globfree(&glob_result); return filenames; } // FIXME: Use C++17 static bool fd_is_open(const char *execname,const char *fn) { const char slashproc[]("/proc"); DIR *dir(opendir(slashproc)); if (!dir) fatal("Cannot opendir %s: %m",slashproc); bool retval(false); for (;;) { errno=0; const struct dirent *de=readdir(dir); if (!de) { if (errno) fatal("Cannot readdir %s: %m",slashproc); break; } if (!isdigit(de->d_name[0])) continue; char buf[PATH_MAX]; ssize_t got(readlinkat(dirfd(dir),stringf("%s/exe",de->d_name).c_str(),buf,sizeof(buf))); if (got==-1||got==sizeof(buf)) continue; buf[got]=0; char *s=strrchr(buf,'/'); if (!s) continue; if (strcmp(s+1,execname)!=0) continue; string procpidfd(stringf("/proc/%s/fd",de->d_name)); DIR *fddir(opendir(procpidfd.c_str())); if (!fddir) fatal("Cannot opendir %s: %m",procpidfd.c_str()); for (;;) { errno=0; const struct dirent *de=readdir(fddir); if (!de) { if (errno) fatal("Cannot readdir %s: %m",procpidfd.c_str()); break; } if (!isdigit(de->d_name[0])) continue; char buf[PATH_MAX]; ssize_t got(readlinkat(dirfd(fddir),de->d_name,buf,sizeof(buf))); if (got==-1||got==sizeof(buf)) continue; buf[got]=0; if (strcmp(buf,fn)==0) { retval=true; break; } } if (closedir(fddir)) fatal("Cannot closedir %s: %m",procpidfd.c_str()); if (retval) break; } if (closedir(dir)) fatal("Cannot closedir %s: %m",slashproc); return retval; } int main(int argc,char **argv) { static struct sigaction sigchld; sigchld.sa_handler=SIG_DFL; sigchld.sa_flags=SA_NOCLDWAIT; int err(sigaction(SIGCHLD,&sigchld,nullptr)); assert(!err); if (argc!=1+2&&argc!=1+3) fatal("streamfer-server [:] [follow-fd-of-executable-basename]"); string prefix; if (argc>=1+2&&*argv[2]) prefix=argv[2]; const char *execname(nullptr); if (argc>=1+3) execname=argv[3]; int listen_fd(socket_bind(argv[1])); int client_fd; for (;;) { client_fd=socket_accept(listen_fd,[&](int client_fd,string addr) { warning("%d:%s",client_fd,addr.c_str()); }); int child(fork()); assert(child!=-1); if (!child) break; int err(close(client_fd)); assert(!err); } err=close(listen_fd); assert(!err); string pattern(read_safe_string(client_fd)); std::vector matched(cxxglob(pattern)); for (size_t ix=0;ix=0) fatal("glob: strcmp(\"%s\",\"%s\")=%d",a.c_str(),b.c_str(),err); } string last(read_safe_string(client_fd)); size_t lastix(SIZE_MAX); for (size_t ix=0;ix0) assert(lastix==SIZE_MAX); else if (lastix==SIZE_MAX) lastix=ix; } if (lastix==SIZE_MAX) fatal("Requested too new file"); uint64_t offset; read_safe(client_fd,offset); struct timespec mtim; read_safe(client_fd,mtim); const string *fnp; int file_fd=-1; struct stat statbuf; for (;lastix(uint64_t)statbuf.st_size) warning("File %s has transferred %zu < %zu which is its size",fn.c_str(),(size_t)offset,(size_t)statbuf.st_size); if (lastix==matched.size()-1&&execname) break; err=close(file_fd); assert(!err); offset=0; } if (file_fd==-1) { string empty(""); write_safe(client_fd,empty); fatal("No more files to transfer"); } const string &fn(*fnp); const char *fn_canon(nullptr); if (!prefix.empty()||execname) { fn_canon=realpath(fn.c_str(),nullptr); if (!fn_canon) fatal("realpath %s: %m",fn.c_str()); } if (fn!=last) offset=0; if (!prefix.empty()&&strncmp(prefix.c_str(),fn_canon,prefix.length())!=0) fatal("prefix=\"%s\" realpath=\"%s\"",prefix.c_str(),fn_canon); warning("%s @%zu",fn.c_str(),(size_t)offset); write_safe(client_fd,fn); write_safe(client_fd,statbuf.st_mtim); off_t got(lseek(file_fd,offset,SEEK_SET)); assert((uint64_t)got==offset); struct pollfd fds; fds.fd=client_fd; fds.events=POLLIN|POLLPRI|POLLRDHUP; for (;;) { transfer(file_fd,fn.c_str(),client_fd,"client fd"); if (!fn_canon||!fd_is_open(execname,fn_canon)) break; int err(poll(&fds,1,1000/*ms*/)); if (err==-1) fatal("poll client fd: %m"); if (err==1) fatal("poll client fd: revents=0x%x",fds.revents); assert(err==0); } }