/*
  file utilites
*/

#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <utime.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>

#include "config.h"

int access_via_stat(char *name,int mode)
/* check F_OK (exist) ok
 * check R_OK (readable) same as F_OK (buggy but ...)
 * assert violation if mode == W_OK (writeable)
 * */
{
	struct stat stat_buf;
	ass(mode==F_OK || mode==R_OK);

	return(stat(name,&stat_buf));
}

/* ln, cp, mv, find_file_by_size_and_crc */
/* all functions returns 0 when success */

void rm_by_mask(char *dirname,char *mask)
/* delete files by mask (dirname/mask) */
{
  char *p; struct stat stat_buf;
  DIR *dir; struct dirent *dirp;

  dir=opendir(dirname);
  if(dir==NULL) return;
  while(1)
  {
    dirp=readdir(dir);
    if(!dirp) break;
    if(wildmat_icase(dirp->d_name,mask))
    {
      p=xstrcpy(dirname);
      p=xstrcat(p,"/");
      p=xstrcat(p,dirp->d_name);
      if(stat(p,&stat_buf)) continue;
      if(!(stat_buf.st_mode & S_IFREG)) continue;
      l_printf("%s deleted by %s",p,mask);
      remove(p);
      xfree(p);
    }
  }
  closedir(dir);
}

static int cp_(char *oldname,char *newname,int add);

int cp(char *oldname,char *newname)
{
  return cp_(oldname,newname,FALSE);
}

int cp_add(char *oldname,char *newname)
{
  return cp_(oldname,newname,TRUE);
}

#define CP_BUFSIZE (1024*1024) /* 1mb blocks */
static int cp_(char *oldname,char *newname,int add)
/*
   copy oldname to newname
   use mmap with maximal buffer size = CP_BUFSIZE
   mmap fails on big size files
*/
{
  struct stat stbuf; int r,fd_in,fd_out; ssize_t write_return;
  char *data; struct utimbuf utbuf; unsigned long a,b,i;
  if((r=stat(oldname,&stbuf)))
    return r;
  fd_in=open(oldname,O_RDONLY|O_BINARY);
  if(fd_in==-1) return -1;
  if(add==FALSE)
    fd_out=open(newname,O_BINARY|O_WRONLY|O_TRUNC|O_CREAT,stbuf.st_mode);
  else
    fd_out=open(newname,O_BINARY|O_WRONLY|O_CREAT|O_APPEND,stbuf.st_mode);
  if(fd_out==-1) { close(fd_in); return -1; }

  a=stbuf.st_size / CP_BUFSIZE; /* how many blocks */
  b=stbuf.st_size % CP_BUFSIZE; /* last block size */

  for(i=0;i<a;i++)
  {
    data=mmap(NULL,CP_BUFSIZE,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd_in,CP_BUFSIZE*i);
    if(data==(char *)-1 || data==NULL)
    {
      close(fd_in);
      close(fd_out);
      remove(newname);
      return -1;
    }
    write_return=write(fd_out,data,CP_BUFSIZE);
    munmap(data,CP_BUFSIZE);
    if(write_return!=CP_BUFSIZE)
    {
      close(fd_in);
      close(fd_out);
      remove(newname);
      return -1;
    }
  }
  if(b)
  {
    data=mmap(NULL,b,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd_in,CP_BUFSIZE*a);
    if(data==(char *)-1 || data==NULL)
    {
      close(fd_in);
      close(fd_out);
      remove(newname);
      return -1;
    }
    write_return=write(fd_out,data,b);
    munmap(data,stbuf.st_size);
  }
  close(fd_in);
  close(fd_out);
  if(b)
    if(write_return!=b)
    {
      remove(newname);
      return -1;
    }
  utbuf.actime=stbuf.st_atime;
  utbuf.modtime=stbuf.st_mtime;
  utime(newname,&utbuf);
  chmod(newname,stbuf.st_mode);
  chown(newname,stbuf.st_uid,stbuf.st_gid);
  return 0;
}

int ln(char *oldname,char *newname)
/* try to make hardlink, and call cp() if link() failed */
{
  int r;
  if(link(oldname,newname))
  {
    if((r=cp(oldname,newname)))
      return r;
    return 0;
  }
  return 0;
}

int mv(char *oldname,char *newname)
/* try to move, and call cp()+unlink() if link() failed */
{
  int r;
  if(rename(oldname,newname))
  {
    if((r=cp(oldname,newname)))
      return r;
    if((r=remove(oldname)))
    {
      remove(newname);
      return r;
    }
    return 0;
  }
  return 0;
}

