/* vgo — 静态文件服务器：data:9000, web:9001 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <fcntl.h>
#include <libgen.h>
#include <dirent.h>
#include <pthread.h>
#include <strings.h>

static char ROOT[4096] = "/data";
static int P_WEB = 9001, P_DATA = 9000;

/* ---- utils ---- */
static int esc_html(const char *s, char *o, size_t n){
    size_t j=0;
    for(size_t i=0; s[i] && j<n-1; i++){
        char c=s[i];
        if(c=='&'){if(j+5>=n)break;memcpy(o+j,"&amp;",5);j+=5;}
        else if(c=='<'){if(j+4>=n)break;memcpy(o+j,"&lt;",4);j+=4;}
        else if(c=='>'){if(j+4>=n)break;memcpy(o+j,"&gt;",4);j+=4;}
        else if(c=='"'){if(j+6>=n)break;memcpy(o+j,"&quot;",6);j+=6;}
        else{o[j++]=c;}
    }
    o[j]=0; return j;
}
static int esc_json(const char *s, char *o, size_t n){
    size_t j=0;
    for(size_t i=0; s[i] && j<n-1; i++){
        unsigned char c=(unsigned char)s[i];
        if(c=='"'||c=='\\'){if(j+2>=n)break;o[j++]='\\';o[j++]=c;}
        else if(c=='\n'){if(j+2>=n)break;o[j++]='\\';o[j++]='n';}
        else if(c=='\r'){if(j+2>=n)break;o[j++]='\\';o[j++]='r';}
        else if(c=='\t'){if(j+2>=n)break;o[j++]='\\';o[j++]='t';}
        else if(c<0x20){int k=snprintf(o+j,n-j,"\\u%04x",c);if(k<0||k>=n-j)break;j+=k;}
        else{o[j++]=c;}
    }
    o[j]=0; return j;
}
static const char *mime(const char *p){
    const char *d=strrchr(p,'.');
    if(!d)return "application/octet-stream";
    #define M(x,t) if(strcmp(d,#x)==0)return t
    M(.html,"text/html; charset=utf-8"); M(.htm,"text/html; charset=utf-8");
    M(.css,"text/css; charset=utf-8"); M(.js,"application/javascript; charset=utf-8");
    M(.json,"application/json"); M(.txt,"text/plain; charset=utf-8");
    M(.md,"text/plain; charset=utf-8");
    M(.png,"image/png"); M(.jpg,"image/jpeg"); M(.jpeg,"image/jpeg");
    M(.gif,"image/gif"); M(.svg,"image/svg+xml"); M(.webp,"image/webp");
    M(.mp4,"video/mp4"); M(.mp3,"audio/mpeg"); M(.zip,"application/zip");
    M(.pdf,"application/pdf"); M(.c,"text/x-c; charset=utf-8");
    M(.h,"text/x-c; charset=utf-8"); M(.py,"text/x-python; charset=utf-8");
    #undef M
    return "application/octet-stream";
}
static int resolve_rel(const char *rel, char *out, size_t n){
    const char *r = rel && rel[0] ? rel : ".";
    char t[8192]; snprintf(t,sizeof(t),"%s%s%s",ROOT,ROOT[strlen(ROOT)-1]=='/'?"":"/",r);
    char *d=dirname(t);
    if(snprintf(out,n,"%s",d)>=n)return -1;
    char rc[8192],oc[8192];
    if(!realpath(ROOT,rc)||!realpath(out,oc))return -1;
    if(strncmp(oc,rc,strlen(rc))!=0)return -1;
    if(strlen(oc)==strlen(rc)){strcpy(out,".");return 0;}
    char *r2=oc+strlen(rc);
    if(r2[0]!='/')return -1;
    snprintf(out,n,"%s",r2+1); return 0;
}
static int qget(const char *q, const char *k, char *o, size_t n){
    size_t kl=strlen(k);
    for(const char *p=q;*p;p++){
        char *eq=strchr(p,'=');
        if(eq && (eq-p)==kl && strncmp(p,k,kl)==0){
            char *v=eq+1,*e=strchr(v,'&');
            size_t vl=e?(size_t)(e-v):strlen(v);
            if(vl>=n)vl=n-1;
            memcpy(o,v,vl); o[vl]=0;
            for(size_t i=0;i<vl;i++){
                if(o[i]=='+') { o[i]=' '; }
                else if(o[i]=='%'&&i+2<vl){
                    char h[3]={o[i+1],o[i+2],0};
                    long v=strtol(h,NULL,16); o[i]=(char)v;
                    memmove(o+i+1,o+i+3,vl-(i+3)+1); vl-=2;
                }
            }
            return 1;
        }
        if(strchr(p,'&'))p+=strcspn(p,"&");
    }
    o[0]=0; return 0;
}
static void send_all(int fd, const char *s){
    size_t n=strlen(s), s0=0;
    while(s0<n){ssize_t r=send(fd,s+s0,n-s0,MSG_NOSIGNAL);if(r<=0)return;s0+=r;}
}
static void send_raw(int fd, const char *s, size_t n){
    size_t s0=0; while(s0<n){ssize_t r=send(fd,s+s0,n-s0,MSG_NOSIGNAL);if(r<=0)return;s0+=r;}
}
static void read_req(int fd, char *m, size_t mn, char *u, size_t un){
    // read whole request (line + headers) into one buffer, parse from memory
    size_t cap=16384, len=0; char *buf=malloc(cap);
    while(len<cap){
        ssize_t r=recv(fd,buf+len,cap-len,MSG_DONTWAIT);
        if(r>0){len+=r;
          // check if we've seen \r\n\r\n
          for(size_t i=0;i+3<len;i++){
            if(buf[i]=='\r'&&buf[i+1]=='\n'&&buf[i+2]=='\r'&&buf[i+3]=='\n'){
              // zero-terminate at blank line
              buf[i]=0;
              // parse first line
              char line[4096];
              size_t ln=0;
              while(ln<i && buf[ln]!='\n')ln++;
              if(ln>=sizeof(line))ln=sizeof(line)-1;
              memcpy(line,buf,ln); line[ln]=0;
              sscanf(line,"%s %s",m,(char*)u);
              // limit u length
              if(strlen(u)>=un)u[un-1]=0;
              free(buf);
              return;
            }
          }
        } else if(r==0) break;
        else if(errno!=EAGAIN&&errno!=EWOULDBLOCK) break;
        else {usleep(1000); continue;}
    }
    buf[len]=(len<cap)?0:buf[cap-1];
    // fallback parse
    char line[4096]; ssize_t ln=0;
    while(ln<(ssize_t)len && buf[ln]!='\n')ln++;
    if(ln>=sizeof(line))ln=sizeof(line)-1;
    memcpy(line,buf,ln); line[ln]=0;
    sscanf(line,"%s %s",m,(char*)u);
    if(strlen(u)>=un)u[un-1]=0;
    free(buf);
    // drain any remaining
    while(recv(fd,buf,1,MSG_DONTWAIT)>0);
}
static int read_body(int fd, char **out){
    size_t cap=4096,len=0; char *buf=malloc(cap);
    char tmp[8192]; ssize_t r;
    while((r=recv(fd,tmp,sizeof(tmp),MSG_DONTWAIT))>0){
        if(len+r>cap){while(len+r>=cap)cap*=2;if(cap>100*1024*1024){free(buf);return -1;}buf=realloc(buf,cap);}
        memcpy(buf+len,tmp,r); len+=r;
    }
    if(r<0&&errno!=EAGAIN&&errno!=EWOULDBLOCK){free(buf);return -1;}
    *out=buf; return len;
}

