/* file.c */

/* file permissions etc. are maintained in data structures & code here */

#include <sys/types.h>
#include <sys/stat.h>
#include "config.h"
#include "object.h"
#include "file.h"
#include "globals.h"
#include "interp.h"
#include "construct.h"

int SYSTEM_mkdir(char *filename) {
  if (mkdir(filename,493))
    return 1;
  else
    return 0;
}

int SYSTEM_rmdir(char *filename) {
  if (rmdir(filename))
    return 1;
  else
    return 0;
}

char *make_path(struct file_entry *entry) {
  char *buf,*buf2;
  struct file_entry *curr;
  unsigned int len;

  len=strlen(mudlib_path);
  curr=entry;
  while (curr!=root_dir) {
    len=len+strlen(curr->filename)+1;
    curr=curr->parent;
  }
  len++;
  buf=MALLOC(len);
  buf2=MALLOC(len);
  *buf='\0';
  *buf2='\0';
  curr=entry;
  while (curr!=root_dir) {
    strcpy(buf2,buf);
    strcpy(buf,"/");
    strcat(buf,curr->filename);
    strcat(buf,buf2);
    curr=curr->parent;
  }
  strcpy(buf2,buf);
  strcpy(buf,mudlib_path);
  strcat(buf,buf2);
  FREE(buf2);
  return buf;
}

struct file_entry *find_entry(char *filename) {
  struct file_entry *curr;
  char *currname,*currptr;

  if (*filename!='/')
    return NULL;
  if (filename[1]=='\0')
    return root_dir;
  curr=root_dir;
  currname=MALLOC(strlen(filename)+1);
  while (*filename=='/') {
    currptr=currname;
    filename++;
    while (*filename!='/' && *filename!='\0')
      *(currptr++)=*(filename++);
    *currptr='\0';
    curr=curr->contents;
    while (curr) {
      if (!strcmp(curr->filename,currname))
        break;
      curr=curr->next_file;
    }
    if (!curr) {
      FREE(currname);
      return NULL;
    }
  }
  FREE(currname);
  return curr;
}

int can_read(struct file_entry *fe, struct object *uid) {
  if (!uid) return 1;
  if (!fe) return 1;
  if (uid->flags & PRIV) return 1;
  if (fe==root_dir) return 1;
  while (fe!=root_dir) {
    if (uid->refno!=fe->owner && !(fe->flags & READ_OK))
      return 0;
    fe=fe->parent;
  }
  return 1;
}

struct file_entry *split_dir(char *filename, char **f) {
  char *buf1,*buf2;
  struct file_entry *fe;
  int count,maxcount,x;

  if (*filename!='/') return NULL;
  count=0;
  maxcount=0;
  while (filename[count]) {
    if (filename[count]=='/') maxcount=count;
    count++;
  }
  buf1=MALLOC(maxcount+2);
  buf2=MALLOC(count-maxcount);
  x=0;
  while (x<maxcount) {
    buf1[x]=filename[x];
    x++;
  }
  buf1[x]='\0';
  while (x<count) {
    x++;
    buf2[x-maxcount-1]=filename[x];
  }
  if (!maxcount) {
    buf1[0]='/';
    buf1[1]='\0';
  }
  fe=find_entry(buf1);
  FREE(buf1);
  if (!fe) {
    FREE(buf2);
    return NULL;
  }
  if (f)
    *f=buf2;
  else
    FREE(buf2);
  return fe;
}