int find_file(char *dirname,char *outbuff,char *origname,
unsigned long crc32,unsigned long size)
{
  char buff[PATH_MAX],pattern[PATH_MAX*5];
  int i,j; struct stat stat_buf;
  DIR *dir; struct dirent *dirp;

  strcpy(outbuff,origname);
  
  /* simple check by access() */
  snprintf(buff,sizeof(buff)-1,"%s/%s",dirname,origname);
  if(!access_via_stat(buff,F_OK))
  {
    strcpy(outbuff,origname);
    return 0;
  }

  /* opendir and try to find filename ignoring case */
  dir=opendir(dirname);
  if(dir==NULL) return -1;
  while(1)
  {
    dirp=readdir(dir);
    if(!dirp) break;
    if(strcasecmp(dirp->d_name,origname)) continue;
    snprintf(buff,sizeof(buff)-1,"%s/%s",dirname,dirp->d_name);
    if(stat(buff,&stat_buf)) continue;
    if(!(stat_buf.st_mode & S_IFREG)) continue;
    strcpy(outbuff,dirp->d_name);
    closedir(dir);
    return 0;
  }
  closedir(dir);

  /* opendir and try to find filename by size and crc32 */
  j=strlen(origname); pattern[0]=0;
  for(i=0;i<j;i++)
  {
    if(origname[i]=='.') break;
    if(isalpha(origname[i]))
      snprintf(buff,sizeof(buff)-1,"[%c%c]",toupper(origname[i]),tolower(origname[i]));
    else
      snprintf(buff,sizeof(buff)-1,"%c",origname[i]);
    strcat(pattern,buff);
  }
  strcat(pattern,".*");
/*printf("DEBUG: pattern=%s\n",pattern);*/
  dir=opendir(dirname);
  if(dir==NULL) return -1;
  while(1)
  {
    dirp=readdir(dir);
    if(!dirp) break;
    if(!wildmat(dirp->d_name,pattern)) continue;
    snprintf(buff,sizeof(buff)-1,"%s/%s",dirname,dirp->d_name);
    if(stat(buff,&stat_buf)) continue;
    if(!(stat_buf.st_mode & S_IFREG)) continue;
    if(size!=(unsigned long)(-1) && size!=stat_buf.st_size) continue;
    if(crc32!=filecrc32(buff)) continue;
    strcpy(outbuff,dirp->d_name);
    closedir(dir);
    return 0;
  }
  closedir(dir);

  return 1;
}

int dirmode(int filemode)
{
  int result;
  result=filemode;
  if(filemode & 0400) result|=0100;
  if(filemode & 0040) result|=0010;
  if(filemode & 0004) result|=0001;
  return result;
}

void write_flag_tab_to_FILE(FILE *fp,int flags,struct flag_tab *tab)
/* write flag_tab structure to fp (comma list)*/
{
  int no_comma=TRUE;
  while(1)
  {
    if(tab->text==NULL) break;
    if(tab->bitmask & flags)
    {
      if(no_comma==FALSE) fprintf(fp,",");
      no_comma=FALSE;
      fprintf(fp,"%s",tab->text);
    }
    tab++;
  }
}

#ifdef TEST
int main(int argc,char *argv[])
{
  if(argc<4) return -1;
  if(!strcmp(argv[1],"ln"))
    printf("ln(%s,%s)=%d\n",argv[2],argv[3],ln(argv[2],argv[3]));
  if(!strcmp(argv[1],"mv"))
    printf("mv(%s,%s)=%d\n",argv[2],argv[3],mv(argv[2],argv[3]));
  if(!strcmp(argv[1],"cp"))
    printf("cp(%s,%s)=%d\n",argv[2],argv[3],cp(argv[2],argv[3]));
  if(!strcmp(argv[1],"zz"))
  {
    char b1[999],b2[999],b3[999]; unsigned long crc32,size;
    strcpy(b1,argv[2]); strcpy(b3,argv[3]); crc32=strtoul(argv[4],NULL,16);
    size=atol(argv[5]);
    printf("crc32 of %s = %lX\n",b3,filecrc32(b3));
    printf("find_file(%s,(b2),%s,%X,%lu)=%d\n",b1,b2,crc32,size,
      find_file(b1,b2,b3,crc32,size));
    printf("b2=%s\n",b2);
  }
  return 0;
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1