/* ---- JSON helpers ---- */
#define JSTART(o,off,m) if(off<m)o[off++]='{'
#define JSTR(o,off,m,k,v) do{char _e[8192];esc_json(v,_e,sizeof(_e));size_t _n=2+strlen(k)+2+strlen(_e)+2;if(off+_n<m){o[off++]='"';memcpy(o+off,k,strlen(k));off+=strlen(k);o[off++]='"';o[off++]=':';o[off++]='"';memcpy(o+off,_e,strlen(_e));off+=strlen(_e);o[off++]='"';o[off++]=',';}}while(0)
#define JINT(o,off,m,k,v) do{char _s[32];int _n=snprintf(_s,sizeof(_s),"%lld",(long long)v);size_t _nn=2+strlen(k)+2+_n+2;if(off+_nn<m){o[off++]='"';memcpy(o+off,k,strlen(k));off+=strlen(k);o[off++]='"';o[off++]=':';memcpy(o+off,_s,_n);off+=_n;o[off++]=',';}}while(0)
#define JBOOL(o,off,m,k,v) do{const char *_s=(v)?"true":"false";size_t _nn=2+strlen(k)+2+6+2;if(off+_nn<m){o[off++]='"';memcpy(o+off,k,strlen(k));off+=strlen(k);o[off++]='"';o[off++]=':';memcpy(o+off,_s,6);off+=6;o[off++]=',';}}while(0)
#define JEND(o,off,m,c) if(off<m){if(off>0&&o[off-1]==',')o[--off]=0;o[off++]=c;}
#define JARR(o,off,m) if(off<m)o[off++]='['
#define JITEM(o,off,m,s) do{size_t _n=strlen(s);if(off+_n<m){memcpy(o+off,s,_n);off+=_n;if(o[off-1]!=']')o[off++]=',';}}while(0)
#define JARR_END(o,off,m) if(off<m){if(off>0&&o[off-1]==',')o[--off]=0;o[off++]=']';}