struct file_entry *make_entry(struct file_entry *dir, char *name,
                              struct object *uid) {
  struct file_entry *fe,*curr,*prev;
  int x;

  if (!is_legal(name)) return NULL;
  curr=dir->contents;
  prev=NULL;
  while (curr) {
    x=strcmp(name,curr->filename);
    if (!x) return curr;
    if (x<0) {
      fe=MALLOC(sizeof(struct file_entry));
      fe->filename=copy_string(name);
      fe->flags=0;
      if (uid)
        fe->owner=uid->refno;
      else
        fe->owner=0;
      fe->contents=NULL;
      fe->parent=dir;
      fe->prev_file=prev;
      fe->next_file=curr;
      curr->prev_file=fe;
      if (prev)
        prev->next_file=fe;
      else
        dir->contents=fe;
      break;
    }
    prev=curr;
    curr=curr->next_file;
  }
  if (!curr) {
    fe=MALLOC(sizeof(struct file_entry));
    fe->filename=name;
    fe->flags=0;
    if (uid)
      fe->owner=uid->refno;
    else
      fe->owner=0;
    fe->contents=NULL;
    fe->parent=dir;
    fe->prev_file=prev;
    fe->next_file=NULL;
    if (prev)
      prev->next_file=fe;
    else
      dir->contents=fe;
  }
  return fe;
}

int remove_entry(struct file_entry *fe) {
  if (fe==root_dir) return 1;
  if (fe->contents) return 1;
  if (fe->parent->contents==fe)
    fe->parent->contents=fe->next_file;
  if (fe->next_file)
    fe->next_file->prev_file=fe->prev_file;
  if (fe->prev_file)
    fe->prev_file->next_file=fe->next_file;
  FREE(fe->filename);
  FREE(fe);
  return 0;
}

int ls_dir(char *filename, struct object *uid, struct object *player) {
  struct file_entry *fe;
  struct fns *listen_func;
  char *buf;
  struct var_stack *rts;
  struct var tmp;
  struct object *rcv;

  if (!uid) return 1;
  rcv=player;
  if (!player) rcv=uid;
  fe=find_entry(filename);
  if (!fe) return 1;
  if (!can_read(fe,uid)) return 1;
  if (!(fe->flags & DIRECTORY)) return 1;
  listen_func=find_fns("listen",rcv);
  if (!listen_func) return 0;
  fe=fe->contents;
  while (fe) {
    buf=MALLOC((2*ITOA_BUFSIZ)+4+strlen(fe->filename));
    sprintf(buf,"%ld %ld %s\n",(long) fe->owner,(long) fe->flags,fe->filename);
    rts=NULL;
    tmp.type=STRING;
    tmp.value.string=buf;
    push(&tmp,&rts);
    FREE(buf);
    tmp.type=NUM_ARGS;
    tmp.value.integer=1;
    push(&tmp,&rts);
    interp(uid,rcv,player,&rts,listen_func);
    free_stack(&rts);
    fe=fe->next_file;
  }
  return 0;
}

FILE *open_file(char *filename, char *mode, struct object *uid) {
  struct file_entry *fe,*homefe;
  char *buf,*newbuf;
  FILE *f;

  fe=find_entry(filename);
  if (!fe) {
    if (*mode=='r') return NULL;
    if (!uid) return NULL;
    homefe=split_dir(filename,&newbuf);
    if (!homefe) return NULL;
    if (!can_read(homefe,uid)) {
      FREE(newbuf);
      return NULL;
    }
    if (!(homefe->owner==uid->refno || (uid->flags & PRIV) ||
          (homefe->flags & WRITE_OK))) {
      FREE(newbuf);
      return NULL;
    }
    fe=make_entry(homefe,newbuf,uid);
    if (!fe) {
      FREE(newbuf);
      return NULL;
    }
  }
  if (fe->flags & DIRECTORY) return NULL;
  if (!can_read(fe->parent,uid)) return NULL;
  buf=make_path(fe);
  if (!uid && *mode=='r') {
    f=fopen(buf,mode);
    FREE(buf);
    return f;
  }
  if (!uid) {
    FREE(buf);
    return NULL;
  }
  if (uid->refno==fe->owner || (uid->flags & PRIV)) {
    f=fopen(buf,mode);
    FREE(buf);
    return f;
  }
  if (*mode=='r' && (fe->flags & READ_OK)) {
    f=fopen(buf,mode);
    FREE(buf);
    return f;
  }
  if ((*mode=='a' || *mode=='w') && (fe->flags & WRITE_OK)) {
    f=fopen(buf,mode);
    FREE(buf);
    return f;
  }
  FREE(buf);
  return NULL;
}

