Loading commands/filesystem/install.cpp +220 −114 Changes for commands/filesystem/install.cpp: 220 added lines, 114 removed lines. Original line number Diff line number Diff line Loading @@ -28,116 +28,168 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <iostream> #include <string> #include <vector> #include <fstream> #include <cstring> #include <cerrno> #include <cstdlib> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> #include <utime.h> #include <fcntl.h> #include <pwd.h> #include <grp.h> #include "shell.h" #include "modeutil.h" #include "../text/textutil.h" namespace fresh { namespace cmd { // "install [-m MODE] [-p] [-d] SRC... DST" / "install -d DIR...": the // BSD install(1) core used throughout %install sections -- copies each // SRC to DST (a directory, or the exact target file name for a single // SRC), creating any missing leading directories in DST (real install(1) // only does this under "-D"; every call here already behaves as if -D // were always on, so that flag is accepted but otherwise a no-op), then // chmod's the result to MODE (default 0755). "-p" additionally copies // SRC's mtime/atime onto DST after that (real install(1) default: // fresh timestamps) -- needed for upstream autotools-generated // "$(INSTALL) -c -p ..." install rules. "-d" instead creates each // argument as a directory (mkdir -p). "-o OWNER"/"-g GROUP" are // accepted (consuming their argument) but not applied -- every %install // here already runs as root installing to a throwaway buildroot, so // ownership beyond that has never mattered; "-c"/"-s"/"-b"/"-v" are // accepted as no-ops (traditional compatibility/strip/backup/verbose // flags real install(1) also supports). // install [-cCDdpsv] [-m MODE] [-o OWNER] [-g GROUP] [-t DIR] [-T] SRC... DST // install -d [-m MODE] DIR... // GNU semantics: files default to 0755, the destination is replaced // (unlinked, then written fresh), leading directories are only created // with -D, -s runs $STRIPPROG (default "strip") on the copy. class InstallCommand : public ShellCommand { public: InstallCommand():ShellCommand("install"){} static bool mkdirsFor(const std::string &path,std::string &err){ std::string dir=path; size_t slash=dir.find_last_of('/'); if(slash==std::string::npos) return true; dir=dir.substr(0,slash); if(dir.empty()) return true; bool verbose=false,preserve=false,strip=false,compare=false; bool haveOwner=false,haveGroup=false; uid_t owner=(uid_t)-1; gid_t group=(gid_t)-1; mode_t mode=0755; std::string stripProg; std::string partial; size_t start=0; if(dir[0]=='/'){ partial="/"; start=1; } while(start<=dir.size()){ size_t next=dir.find('/',start); std::string comp=(next==std::string::npos) ? dir.substr(start) : dir.substr(start,next-start); if(!comp.empty()){ if(!partial.empty() && partial.back()!='/') partial+="/"; partial+=comp; if(mkdir(partial.c_str(),0755)!=0 && errno!=EEXIST){ err=partial+": "+strerror(errno); bool mkdirs(const std::string &dir,mode_t last,bool applyLast){ std::string cur; size_t p=0; if(!dir.empty() && dir[0]=='/'){ cur="/"; p=1; } while(p<=dir.size()){ size_t sl=dir.find('/',p); std::string comp=dir.substr(p,sl==std::string::npos ? std::string::npos : sl-p); p=sl==std::string::npos ? dir.size()+1 : sl+1; if(comp.empty()) continue; if(!cur.empty() && cur.back()!='/') cur+='/'; cur+=comp; bool isLast=p>dir.size() || dir.find_first_not_of('/',p)==std::string::npos; struct stat st; if(stat(cur.c_str(),&st)==0){ if(!S_ISDIR(st.st_mode)){ std::cerr << "install: cannot create directory '" << cur << "': File exists" << std::endl; return false; } }else{ if(mkdir(cur.c_str(),0755)!=0 && errno!=EEXIST){ std::cerr << "install: cannot create directory '" << cur << "': " << strerror(errno) << std::endl; return false; } if(verbose) std::cout << "install: creating directory '" << cur << "'" << std::endl; } if(isLast && applyLast){ if(chmod(cur.c_str(),last)!=0){ std::cerr << "install: cannot change permissions of '" << cur << "': " << strerror(errno) << std::endl; return false; } if((haveOwner || haveGroup) && chown(cur.c_str(),owner,group)!=0){ std::cerr << "install: cannot change ownership of '" << cur << "': " << strerror(errno) << std::endl; return false; } } if(next==std::string::npos) break; start=next+1; } return true; } static bool makeDir(const std::string &path){ std::string err; if(!mkdirsFor(path+"/x",err)){ std::cerr << "install: " << err << std::endl; static bool sameContent(const std::string &a,const std::string &b){ std::string x,y; if(!text::readInput(a,x) || !text::readInput(b,y)) return false; return x==y; } bool installFile(const std::string &src,const std::string &dst){ struct stat ss; if(stat(src.c_str(),&ss)!=0){ std::cerr << "install: cannot stat '" << src << "': " << strerror(errno) << std::endl; return false; } if(S_ISDIR(ss.st_mode)){ std::cerr << "install: omitting directory '" << src << "'" << std::endl; return false; } if(mkdir(path.c_str(),0755)!=0 && errno!=EEXIST){ std::cerr << "install: " << path << ": " << strerror(errno) << std::endl; struct stat ds; if(stat(dst.c_str(),&ds)==0 && S_ISDIR(ds.st_mode)){ std::cerr << "install: cannot overwrite directory '" << dst << "' with non-directory '" << src << "'" << std::endl; return false; } if(stat(dst.c_str(),&ds)==0 && ds.st_dev==ss.st_dev && ds.st_ino==ss.st_ino){ std::cerr << "install: '" << src << "' and '" << dst << "' are the same file" << std::endl; return false; } if(compare && stat(dst.c_str(),&ds)==0 && (ds.st_mode&07777)==mode && S_ISREG(ds.st_mode) && (!haveOwner || ds.st_uid==owner) && (!haveGroup || ds.st_gid==group) && sameContent(src,dst)) return true; int in=open(src.c_str(),O_RDONLY); if(in<0){ std::cerr << "install: cannot open '" << src << "' for reading: " << strerror(errno) << std::endl; return false; } static bool copyFile(const std::string &src,const std::string &dst,mode_t mode,bool preserveTimes){ std::string err; if(!mkdirsFor(dst,err)){ std::cerr << "install: " << err << std::endl; if(unlink(dst.c_str())!=0 && errno!=ENOENT){ std::cerr << "install: cannot remove '" << dst << "': " << strerror(errno) << std::endl; close(in); return false; } std::ifstream in(src.c_str(),std::ios::binary); if(!in.is_open()){ std::cerr << "install: " << src << ": " << strerror(errno) << std::endl; int out=open(dst.c_str(),O_WRONLY|O_CREAT|O_EXCL,0600); if(out<0){ std::cerr << "install: cannot create regular file '" << dst << "': " << strerror(errno) << std::endl; close(in); return false; } std::ofstream out(dst.c_str(),std::ios::binary|std::ios::trunc); if(!out.is_open()){ std::cerr << "install: " << dst << ": " << strerror(errno) << std::endl; char buf[65536]; bool ok=true; for(;;){ ssize_t r=read(in,buf,sizeof buf); if(r<0){ if(errno==EINTR) continue; ok=false; break; } if(r==0) break; ssize_t off=0; while(off<r){ ssize_t w=write(out,buf+off,(size_t)(r-off)); if(w<0){ if(errno==EINTR) continue; ok=false; break; } off+=w; } if(!ok) break; } close(in); if(close(out)!=0) ok=false; if(!ok){ std::cerr << "install: error copying '" << src << "' to '" << dst << "': " << strerror(errno) << std::endl; return false; } out << in.rdbuf(); in.close(); out.close(); if(chmod(dst.c_str(),mode)!=0){ std::cerr << "install: " << dst << ": " << strerror(errno) << std::endl; if(verbose) std::cout << "'" << src << "' -> '" << dst << "'" << std::endl; if(strip){ pid_t pid=fork(); if(pid==0){ execlp(stripProg.c_str(),stripProg.c_str(),dst.c_str(),(char*)nullptr); std::cerr << "install: cannot run '" << stripProg << "': " << strerror(errno) << std::endl; _exit(127); } int st=0; if(pid<0 || waitpid(pid,&st,0)<0 || !WIFEXITED(st) || WEXITSTATUS(st)!=0){ std::cerr << "install: strip process terminated abnormally" << std::endl; unlink(dst.c_str()); return false; } if(preserveTimes){ struct stat st; if(stat(src.c_str(),&st)==0){ struct utimbuf times; times.actime=st.st_atime; times.modtime=st.st_mtime; // Not fatal on its own -- a copy that succeeded but // couldn't have its timestamp reset is still a correct // install, just without the one cosmetic extra -p asked // for. if(utime(dst.c_str(),×)!=0) std::cerr << "install: " << dst << ": " << strerror(errno) << std::endl; } if((haveOwner || haveGroup) && chown(dst.c_str(),owner,group)!=0){ std::cerr << "install: cannot change ownership of '" << dst << "': " << strerror(errno) << std::endl; return false; } if(chmod(dst.c_str(),mode)!=0){ std::cerr << "install: cannot change permissions of '" << dst << "': " << strerror(errno) << std::endl; return false; } if(preserve){ struct timespec ts[2]={ss.st_atim,ss.st_mtim}; utimensat(AT_FDCWD,dst.c_str(),ts,0); } return true; } Loading @@ -146,61 +198,115 @@ namespace fresh { namespace cmd { struct stat st; return stat(path.c_str(),&st)==0 && S_ISDIR(st.st_mode); } static std::string baseName(const std::string &path){ size_t slash=path.find_last_of('/'); return (slash==std::string::npos) ? path : path.substr(slash+1); static std::string baseName(std::string p){ while(p.size()>1 && p.back()=='/') p.pop_back(); size_t sl=p.find_last_of('/'); return sl==std::string::npos ? p : p.substr(sl+1); } static std::string dirName(std::string p){ while(p.size()>1 && p.back()=='/') p.pop_back(); size_t sl=p.find_last_of('/'); if(sl==std::string::npos) return ""; return sl==0 ? "/" : p.substr(0,sl); } int runCommand(int argc,const char **argv){ bool makeDirs=false; bool preserveTimes=false; mode_t mode=0755; std::vector<std::string> operands; for(int i=1;i<argc;++i){ std::string a=argv[i]; if(a=="-d") makeDirs=true; else if(a=="-m" && i+1<argc) mode=(mode_t)strtol(argv[++i],nullptr,8); else if(a=="-p") preserveTimes=true; else if(a=="-D") /* every install here already implies -D */; else if((a=="-o" || a=="-g") && i+1<argc) ++i; else if(a=="-c" || a=="-s" || a=="-b" || a=="-v") /* accepted no-op */; else operands.push_back(a); verbose=preserve=strip=compare=haveOwner=haveGroup=false; owner=(uid_t)-1; group=(gid_t)-1; const char *sp=getenv("STRIPPROG"); stripProg=sp && *sp ? sp : "strip"; bool dirMode=false,leading=false,noTargetDir=false; std::string modeSpec,targetDir,ownerName,groupName; text::Opts o=text::parseOpts(argc,argv,"mogtSZ",[&](char c,const std::string &v)->bool{ switch(c){ case 'b': case 'c': case 'Z': return true; case 'S': return true; case 'C': compare=true; return true; case 'd': dirMode=true; return true; case 'D': leading=true; return true; case 'p': preserve=true; return true; case 's': strip=true; return true; case 'v': verbose=true; return true; case 'T': noTargetDir=true; return true; case 'm': modeSpec=v; return true; case 'o': ownerName=v; return true; case 'g': groupName=v; return true; case 't': targetDir=v; return true; default: return text::badOpt("install",c); } if(operands.empty()){ std::cerr << "install: usage: install [-m MODE] SRC... DST | install -d DIR..." << std::endl; },[&](const std::string &n,const std::string &v,bool hasV)->int{ if(n=="directory"){ dirMode=true; return 0; } if(n=="preserve-timestamps"){ preserve=true; return 0; } if(n=="strip"){ strip=true; return 0; } if(n=="verbose"){ verbose=true; return 0; } if(n=="compare"){ compare=true; return 0; } if(n=="no-target-directory"){ noTargetDir=true; return 0; } if(n=="backup" || n=="preserve-context" || n=="debug") return 0; if(n=="mode"){ if(!hasV) return 1; modeSpec=v; return 0; } if(n=="owner"){ if(!hasV) return 1; ownerName=v; return 0; } if(n=="group"){ if(!hasV) return 1; groupName=v; return 0; } if(n=="target-directory"){ if(!hasV) return 1; targetDir=v; return 0; } if(n=="strip-program"){ if(!hasV) return 1; stripProg=v; return 0; } if(n=="suffix" || n=="context") return hasV ? 0 : 1; return text::badLong("install",n); }); if(!o.ok) return 1; mode=0755; if(!modeSpec.empty() && !mode::apply(modeSpec,0,dirMode,0,mode)){ std::cerr << "install: invalid mode '" << modeSpec << "'" << std::endl; return 1; } if(makeDirs){ if(!ownerName.empty()){ struct passwd *pw=getpwnam(ownerName.c_str()); char *e; unsigned long v=strtoul(ownerName.c_str(),&e,10); if(pw) owner=pw->pw_uid; else if(!*e) owner=(uid_t)v; else{ std::cerr << "install: invalid user '" << ownerName << "'" << std::endl; return 1; } haveOwner=true; } if(!groupName.empty()){ struct group *gr=getgrnam(groupName.c_str()); char *e; unsigned long v=strtoul(groupName.c_str(),&e,10); if(gr) group=gr->gr_gid; else if(!*e) group=(gid_t)v; else{ std::cerr << "install: invalid group '" << groupName << "'" << std::endl; return 1; } haveGroup=true; } auto &ops=o.operands; if(dirMode){ if(ops.empty()){ std::cerr << "install: missing operand" << std::endl; return 1; } bool ok=true; for(auto &d: operands) if(!makeDir(d)) ok=false; for(auto &d: ops) if(!mkdirs(d,mode,true)) ok=false; return ok ? 0 : 1; } if(operands.size()<2){ std::cerr << "install: missing destination operand" << std::endl; return 1; if(!targetDir.empty()){ if(ops.empty()){ std::cerr << "install: missing file operand" << std::endl; return 1; } if(leading && !mkdirs(targetDir,0755,false)) return 1; if(!isDir(targetDir)){ std::cerr << "install: target '" << targetDir << "' is not a directory" << std::endl; return 1; } bool ok=true; for(auto &s: ops) if(!installFile(s,targetDir+"/"+baseName(s))) ok=false; return ok ? 0 : 1; } std::string dst=operands.back(); std::vector<std::string> srcs(operands.begin(),operands.end()-1); bool dstIsDir=isDir(dst); if(srcs.size()>1 && !dstIsDir){ std::cerr << "install: " << dst << ": not a directory" << std::endl; if(ops.size()<2){ if(ops.empty()) std::cerr << "install: missing file operand" << std::endl; else std::cerr << "install: missing destination file operand after '" << ops[0] << "'" << std::endl; return 1; } bool ok=true; for(auto &src: srcs){ std::string target=dstIsDir ? (dst+"/"+baseName(src)) : dst; if(!copyFile(src,target,mode,preserveTimes)) ok=false; std::string dst=ops.back(); ops.pop_back(); if(ops.size()==1 && (noTargetDir || !isDir(dst))){ if(leading){ std::string d=dirName(dst); if(!d.empty() && !mkdirs(d,0755,false)) return 1; } return installFile(ops[0],dst) ? 0 : 1; } if(noTargetDir){ std::cerr << "install: extra operand '" << dst << "'" << std::endl; return 1; } if(!isDir(dst)){ std::cerr << "install: target '" << dst << "' is not a directory" << std::endl; return 1; } bool ok=true; for(auto &s: ops) if(!installFile(s,dst+"/"+baseName(s))) ok=false; return ok ? 0 : 1; } }; Loading Loading
commands/filesystem/install.cpp +220 −114 Changes for commands/filesystem/install.cpp: 220 added lines, 114 removed lines. Original line number Diff line number Diff line Loading @@ -28,116 +28,168 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <iostream> #include <string> #include <vector> #include <fstream> #include <cstring> #include <cerrno> #include <cstdlib> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> #include <utime.h> #include <fcntl.h> #include <pwd.h> #include <grp.h> #include "shell.h" #include "modeutil.h" #include "../text/textutil.h" namespace fresh { namespace cmd { // "install [-m MODE] [-p] [-d] SRC... DST" / "install -d DIR...": the // BSD install(1) core used throughout %install sections -- copies each // SRC to DST (a directory, or the exact target file name for a single // SRC), creating any missing leading directories in DST (real install(1) // only does this under "-D"; every call here already behaves as if -D // were always on, so that flag is accepted but otherwise a no-op), then // chmod's the result to MODE (default 0755). "-p" additionally copies // SRC's mtime/atime onto DST after that (real install(1) default: // fresh timestamps) -- needed for upstream autotools-generated // "$(INSTALL) -c -p ..." install rules. "-d" instead creates each // argument as a directory (mkdir -p). "-o OWNER"/"-g GROUP" are // accepted (consuming their argument) but not applied -- every %install // here already runs as root installing to a throwaway buildroot, so // ownership beyond that has never mattered; "-c"/"-s"/"-b"/"-v" are // accepted as no-ops (traditional compatibility/strip/backup/verbose // flags real install(1) also supports). // install [-cCDdpsv] [-m MODE] [-o OWNER] [-g GROUP] [-t DIR] [-T] SRC... DST // install -d [-m MODE] DIR... // GNU semantics: files default to 0755, the destination is replaced // (unlinked, then written fresh), leading directories are only created // with -D, -s runs $STRIPPROG (default "strip") on the copy. class InstallCommand : public ShellCommand { public: InstallCommand():ShellCommand("install"){} static bool mkdirsFor(const std::string &path,std::string &err){ std::string dir=path; size_t slash=dir.find_last_of('/'); if(slash==std::string::npos) return true; dir=dir.substr(0,slash); if(dir.empty()) return true; bool verbose=false,preserve=false,strip=false,compare=false; bool haveOwner=false,haveGroup=false; uid_t owner=(uid_t)-1; gid_t group=(gid_t)-1; mode_t mode=0755; std::string stripProg; std::string partial; size_t start=0; if(dir[0]=='/'){ partial="/"; start=1; } while(start<=dir.size()){ size_t next=dir.find('/',start); std::string comp=(next==std::string::npos) ? dir.substr(start) : dir.substr(start,next-start); if(!comp.empty()){ if(!partial.empty() && partial.back()!='/') partial+="/"; partial+=comp; if(mkdir(partial.c_str(),0755)!=0 && errno!=EEXIST){ err=partial+": "+strerror(errno); bool mkdirs(const std::string &dir,mode_t last,bool applyLast){ std::string cur; size_t p=0; if(!dir.empty() && dir[0]=='/'){ cur="/"; p=1; } while(p<=dir.size()){ size_t sl=dir.find('/',p); std::string comp=dir.substr(p,sl==std::string::npos ? std::string::npos : sl-p); p=sl==std::string::npos ? dir.size()+1 : sl+1; if(comp.empty()) continue; if(!cur.empty() && cur.back()!='/') cur+='/'; cur+=comp; bool isLast=p>dir.size() || dir.find_first_not_of('/',p)==std::string::npos; struct stat st; if(stat(cur.c_str(),&st)==0){ if(!S_ISDIR(st.st_mode)){ std::cerr << "install: cannot create directory '" << cur << "': File exists" << std::endl; return false; } }else{ if(mkdir(cur.c_str(),0755)!=0 && errno!=EEXIST){ std::cerr << "install: cannot create directory '" << cur << "': " << strerror(errno) << std::endl; return false; } if(verbose) std::cout << "install: creating directory '" << cur << "'" << std::endl; } if(isLast && applyLast){ if(chmod(cur.c_str(),last)!=0){ std::cerr << "install: cannot change permissions of '" << cur << "': " << strerror(errno) << std::endl; return false; } if((haveOwner || haveGroup) && chown(cur.c_str(),owner,group)!=0){ std::cerr << "install: cannot change ownership of '" << cur << "': " << strerror(errno) << std::endl; return false; } } if(next==std::string::npos) break; start=next+1; } return true; } static bool makeDir(const std::string &path){ std::string err; if(!mkdirsFor(path+"/x",err)){ std::cerr << "install: " << err << std::endl; static bool sameContent(const std::string &a,const std::string &b){ std::string x,y; if(!text::readInput(a,x) || !text::readInput(b,y)) return false; return x==y; } bool installFile(const std::string &src,const std::string &dst){ struct stat ss; if(stat(src.c_str(),&ss)!=0){ std::cerr << "install: cannot stat '" << src << "': " << strerror(errno) << std::endl; return false; } if(S_ISDIR(ss.st_mode)){ std::cerr << "install: omitting directory '" << src << "'" << std::endl; return false; } if(mkdir(path.c_str(),0755)!=0 && errno!=EEXIST){ std::cerr << "install: " << path << ": " << strerror(errno) << std::endl; struct stat ds; if(stat(dst.c_str(),&ds)==0 && S_ISDIR(ds.st_mode)){ std::cerr << "install: cannot overwrite directory '" << dst << "' with non-directory '" << src << "'" << std::endl; return false; } if(stat(dst.c_str(),&ds)==0 && ds.st_dev==ss.st_dev && ds.st_ino==ss.st_ino){ std::cerr << "install: '" << src << "' and '" << dst << "' are the same file" << std::endl; return false; } if(compare && stat(dst.c_str(),&ds)==0 && (ds.st_mode&07777)==mode && S_ISREG(ds.st_mode) && (!haveOwner || ds.st_uid==owner) && (!haveGroup || ds.st_gid==group) && sameContent(src,dst)) return true; int in=open(src.c_str(),O_RDONLY); if(in<0){ std::cerr << "install: cannot open '" << src << "' for reading: " << strerror(errno) << std::endl; return false; } static bool copyFile(const std::string &src,const std::string &dst,mode_t mode,bool preserveTimes){ std::string err; if(!mkdirsFor(dst,err)){ std::cerr << "install: " << err << std::endl; if(unlink(dst.c_str())!=0 && errno!=ENOENT){ std::cerr << "install: cannot remove '" << dst << "': " << strerror(errno) << std::endl; close(in); return false; } std::ifstream in(src.c_str(),std::ios::binary); if(!in.is_open()){ std::cerr << "install: " << src << ": " << strerror(errno) << std::endl; int out=open(dst.c_str(),O_WRONLY|O_CREAT|O_EXCL,0600); if(out<0){ std::cerr << "install: cannot create regular file '" << dst << "': " << strerror(errno) << std::endl; close(in); return false; } std::ofstream out(dst.c_str(),std::ios::binary|std::ios::trunc); if(!out.is_open()){ std::cerr << "install: " << dst << ": " << strerror(errno) << std::endl; char buf[65536]; bool ok=true; for(;;){ ssize_t r=read(in,buf,sizeof buf); if(r<0){ if(errno==EINTR) continue; ok=false; break; } if(r==0) break; ssize_t off=0; while(off<r){ ssize_t w=write(out,buf+off,(size_t)(r-off)); if(w<0){ if(errno==EINTR) continue; ok=false; break; } off+=w; } if(!ok) break; } close(in); if(close(out)!=0) ok=false; if(!ok){ std::cerr << "install: error copying '" << src << "' to '" << dst << "': " << strerror(errno) << std::endl; return false; } out << in.rdbuf(); in.close(); out.close(); if(chmod(dst.c_str(),mode)!=0){ std::cerr << "install: " << dst << ": " << strerror(errno) << std::endl; if(verbose) std::cout << "'" << src << "' -> '" << dst << "'" << std::endl; if(strip){ pid_t pid=fork(); if(pid==0){ execlp(stripProg.c_str(),stripProg.c_str(),dst.c_str(),(char*)nullptr); std::cerr << "install: cannot run '" << stripProg << "': " << strerror(errno) << std::endl; _exit(127); } int st=0; if(pid<0 || waitpid(pid,&st,0)<0 || !WIFEXITED(st) || WEXITSTATUS(st)!=0){ std::cerr << "install: strip process terminated abnormally" << std::endl; unlink(dst.c_str()); return false; } if(preserveTimes){ struct stat st; if(stat(src.c_str(),&st)==0){ struct utimbuf times; times.actime=st.st_atime; times.modtime=st.st_mtime; // Not fatal on its own -- a copy that succeeded but // couldn't have its timestamp reset is still a correct // install, just without the one cosmetic extra -p asked // for. if(utime(dst.c_str(),×)!=0) std::cerr << "install: " << dst << ": " << strerror(errno) << std::endl; } if((haveOwner || haveGroup) && chown(dst.c_str(),owner,group)!=0){ std::cerr << "install: cannot change ownership of '" << dst << "': " << strerror(errno) << std::endl; return false; } if(chmod(dst.c_str(),mode)!=0){ std::cerr << "install: cannot change permissions of '" << dst << "': " << strerror(errno) << std::endl; return false; } if(preserve){ struct timespec ts[2]={ss.st_atim,ss.st_mtim}; utimensat(AT_FDCWD,dst.c_str(),ts,0); } return true; } Loading @@ -146,61 +198,115 @@ namespace fresh { namespace cmd { struct stat st; return stat(path.c_str(),&st)==0 && S_ISDIR(st.st_mode); } static std::string baseName(const std::string &path){ size_t slash=path.find_last_of('/'); return (slash==std::string::npos) ? path : path.substr(slash+1); static std::string baseName(std::string p){ while(p.size()>1 && p.back()=='/') p.pop_back(); size_t sl=p.find_last_of('/'); return sl==std::string::npos ? p : p.substr(sl+1); } static std::string dirName(std::string p){ while(p.size()>1 && p.back()=='/') p.pop_back(); size_t sl=p.find_last_of('/'); if(sl==std::string::npos) return ""; return sl==0 ? "/" : p.substr(0,sl); } int runCommand(int argc,const char **argv){ bool makeDirs=false; bool preserveTimes=false; mode_t mode=0755; std::vector<std::string> operands; for(int i=1;i<argc;++i){ std::string a=argv[i]; if(a=="-d") makeDirs=true; else if(a=="-m" && i+1<argc) mode=(mode_t)strtol(argv[++i],nullptr,8); else if(a=="-p") preserveTimes=true; else if(a=="-D") /* every install here already implies -D */; else if((a=="-o" || a=="-g") && i+1<argc) ++i; else if(a=="-c" || a=="-s" || a=="-b" || a=="-v") /* accepted no-op */; else operands.push_back(a); verbose=preserve=strip=compare=haveOwner=haveGroup=false; owner=(uid_t)-1; group=(gid_t)-1; const char *sp=getenv("STRIPPROG"); stripProg=sp && *sp ? sp : "strip"; bool dirMode=false,leading=false,noTargetDir=false; std::string modeSpec,targetDir,ownerName,groupName; text::Opts o=text::parseOpts(argc,argv,"mogtSZ",[&](char c,const std::string &v)->bool{ switch(c){ case 'b': case 'c': case 'Z': return true; case 'S': return true; case 'C': compare=true; return true; case 'd': dirMode=true; return true; case 'D': leading=true; return true; case 'p': preserve=true; return true; case 's': strip=true; return true; case 'v': verbose=true; return true; case 'T': noTargetDir=true; return true; case 'm': modeSpec=v; return true; case 'o': ownerName=v; return true; case 'g': groupName=v; return true; case 't': targetDir=v; return true; default: return text::badOpt("install",c); } if(operands.empty()){ std::cerr << "install: usage: install [-m MODE] SRC... DST | install -d DIR..." << std::endl; },[&](const std::string &n,const std::string &v,bool hasV)->int{ if(n=="directory"){ dirMode=true; return 0; } if(n=="preserve-timestamps"){ preserve=true; return 0; } if(n=="strip"){ strip=true; return 0; } if(n=="verbose"){ verbose=true; return 0; } if(n=="compare"){ compare=true; return 0; } if(n=="no-target-directory"){ noTargetDir=true; return 0; } if(n=="backup" || n=="preserve-context" || n=="debug") return 0; if(n=="mode"){ if(!hasV) return 1; modeSpec=v; return 0; } if(n=="owner"){ if(!hasV) return 1; ownerName=v; return 0; } if(n=="group"){ if(!hasV) return 1; groupName=v; return 0; } if(n=="target-directory"){ if(!hasV) return 1; targetDir=v; return 0; } if(n=="strip-program"){ if(!hasV) return 1; stripProg=v; return 0; } if(n=="suffix" || n=="context") return hasV ? 0 : 1; return text::badLong("install",n); }); if(!o.ok) return 1; mode=0755; if(!modeSpec.empty() && !mode::apply(modeSpec,0,dirMode,0,mode)){ std::cerr << "install: invalid mode '" << modeSpec << "'" << std::endl; return 1; } if(makeDirs){ if(!ownerName.empty()){ struct passwd *pw=getpwnam(ownerName.c_str()); char *e; unsigned long v=strtoul(ownerName.c_str(),&e,10); if(pw) owner=pw->pw_uid; else if(!*e) owner=(uid_t)v; else{ std::cerr << "install: invalid user '" << ownerName << "'" << std::endl; return 1; } haveOwner=true; } if(!groupName.empty()){ struct group *gr=getgrnam(groupName.c_str()); char *e; unsigned long v=strtoul(groupName.c_str(),&e,10); if(gr) group=gr->gr_gid; else if(!*e) group=(gid_t)v; else{ std::cerr << "install: invalid group '" << groupName << "'" << std::endl; return 1; } haveGroup=true; } auto &ops=o.operands; if(dirMode){ if(ops.empty()){ std::cerr << "install: missing operand" << std::endl; return 1; } bool ok=true; for(auto &d: operands) if(!makeDir(d)) ok=false; for(auto &d: ops) if(!mkdirs(d,mode,true)) ok=false; return ok ? 0 : 1; } if(operands.size()<2){ std::cerr << "install: missing destination operand" << std::endl; return 1; if(!targetDir.empty()){ if(ops.empty()){ std::cerr << "install: missing file operand" << std::endl; return 1; } if(leading && !mkdirs(targetDir,0755,false)) return 1; if(!isDir(targetDir)){ std::cerr << "install: target '" << targetDir << "' is not a directory" << std::endl; return 1; } bool ok=true; for(auto &s: ops) if(!installFile(s,targetDir+"/"+baseName(s))) ok=false; return ok ? 0 : 1; } std::string dst=operands.back(); std::vector<std::string> srcs(operands.begin(),operands.end()-1); bool dstIsDir=isDir(dst); if(srcs.size()>1 && !dstIsDir){ std::cerr << "install: " << dst << ": not a directory" << std::endl; if(ops.size()<2){ if(ops.empty()) std::cerr << "install: missing file operand" << std::endl; else std::cerr << "install: missing destination file operand after '" << ops[0] << "'" << std::endl; return 1; } bool ok=true; for(auto &src: srcs){ std::string target=dstIsDir ? (dst+"/"+baseName(src)) : dst; if(!copyFile(src,target,mode,preserveTimes)) ok=false; std::string dst=ops.back(); ops.pop_back(); if(ops.size()==1 && (noTargetDir || !isDir(dst))){ if(leading){ std::string d=dirName(dst); if(!d.empty() && !mkdirs(d,0755,false)) return 1; } return installFile(ops[0],dst) ? 0 : 1; } if(noTargetDir){ std::cerr << "install: extra operand '" << dst << "'" << std::endl; return 1; } if(!isDir(dst)){ std::cerr << "install: target '" << dst << "' is not a directory" << std::endl; return 1; } bool ok=true; for(auto &s: ops) if(!installFile(s,dst+"/"+baseName(s))) ok=false; return ok ? 0 : 1; } }; Loading