static void respond_json(int fd, int ok, const char *err, char *body){
    char o[65536]; size_t off=0;
    JSTART(o,off,sizeof(o));
    JBOOL(o,off,sizeof(o),"ok",ok);
    if(err&&err[0])JSTR(o,off,sizeof(o),"error",err);
    if(body&&body[0])JSTR(o,off,sizeof(o),"body",body);
    JEND(o,off,sizeof(o),'}');
    char hd[512];
    int n=snprintf(hd,sizeof(hd),
        "HTTP/1.1 200 OK\r\nContent-Type: application/json; charset=utf-8\r\n"
        "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS\r\n"
        "Access-Control-Allow-Headers: Content-Type,Authorization\r\n"
        "Content-Length: %zu\r\nConnection: close\r\n\r\n", off);
    send_all(fd,hd); send_raw(fd,o,off);
}

/* ---- file list as JSON array string ---- */
static char *ls_json(const char *ab){
    DIR *d=opendir(ab); if(!d)return NULL;
    static char buf[65536]; size_t off=0;
    JARR(buf,off,sizeof(buf));
    int first=1;
    struct dirent *e;
    while((e=readdir(d))){
        if(strcmp(e->d_name,".")==0||strcmp(e->d_name,"..")==0)continue;
        char full[8192]; snprintf(full,sizeof(full),"%s/%s",ab,e->d_name);
        struct stat st; if(lstat(full,&st)<0)continue;
        char item[8192]; size_t io=0;
        char namebuf[1024]; snprintf(namebuf,sizeof(namebuf),"%s",e->d_name);
        JSTART(item,io,sizeof(item));
        JSTR(item,io,sizeof(item),"name",namebuf);
        JBOOL(item,io,sizeof(item),"isDir",S_ISDIR(st.st_mode));
        JINT(item,io,sizeof(item),"size",(long long)st.st_size);
        JINT(item,io,sizeof(item),"mtime",(long long)st.st_mtime*1000);
        JEND(item,io,sizeof(item),'}');
        if(!first)buf[off++]=','; first=0;
        size_t n=strlen(item);
        if(off+n>=sizeof(buf))break;
        memcpy(buf+off,item,n); off+=n;
    }
    closedir(d);
    JARR_END(buf,off,sizeof(buf));
    return buf;
}