int remove_file(char *filename, struct object *uid) {
  struct file_entry *fe;
  char *buf;

  if (!uid) return 1;
  fe=find_entry(filename);
  if (!fe) return 1;
  if (fe->flags & DIRECTORY) return 1;
  if (!can_read(fe->parent,uid)) return 1;
  if (fe->parent->owner!=uid->refno && !(fe->parent->flags & WRITE_OK)
      && !(uid && (uid->flags & PRIV)))
    return 1;
  buf=make_path(fe);
  if (remove(buf)) {
    FREE(buf);
    return 1;
  }
  remove_entry(fe);
  FREE(buf);
  return 0;
}

int copy_file(char *src, char *dest, struct object *uid) {
  FILE *srcf,*destf;
  int c;

  if (!uid) return 1;
  srcf=open_file(src,"r",uid);
  if (!srcf) return 1;
  destf=open_file(dest,"w",uid);
  if (!destf) {
    close_file(srcf);
    return 1;
  }
  c=fgetc(srcf);
  while (c!=EOF) {
    fputc(c,destf);
    c=fgetc(srcf);
  }
  close_file(srcf);
  close_file(destf);
  return 0;
}

int move_file(char *src, char *dest, struct object *uid) {
  struct file_entry *homefe,*srcf,*destf;
  char *buf;
  FILE *f1,*f2;
  int c;

  if (!uid) return 1;
  if (!strcmp(src,dest)) return 1;
  srcf=find_entry(src);
  if (!srcf) return 1;
  if (srcf->flags & DIRECTORY) return 1;
  destf=find_entry(dest);
  if (!destf) {
    homefe=split_dir(dest,&buf);
    if (!homefe) return 1;
    if (!can_read(homefe,uid)) {
      FREE(buf);
      return 1;
    }
    if (!(homefe->owner==uid->refno || (uid->flags & PRIV) ||
          (homefe->flags & WRITE_OK))) {
      FREE(buf);
      return 1;
    }
    destf=make_entry(homefe,buf,uid);
    if (!destf) {
      FREE(buf);
      return 1;
    }
  }
  if (destf->flags & DIRECTORY) return 1;
  if (!can_read(srcf,uid)) return 1;
  if (!(srcf->parent->owner==uid->refno || (uid->flags & PRIV) ||
        (srcf->parent->flags & WRITE_OK)))
    return 1;
  if (!can_read(destf->parent,uid)) return 1;
  if (!(destf->owner==uid->refno || (uid->flags & PRIV) ||
        (destf->flags & WRITE_OK)))
    return 1;
  f1=open_file(src,"r",uid);
  if (!f1) return 1;
  f2=open_file(dest,"w",uid);
  if (!f2) {
    close_file(f1);
    return 1;
  }
  c=fgetc(f1);
  while (c!=EOF) {
    fputc(c,f2);
    c=fgetc(f1);
  }
  close_file(f1);
  close_file(f2);
  buf=make_path(srcf);
  if (remove(buf)) {
    FREE(buf);
    return 1;
  }
  FREE(buf);
  remove_entry(srcf);
  return 0;
}

int make_dir(char *filename, struct object *uid) {
  struct file_entry *fe,*homefe;
  char *buf;

  if (!uid) return 1;
  fe=find_entry(filename);
  if (fe) return 1;
  homefe=split_dir(filename,&buf);
  if (!homefe) return 1;
  if (!(homefe->owner==uid->refno || (uid->flags & PRIV) ||
        (homefe->flags & WRITE_OK))) {
    FREE(buf);
    return 1;
  }
  if (!(fe=make_entry(homefe,buf,uid))) {
    FREE(buf);
    return 1;
  }
  fe->flags=DIRECTORY;
  buf=make_path(fe);
  if (SYSTEM_mkdir(buf)) {
    FREE(buf);
    remove_entry(fe);
    return 1;
  }
  FREE(buf);
  return 0;
}

