fix: close task5 state and capability blockers

This commit is contained in:
2026-08-11 19:52:10 +02:00
parent e35e62a5c6
commit 11ca7d111d
9 changed files with 200 additions and 185 deletions
@@ -1,8 +1,12 @@
#include <node_api.h>
#include <fcntl.h>
#include <sys/file.h>
#ifdef __linux__
#include <sys/syscall.h>
#endif
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <string>
@@ -85,6 +89,45 @@ static napi_value openatFn(napi_env env,napi_callback_info info) {
if(kind=="regular_lock" && (!S_ISREG(st.st_mode)||(st.st_mode&0777)!=0600||st.st_uid!=(uid_t)::geteuid()||st.st_nlink!=1)) {::close(fd);return fail(env,"openat",EPERM);}
return result(env,fd,kind=="directory",st);
}
static napi_value openFileAtFn(napi_env env,napi_callback_info info) {
size_t n=1; napi_value a[1]; if(!getInput(env,info,a,&n)||n!=1)return fail(env,"openat",EINVAL);
napi_value pv,nv,av; if(napi_get_named_property(env,a[0],"parent",&pv)!=napi_ok||napi_get_named_property(env,a[0],"name",&nv)!=napi_ok||napi_get_named_property(env,a[0],"access",&av)!=napi_ok)return fail(env,"openat",EINVAL);
Handle* ph=nullptr; std::string name,access; if(!requireHandle(env,pv,&ph,"openat")||!ph->directory||!str(env,nv,name)||!validComponent(name)||!str(env,av,access)||(access!="read"&&access!="create"))return fail(env,"openat",EINVAL);
int flags=O_RDONLY|O_CLOEXEC|O_NOFOLLOW; if(access=="create") flags=O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW;
int fd=openRetry(ph->fd,name.c_str(),flags,0600); if(fd<0)return fail(env,"openat",errno);
struct stat st; if(statRetry(fd,&st)<0){int e=errno;::close(fd);return fail(env,"fstat",e);}
if(!S_ISREG(st.st_mode)||(st.st_mode&0777)!=0600||st.st_uid!=(uid_t)::geteuid()||st.st_nlink!=1){::close(fd);return fail(env,"openat",EPERM);}
return result(env,fd,false,st);
}
static napi_value readFileFn(napi_env env,napi_callback_info info) {
size_t n=2; napi_value a[2]; if(napi_get_cb_info(env,info,&n,a,nullptr,nullptr)!=napi_ok||n!=2)return fail(env,"read",EINVAL);
Handle*h=nullptr; int64_t max=0; if(!requireHandle(env,a[0],&h,"read")||h->directory||napi_get_value_int64(env,a[1],&max)!=napi_ok||max<0||max>67108864)return fail(env,"read",EINVAL);
struct stat st; if(statRetry(h->fd,&st)<0)return fail(env,"fstat",errno); if(!S_ISREG(st.st_mode)||(uint64_t)st.st_size>(uint64_t)max)return fail(env,"read",EFBIG);
void* data=nullptr; napi_value out; if(napi_create_buffer(env,(size_t)st.st_size,&data,&out)!=napi_ok)return fail(env,"read",ENOMEM);
size_t got=0; while(got<(size_t)st.st_size){ssize_t rc=::read(h->fd,(char*)data+got,(size_t)st.st_size-got);if(rc<0&&errno==EINTR)continue;if(rc<=0)return fail(env,"read",rc==0?EIO:errno);got+=(size_t)rc;} return out;
}
static napi_value writeFileFn(napi_env env,napi_callback_info info) {
size_t n=2; napi_value a[2]; if(napi_get_cb_info(env,info,&n,a,nullptr,nullptr)!=napi_ok||n!=2)return fail(env,"write",EINVAL);
Handle*h=nullptr; void* data=nullptr; size_t len=0; if(!requireHandle(env,a[0],&h,"write")||h->directory||napi_get_buffer_info(env,a[1],&data,&len)!=napi_ok||len>67108864)return fail(env,"write",EINVAL);
size_t off=0; while(off<len){ssize_t rc=::write(h->fd,(char*)data+off,len-off);if(rc<0&&errno==EINTR)continue;if(rc<=0)return fail(env,"write",rc==0?EIO:errno);off+=(size_t)rc;} return nullptr;
}
static napi_value fsyncFileFn(napi_env env,napi_callback_info info) {
size_t n=1; napi_value a[1]; if(napi_get_cb_info(env,info,&n,a,nullptr,nullptr)!=napi_ok||n!=1)return fail(env,"fsync",EINVAL); Handle*h=nullptr; if(!requireHandle(env,a[0],&h,"fsync")||h->directory)return fail(env,"fsync",EBADF); int rc; do{rc=::fsync(h->fd);}while(rc<0&&errno==EINTR); if(rc<0)return fail(env,"fsync",errno); return nullptr;
}
static napi_value renameAtFn(napi_env env,napi_callback_info info) {
size_t n=4; napi_value a[4]; if(napi_get_cb_info(env,info,&n,a,nullptr,nullptr)!=napi_ok||n!=4)return fail(env,"renameat",EINVAL); Handle*h=nullptr; std::string from,to; bool replace=false; bool b=false; if(!requireHandle(env,a[0],&h,"renameat")||!h->directory||!str(env,a[1],from)||!str(env,a[2],to)||!validComponent(from)||!validComponent(to)||napi_get_value_bool(env,a[3],&b)!=napi_ok)return fail(env,"renameat",EINVAL); replace=b;
#ifdef __linux__
if(!replace){ int rc=(int)syscall(SYS_renameat2,h->fd,from.c_str(),h->fd,to.c_str(),1 /* RENAME_NOREPLACE */); if(rc<0)return fail(env,"renameat",errno); return nullptr; }
#endif
if(!replace){ struct stat st; if(::fstatat(h->fd,to.c_str(),&st,AT_SYMLINK_NOFOLLOW)==0)return fail(env,"renameat",EEXIST); if(errno!=ENOENT)return fail(env,"renameat",errno); }
int rc; do{rc=::renameat(h->fd,from.c_str(),h->fd,to.c_str());}while(rc<0&&errno==EINTR); if(rc<0)return fail(env,"renameat",errno); return nullptr;
}
static napi_value listDirectoryFn(napi_env env,napi_callback_info info) {
size_t n=1; napi_value a[1]; if(napi_get_cb_info(env,info,&n,a,nullptr,nullptr)!=napi_ok||n!=1)return fail(env,"readdir",EINVAL); Handle*h=nullptr; if(!requireHandle(env,a[0],&h,"readdir")||!h->directory)return fail(env,"readdir",EBADF); int dupfd=::dup(h->fd); if(dupfd<0)return fail(env,"readdir",errno); DIR*d=::fdopendir(dupfd); if(!d){int e=errno;::close(dupfd);return fail(env,"readdir",e);} napi_value out; if(napi_create_array(env,&out)!=napi_ok){::closedir(d);return fail(env,"readdir",ENOMEM);} uint32_t index=0; errno=0; while(dirent* ent=::readdir(d)){ if(strcmp(ent->d_name,".")==0||strcmp(ent->d_name,"..")==0)continue; napi_value s; napi_create_string_utf8(env,ent->d_name,NAPI_AUTO_LENGTH,&s); napi_set_element(env,out,index++,s); if(index>4096){::closedir(d);return fail(env,"readdir",EOVERFLOW);} } int e=errno;::closedir(d);if(e)return fail(env,"readdir",e);return out;
}
static napi_value unlinkAtFn(napi_env env,napi_callback_info info) {
size_t n=2; napi_value a[2]; if(napi_get_cb_info(env,info,&n,a,nullptr,nullptr)!=napi_ok||n!=2)return fail(env,"unlinkat",EINVAL); Handle*h=nullptr; std::string name; if(!requireHandle(env,a[0],&h,"unlinkat")||!h->directory||!str(env,a[1],name)||!validComponent(name))return fail(env,"unlinkat",EINVAL); int rc; do{rc=::unlinkat(h->fd,name.c_str(),0);}while(rc<0&&errno==EINTR); if(rc<0)return fail(env,"unlinkat",errno); return nullptr;
}
static napi_value mkdiratFn(napi_env env,napi_callback_info info){size_t n=3;napi_value a[3];napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*h;std::string s;int32_t m;if(n!=3||!requireHandle(env,a[0],&h,"workspace-fs-at")||!h->directory||!str(env,a[1],s)||napi_get_value_int32(env,a[2],&m)!=napi_ok||m!=0700||!validComponent(s))return fail(env,"mkdirat",EINVAL);int rc;do{rc=::mkdirat(h->fd,s.c_str(),0700);}while(rc<0&&errno==EINTR);if(rc<0)return fail(env,"mkdirat",errno);return nullptr;}
static napi_value fstatatFn(napi_env env,napi_callback_info info){size_t n=2;napi_value a[2];napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*h;std::string s;if(n!=2||!requireHandle(env,a[0],&h,"workspace-fs-at")||!h->directory||!str(env,a[1],s)||!validComponent(s))return fail(env,"fstatat",EINVAL);struct stat st;int rc;do{rc=::fstatat(h->fd,s.c_str(),&st,AT_SYMLINK_NOFOLLOW);}while(rc<0&&errno==EINTR);if(rc<0)return fail(env,"fstatat",errno);return statObj(env,st);}
static napi_value fsyncFn(napi_env env,napi_callback_info info){size_t n=1;napi_value a[1];napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*h;if(n!=1||!requireHandle(env,a[0],&h,"workspace-fs-at")||!h->directory)return fail(env,"fsync",EBADF);int rc;do{rc=::fsync(h->fd);}while(rc<0&&errno==EINTR);
@@ -95,6 +138,6 @@ static napi_value fsyncFn(napi_env env,napi_callback_info info){size_t n=1;napi_
static napi_value closeFn(napi_env env,napi_callback_info info){ size_t n=1; napi_value a[1]; napi_get_cb_info(env,info,&n,a,nullptr,nullptr); Handle*h=nullptr; if(n!=1) return fail(env,"close",EINVAL,"ERR_WORKSPACE_FS_AT_ARGUMENT"); auto hr=getHandle(env,a[0],&h); if(hr!=HANDLE_OK) return fail(env,"close",EINVAL,hr==HANDLE_CLOSED?"ERR_WORKSPACE_FS_AT_HANDLE_CLOSED":"ERR_WORKSPACE_FS_AT_ARGUMENT"); if(h->borrows) return fail(env,"close",EBUSY); auto *st=stateFor(env); { std::lock_guard<std::mutex> g(st->mutex); h->closed=true; st->handles.erase(h); } int rc=::close(h->fd); if(rc<0){ int e=errno; if(e==EINTR)return fail(env,"close",e,"ERR_WORKSPACE_FS_AT_CLOSE_UNCERTAIN"); return fail(env,"close",e); } return nullptr; }
static napi_value withFdFn(napi_env env,napi_callback_info info){size_t n=2;napi_value a[2];napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*h; napi_valuetype t;if(n!=2||!requireHandle(env,a[0],&h,"workspace-fs-at")||napi_typeof(env,a[1],&t)!=napi_ok||t!=napi_function)return fail(env,"borrow",EINVAL);h->borrows++;napi_value argv; napi_create_int32(env,h->fd,&argv); napi_value out; napi_value global; napi_get_global(env, &global); napi_status rc=napi_call_function(env, global,a[1],1,&argv,&out);h->borrows--;if(rc!=napi_ok)return nullptr;return out;}
static napi_value duplicateForChildStdioFn(napi_env env,napi_callback_info info){size_t n=4;napi_value a[4];napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*w,*r;int32_t wf,rf;if(n!=4||!requireHandle(env,a[0],&w,"dup2")||!requireHandle(env,a[1],&r,"dup2")||w->directory||!r->directory||napi_get_value_int32(env,a[2],&wf)!=napi_ok||napi_get_value_int32(env,a[3],&rf)!=napi_ok||wf<0||rf<0)return fail(env,"dup2",EINVAL);if(w->borrows||r->borrows)return fail(env,"dup2",EBUSY);int rc;do{rc=::dup2(w->fd,wf);}while(rc<0&&errno==EINTR);if(rc<0)return fail(env,"dup2",errno);do{rc=::dup2(r->fd,rf);}while(rc<0&&errno==EINTR);if(rc<0)return fail(env,"dup2",errno);return nullptr;}
static napi_value init(napi_env env,napi_value exports){napi_property_descriptor pub[]={{"openat",0,openatFn,0,0,0,napi_enumerable,0},{"mkdirat",0,mkdiratFn,0,0,0,napi_enumerable,0},{"fstatat",0,fstatatFn,0,0,0,napi_enumerable,0},{"fsyncDirectory",0,fsyncFn,0,0,0,napi_enumerable,0},{"close",0,closeFn,0,0,0,napi_enumerable,0}};napi_define_properties(env,exports,5,pub);napi_property_descriptor priv[]={{"withFd",0,withFdFn,0,0,0,napi_default,0},{"duplicateForChildStdio",0,duplicateForChildStdioFn,0,0,0,napi_default,0}};napi_define_properties(env,exports,2,priv);return exports;}
static napi_value init(napi_env env,napi_value exports){napi_property_descriptor pub[]={{"openat",0,openatFn,0,0,0,napi_enumerable,0},{"openFileAt",0,openFileAtFn,0,0,0,napi_default,0},{"readFile",0,readFileFn,0,0,0,napi_default,0},{"writeFile",0,writeFileFn,0,0,0,napi_default,0},{"fsyncFile",0,fsyncFileFn,0,0,0,napi_default,0},{"renameAt",0,renameAtFn,0,0,0,napi_default,0},{"unlinkAt",0,unlinkAtFn,0,0,0,napi_default,0},{"listDirectory",0,listDirectoryFn,0,0,0,napi_default,0},{"mkdirat",0,mkdiratFn,0,0,0,napi_enumerable,0},{"fstatat",0,fstatatFn,0,0,0,napi_enumerable,0},{"fsyncDirectory",0,fsyncFn,0,0,0,napi_enumerable,0},{"close",0,closeFn,0,0,0,napi_enumerable,0}};napi_define_properties(env,exports,12,pub);napi_property_descriptor priv[]={{"withFd",0,withFdFn,0,0,0,napi_default,0},{"duplicateForChildStdio",0,duplicateForChildStdioFn,0,0,0,napi_default,0}};napi_define_properties(env,exports,2,priv);return exports;}
}
NAPI_MODULE(NODE_GYP_MODULE_NAME,init)