/* ---- API ops ---- */
static void op_stat(int fd, const char *rel){
    char ab[4096];
    if(resolve_rel(rel,ab,sizeof(ab))<0){respond_json(fd,0,"bad path",NULL);return;}
    struct stat st;
    if(stat(ab,&st)<0){respond_json(fd,0,strerror(errno),NULL);return;}
    char b[4096]; size_t off=0; char namebuf[1024];
    char *bn=strrchr(ab,'/'); snprintf(namebuf,sizeof(namebuf),"%s",bn?bn+1:ab);
    JSTART(b,off,sizeof(b));
    JSTR(b,off,sizeof(b),"name",namebuf);
    JBOOL(b,off,sizeof(b),"isDir",S_ISDIR(st.st_mode));
    JINT(b,off,sizeof(b),"size",(long long)st.st_size);
    JINT(b,off,sizeof(b),"mtime",(long long)st.st_mtime*1000);
    JEND(b,off,sizeof(b),'}');
    respond_json(fd,1,NULL,b);
}
static void op_ls(int fd, const char *rel){
    char ab[4096];
    if(resolve_rel(rel,ab,sizeof(ab))<0){respond_json(fd,0,"bad path",NULL);return;}
    struct stat st;
    if(stat(ab,&st)<0){respond_json(fd,0,strerror(errno),NULL);return;}
    if(!S_ISDIR(st.st_mode)){respond_json(fd,0,"not a directory",NULL);return;}
    char *items=ls_json(ab);
    char b[4096]; size_t off=0;
    JSTART(b,off,sizeof(b));
    JSTR(b,off,sizeof(b),"path",rel&&rel[0]?rel:"");
    // manually append items array
    size_t ilen = items ? strlen(items) : 0;
    size_t need = 2+5+2+ilen+2;
    if(off+need < sizeof(b)){
        b[off++]='"';memcpy(b+off,"items",5);off+=5;b[off++]='"';b[off++]=':';
        memcpy(b+off,items?items:"[]",ilen?ilen:2);off+=ilen?ilen:2;
        b[off++]=',';
    }
    JEND(b,off,sizeof(b),'}');
    respond_json(fd,1,NULL,b);
}
static void op_download(int fd, const char *rel){
    char ab[4096];
    if(resolve_rel(rel,ab,sizeof(ab))<0){respond_json(fd,0,"bad path",NULL);return;}
    struct stat st;
    if(stat(ab,&st)<0){respond_json(fd,0,strerror(errno),NULL);return;}
    if(S_ISDIR(st.st_mode)){respond_json(fd,0,"is directory",NULL);return;}
    int h=open(ab,O_RDONLY); if(h<0){respond_json(fd,0,strerror(errno),NULL);return;}
    char *bn=strrchr(ab,'/'); char fn[2048]; esc_json(bn?bn+1:ab,fn,sizeof(fn));
    char hd[1024];
    int n=snprintf(hd,sizeof(hd),
        "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n"
        "Content-Disposition: attachment; filename=\"%s\"\r\n"
        "Content-Length: %lld\r\nConnection: close\r\n\r\n",
        mime(ab),fn,(long long)st.st_size);
    send_all(fd,hd);
    char buf[65536]; ssize_t r;
    while((r=read(h,buf,sizeof(buf)))>0)send_raw(fd,buf,r);
    close(h);
}
static void op_mkdir(int fd, const char *rel){
    char ab[4096];
    if(resolve_rel(rel,ab,sizeof(ab))<0){respond_json(fd,0,"bad path",NULL);return;}
    if(mkdir(ab,0755)<0&&errno!=EEXIST){respond_json(fd,0,strerror(errno),NULL);return;}
    respond_json(fd,1,NULL,NULL);
}
static void op_newfile(int fd, const char *rel, const char *content){
    char ab[4096];
    if(resolve_rel(rel,ab,sizeof(ab))<0){respond_json(fd,0,"bad path",NULL);return;}
    FILE *f=fopen(ab,"wb"); if(!f){respond_json(fd,0,strerror(errno),NULL);return;}
    if(content)fputs(content,f);
    fclose(f); respond_json(fd,1,NULL,NULL);
}
static void op_save(int fd, const char *rel, char *body, size_t blen){
    char ab[4096];
    if(resolve_rel(rel,ab,sizeof(ab))<0){respond_json(fd,0,"bad path",NULL);return;}
    FILE *f=fopen(ab,"wb"); if(!f){respond_json(fd,0,strerror(errno),NULL);return;}
    if(blen>0)fwrite(body,1,blen,f);
    fclose(f); respond_json(fd,1,NULL,NULL);
}
static void op_delete(int fd, const char *rel){
    char ab[4096];
    if(resolve_rel(rel,ab,sizeof(ab))<0){respond_json(fd,0,"bad path",NULL);return;}
    char rc[4096],ac[4096];
    if(!realpath(ROOT,rc)||!realpath(ab,ac)){respond_json(fd,0,strerror(errno),NULL);return;}
    if(strcmp(ac,rc)==0){respond_json(fd,0,"cannot delete root",NULL);return;}
    struct stat st;
    if(stat(ab,&st)<0){respond_json(fd,0,strerror(errno),NULL);return;}
    char cmd[8192];
    if(S_ISDIR(st.st_mode))snprintf(cmd,sizeof(cmd),"rm -rf \"%s\"",ab);
    else snprintf(cmd,sizeof(cmd),"rm -f \"%s\"",ab);
    if(system(cmd)!=0){respond_json(fd,0,"rm failed",NULL);return;}
    respond_json(fd,1,NULL,NULL);
}
static void op_rename(int fd, const char *old_rel, const char *new_rel){
    char ab1[4096],ab2[4096];
    if(resolve_rel(old_rel,ab1,sizeof(ab1))<0){respond_json(fd,0,"bad old",NULL);return;}
    if(resolve_rel(new_rel,ab2,sizeof(ab2))<0){respond_json(fd,0,"bad new",NULL);return;}
    if(rename(ab1,ab2)<0){respond_json(fd,0,strerror(errno),NULL);return;}
    respond_json(fd,1,NULL,NULL);
}
static void op_upload(int fd, const char *rel, const char *name, char *body, size_t blen){
    char ab[4096];
    if(resolve_rel(rel,ab,sizeof(ab))<0){respond_json(fd,0,"bad path",NULL);return;}
    if(!name||!name[0]){respond_json(fd,0,"missing name",NULL);return;}
    const char *bn=strrchr(name,'/'); const char *n=bn?bn+1:name;
    char tgt[8192]; snprintf(tgt,sizeof(tgt),"%s/%s",ab,n);
    if(mkdir(ab,0755)<0&&errno!=EEXIST){respond_json(fd,0,strerror(errno),NULL);return;}
    // skip multipart preamble: boundary + header block ending in \r\n\r\n
    size_t s=0, e=blen;
    { size_t dots=0;
      while(s<blen){
        if(body[s]=='\r')dots=1;
        else if(body[s]=='\n'&&dots==1)dots=2;
        else if(body[s]=='\r'&&dots==2)dots=3;
        else if(body[s]=='\n'&&dots==3){s++;break;} // past the blank line
        else dots=0;
        s++;
      }
    }
    // strip trailing boundary
    while(e>s&&body[e-1]=='\n')e--;
    while(e>s&&body[e-1]=='\r')e--;
    while(e>s+4&&body[e-1]=='-'&&body[e-2]=='-'&&body[e-3]=='-'&&body[e-4]=='-')e-=4;
    while(e>s+2&&body[e-1]=='-'&&body[e-2]=='-')e-=2;
    size_t dlen=(e>s)?e-s:0;
    FILE *f=fopen(tgt,"wb"); if(!f){respond_json(fd,0,strerror(errno),NULL);return;}
    if(dlen>0)fwrite(body+s,1,dlen,f);
    fclose(f);
    struct stat st; stat(tgt,&st);
    char b[512],ne[1024]; esc_json(n,ne,sizeof(ne));
    int n2=snprintf(b,sizeof(b),"{\"ok\":true,\"size\":%lld,\"name\":\"%s\"}",(long long)st.st_size,ne);
    char hd[256];
    int h=snprintf(hd,sizeof(hd),
        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n"
        "Access-Control-Allow-Origin: *\r\nContent-Length: %d\r\nConnection: close\r\n\r\n",n2);
    send_all(fd,hd); send_raw(fd,b,n2);
}
static void op_info(int fd){
    char b[512];
    int n=snprintf(b,sizeof(b),
        "{\"ok\":true,\"root\":\"%s\",\"platform\":\"linux\",\"arch\":\"%s\",\"web\":%d,\"data\":%d}",
        ROOT,sizeof(long)==8?"x86_64":"i386",P_WEB,P_DATA);
    char hd[256];
    int h=snprintf(hd,sizeof(hd),
        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n"
        "Access-Control-Allow-Origin: *\r\nContent-Length: %d\r\nConnection: close\r\n\r\n",n);
    send_all(fd,hd); send_raw(fd,b,n);
}