int remove_dir(char *filename, struct object *uid) {
  struct file_entry *fe;
  char *buf;

  if (!uid) return 1;
  fe=find_entry(filename);
  if (!(fe->flags & DIRECTORY)) return 1;
  if (fe->contents) return 1;
  if (fe==root_dir) return 1;
  if (!(fe->parent->owner==uid->refno || (uid->flags & PRIV) ||
        (fe->parent->flags & WRITE_OK)))
    return 1;
  buf=make_path(fe);
  if (SYSTEM_rmdir(buf)) {
    FREE(buf);
    return 1;
  }
  FREE(buf);
  remove_entry(fe);
  return 0;
}

int hide(char *filename) {
  struct file_entry *fe;

  fe=find_entry(filename);
  if (!fe) return 1;
  return remove_entry(fe);
}

int unhide(char *filename, struct object *uid, int flags) {
  struct file_entry *fe,*homefe;
  char *buf;

  fe=find_entry(filename);
  if (fe) return 1;
  homefe=split_dir(filename,&buf);
  if (!homefe) return 1;
  if (!(fe=make_entry(homefe,buf,uid))) {
    FREE(buf);
    return 1;
  }
  fe->flags=flags;
  return 0;
}

int db_add_entry(char *filename, signed long uid, int flags) {
  struct file_entry *fe,*homefe;
  char *buf;

  fe=find_entry(filename);
  if (fe) return 1;
  homefe=split_dir(filename,&buf);
  if (!homefe) return 1;
  if (!(fe=make_entry(homefe,buf,NULL))) {
    FREE(buf);
    return 1;
  }
  fe->flags=flags;
  fe->owner=uid;
  return 0;
}

int chown_file(char *filename, struct object *uid, struct object *new_owner) {
  struct file_entry *fe;

  fe=find_entry(filename);
  if (!uid) return 1;
  if (!new_owner) return 1;
  if (!fe) return 1;
  if (fe!=root_dir)
    if (!can_read(fe->parent,uid))
      return 1;
  if (!(fe->owner==uid->refno || (uid->flags & PRIV))) return 1;
  fe->owner=new_owner->refno;
  return 0;
}

int chmod_file(char *filename, struct object *uid, int flags) {
  struct file_entry *fe;

  fe=find_entry(filename);
  if (!fe) return 1;
  if (!uid) return 1;
  if (fe!=root_dir)
    if (!can_read(fe->parent,uid))
      return 1;
  if (!(fe->owner==uid->refno || (uid->flags & PRIV))) return 1;
  if (fe==root_dir && (!(flags & READ_OK))) return 1;
  flags&=(WRITE_OK | READ_OK);
  fe->flags&=DIRECTORY;
  fe->flags|=flags;
  return 0;
}

void log_sysmsg(char *msg) {
  char timebuf[20];
  time_t now_time;
  FILE *logfile;
  struct tm *time_s;

  now_time=time(NULL);
  time_s=localtime(&now_time);
  sprintf(timebuf,"%2d/%02d/%2d %2d:%02d %s",(int) (time_s->tm_mon+1),
          (int) time_s->tm_mday,
          (int) time_s->tm_year,
          (int) (((time_s->tm_hour)%12) ? ((time_s->tm_hour)%12):12),
          (int) time_s->tm_min,
          ((time_s->tm_hour<12) ? "AM":"PM"));
  if (noisy)
    fprintf(stderr,"%s %s\n",timebuf,msg);
  logfile=fopen(syslog_path,"a");
  if (!logfile) {
    fprintf(stderr,"%s  system: couldn't open system log %s\n",timebuf,
            syslog_path);
    return;
  }
  fprintf(logfile,"%s %s\n",timebuf,msg);
  fclose(logfile);
}