diff --git a/.gitignore b/.gitignore index 98e5cd54..363f8684 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ __pycache__ *.d *.hh.gch *.so -/ext/**.patched* *.stamp */stamp *.inc_stamp diff --git a/Makefile b/Makefile index c11d2647..ffcbb4db 100644 --- a/Makefile +++ b/Makefile @@ -299,14 +299,6 @@ ext/%.dir.stamp : ext/%.zip @mkdir -p $(@:%.stamp=%) unzip -d $(@:%.stamp=%) $< touch $@ -ext/%.patched.stamp : ext/%.dir.stamp ext/%.patch_script - rm -rf $(@:%.stamp=%) - cp -r $(@:%.patched.stamp=%.dir) $(@:%.patched.stamp=%.patched) - cd $(@:%.patched.stamp=%.patched) ; $(ROOT_DIR)/$(@:%.patched.stamp=%.patch_script) - touch $@ -ext/%.patched.h : ext/%.h ext/%.patch_script - cp $< $@ - cd $(@D) ; $(ROOT_DIR)/$(@:%.patched.h=%.patch_script) $(@F) .SECONDARY : @@ -394,7 +386,7 @@ $(STORE_LIB)/big_test.dir/tok : $(STORE_LIB)/big_test.py LMAKE # engine # -ALL_H := version.hh sys_config.h ext/xxhash.patched.h +ALL_H := version.hh sys_config.h ext/xxhash.h # On ubuntu, seccomp.h is in /usr/include. On CenOS7, it is in /usr/include/linux, but beware that otherwise, /usr/include must be prefered, hence -idirafter CPP_OPTS := -iquote ext -iquote $(SRC) -iquote $(SRC_ENGINE) -iquote . -idirafter /usr/include/linux diff --git a/Manifest b/Manifest index 56a2d21a..cd2c3400 100644 --- a/Manifest +++ b/Manifest @@ -36,7 +36,6 @@ docker/ubuntu20.docker docker/ubuntu22.docker ext/cxxopts.hpp ext/xxhash.h -ext/xxhash.patch_script lmake_env/Lmakefile.py lmake_env/ext_lnk src/align_comments.cc @@ -193,6 +192,7 @@ unit_tests/misc5.py unit_tests/misc6.py unit_tests/misc7.py unit_tests/misc8.py +unit_tests/misc9.py unit_tests/modules.py unit_tests/name.py unit_tests/numba.py diff --git a/TO_DO b/TO_DO index c018ec46..d38d5001 100644 --- a/TO_DO +++ b/TO_DO @@ -104,6 +104,9 @@ items : * implement a lshow -r to see what is running now and -B to see BOM (list of sources) - implement a generic walk through deps - use it for check_deps, which will prevent jobs post critical deps to be run +* improve job isolation by using namespaces + - much like faketree : https://github.com/enfabrica/enkit/tree/master/faketree + - generalize tmp mapping * implement cache v2 (copy & link) : - put typeid(StartInfo,EndInfo,...) as version tag to ensure no inter-version clashes - 2 levels : disk level, global level diff --git a/ext/xxhash.patch_script b/ext/xxhash.patch_script deleted file mode 100755 index ef399434..00000000 --- a/ext/xxhash.patch_script +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -ex - -# This file is part of the open-lmake distribution (git@github.com:cesar-douady/open-lmake.git) -# Copyright (c) 2023 Doliam -# This program is free software: you can redistribute/modify under the terms of the GPL-v3 (https://www.gnu.org/licenses/gpl-3.0.html). -# This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -# suppress assert that prevents empty input -ed $1 <<"EOF" -6400d -w -EOF diff --git a/lmake_env/Lmakefile.py b/lmake_env/Lmakefile.py index 44d09aeb..f3367195 100644 --- a/lmake_env/Lmakefile.py +++ b/lmake_env/Lmakefile.py @@ -92,8 +92,8 @@ class Unpack(BaseRule) : # it is unacceptable to have a pack inside a pack, as this creates ambiguities class AntiPackPack(BaseRule,AntiRule) : targets = { - 'TAR' : '{Dir}.{DirExt:(patched_)?dir}/{File}.tar.gz' - , 'ZIP' : '{Dir}.{DirExt }/{File}.zip' + 'TAR' : '{Dir}..dir/{File}.tar.gz' + , 'ZIP' : '{Dir}..dir/{File}.zip' } class Untar(Unpack) : @@ -134,38 +134,6 @@ def cmd() : n_files += 1 print(f'unzip {n_files} files',file=sys.stderr) -class PatchDir(BaseRule) : - targets = { - 'PATCHED_FILE' : 'ext/{Dir}.patched_dir/{File*}' - , 'PATCHED_MANIFEST' : 'ext/{Dir}.patched_manifest' - } - deps = { - 'MANIFEST' : 'ext/{Dir}.manifest' - , 'PATCH_SCRIPT' : 'ext/{Dir}.patch_script' - } - def cmd() : - sdir = f'ext/{Dir}.dir' - pdir = f'ext/{Dir}.patched_dir' - with open(PATCHED_MANIFEST,'w') as pm : - for f in open(MANIFEST).read().splitlines() : - src = f'{sdir}/{f}' - dest = f'{pdir}/{f}' - dir_guard(dest) - open(dest,'w').write(open(src,'r').read()) - print(f,file=pm) - run((osp.abspath(PATCH_SCRIPT),),cwd=pdir,stdin=DEVNULL,stderr=STDOUT,check=True) - -class PatchFile(BaseRule) : - targets = { 'DST' : 'ext/{DirS}{ File}.patched.{Ext}' } - deps = { - 'SRC' : 'ext/{DirS}{File}.{Ext}' - , 'PATCH_SCRIPT' : 'ext/{DirS}{File}.patch_script' - } - def cmd() : - dir = ('ext/'+DirS)[:-1] - open(DST,'w').write(open(SRC,'r').read()) - run((osp.abspath(PATCH_SCRIPT),File+'.patched.'+Ext),cwd=dir,stdin=DEVNULL,stderr=STDOUT,check=True) - class ConfigH(BaseRule) : targets = { 'CONFIG_H' : 'ext/{DirS}config.h' } side_targets = { 'SCRATCHPAD' : 'ext/{DirS}{File*}' } diff --git a/src/autodep/clmake.cc b/src/autodep/clmake.cc index 9db678a3..9ab1432b 100644 --- a/src/autodep/clmake.cc +++ b/src/autodep/clmake.cc @@ -245,11 +245,12 @@ static PyObject* get_autodep( PyObject* /*null*/ , PyObject* args , PyObject* kw size_t n_args = py_args.size() ; if (kwds ) return py_err_set(Exception::TypeErr,"expected no keyword args") ; if (n_args>0) return py_err_set(Exception::TypeErr,"expected no args" ) ; - char c[2] ; + char c = 0/*garbage*/ ; // we have a private Record with a private AutodepEnv, so we must go through the backdoor to alter the regular AutodepEnv - int rc [[maybe_unused]] ; // avoid compiler warning - rc = ::readlink( (PrivateAdminDir+"/backdoor/autodep"s ).c_str() , c , 2 ) ; - return Ptr(c[0]!='0')->to_py_boost() ; + int rc [[maybe_unused]] = ::readlink( (PrivateAdminDir+"/backdoor/autodep"s ).c_str() , &c , 1 ) ; + SWEAR( c=='0' || c=='1' , int(c) ) ; + SWEAR( rc==1 , rc ) ; + return Ptr(c!='0')->to_py_boost() ; } static PyObject* set_autodep( PyObject* /*null*/ , PyObject* args , PyObject* kwds ) { diff --git a/src/autodep/ld_common.x.cc b/src/autodep/ld_common.x.cc index 558a4aa1..77222291 100644 --- a/src/autodep/ld_common.x.cc +++ b/src/autodep/ld_common.x.cc @@ -82,20 +82,20 @@ template struct AuditAction : Ctx,Action { template T operator()(T res) { save_errno() ; return Action::operator()(auditer(),res) ; } } ; // n paths -using Chdir = AuditAction ; -using Chmod = AuditAction ; -using Hide = AuditAction ; -using Mkdir = AuditAction ; -using Lnk = AuditAction ; -using Open = AuditAction ; -using Read = AuditAction ; -using Readlnk = AuditAction ; -using Rename = AuditAction ; -using Solve = AuditAction ; -using Stat = AuditAction ; -using Symlnk = AuditAction ; -using Unlnk = AuditAction ; -using WSolve = AuditAction ; +using Chdir = AuditAction ; +using Chmod = AuditAction ; +using Hide = AuditAction ; +using Mkdir = AuditAction ; +using Lnk = AuditAction ; +using Open = AuditAction ; +using Read = AuditAction ; +using Readlink = AuditAction ; +using Rename = AuditAction ; +using Solve = AuditAction ; +using Stat = AuditAction ; +using Symlnk = AuditAction ; +using Unlnk = AuditAction ; +using WSolve = AuditAction ; #ifdef LD_PRELOAD @@ -458,14 +458,14 @@ struct Mkstemp : WSolve { // once init phase is passed, we proceed normally ssize_t readlink(CC* p,char* b,size_t sz) NE { if (!started()) return __readlink_chk(p,b,sz,sz) ; - HEADER1(readlink,p,(p,b,sz)) ; Readlnk r{p ,b,sz,"readlink"} ; return r(orig(F(r),b,sz)) ; + HEADER1(readlink,p,(p,b,sz)) ; Readlink r{p ,b,sz,"readlink"} ; return r(orig(F(r),b,sz)) ; } #else - ssize_t readlink (CC* p,char* b,size_t sz ) NE { HEADER1(readlink ,p,(p,b,sz )) ; Readlnk r{p ,b,sz,"readlink" } ; return r(orig(F(r),b,sz )) ; } - ssize_t __readlink_chk(CC* p,char* b,size_t sz,size_t bsz) NE { HEADER1(__readlink_chk,p,(p,b,sz,bsz)) ; Readlnk r{p ,b,sz,"__readlink__chk"} ; return r(orig(F(r),b,sz,bsz)) ; } + ssize_t readlink (CC* p,char* b,size_t sz ) NE { HEADER1(readlink ,p,(p,b,sz )) ; Readlink r{p ,b,sz,"readlink" } ; return r(orig(F(r),b,sz )) ; } + ssize_t __readlink_chk(CC* p,char* b,size_t sz,size_t bsz) NE { HEADER1(__readlink_chk,p,(p,b,sz,bsz)) ; Readlink r{p ,b,sz,"__readlink__chk"} ; return r(orig(F(r),b,sz,bsz)) ; } #endif - ssize_t readlinkat (int d,CC* p,char* b,size_t sz ) NE { HEADER1(readlinkat ,p,(d,p,b,sz )) ; Readlnk r{{d,p},b,sz,"readlinkat" } ; return r(orig(P(r),b,sz )) ; } - ssize_t __readlinkat_chk(int d,CC* p,char* b,size_t sz,size_t bsz) NE { HEADER1(__readlinkat_chk,p,(d,p,b,sz,bsz)) ; Readlnk r{{d,p},b,sz,"__readlinkat_chk"} ; return r(orig(P(r),b,sz,bsz)) ; } + ssize_t readlinkat (int d,CC* p,char* b,size_t sz ) NE { HEADER1(readlinkat ,p,(d,p,b,sz )) ; Readlink r{{d,p},b,sz,"readlinkat" } ; return r(orig(P(r),b,sz )) ; } + ssize_t __readlinkat_chk(int d,CC* p,char* b,size_t sz,size_t bsz) NE { HEADER1(__readlinkat_chk,p,(d,p,b,sz,bsz)) ; Readlink r{{d,p},b,sz,"__readlinkat_chk"} ; return r(orig(P(r),b,sz,bsz)) ; } // rename #ifdef RENAME_EXCHANGE diff --git a/src/autodep/ptrace.cc b/src/autodep/ptrace.cc index c6079e9a..99beacb5 100644 --- a/src/autodep/ptrace.cc +++ b/src/autodep/ptrace.cc @@ -91,18 +91,18 @@ ::pair AutodepPtrace::_changed( pid_t pid , int wst if (WIFSTOPPED(wstatus)) { int sig = WSTOPSIG(wstatus) ; int event = wstatus>>16 ; - if (sig==(SIGTRAP|0x80)) goto DoSyscall ; // if HAS_SECCOMP => syscall exit, else syscall enter or exit + if (sig==(SIGTRAP|0x80)) goto DoSyscall ; // if HAS_SECCOMP => syscall exit, else syscall enter or exit switch (event) { #if HAS_SECCOMP - case PTRACE_EVENT_SECCOMP : SWEAR(!info.on_going) ; goto DoSyscall ; // syscall enter + case PTRACE_EVENT_SECCOMP : SWEAR(!info.on_going) ; goto DoSyscall ; // syscall enter #endif - case 0 : if (sig==SIGTRAP) sig = 0 ; goto NextSyscall ; // other stop reasons, wash spurious SIGTRAP, ignore - default : SWEAR(sig==SIGTRAP,sig) ; sig = 0 ; goto NextSyscall ; // ignore other events + case 0 : if (sig==SIGTRAP) sig = 0 ; goto NextSyscall ; // other stop reasons, wash spurious SIGTRAP, ignore + default : SWEAR(sig==SIGTRAP,sig) ; sig = 0 ; goto NextSyscall ; // ignore other events } DoSyscall : { static SyscallDescr::Tab const& tab = SyscallDescr::s_tab(true/*for_ptrace*/) ; sig = 0 ; - #if HAS_PTRACE_GET_SYSCALL_INFO // use portable calls if implemented + #if HAS_PTRACE_GET_SYSCALL_INFO // use portable calls if implemented struct ptrace_syscall_info syscall_info ; ::ptrace( PTRACE_GET_SYSCALL_INFO , pid , sizeof(struct ptrace_syscall_info) , &syscall_info ) ; #endif @@ -110,35 +110,35 @@ ::pair AutodepPtrace::_changed( pid_t pid , int wst #if HAS_PTRACE_GET_SYSCALL_INFO SWEAR( syscall_info.op==(HAS_SECCOMP?PTRACE_SYSCALL_INFO_SECCOMP:PTRACE_SYSCALL_INFO_ENTRY) ) ; #if HAS_SECCOMP - auto& entry_info = syscall_info.seccomp ; // access available info upon syscall entry, i.e. seccomp field when seccomp is used + auto& entry_info = syscall_info.seccomp ; // access available info upon syscall entry, i.e. seccomp field when seccomp is used #else - auto& entry_info = syscall_info.entry ; // access available info upon syscall entry, i.e. entry field when seccomp is not used + auto& entry_info = syscall_info.entry ; // access available info upon syscall entry, i.e. entry field when seccomp is not used #endif int syscall = entry_info.nr ; #else - int syscall = np_ptrace_get_syscall(pid) ; // use non-portable calls if portable accesses are not implemented + int syscall = np_ptrace_get_nr(pid) ; // use non-portable calls if portable accesses are not implemented #endif SWEAR( syscall>=0 && syscall> ) ; uint64_t* args = reinterpret_cast(entry_info.args) ; #else - ::array arg_array = np_ptrace_get_args(pid) ; // use non-portable calls if portable accesses are not implemented - uint64_t * args = arg_array.data() ; // we need a variable to hold the data while we pass the pointer + ::array arg_array = np_ptrace_get_args(pid) ; // use non-portable calls if portable accesses are not implemented + uint64_t * args = arg_array.data() ; // we need a variable to hold the data while we pass the pointer #endif - SWEAR(!info.ctx,syscall) ; // ensure following SWEAR on info.ctx is pertinent + SWEAR(!info.ctx,syscall) ; // ensure following SWEAR on info.ctx is pertinent descr.entry( info.ctx , info.record , pid , args , descr.comment ) ; info.has_exit_proc = descr.exit ; - if (!info.has_exit_proc) SWEAR(!info.ctx,syscall) ; // no need for a context if we are not called at exit + if (!info.has_exit_proc) SWEAR(!info.ctx,syscall) ; // no need for a context if we are not called at exit } else { info.has_exit_proc = false ; } - info.on_going = !HAS_SECCOMP || info.has_exit_proc ; // if using seccomp and we have no exit proc, we skip the syscall-exit + info.on_going = !HAS_SECCOMP || info.has_exit_proc ; // if using seccomp and we have no exit proc, we skip the syscall-exit if (info.has_exit_proc) goto SyscallExit ; else goto NextSyscall ; } else { @@ -147,14 +147,14 @@ ::pair AutodepPtrace::_changed( pid_t pid , int wst #endif if (HAS_SECCOMP) SWEAR(info.has_exit_proc,"should not have been stopped on exit") ; if (info.has_exit_proc) { - #if HAS_PTRACE_GET_SYSCALL_INFO // use portable calls if implemented + #if HAS_PTRACE_GET_SYSCALL_INFO // use portable calls if implemented int64_t res = syscall_info.exit.rval ; #else - int64_t res = np_ptrace_get_res(pid) ; // use non-portable calls if portable accesses are not implemented + int64_t res = np_ptrace_get_res(pid) ; // use non-portable calls if portable accesses are not implemented #endif int64_t new_res = tab[info.idx].exit( info.ctx , info.record , pid , res ) ; - if (new_res!=res) FAIL("modified syscall result ",new_res,"!=",res," not yet implemented for ptrace") ; // there is no such cases for now, if it arises, new_res must be reported - info.ctx = nullptr ; // ctx is used to retain some info between syscall entry and exit + if (new_res!=res) np_ptrace_set_res( pid , new_res ) ; + info.ctx = nullptr ; // ctx is used to retain some info between syscall entry and exit } info.on_going = false ; goto NextSyscall ; @@ -166,9 +166,9 @@ ::pair AutodepPtrace::_changed( pid_t pid , int wst SyscallExit : ::ptrace( StopAtSyscallExit , pid , 0/*addr*/ , sig ) ; goto Done ; - } else if ( WIFEXITED(wstatus) || WIFSIGNALED(wstatus) ) { // not sure we are informed that a process exits + } else if ( WIFEXITED(wstatus) || WIFSIGNALED(wstatus) ) { // not sure we are informed that a process exits if (pid==child_pid) return {true/*done*/,wstatus} ; - PidInfo::s_tab.erase(pid) ; // free resources if possible + PidInfo::s_tab.erase(pid) ; // free resources if possible } else { fail("unrecognized wstatus ",wstatus," for pid ",pid) ; } diff --git a/src/autodep/record.cc b/src/autodep/record.cc index 73854f3f..4243c763 100644 --- a/src/autodep/record.cc +++ b/src/autodep/record.cc @@ -205,42 +205,50 @@ Record::Read::Read( Record& r , Path&& path , bool no_follow , bool keep_real , else r._report_dep( ::move(real) , accesses|Access::Reg , ::move(c) ) ; } -Record::Readlnk::Readlnk( Record& r , Path&& path , char* buf_ , size_t sz_ , ::string&& c ) : Solve{r,::move(path),true/*no_follow*/,true/*read*/,true/*allow_tmp_map*/,c} , buf{buf_} , sz{sz_} { - if (file_loc<=FileLoc::Dep) { - r._report_dep( ::copy(real) , accesses|Access::Lnk , ::move(c) ) ; - } else if (file_loc==FileLoc::Admin) { - static constexpr char Backdoor[] = ADMIN_DIR "/" PRIVATE_ADMIN_SUBDIR "/backdoor/" ; - static constexpr size_t BackdoorLen = (sizeof(Backdoor)-1)/sizeof(char) ; // -1 to account for terminating null - if ( strncmp(file,Backdoor,BackdoorLen)==0 ) { - switch (file[BackdoorLen]) { - case 'a' : SWEAR(strcmp(file+BackdoorLen,"autodep")==0) ; SWEAR(sz_>=2) ; buf[0] = '0'+!s_autodep_env().disabled ; buf[1]=0 ; break ; - case 'd' : SWEAR(strcmp(file+BackdoorLen,"disable")==0) ; s_set_enable(false) ; break ; - case 'e' : SWEAR(strcmp(file+BackdoorLen,"enable" )==0) ; s_set_enable(true ) ; break ; - DF} - } - } +Record::Readlink::Readlink( Record& r , Path&& path , char* buf_ , size_t sz_ , ::string&& c ) : Solve{r,::move(path),true/*no_follow*/,true/*read*/,true/*allow_tmp_map*/,c} , buf{buf_} , sz{sz_} { + if (file_loc<=FileLoc::Dep) r._report_dep( ::copy(real) , accesses|Access::Lnk , ::move(c) ) ; } -ssize_t Record::Readlnk::operator()( Record& , ssize_t len ) { - if ( Record::s_has_tmp_view() && file_loc==FileLoc::Proc && len>0 ) { // /proc may contain links to tmp_dir that we must show to job as pointing to tmp_view - ::string const& tmp_dir = s_autodep_env().tmp_dir ; - ::string const& tmp_view = s_autodep_env().tmp_view ; - size_t ulen = len ; - if (ulentmp_dir.size()) ::memmove( buf+tmp_view.size() , buf+tmp_dir.size() , ::min(ulen-tmp_dir.size(),sz-tmp_view.size()) ) ; // memmove takes care of overlap - /**/ ::memcpy ( buf , tmp_view.c_str() , tmp_view.size() ) ; // memcopy does not but is fast - if (tmp_view.size()0 ) { + ::string const& tmp_dir = s_autodep_env().tmp_dir ; + ::string const& tmp_view = s_autodep_env().tmp_view ; + size_t tdsz = tmp_dir .size() ; + size_t tvsz = tmp_view.size() ; + size_t ulen = len ; + if (ulentdsz) ::memmove( &buf[tvsz] , &buf [tdsz] , ::min(ulen-tdsz,sz-tvsz) ) ; // memmove takes care of overlap + /**/ ::memcpy ( &buf[0 ] , &tmp_view[0 ] , tvsz ) ; // memcopy does not but is fast + if (tvsztvsz) ::memcpy( &buf[tvsz] , &target [tdsz] , ::min(sz-tvsz,target.size()-tdsz) ) ; // . + len = ::min( target.size()+tvsz-tdsz , sz ) ; + } + } + emulated = true ; } - } else { // difficult, we only have part of the info, this should be rare, no need to optimize - ::string target = ::read_lnk(real) ; // restart access from scratch, we enough memory - if ( target.starts_with(tmp_dir) && (target.size()==tmp_dir.size()||target[tmp_dir.size()]=='/') ) { - /**/ ::memcpy( buf , tmp_view.c_str() , ::min(sz ,tmp_view.size() ) ) ; // no overlap - if (sz>tmp_view.size()) ::memcpy( buf+tmp_view.size() , target.c_str()+tmp_dir.size() , ::min(sz-tmp_view.size(),target.size()-tmp_dir.size()) ) ; // . - len = ::min( target.size()+tmp_view.size()-tmp_dir.size() , sz ) ; + break ; + case FileLoc::Admin : { // Admin may contain accesses to backdoor we must emulate + static constexpr char Backdoor[] = ADMIN_DIR "/" PRIVATE_ADMIN_SUBDIR "/backdoor/" ; + static constexpr size_t BackdoorLen = (sizeof(Backdoor)-1)/sizeof(char) ; // -1 to account for terminating null + if ( strncmp(file,Backdoor,BackdoorLen)==0 ) { + switch (file[BackdoorLen]) { + case 'a' : SWEAR(strcmp(file+BackdoorLen,"autodep")==0) ; buf[0]='0'+!s_autodep_env().disabled ; len=1 ; break ; + case 'd' : SWEAR(strcmp(file+BackdoorLen,"disable")==0) ; s_set_enable(false) ; len=0 ; break ; + case 'e' : SWEAR(strcmp(file+BackdoorLen,"enable" )==0) ; s_set_enable(true ) ; len=0 ; break ; + DF} + emulated = true ; } - } + } break ; + default : ; } return len ; } diff --git a/src/autodep/record.hh b/src/autodep/record.hh index 37ed2d94..c46934b0 100644 --- a/src/autodep/record.hh +++ b/src/autodep/record.hh @@ -286,17 +286,17 @@ public : Read() = default ; Read( Record& , Path&& , bool no_follow , bool keep_real , bool allow_tmp_map , ::string&& comment ) ; } ; - struct Readlnk : Solve { + struct Readlink : Solve { // cxtors & casts - Readlnk() = default ; + Readlink() = default ; // buf and sz are only used when mapping tmp - Readlnk( Record& , Path&& , char* buf , size_t sz , ::string&& comment ) ; - Readlnk( Record& r , Path&& p , ::string&& c ) : Readlnk{r,::move(p),nullptr/*buf*/,0/*sz*/,::move(c)} {} + Readlink( Record& , Path&& , char* buf , size_t sz , ::string&& comment ) ; // services ssize_t operator()( Record& , ssize_t len=0 ) ; // data - char* buf = nullptr/*garbage*/ ; - size_t sz = 0 /*garbage*/ ; + char* buf = nullptr ; + size_t sz = 0 ; + bool emulated = false ; // if true <=> backdoor was used } ; struct Rename { // cxtors & casts @@ -342,3 +342,9 @@ private : mutable AutoCloseFd _report_fd ; mutable bool _tmp_cache = false ; // record that tmp usage has been reported, no need to report any further } ; + +template ::ostream& operator<<( ::ostream& os , Record::_Path const& p ) { + /**/ os << "Path(" ; + if (p.at!=Fd::Cwd) os << p.at <<',' ; + return os << p.file <<')' ; +} diff --git a/src/autodep/syscall_tab.cc b/src/autodep/syscall_tab.cc index 808774ca..48c5a6ab 100644 --- a/src/autodep/syscall_tab.cc +++ b/src/autodep/syscall_tab.cc @@ -36,18 +36,50 @@ ::pair fix_cwd( char* buf , size_t buf_sz , ssize_t sz , Bool3 al return {buf,sz} ; } -static ::string _get_str( pid_t pid , uint64_t val ) { - if (!pid) return {reinterpret_cast(val)} ; +// return null terminated string pointed by src in process pid's space +static ::string _get_str( pid_t pid , uint64_t src ) { + if (!pid) return {reinterpret_cast(src)} ; ::string res ; errno = 0 ; for(;;) { - uint64_t offset = val%sizeof(long) ; - long word = ptrace( PTRACE_PEEKDATA , pid , val-offset , nullptr/*data*/ ) ; + uint64_t offset = src%sizeof(long) ; + long word = ptrace( PTRACE_PEEKDATA , pid , src-offset , nullptr/*data*/ ) ; if (errno) throw 0 ; char buf[sizeof(long)] ; ::memcpy( buf , &word , sizeof(long) ) ; for( uint64_t len=0 ; len(&word)+offset , src , chunk ) ; + ptrace( PTRACE_POKEDATA , pid , dst-offset , word ) ; + if (errno) throw 0 ; + } +} + +// copy src to process pid's space @ dst +static void _peek( pid_t pid , char* dst , uint64_t src , size_t sz ) { + SWEAR(pid) ; + errno = 0 ; + for( size_t chunk ; sz ; src+=chunk , dst+=chunk , sz-=chunk) { // invariant : copy src[i:sz] to dst + uint64_t offset = src%sizeof(long) ; + long word = ptrace( PTRACE_PEEKDATA , pid , src-offset , nullptr/*data*/ ) ; + if (errno) throw 0 ; + chunk = ::min( sizeof(long) - offset , sz ) ; + ::memcpy( dst , reinterpret_cast(&word)+offset , chunk ) ; } } @@ -132,9 +164,9 @@ static void _entry_getcwd( void* & ctx , Record& , pid_t , uint64_t args[6] , co ctx = sz ; } static int64_t/*res*/ _exit_getcwd( void* ctx , Record& , pid_t pid , int64_t res ) { - if (!res ) return res ; // in case of error, man getcwd says buffer is undefined => nothing to do - if (!Record::s_has_tmp_view()) return res ; // no tmp mapping => nothing to do - SWEAR(pid==0,pid) ; // tmp mapping is not supported with ptrace (need to report fixed result to caller) + if (!res ) return res ; // in case of error, man getcwd says buffer is undefined => nothing to do + if (!Record::s_has_tmp_view()) return res ; // no tmp mapping => nothing to do + SWEAR(pid==0,pid) ; // tmp mapping is not supported with ptrace (need to report fixed result to caller) char* buf = reinterpret_cast(res) ; size_t* sz = reinterpret_cast(ctx) ; res = fix_cwd(buf,*sz,res).second ; @@ -185,19 +217,28 @@ static int64_t/*res*/ _exit_open( void* ctx , Record& r , pid_t /*pid*/ , int64_ } // read_lnk +using RLB = ::pair ; template void _entry_read_lnk( void* & ctx , Record& r , pid_t pid , uint64_t args[6] , const char* comment ) { try { - Record::Readlnk* rl = new Record::Readlnk( r , _path(pid,args+0) , comment ) ; - ctx = rl ; - _update(args+0,*rl) ; + uint64_t orig_buf = args[At+1] ; + size_t sz = args[At+2] ; + char* buf = pid ? new char[sz] : reinterpret_cast(orig_buf) ; + RLB* rlb = new RLB( Record::Readlink( r , _path(pid,args+0) , buf , sz , comment ) , orig_buf ) ; + ctx = rlb ; + _update(args+0,rlb->first) ; } catch (int) {} } static int64_t/*res*/ _exit_read_lnk( void* ctx , Record& r , pid_t pid , int64_t res ) { if (!ctx) return res ; - Record::Readlnk* rl = static_cast(ctx) ; - SWEAR( pid==0 || !Record::s_has_tmp_view() , pid ) ; // tmp mapping is not supported with ptrace (need to report new value to caller) - (*rl)(r,res) ; - delete rl ; + RLB* rlb = static_cast(ctx) ; + SWEAR( res<=ssize_t(rlb->first.sz) , res , rlb->first.sz ) ; + if ( pid && res>=0 ) _peek( pid , rlb->first.buf , rlb->second , res ) ; + res = (rlb->first)(r,res) ; + if (pid) { + if (rlb->first.emulated) _poke( pid , rlb->second , rlb->first.buf , res ) ; // access to backdoor was emulated, we must transport result to actual use space + delete[] rlb->first.buf ; + } + delete rlb ; return res ; } diff --git a/src/disk.cc b/src/disk.cc index 0de8e8fb..283f14d4 100644 --- a/src/disk.cc +++ b/src/disk.cc @@ -314,20 +314,16 @@ namespace Disk { // FileMap::FileMap( Fd at , ::string const& filename ) { - FileInfo fi{at,filename,false/*no_follow*/} ; - if (!fi.is_reg()) return ; - sz = fi.sz ; - if (!sz) { - _ok = true ; - return ; - } _fd = open_read(at,filename) ; if (!_fd) return ; - data = static_cast(::mmap( nullptr , sz , PROT_READ , MAP_PRIVATE , _fd , 0 )) ; - if (data==MAP_FAILED) { - _fd.detach() ; // report error - data = nullptr ; // avoid garbage info - return ; + sz = FileInfo(_fd,{},false/*no_follow*/).sz ; + if (sz) { + data = static_cast(::mmap( nullptr , sz , PROT_READ , MAP_PRIVATE , _fd , 0 )) ; + if (data==MAP_FAILED) { + _fd.detach() ; // report error + data = nullptr ; // avoid garbage info + return ; + } } _ok = true ; } diff --git a/src/hash.cc b/src/hash.cc index 2b3a50c0..1883f983 100644 --- a/src/hash.cc +++ b/src/hash.cc @@ -3,6 +3,7 @@ // This program is free software: you can redistribute/modify under the terms of the GPL-v3 (https://www.gnu.org/licenses/gpl-3.0.html). // This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#include "fd.hh" #include "hash.hh" namespace Hash { @@ -18,34 +19,53 @@ namespace Hash { else return os << "Crc("< 0) { has_data = true ; ctx.update(buf,cnt) ; } + else if (cnt==0) break ; + else switch (errno) { + case EAGAIN : + case EINTR : continue ; + case EISDIR : return ; + default : throw to_string("I/O error while reading file ",filename) ; + } + } + if (has_data) *this = ::move(ctx).digest() ; + else *this = Empty ; + } break ; + case Algo::Xxh : { + Xxh ctx { FileTag::Reg } ; + bool has_data = false ; + char buf[4096] ; + for(;;) { + ssize_t cnt = ::read( fd , buf , sizeof(buf) ) ; + if (cnt> 0) { has_data = true ; ctx.update(buf,cnt) ; } + else if (cnt==0) break ; + else switch (errno) { + case EAGAIN : + case EINTR : continue ; + default : throw to_string("I/O error while reading file ",filename) ; + } + } + if (has_data) *this = ctx.digest() ; + else *this = Empty ; + } break ; + DF} + } else if ( ::string lnk_target = read_lnk(filename) ; +lnk_target ) { + switch (algo) { //! vvvvvvvvvvvvvvvvvvvvvv vvvvvvvvvvvvvvvvvvvv + case Algo::Md5 : { Md5 ctx{FileTag::Lnk} ; ctx.update(lnk_target) ; *this = ::move(ctx).digest() ; } break ; + case Algo::Xxh : { Xxh ctx{FileTag::Lnk} ; ctx.update(lnk_target) ; *this = ctx .digest() ; } break ; + DF} //! ^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ } - if (file_date(filename)!=fi.date) *this = Crc(tag) ; // file was moving, crc is unreliable } Crc::operator ::string() const { diff --git a/src/hash.hh b/src/hash.hh index d230a990..8db08591 100644 --- a/src/hash.hh +++ b/src/hash.hh @@ -13,7 +13,7 @@ #else #define XXH_DEBUGLEVEL 1 #endif -#include "xxhash.patched.h" +#include "xxhash.h" #include "disk.hh" #include "serialize.hh" @@ -66,15 +66,9 @@ namespace Hash { return !crc.match(crc,a) ; } // cxtors & casts - constexpr Crc( ) = default ; - constexpr Crc( uint64_t v , bool is_lnk ) : _val{v} { if (is_lnk) _val |= 0x1 ; else _val &= ~0x1 ; } - /**/ Crc( ::string const& filename , Algo a ) : Crc{ Disk::FileInfo(filename) , filename , a } {} - /**/ Crc( Time::Ddate&/*out*/ d , ::string const& filename , Algo a ) { - Disk::FileInfo fi{filename} ; - d = fi.date ; - *this = Crc(fi,filename,a) ; - } - Crc(FileTag tag) { + constexpr Crc( ) = default ; + constexpr Crc( uint64_t v , bool is_lnk ) : _val{bit(v,0,is_lnk)} {} + constexpr Crc(FileTag tag) { switch (tag) { case FileTag::Reg : case FileTag::Exe : *this = Crc::Reg ; break ; @@ -84,9 +78,14 @@ namespace Hash { default : *this = Crc::Unknown ; break ; } } + Crc( ::string const& filename , Algo a ) ; + Crc( Time::Ddate&/*out*/ d , ::string const& filename , Algo a ) { + d = Disk::file_date(filename) ; + *this = Crc(filename,a) ; + if (Disk::file_date(filename)!=d) *this = Crc(d.tag()) ; // file was moving, association date<=>crc is not reliable + } private : - constexpr Crc( CrcSpecial special ) : _val{+special} {} - /**/ Crc( Disk::FileInfo const& , ::string const& filename , Algo ) ; + constexpr Crc( CrcSpecial special ) : _val{+special} {} // constexpr operator CrcSpecial() const { return _val>=+CrcSpecial::Plain ? CrcSpecial::Plain : CrcSpecial(_val) ; } public : diff --git a/src/job_exec.cc b/src/job_exec.cc index 5ccad4bc..a9361065 100644 --- a/src/job_exec.cc +++ b/src/job_exec.cc @@ -304,8 +304,6 @@ void crc_thread_func( size_t id , vmap_s* targets , ::vectorsize(),crcs->size()) ; NodeIdx cnt = 0 ; // cnt is for trace only for( NodeIdx ci=0 ; (ci=crc_idx++)size() ; cnt++ ) { -trace(ci); -trace((*crcs)[ci]); ::pair_s& e = (*targets)[(*crcs)[ci]] ; Pdate before = New ; e.second.crc = Crc( e.second.date/*out*/ , e.first , g_start_info.hash_algo ) ; diff --git a/src/lmakeserver/job.cc b/src/lmakeserver/job.cc index 1394d876..b971900f 100644 --- a/src/lmakeserver/job.cc +++ b/src/lmakeserver/job.cc @@ -125,7 +125,7 @@ namespace Engine { // ::ostream& operator<<( ::ostream& os , JobReqInfo const& ri ) { - return os<<"JRI(" << ri.req <<','<< (ri.full?"full":"makable") <<','<< ri.speculate <<','<< ri.step()<<':'<special<=Special::HasJobs , rule->special ) ; + Rule rule = match.rule ; SWEAR( rule->special<=Special::HasJobs , rule->special ) ; ::vmap_s>> dep_names ; try { dep_names = rule->deps_attrs.eval(match) ; @@ -187,10 +187,10 @@ namespace Engine { if ( auto [it,ok] = dis.emplace(d,deps.size()) ; ok ) deps.emplace_back( d , a , df , true/*parallel*/ ) ; else { deps[it->second].dflags |= df ; deps[it->second].accesses &= a ; } // uniquify deps by combining accesses and flags } - //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - // args for store args for JobData - *this = Job( match.full_name(),Dflt , match,deps ) ; // initially, static deps are deemed read, then actual accesses will be considered - //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + // args for store args for JobData + *this = Job( match.full_name(),Dflt , match,deps ) ; // initially, static deps are deemed read, then actual accesses will be considered + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // do not generate error if *_none_attrs is not available, as we will not restart job when fixed : do our best by using static info try { (*this)->tokens1 = rule->create_none_attrs.eval( *this , match , &::ref(::vmap_s()) ).tokens1 ; // we cant record deps here, but we dont care, no impact on target @@ -354,7 +354,7 @@ namespace Engine { // ::uset old_targets ; for( Node t : (*this)->targets ) if (t->has_actual_job(*this)) { - t->actual_job().clear() ; // ensure targets we no more generate do not keep pointing to us + t->actual_job().clear() ; // ensure targets we no more generate do not keep pointing to us old_targets.insert(t) ; } // @@ -366,7 +366,7 @@ namespace Engine { Crc crc = td.crc ; bool target_modified = false ; // - SWEAR( !( tflags[Tflag::Target] && crc==Crc::None && !static_phony ) , tn , td ) ; // else job_exec should have suppressed the Target flag + SWEAR( !( tflags[Tflag::Target] && crc==Crc::None && !static_phony ) , tn , td ) ; // else job_exec should have suppressed the Target flag // target->set_buildable() ; // @@ -620,10 +620,10 @@ namespace Engine { Trace trace("set_pressure",idx(),ri,pressure) ; Req req = ri.req ; CoarseDelay dep_pressure = ri.pressure + best_exec_time().first ; - switch (ri.step()) { //! vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - case JobStep::Dep : for( Dep const& d : deps.subvec(ri.dep_lvl) ) d-> set_pressure( d->req_info(req) , dep_pressure ) ; break ; - case JobStep::Queued : Backend::s_set_pressure( ri.backend , +idx() , +req , {.pressure=dep_pressure} ) ; break ; - default : ; //! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + switch (ri.step()) { //! vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + case JobStep::Dep : for( Dep const& d : deps.subvec(ri.i_dep) ) d-> set_pressure( d->req_info(req) , dep_pressure ) ; break ; + case JobStep::Queued : Backend::s_set_pressure( ri.backend , +idx() , +req , {.pressure=dep_pressure} ) ; break ; + default : ; //! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } } @@ -640,13 +640,13 @@ namespace Engine { CoarseDelay dep_pressure = ri.pressure + best_exec_time().first ; bool archive = req->options.flags[ReqFlag::Archive] ; // - Trace trace("Jmake",idx(),ri,ri.reason,make_action,reason,speculate,STR(stop_speculate),old_exec_time?*old_exec_time:CoarseDelay(),STR(wakeup_watchers),prev_step) ; + Trace trace("Jmake",idx(),ri,ri.reasons,make_action,reason,speculate,STR(stop_speculate),old_exec_time?*old_exec_time:CoarseDelay(),STR(wakeup_watchers),prev_step) ; RestartFullAnalysis : switch (make_action) { case MakeAction::End : SWEAR(cmd_ok()) ; ri.new_cmd = false ; // cmd has been executed, it is not new any more - ri.reason = {} ; // reason was to trigger the ending job, there are none now + ri.reasons = {} ; // reasons were to trigger the ending job, there are none now ri.reset() ; // deps have changed [[fallthrough]] ; case MakeAction::Wakeup : @@ -663,8 +663,10 @@ namespace Engine { if ( !ri.waiting() && !ri.full && sure() ) goto Done ; break ; DF} + ri.reasons.second |= reason ; + ri.speculate = ri.speculate & speculate ; // cannot use &= with bit fields if (ri.done()) { - if ( !reason ) goto Wakeup ; + if ( !ri.reason() ) goto Wakeup ; if ( req.zombie() ) goto Wakeup ; /**/ goto Run ; } else { @@ -674,46 +676,47 @@ namespace Engine { if ( special==Special::Infinite ) goto Run ; // special case : Infinite actually has no dep, just a list of node showing infinity if ( rule->n_submits && ri.n_submits>rule->n_submits ) { SWEAR(is_ok(status)==No) ; goto Done ; } // we do not analyze, ensure we have an error state } - /**/ ri.speculate = ri.speculate & speculate ; // cannot use &= with bit fields - /**/ ri.reason |= reason ; - if ( is_ok(status)==Maybe && +mk_reason(status) ) ri.reason |= mk_reason(status) ; - if ( ri.step()==Step::None ) { - if (ri.full) { - if ( rule->force ) ri.reason |= JobReasonTag::Force ; - else if ( !cmd_ok() ) { ri.new_cmd = true ; ri.reason |= JobReasonTag::Cmd ; } - else if ( req->options.flags[ReqFlag::ForgetOldErrors] && err() ) { ri.new_cmd = true ; ri.reason |= JobReasonTag::OldErr ; } // probably a transcient error, process as cmd modif - else if ( !rsrcs_ok() ) { ri.new_cmd = true ; ri.reason |= JobReasonTag::Rsrcs ; } // probably a resource error, process as cmd modif - } - ri.step(Step::Dep) ; + if ( is_ok(status)==Maybe && +mk_reason(status) ) ri.reasons.first |= mk_reason(status) ; + if ( ri.step()==Step::None ) { + if (ri.full) { + if ( rule->force ) ri.reasons.first |= JobReasonTag::Force ; + else if ( !cmd_ok() ) { ri.new_cmd = true ; ri.reasons.first |= JobReasonTag::Cmd ; } + else if ( req->options.flags[ReqFlag::ForgetOldErrors] && err() ) { ri.new_cmd = true ; ri.reasons.first |= JobReasonTag::OldErr ; } // probably a transcient error, do as cmd modif + else if ( !rsrcs_ok() ) { ri.new_cmd = true ; ri.reasons.first |= JobReasonTag::Rsrcs ; } // probably a resource error, do as cmd modif } - SWEAR(ri.step()==Step::Dep) ; - // - { auto need_dsk = [&]( bool care , bool force )->bool { - force |= (+ri.reason||+reason||archive) && ri.reason.tagbool { + bool need_run = r.need_run() || ri.reason().need_run() ; + return ri.full && care && (need_run||archive) ; } ; Idx n_deps = deps.size() ; - RestartAnalysis : // restart analysis here when it is discovered we need deps to run the job + RestartAnalysis : // restart analysis here when it is discovered we need deps to run the job bool stamped_seen_waiting = false ; bool proto_seen_waiting = false ; bool critical_modif = false ; bool critical_waiting = false ; bool sure = true ; // - ri.speculative_deps = false ; // initially, we are not speculatively waiting + ri.speculative_deps = false ; // initially, we are not speculatively waiting ri.stamped_modif = ri.proto_modif = ri.new_cmd ; // - for ( NodeIdx i_dep = ri.dep_lvl ; SWEAR(i_dep<=n_deps,i_dep,n_deps),true ; i_dep++ ) { - if (!proto_seen_waiting) ri.dep_lvl = i_dep ; // fast path : info is recorded in ri, next time, restart analysis here + for ( auto [cur_i_dep,cur_reason] = ::pair(ri.i_dep,ri.reasons.first) ; SWEAR(cur_i_dep<=n_deps,cur_i_dep,n_deps),true ; cur_i_dep++ ) { + if (!proto_seen_waiting) { + ri.i_dep = cur_i_dep ; // fast path : info is recorded in ri, next time, restart analysis here + ri.reasons.first = cur_reason ; + } // - bool seen_all = i_dep==n_deps ; - Dep const& dep = seen_all ? Sentinel : deps[i_dep] ; // use empty dep as sentinel + bool seen_all = cur_i_dep==n_deps ; + Dep const& dep = seen_all ? Sentinel : deps[cur_i_dep] ; // use empty dep as sentinel if ( !dep.parallel || seen_all ) { - ri.stamped_err = ri.proto_err ; // proto-errors become errors when stamped by a sequential dep - ri.stamped_modif = ri.proto_modif ; // idem, reason used as proto_modifs + ri.stamped_err = ri.proto_err ; // proto become stamped upon sequential dep + ri.stamped_modif = ri.proto_modif ; // . if ( critical_modif && !seen_all ) { - NodeIdx j = i_dep ; - for( NodeIdx i=i_dep ; ic_req_info(req) ; // avoid allocating req_info as long as not necessary - NodeReqInfo * dri = nullptr ; // . + NodeReqInfo const* cdri = &dep->c_req_info(req) ; // avoid allocating req_info as long as not necessary + NodeReqInfo * dri = nullptr ; // . NodeGoal dg = - need_dsk(care,false/*force*/) ? NodeGoal::Dsk + need_dsk(care,cur_reason) ? NodeGoal::Dsk : ri.full && ( care || sense_err ) ? NodeGoal::Status : required ? NodeGoal::Makable : NodeGoal::None ; - if (!dg ) continue ; // this is not a dep (not static while asked for makable only) + if (!dg ) continue ; // this is not a dep (not static while asked for makable only) if (!cdri->waiting()) { - ReqInfo::WaitInc sav_n_wait{ri} ; // appear waiting in case of recursion loop (loop will be caught because of no job on going) - if (!dri ) cdri = dri = &dep->req_info(*cdri) ; // refresh cdri in case dri allocated a new one - if (dep_live_out) dri->live_out = true ; // ask live output for last level if user asked it + ReqInfo::WaitInc sav_n_wait{ri} ; // appear waiting in case of recursion loop (loop will be caught because of no job on going) + if (!dri ) cdri = dri = &dep->req_info(*cdri) ; // refresh cdri in case dri allocated a new one + if (dep_live_out) dri->live_out = true ; // ask live output for last level if user asked it Bool3 speculate_dep = - is_static ? ri.speculate // static deps do not disappear - : stamped_seen_waiting || ri.stamped_modif ? Yes // this dep may disappear - : +ri.stamped_err ? ri.speculate|Maybe // this dep is not the origin of the error - : ri.speculate // this dep will not disappear from us + is_static ? ri.speculate // static deps do not disappear + : stamped_seen_waiting || ri.stamped_modif ? Yes // this dep may disappear + : +ri.stamped_err ? ri.speculate|Maybe // this dep is not the origin of the error + : ri.speculate // this dep will not disappear from us ; - if (special!=Special::Req) dnd.asking = idx() ; // Req jobs are fugitive, dont record them + if (special!=Special::Req) dnd.asking = idx() ; // Req jobs are fugitive, dont record them //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv dnd.make( *dri , mk_action(dg) , speculate_dep ) ; //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } - if ( is_static && dnd.buildablewaiting()) { if ( is_static ) ri.speculative_deps = false ; // we are non-speculatively waiting, even if after a speculative wait else if ( !stamped_seen_waiting && (+ri.stamped_err||ri.stamped_modif) ) ri.speculative_deps = true ; @@ -770,68 +773,68 @@ namespace Engine { critical_waiting |= is_critical ; goto Continue ; } - SWEAR(dnd.done(*cdri,dg)) ; // after having called make, dep must be either waiting or done - if ( need_dsk(care,true/*force*/) && !dnd.done(*cdri,NodeGoal::Dsk) ) ri.missing_dsk = true ; // job needs this dep if it must run - if (dg==NodeGoal::Makable ) continue ; // dont check modifs and errors + SWEAR(dnd.done(*cdri,dg)) ; // after having called make, dep must be either waiting or done + if ( need_dsk(care) && !dnd.done(*cdri,NodeGoal::Dsk) ) ri.missing_dsk = true ; // job needs this dep if it must run + if (dg==NodeGoal::Makable ) continue ; // dont check modifs and errors dep_modif = care && !dep.up_to_date() ; - if ( dep_modif && status==Status::Ok && dep.no_trigger() ) { // no_trigger only applies to successful jobs + if ( dep_modif && status==Status::Ok && dep.no_trigger() ) { // no_trigger only applies to successful jobs trace("no_trigger",dep) ; - req->no_triggers.emplace(dep,req->no_triggers.size()) ; // record to repeat in summary, value is just to order summary in discovery order + req->no_triggers.emplace(dep,req->no_triggers.size()) ; // record to repeat in summary, value is just to order summary in discovery order dep_modif = false ; } - if (dep_modif) ri.reason |= {JobReasonTag::DepOutOfDate,+dep} ; - if ( +ri.stamped_err ) goto Continue ; // we are already in error, no need to analyze errors any further - if ( !is_static && ri.stamped_modif ) goto Continue ; // if not static, errors may be washed by previous modifs, dont record them + if (dep_modif) cur_reason |= {JobReasonTag::DepOutOfDate,+dep} ; + if ( +ri.stamped_err ) goto Continue ; // we are already in error, no need to analyze errors any further + if ( !is_static && ri.stamped_modif ) goto Continue ; // if not static, errors may be washed by previous modifs, dont record them // { Bool3 dep_ok = dnd.ok(*cdri,dep.accesses) ; switch (dep_ok) { case Yes : break ; case Maybe : if (required) { - if (is_static) { dep_err = RunStatus::MissingStatic ; ri.reason |= {JobReasonTag::DepMissingStatic ,+dep} ; } - else { dep_err = RunStatus::DepErr ; ri.reason |= {JobReasonTag::DepMissingRequired,+dep} ; } + if (is_static) { dep_err = RunStatus::MissingStatic ; cur_reason |= {JobReasonTag::DepMissingStatic ,+dep} ; } + else { dep_err = RunStatus::DepErr ; cur_reason |= {JobReasonTag::DepMissingRequired,+dep} ; } trace("missing",STR(is_static),dep) ; goto Continue ; } break ; case No : trace("dep_err",dep,STR(sense_err)) ; - if (+(cdri->overwritten&dep.accesses)) { dep_err = RunStatus::DepErr ; ri.reason |= {JobReasonTag::DepOverwritten,+dep} ; goto Continue ; } // even with !sense_err - if (sense_err ) { dep_err = RunStatus::DepErr ; ri.reason |= {JobReasonTag::DepErr ,+dep} ; goto Continue ; } + if (+(cdri->overwritten&dep.accesses)) { dep_err = RunStatus::DepErr ; cur_reason |= {JobReasonTag::DepOverwritten,+dep} ; goto Continue ; } // even with !sense_err + if (sense_err ) { dep_err = RunStatus::DepErr ; cur_reason |= {JobReasonTag::DepErr ,+dep} ; goto Continue ; } break ; DF} - if ( care && dep.is_date && +dep.date() ) { // if still waiting for a crc here, it will never come + if ( care && dep.is_date && +dep.date() ) { // if still waiting for a crc here, it will never come SWEAR(!dnd.running(*cdri)) ; - SWEAR(dep_modif ) ; // this dep was moving, retry job - if (!dri) cdri = dri = &dnd.req_info(*cdri) ; // refresh cdri in case dri allocated a new one + SWEAR(dep_modif ) ; // this dep was moving, retry job + if (!dri) cdri = dri = &dnd.req_info(*cdri) ; // refresh cdri in case dri allocated a new one Manual manual = dnd.manual_wash(*dri) ; - if (manual>=Manual::Changed) { trace("dangling",dep ) ; dep_err = RunStatus::DepErr ; ri.reason |= {JobReasonTag::DepDangling,+dep} ; } - else if (manual==Manual::Unlnked) { trace("washed" ,dep ) ; ri.reason |= {JobReasonTag::DepUnlnked ,+dep} ; } + if (manual>=Manual::Changed) { trace("dangling",dep ) ; dep_err = RunStatus::DepErr ; cur_reason |= {JobReasonTag::DepDangling,+dep} ; } + else if (manual==Manual::Unlnked) { trace("washed" ,dep ) ; cur_reason |= {JobReasonTag::DepUnlnked ,+dep} ; } else trace("unstable",dep,manual) ; } } Continue : - trace("dep",ri,dep,*cdri,STR(dnd.done(*cdri)),STR(dnd.ok()),dnd.crc,dep_err,STR(dep_modif),STR(critical_modif),STR(critical_waiting),ri.reason) ; + trace("dep",ri,dep,*cdri,STR(dnd.done(*cdri)),STR(dnd.ok()),dnd.crc,dep_err,STR(dep_modif),STR(critical_modif),STR(critical_waiting),cur_reason) ; // - if ( ri.missing_dsk && ri.reason.need_run() ) { + if ( ri.missing_dsk && cur_reason.need_run() ) { trace("restart_analysis") ; ri.reset() ; - goto RestartAnalysis ; // BACKWARD + SWEAR(!ri.reasons.second) ; // we should have asked for dep on disk if we had a reason to run + ri.reasons.second = cur_reason ; // record that we must ask for dep on disk + goto RestartAnalysis ; // BACKWARD } - SWEAR( !dep_err || !ri.stamped_modif || is_static ) ; // if earlier modifs have been seen, we do not want to record errors as they can be washed, unless static - ri.proto_err = ri.proto_err | dep_err ; // |= is forbidden for bit fields - ri.proto_modif = ri.proto_modif | dep_modif ; // . + SWEAR( !dep_err || !ri.stamped_modif || is_static ) ; // if earlier modifs have been seen, we do not want to record errors as they can be washed, unless static + ri.proto_err = ri.proto_err | dep_err ; // |= is forbidden for bit fields + ri.proto_modif = ri.proto_modif | dep_modif ; // . critical_modif |= dep_modif && is_critical ; } - ri.reason |= reason ; // this is done at the end so as to appear as a reason only if there is no other ones - if ( ri.waiting() ) goto Wait ; - if ( sure ) mk_sure() ; // improve sure (sure is pessimistic) - if (+( run_status = ri.stamped_err )) goto Done ; - if ( !ri.reason ) goto Done ; - trace("run",ri,run_status,ri.reason) ; + if ( ri.waiting() ) goto Wait ; + if ( sure ) mk_sure() ; // improve sure (sure is pessimistic) + if ( +(run_status=ri.stamped_err) ) goto Done ; + if ( !ri.reason() ) goto Done ; } Run : - SWEAR(ri.reason.tagn_submits) { if (ri.n_submits>=rule->n_submits) { trace("submit_loop") ; @@ -841,17 +844,17 @@ namespace Engine { } ri.n_submits++ ; } - // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - if (is_special()) { if (!_submit_special(ri )) goto Done ; } // if no new deps, we cannot be waiting and we are done - else { if (!_submit_plain (ri,ri.reason,dep_pressure)) goto Done ; } // . - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + if (is_special()) { if (!_submit_special(ri )) goto Done ; } // if no new deps, we cannot be waiting and we are done + else { if (!_submit_plain (ri,ri.reason(),dep_pressure)) goto Done ; } // . + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ if (!ri.waiting()) { SWEAR(!ri.running()) ; - make_action = MakeAction::End ; // restart analysis as if called by end() as in case of flash execution, submit has called end() - if (is_ok(status)!=Maybe) ri.reason = {} ; // . - else ri.reason = mk_reason(status) ; // . + make_action = MakeAction::End ; // restart analysis as if called by end() as in case of flash execution, submit has called end() + if (is_ok(status)!=Maybe) ri.reasons = { {} , {} } ; // . + else ri.reasons = { mk_reason(status) , {} } ; // . trace("restart_analysis",ri) ; - goto RestartFullAnalysis ; // BACKWARD + goto RestartFullAnalysis ; // BACKWARD } goto Wait ; Done : @@ -865,12 +868,12 @@ namespace Engine { /**/ deserialize(job_stream) ; ::string stderr = deserialize(job_stream).end.digest.stderr ; // - if (ja.report!=JobReport::Hit) { // if not Hit, then job was rerun and ja.report is the report that would have been done w/o rerun + if (ja.report!=JobReport::Hit) { // if not Hit, then job was rerun and ja.report is the report that would have been done w/o rerun SWEAR(req->stats.ended(JobReport::Rerun)>0) ; - req->stats.ended(JobReport::Rerun)-- ; // we tranform a rerun into a completed job, subtract what was accumulated as rerun - req->stats.ended(ja.report )++ ; // we tranform a rerun into a completed job, subtract what was accumulated as rerun - req->stats.jobs_time[false/*useful*/] -= exec_time ; // exec time is not added to useful as it is not provided to audit_end - req->stats.jobs_time[true /*useful*/] += exec_time ; // . + req->stats.ended(JobReport::Rerun)-- ; // we tranform a rerun into a completed job, subtract what was accumulated as rerun + req->stats.ended(ja.report )++ ; // we tranform a rerun into a completed job, subtract what was accumulated as rerun + req->stats.jobs_time[false/*useful*/] -= exec_time ; // exec time is not added to useful as it is not provided to audit_end + req->stats.jobs_time[true /*useful*/] += exec_time ; // . } // size_t max_stderr_len ; @@ -883,8 +886,8 @@ namespace Engine { req->audit_job(Color::Note,"dynamic",idx()) ; req->audit_stderr( ensure_nl(rule->end_none_attrs.s_exc_msg(true/*using_static*/))+msg_err.first , msg_err.second , -1 , 1 ) ; } - if (ri.reason.tag>=JobReasonTag::Err) audit_end( ja.report==JobReport::Hit?"hit_":"was_" , ri , reason_str(ri.reason) , stderr , max_stderr_len , ja.modified ) ; - else audit_end( ja.report==JobReport::Hit?"hit_":"was_" , ri , ja.backend_msg , stderr , max_stderr_len , ja.modified ) ; + if (ri.reason().tag>=JobReasonTag::Err) audit_end( ja.report==JobReport::Hit?"hit_":"was_" , ri , reason_str(ri.reason()) , stderr , max_stderr_len , ja.modified ) ; + else audit_end( ja.report==JobReport::Hit?"hit_":"was_" , ri , ja.backend_msg , stderr , max_stderr_len , ja.modified ) ; req->missing_audits.erase(it) ; } trace("wakeup",ri) ; @@ -899,7 +902,7 @@ namespace Engine { bool add_new = ri.step()>=Step::MinCurStats && ri.step()=Step::Done && (!is_full||full) ; } + bool done (bool is_full=false) const { return step()>=Step::Done && (!is_full||full) ; } + JobReason reason( ) const { return reasons.first | reasons.second ; } // Step step( ) const { return _step ; } void step(Step s) ; @@ -193,7 +194,8 @@ namespace Engine { void reset() { if (step()>Step::Dep) step(Step::Dep) ; missing_dsk = false ; - dep_lvl = 0 ; + i_dep = 0 ; + reasons.first = {} ; stamped_err = proto_err = {} ; // no errors in dep as no dep analyzed yet stamped_modif = proto_modif = false ; } @@ -212,25 +214,25 @@ namespace Engine { } // data // req independent (identical for all Req's) : these fields are there as there is no Req-independent non-persistent table - JobReason reason ; // 36<=64 bits, reason to run job when deps are ready - NodeIdx dep_lvl = 0 ; // ~20<=32 bits, deps up to this one statisfy required action - uint8_t n_submits = 0 ; // 8 bits, number of times job has been submitted to avoid infinite loop - bool new_cmd :1 = false ; // 1 bit , if true <=> cmd has been modified - bool full :1 = false ; // 1 bit , if true <=>, job result is asked, else only makable - bool missing_dsk :1 = false ; // 1 bit , if true <=>, a dep has been checked but not on disk - RunStatus stamped_err :2 = {} ; // 2 bits, errors seen in dep until dep_lvl before last parallel chunk, Maybe means missing static - RunStatus proto_err :2 = {} ; // 2 bits, errors seen in dep until dep_lvl including last parallel chunk, Maybe means missing static - bool stamped_modif :1 = false ; // 1 bit , modifs seen in dep until dep_lvl before last parallel chunk - bool proto_modif :1 = false ; // 1 bit , modifs seen in dep until dep_lvl including last parallel chunk - bool start_reported :1 = false ; // 1 bit , if true <=> start message has been reported to user - bool speculative_deps:1 = false ; // 1 bit , if true <=> job is waiting for speculative deps only - Bool3 speculate :2 = Yes ; // 2 bits, Yes : prev dep not ready, Maybe : prev dep in error (percolated) - bool reported :1 = false ; // 1 bit , used for delayed report when speculating - BackendTag backend :2 = {} ; // 2 bits + ::pair reasons ; // 36+36<=128 bits, reasons to run job when deps are ready + NodeIdx i_dep = 0 ; // ~20 <= 32 bits, deps up to this one statisfy required action + uint8_t n_submits = 0 ; // 8 bits, number of times job has been submitted to avoid infinite loop + bool new_cmd :1 = false ; // 1 bit , if true <=> cmd has been modified + bool full :1 = false ; // 1 bit , if true <=>, job result is asked, else only makable + bool missing_dsk :1 = false ; // 1 bit , if true <=>, a dep has been checked but not on disk + RunStatus stamped_err :2 = {} ; // 2 bits, errors seen in dep until i_dep before last parallel chunk, Maybe means missing static + RunStatus proto_err :2 = {} ; // 2 bits, errors seen in dep until i_dep including last parallel chunk, Maybe means missing static + bool stamped_modif :1 = false ; // 1 bit , modifs seen in dep until i_dep before last parallel chunk + bool proto_modif :1 = false ; // 1 bit , modifs seen in dep until i_dep including last parallel chunk + bool start_reported :1 = false ; // 1 bit , if true <=> start message has been reported to user + bool speculative_deps:1 = false ; // 1 bit , if true <=> job is waiting for speculative deps only + Bool3 speculate :2 = Yes ; // 2 bits, Yes : prev dep not ready, Maybe : prev dep in error (percolated) + bool reported :1 = false ; // 1 bit , used for delayed report when speculating + BackendTag backend :2 = {} ; // 2 bits private : Step _step :3 = {} ; // 3 bits } ; - static_assert(sizeof(JobReqInfo)==32) ; // check expected size + static_assert(sizeof(JobReqInfo)==40) ; // check expected size } diff --git a/src/lmakeserver/node.cc b/src/lmakeserver/node.cc index 8bb2a82f..08cc32b6 100644 --- a/src/lmakeserver/node.cc +++ b/src/lmakeserver/node.cc @@ -245,7 +245,8 @@ namespace Engine { case Buildable::SrcDir : case Buildable::Anti : SWEAR(!rule_tgts(),rule_tgts()) ; goto Return ; case Buildable::Decode : - case Buildable::Encode : goto Return ; + case Buildable::Encode : + case Buildable::Loop : goto Return ; default : ; } status(NodeStatus::Unknown) ; @@ -639,15 +640,15 @@ namespace Engine { bool/*modified*/ NodeData::refresh( Crc crc_ , Ddate date_ ) { if (crc.match(crc_)) { - Trace trace("refresh",idx(),date,"->",date_) ; + Trace trace("refresh_idle",idx(),date,"->",date_) ; date = date_ ; return false ; } else { - Trace trace("refresh",idx(),crc,"->",crc_,date,"->",date_) ; + Trace trace("refresh",idx(),reqs(),crc,"->",crc_,date,"->",date_) ; crc = {} ; fence() ; date = date_ ; fence() ; - crc = crc_ ; // ensure crc is never associated with a wrong date, even in case of crash - for( Req r : reqs() ) { trace("overwrite",*this) ; req_info(r).reset(NodeGoal::Status) ; } // target is not conform on disk any more + crc = crc_ ; // ensure crc is never associated with a wrong date, even in case of crash + for( Req r : reqs() ) req_info(r).reset(NodeGoal::Status) ; // target is not conform on disk any more return true ; } } diff --git a/src/lmakeserver/req.cc b/src/lmakeserver/req.cc index dadd2df7..7b14db5a 100644 --- a/src/lmakeserver/req.cc +++ b/src/lmakeserver/req.cc @@ -203,7 +203,7 @@ namespace Engine { } if ( +to_forget || +to_raise ) { (*this)->audit_info( Color::Note , "consider :\n" ) ; - for( Node n : to_forget ) (*this)->audit_node( Color::Note , "lforget -d " , n , 1 ) ; + for( Node n : to_forget ) (*this)->audit_node( Color::Note , "lforget - d" , n , 1 ) ; if (+to_raise) (*this)->audit_info( Color::Note , "add to Lmakefile.py :" , 1 ) ; for( Rule r : to_raise ) (*this)->audit_info( Color::Note , to_string(r->name,".prio = ",r->prio,"+1") , 2 ) ; } diff --git a/src/non_portable.cc b/src/non_portable.cc index 6cf8281a..7d1c65e2 100644 --- a/src/non_portable.cc +++ b/src/non_portable.cc @@ -5,7 +5,7 @@ #include "sys_config.h" -#include // NT_PRSTATUS definition on ARM +#include // NT_PRSTATUS definition on ARM #include #include "non_portable.hh" @@ -18,37 +18,13 @@ int np_get_fd(::filebuf& fb) { return static_cast(fb).fd() ; } -long np_ptrace_get_syscall(int pid) { - errno = 0 ; - struct ::user_regs_struct regs ; - #ifdef __x86_64__ - ptrace( PTRACE_GETREGS , pid , nullptr/*addr*/ , ®s ) ; - if (errno) throw 0 ; - return regs.orig_rax ; - #elif __aarch64__ || __arm__ - long rc ; - struct iovec iov ; - iov.iov_base = ®s ; - iov.iov_len = 9 * sizeof(unsigned long long) ; // read/write only 9 registers - rc = ptrace( PTRACE_GETREGSET , pid , (void*)NT_PRSTATUS , &iov ) ; - if ( rc==-1 || errno ) throw 0 ; - #if __arm__ - return regs.r7 ; - #elif __aarch64__ - return regs.regs[8] ; - #endif - #else - #error "np_clear_syscall not implemented for this architecture" // if situation arises, please provide the adequate code using x86_64 case as a template - #endif -} - ::array np_ptrace_get_args(int pid) { struct ::user_regs_struct regs ; ::array res ; errno = 0 ; #ifdef __x86_64__ ptrace( PTRACE_GETREGS , pid , nullptr/*addr*/ , ®s ) ; - res[0] = regs.rdi ; // from man 2 syscall + res[0] = regs.rdi ; // from man 2 syscall res[1] = regs.rsi ; res[2] = regs.rdx ; res[3] = regs.r10 ; @@ -57,18 +33,18 @@ ::array np_ptrace_get_args(int pid) { #elif __aarch64__ || __arm__ struct iovec iov ; iov.iov_base = ®s ; - iov.iov_len = sizeof(unsigned long long) ; // read only the 1st register + iov.iov_len = sizeof(unsigned long long) ; // read only the 1st register long rc = ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &iov) ; SWEAR(rc!=-1) ; #if __arm__ - res[0] = regs.r0 ; // from man 2 syscall + res[0] = regs.r0 ; // from man 2 syscall res[1] = regs.r1 ; res[2] = regs.r2 ; res[3] = regs.r3 ; res[4] = regs.r4 ; res[5] = regs.r5 ; #elif __aarch64__ - res[0] = regs.regs[0] ; // XXX : find a source of info + res[0] = regs.regs[0] ; // XXX : find a source of info res[1] = regs.regs[1] ; res[2] = regs.regs[2] ; res[3] = regs.regs[3] ; @@ -76,7 +52,7 @@ ::array np_ptrace_get_args(int pid) { res[5] = regs.regs[5] ; #endif #else - #error "np_get_errno not implemented for this architecture" // if situation arises, please provide the adequate code using x86_64 case as a template + #error "np_get_errno not implemented for this architecture" // if situation arises, please provide the adequate code using x86_64 case as a template #endif SWEAR( !errno , errno ) ; return res ; @@ -93,7 +69,7 @@ int64_t np_ptrace_get_res(int pid) { #elif __aarch64__ || __arm__ struct iovec iov ; iov.iov_base = ®s ; - iov.iov_len = sizeof(unsigned long long) ; // read only the 1st register + iov.iov_len = sizeof(unsigned long long) ; // read only the 1st register long rc = ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &iov) ; SWEAR(rc!=-1) ; #if __arm__ @@ -102,8 +78,58 @@ int64_t np_ptrace_get_res(int pid) { res = regs.regs[0] ; #endif #else - #error "np_get_errno not implemented for this architecture" // if situation arises, please provide the adequate code using x86_64 case as a template + #error "np_get_errno not implemented for this architecture" // if situation arises, please provide the adequate code using x86_64 case as a template #endif SWEAR( !errno , errno ) ; return res ; } + +long np_ptrace_get_nr(int pid) { + errno = 0 ; + struct ::user_regs_struct regs ; + #ifdef __x86_64__ + ptrace( PTRACE_GETREGS , pid , nullptr/*addr*/ , ®s ) ; + if (errno) throw 0 ; + return regs.orig_rax ; + #elif __aarch64__ || __arm__ + struct iovec iov ; + iov.iov_base = ®s ; + iov.iov_len = 9 * sizeof(unsigned long long) ; // read/write only 9 registers + ptrace( PTRACE_GETREGSET , pid , (void*)NT_PRSTATUS , &iov ) ; + if (errno) throw 0 ; + #if __arm__ + return regs.r7 ; + #elif __aarch64__ + return regs.regs[8] ; + #endif + #else + #error "np_ptrace_get_syscall not implemented for this architecture" // if situation arises, please provide the adequate code using x86_64 case as a template + #endif +} + +void np_ptrace_set_res( int pid , long val ) { + errno = 0 ; + struct ::user_regs_struct regs ; + #ifdef __x86_64__ + ptrace( PTRACE_GETREGS , pid , nullptr/*addr*/ , ®s ) ; + if (errno) throw 0 ; + regs.rax = val ; + ptrace( PTRACE_SETREGS , pid , nullptr/*addr*/ , ®s ) ; + if (errno) throw 0 ; + #elif __aarch64__ || __arm__ + struct iovec iov ; + iov.iov_base = ®s ; + iov.iov_len = 9 * sizeof(unsigned long long) ; // read/write only 9 registers + ptrace( PTRACE_GETREGSET , pid , (void*)NT_PRSTATUS , &iov ) ; + if (errno) throw 0 ; + #if __arm__ + regs.r0 = val ; // XXX : to be validated + #elif __aarch64__ + regs.regs[0] = val ; // XXX : to be validated + #endif + ptrace( PTRACE_SETREGSET , pid , (void*)NT_PRSTATUS , &iov ) ; + if (errno) throw 0 ; + #else + #error "np_ptrace_get_syscall not implemented for this architecture" // if situation arises, please provide the adequate code using x86_64 case as a template + #endif +} diff --git a/src/non_portable.hh b/src/non_portable.hh index cd10f71d..b9e2953e 100644 --- a/src/non_portable.hh +++ b/src/non_portable.hh @@ -17,6 +17,7 @@ static constexpr char NpErrnoSymbolName[] = "__errno_location" ; / int np_get_fd(std::filebuf& fb) ; -long np_ptrace_get_syscall(int pid) ; -::array np_ptrace_get_args (int pid) ; -int64_t np_ptrace_get_res (int pid) ; +::array np_ptrace_get_args( int pid ) ; +int64_t np_ptrace_get_res ( int pid ) ; +long np_ptrace_get_nr ( int pid ) ; +void np_ptrace_set_res ( int pid , long val ) ; diff --git a/src/rpc_job.hh b/src/rpc_job.hh index e65f6ecf..53cbb006 100644 --- a/src/rpc_job.hh +++ b/src/rpc_job.hh @@ -165,9 +165,9 @@ ENUM_3( JobReasonTag // see explanations in table , DepOverwritten , DepDangling , DepErr -, DepMissingRequired +, DepMissingRequired // this is actually an error // with missing -, DepMissingStatic +, DepMissingStatic // this prevents the job from being selected ) static constexpr const char* JobReasonTagStrs[] = { "no reason" // None @@ -337,7 +337,6 @@ struct JobReason { if (tag