/* ---- data handler ---- */
static void handle_data(int fd){
    char method[16], target[8192];
    read_req(fd, method, sizeof(method), target, sizeof(target));
    if(target[0]==0)return;
    if(strcmp(method,"OPTIONS")==0){
        send_all(fd,"HTTP/1.1 204 No Content\r\nAccess-Control-Allow-Origin: *\r\n"
                    "Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS\r\n"
                    "Access-Control-Allow-Headers: Content-Type,Authorization\r\n\r\n");
        return;
    }
    char *qs=strchr(target,'?');
    char query[4096]="";
    if(qs){*qs=0;snprintf(query,sizeof(query),"%s",qs+1);}
    char *slash=strchr(target,'/');
    char path[4096]; snprintf(path,sizeof(path),"%s",slash?slash+1:"");
    char *op=strtok(path,"/");
    char *body=NULL; int blen=0;
    if(strcmp(method,"POST")==0||strcmp(method,"PUT")==0){
        blen=read_body(fd,&body); if(blen<0)blen=0;
    }
    char r1[4096],r2[4096];
    qget(query,"path",r1,sizeof(r1));
    if(strcmp(op,"stat")==0)op_stat(fd,r1);
    else if(strcmp(op,"ls")==0)op_ls(fd,r1);
    else if(strcmp(op,"download")==0)op_download(fd,r1);
    else if(strcmp(op,"mkdir")==0)op_mkdir(fd,r1);
    else if(strcmp(op,"newfile")==0){char c[4096];qget(query,"content",c,sizeof(c));op_newfile(fd,r1,c);}
    else if(strcmp(op,"save")==0)op_save(fd,r1,body,blen);
    else if(strcmp(op,"delete")==0)op_delete(fd,r1);
    else if(strcmp(op,"rename")==0){qget(query,"old",r1,sizeof(r1));qget(query,"new",r2,sizeof(r2));op_rename(fd,r1,r2);}
    else if(strcmp(op,"upload")==0){char nm[1024];qget(query,"name",nm,sizeof(nm));op_upload(fd,r1,nm,body,blen);}
    else if(strcmp(op,"info")==0)op_info(fd);
    else respond_json(fd,0,"unknown op",NULL);
    free(body);
}

/* ---- web handler ---- */
static void handle_web(int fd){
    char method[16], target[8192];
    read_req(fd, method, sizeof(method), target, sizeof(target));
    if(target[0]==0)return;
    char *qs=strchr(target,'?'); if(qs)*qs=0;
    char rel[4096];
    if(strcmp(target,"/")==0||target[1]==0)snprintf(rel,sizeof(rel),"/index.html");
    else snprintf(rel,sizeof(rel),"%s",target);
    // read from <vgo dir>/public/<rel>
    char exe[4096]; ssize_t nl=readlink("/proc/self/exe",exe,sizeof(exe)-1);
    if(nl<0||nl>=sizeof(exe)){send_all(fd,"HTTP/1.1 500\r\n\r\n");return;}
    exe[nl]=0;
    char *slash=strrchr(exe,'/');
    if(!slash){send_all(fd,"HTTP/1.1 500\r\n\r\n");return;}
    char d4096[4096];
    size_t dlen=(size_t)(slash-exe);
    if(dlen>=sizeof(d4096))dlen=sizeof(d4096)-1;
    memcpy(d4096,exe,dlen); d4096[dlen]=0;
    char pub[8192]; snprintf(pub,sizeof(pub),"%s/public%s",d4096,rel);
    int hfd=open(pub,O_RDONLY);
    if(hfd<0){send_raw(fd,"HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\nConnection: close\r\n\r\nnot found",51);return;}
    struct stat st; fstat(hfd,&st);
    const char *m = (strcmp(rel,"/index.html")==0||strcmp(rel,"/")==0) ? "text/html; charset=utf-8" : mime(pub);
    char hd[256];
    int h=snprintf(hd,sizeof(hd),
        "HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lld\r\nConnection: close\r\n\r\n",
        m,(long long)st.st_size);
    send_all(fd,hd);
    char buf[65536]; ssize_t r;
    while((r=read(hfd,buf,sizeof(buf)))>0)send_raw(fd,buf,r);
    close(hfd);
}

/* ---- server setup ---- */
static int make_server(int port){
    int fd=socket(AF_INET,SOCK_STREAM,0); if(fd<0)return -1;
    int one=1; setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&one,sizeof(one));
    struct sockaddr_in a; memset(&a,0,sizeof(a));
    a.sin_family=AF_INET; a.sin_addr.s_addr=INADDR_ANY; a.sin_port=htons(port);
    if(bind(fd,(struct sockaddr*)&a,sizeof(a))<0){close(fd);return -1;}
    if(listen(fd,32)<0){close(fd);return -1;}
    fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK);
    return fd;
}
typedef struct{int fd;int is_data;}CA;
static void *client_thread(void *p){
    CA *a=(CA*)p;
    if(a->is_data)handle_data(a->fd); else handle_web(a->fd);
    close(a->fd); free(a); return NULL;
}

int main(int argc,char**argv){
    for(int i=1;i<argc;i++){
        if(strcmp(argv[i],"--root")==0&&i+1<argc)strncpy(ROOT,argv[++i],sizeof(ROOT)-1);
        else if(strcmp(argv[i],"--web")==0&&i+1<argc)P_WEB=atoi(argv[++i]);
        else if(strcmp(argv[i],"--data")==0&&i+1<argc)P_DATA=atoi(argv[++i]);
        else if(strcmp(argv[i],"-h")==0||strcmp(argv[i],"--help")==0){fprintf(stderr,"Usage: vgo [--root /data] [--web 9001] [--data 9000]\n");return 0;}
        else{fprintf(stderr,"unknown arg: %s\n",argv[i]);return 1;}
    }
    signal(SIGPIPE,SIG_IGN);
    mkdir(ROOT,0755);
    int dfd=make_server(P_DATA); if(dfd<0){fprintf(stderr,"bind data %d: %s\n",P_DATA,strerror(errno));return 1;}
    int wfd=make_server(P_WEB);  if(wfd<0){fprintf(stderr,"bind web %d: %s\n",P_WEB,strerror(errno));return 1;}
    printf("[vgo] data port : %d\n[vgo] web port  : %d\n[vgo] root      : %s\n",P_DATA,P_WEB,ROOT);
    fflush(stdout);
    while(1){
        struct sockaddr_in cli; socklen_t clen=sizeof(cli);
        int fd=accept(dfd,(struct sockaddr*)&cli,&clen);
        if(fd>=0){CA*a=malloc(sizeof(CA));a->fd=fd;a->is_data=1;
          pthread_t t; pthread_attr_t at; pthread_attr_init(&at); pthread_attr_setdetachstate(&at,PTHREAD_CREATE_DETACHED);
          pthread_create(&t,&at,client_thread,a); pthread_attr_destroy(&at);}
        fd=accept(wfd,(struct sockaddr*)&cli,&clen);
        if(fd>=0){CA*a=malloc(sizeof(CA));a->fd=fd;a->is_data=0;
          pthread_t t; pthread_attr_t at; pthread_attr_init(&at); pthread_attr_setdetachstate(&at,PTHREAD_CREATE_DETACHED);
          pthread_create(&t,&at,client_thread,a); pthread_attr_destroy(&at);}
        usleep(200);
    }
    return 0;
}


