windowsでmmap

windows.hをはじめて使ってみました。CreateFileWでファイル名を指定するために、mbtowcという関数を使わないといけないようです。windowsでコンパイルするときは"gcc file.c -DOS_WINDOWS"とします。linuxなどの場合は"gcc file.c"だけでokです。

使い方


コンパイルと自動テスト

"./a.out test"でテストを実行します。何も出力しなければ、テストは成功しています。

$ gcc file.c
$ ./a.out test

ファイルを読み込む

$ ./a.out ./input.txt 0
10.11.17, 0, 2000, 12000, 備考2
10.11.17, 3000, 0, 13000, 備考3
10.12.17, 2000, 3000, 10000, 備考4
$ ./a.out  ./input.txt  1
10.11.17, 0, 2000, 12000, 備考2
10.11.17, 3000, 0, 13000, 備考3
2行目に入力ミスを発見しました。
残高は13000と入力されていますが、15000であるべきです。

ここでinput.txtのフォーマットは「日付 入金 出金 残高 備考」で次の通りです。

10.11.07, 0, 1000, 12000, "備考1"
10.11.17, 0, 2000, 12000, "備考2"
10.11.17, 3000, 0, 13000, "備考3"
10.12.17, 2000, 3000, 10000, "備考4"

コード

#include <stdio.h>
#include <assert.h>
#include <time.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>

#ifdef OS_WINDOWS
#include <stdio.h>
#include <windows.h>
#else

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/mman.h>

#endif

#ifdef OS_WINDOWS
int get_filecontent(char** buf, char* fpath, HANDLE* map_handle) {
HANDLE handle;
unsigned int mode1, mode2, mode3, mode4;
int size;
mode1 = GENERIC_READ;
//assert(0x80000000 == mode1);
mode2 = PAGE_READONLY;
mode3 = FILE_MAP_READ;
mode4 = FILE_SHARE_READ;
wchar_t fname[80];
int len;
char* p = fpath;
int i = 0;
while(*p != '\0')  {
  mbtowc(&fname[i++], p, MB_CUR_MAX);
  p++;
}
fname[i] = '\0';
handle = CreateFileW(fname, mode1, mode4, 0,
                     OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(handle == INVALID_HANDLE_VALUE) {
  fprintf(stderr, "file open failed\n");
  exit(1);
}
size = GetFileSize(handle, 0);
*map_handle = CreateFileMapping(handle, 0, mode2, 0, 0, 0);
 *buf = (char*)MapViewOfFile(*map_handle, mode3, 0, 0, 0);
if(handle != INVALID_HANDLE_VALUE) {
  CloseHandle(handle);
  handle = INVALID_HANDLE_VALUE;
}
  return size;
}

void close_map(char** inp, HANDLE* map_handle) {
UnmapViewOfFile(*inp);
if(*map_handle != INVALID_HANDLE_VALUE) {
  CloseHandle(*map_handle);
  *map_handle = INVALID_HANDLE_VALUE;
}
*inp = NULL;
}

#else

int get_filecontent(char** buf, char* fpath) {
int fd = open(fpath, O_RDONLY);
  assert(fd > 0);
struct stat st;
  assert(fstat(fd, &st) >= 0);
  *buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
  close(fd);
  return st.st_size;
}
#endif

void tm_init(struct tm* t) {
  t->tm_year = 0;    
  t->tm_mon  = 0;      
  t->tm_mday = 0;    
  t->tm_hour = 0;
  t->tm_min  = 0;
  t->tm_sec  = 0;  
  t->tm_isdst= -1;
}
char* str_to_int(int* n, char* str) {
int tot = 0;
  while(!isdigit(*str) && *str != ',')
    str++;
  if(*str == ',') {
    *n = 0;
    return str;
  }
  while(isdigit(*str)) {
    tot = tot * 10 + (*str++ - '0');
  }
  *n = tot;
  return str;
}
// returns unix time
char* str_to_time(time_t* time, char* str, char end) {
struct tm t;
char buf[20], *p;
int i = 0;
  p = &buf[0];
  tm_init(&t);
  while(*str != end) {
    switch(*str) {
      default:
        p = &buf[0];
        do {
          *p++ = *str++;
	} while(*str != '.' && *str != end);
	if(*str != end)
	  str++;
	*p = '\0';
	assert(i < 3);
	switch(i) {
          case 0:
	    str_to_int(&t.tm_year, buf);
	    t.tm_year += 100;
	    break;
	  case 1:
	    str_to_int(&t.tm_mon, buf);
	    t.tm_mon -= 1;
	    break;
	  case 2:
	    str_to_int(&t.tm_mday, buf);	   
	    break;
	}
	i++;
        break;
      case ' ':
      case '\t':
        *str++;
	break;
    }
  }
   *time = mktime(&t);
   return ++str;
}
void string_time(char* buf, const struct tm* t) {
  sprintf(buf,"%02d.%02d.%02d",t->tm_year-100,
          t->tm_mon+1, t->tm_mday);
}
struct TransAction {
  time_t time;
  int in;
  int out;
  int total;
  char description[20];
};

char* transaction(struct TransAction** taction, char* source) {
struct TransAction*  ta = malloc(sizeof(struct TransAction));
  source = str_to_time(&ta->time, source, ',');
  source = str_to_int(&ta->in, source);
  if(*source == ',')
    source++;
  source = str_to_int(&ta->out, source);
  if(*source == ',')
    source++;
  source = str_to_int(&ta->total, source);
char* p = ta->description;  
  while(isspace(*source))
    source++;
  assert(*source++ == ',');
  while(isspace(*source))
    source++;
  if(*source == '"') {
    source++;
    while(*source != '"')
     *p++ = *source++;
    source++;
  } else {
    while(*source != '\n')
     *p++ = *source++;
  }
  *p = '\0';
  *taction = ta;
  while(*source != '\n')
    source++;
  source++;
  return source;
}
struct PassBook {
  struct TransAction** ta;
  struct TransAction** cursor;
  struct TransAction** limit;
  int size;
  int capacity;
};

struct PassBook* passbook(int size) {
struct PassBook* book = malloc(sizeof(struct PassBook));
  book->ta = malloc(size * sizeof(struct TransAction*));
  book->cursor = &book->ta[0];
  book->limit = &book->ta[size];
  book->size = 0;
  book->capacity = size;
  return book;
}
int passbook_pos(struct TransAction** tas, time_t time, int size) {
  int first = 0;
  int last = size;
  while(last > first) {
    int next = (first + last) >> 1;
    struct TransAction* ta = tas[next];
    if(!difftime(time, ta->time)) {
      int pos = next;
      while(difftime(time, ta->time) == 0) {
	pos++;
	if(pos < last)
          ta = tas[pos];
	else
	  break;
      }
      return pos;
    } 
    if(difftime(time, ta->time) < 0) {
      last = next;
      continue;
    } 
    first = next + 1;
  }
  if(difftime(time, tas[size-1]->time) > 0)
    return size;
  return first;
}
char* passbook_add(struct PassBook* pbook, char* source) {
struct TransAction* ta = NULL;
 source = transaction(&ta, source);
 if(pbook->size == 0) {
   *pbook->cursor++ = ta;
   pbook->size++;
   return source;
 }
 int pos = passbook_pos(pbook->ta, ta->time, pbook->size);
  if(pbook->capacity == pbook->size) {
    pbook->ta = realloc(pbook->ta,
     2 * pbook->capacity * sizeof(struct TransAction*));
    pbook->capacity = 2* pbook->capacity;  
  }
   pbook->size++;
   if(pbook->size > pos)
     memmove(pbook->ta + pos + 1, pbook->ta + pos, 
             (pbook->size - pos - 1) * sizeof(struct TransAction*));
   pbook->ta[pos] = ta;
  return source;
}
void passbook_free(struct PassBook* pbook) {
int i;
  for(i = 0; i < pbook->size; i++)
    free(pbook->ta[i]);
  free(pbook);
}
void passbook_print(struct PassBook* pbook, int check) {
char datetime[20];
  struct TransAction* ta = pbook->ta[0];
  string_time(datetime, localtime(&ta->time));
  printf("%s, %d, %d, %d, %s\n", datetime, ta->in, 
         ta->out, ta->total, ta->description);
int total = ta->total;
int i; 
  for(i = 1; i < pbook->size; i++) {
    struct TransAction* ta = pbook->ta[i];
    string_time(datetime, localtime(&pbook->ta[i]->time));
    printf("%s, %d, %d, %d, %s\n", datetime, ta->in, 
    ta->out, ta->total, ta->description);
    if(check  && (total + ta->in - ta->out) != ta->total) {
      fprintf(stderr, "%d行目に入力ミスを発見しました。\n", i+1);
      fprintf(stderr,
     "残高は%dと入力されていますが、%dであるべきです。\n", 
      ta->total, total+ ta->in - ta->out);
      exit(1);
    }
    total = ta->total;
  }
}
void test(void);

int main(int argc, char* argv[]) {
  if(argc == 2 && !strcmp(argv[1], "test")) {
    test();
    return 0;
  }

  if(argc != 3) {
    fprintf(stderr, 
    "usage:%s <filepath> check_flag={0:only soat, 1:check}\n", argv[0]);
    return 1;
  }
  int check = argv[2][0] - '0';
  assert(check == 0 || check == 1);
  char *inp, *p;
  int size;
setlocale(LC_CTYPE, "jpn");
#ifdef OS_WINDOWS
HANDLE map_handle;
  size = get_filecontent(&inp, argv[1], &map_handle);
#else
  size = get_filecontent(&inp, argv[1]);
#endif
  p = inp;
  // skip head of file
  while(*p != '\n')
    p++;
  p++;

struct PassBook* pbook;
  pbook = passbook(3);
  while(*p != '\0') 
    p = passbook_add(pbook, p); 
  passbook_print(pbook, check);

#ifdef OS_WINDOWS
  close_map(&inp, &map_handle);
#else
  munmap(inp, size);
#endif
  passbook_free(pbook);
  return 0;
}

void test_str_to_int(void) {
char source[] = " 324 5986,";
char* p = source;
int n1, n2;
  p = str_to_int(&n1, p);
  str_to_int(&n2, p);
  assert(324 == n1);
  assert(5986 == n2);
}
void test_str_to_time(void) {
  time_t t1, t2; 
  char source[] = "11.06.17, 08.12.07, 2000";
  char* p = &source[0];
  p = str_to_time(&t1, p, ',');
{
  struct tm* st_time = localtime(&t1);
  char actual[20];
  string_time(actual, st_time);
  assert(!strcmp("11.06.17", actual));
}
  p = str_to_time(&t2, p, ',');
{
  struct tm* st_time = localtime(&t2);
  char actual[20];
  string_time(actual, st_time);
  assert(!strcmp("08.12.07", actual));
}
}
void test_difftime(void) {
  time_t t1, t2;
  char source[] = "10.12.17, 11.06.17";
  char* p = &source[0];
  p = str_to_time(&t1, p, ',');
  str_to_time(&t2, p, '0');
  assert(difftime(t2, t1) > 0);
}
void CheckTransAction(struct TransAction* ta, 
    char* st_time, int in, int out, int total, char* desc) {
char buf[20];  
  string_time(buf, localtime(&ta->time));
  assert(!strcmp(st_time, buf));
  assert(in == ta->in);
  assert(out== ta->out);
  assert(total == ta->total);
  assert(!strcmp(desc, ta->description));
}
void test_transaction(void) {
{
struct TransAction* ta = NULL;
char source[] = "10.12.17, 2000, 3000,10000 , \"備考 \"\n";
char* p = &source[0];
p = transaction(&ta, p);
CheckTransAction(ta, "10.12.17", 2000, 3000, 10000, "備考 ");
  free(ta);
}

{
struct TransAction* ta = NULL;
char source[] = "10.12.17,,,10000 , \"備考2 \"\n";
char* p = &source[0];
p = transaction(&ta, p);
CheckTransAction(ta, "10.12.17", 0, 0, 10000, "備考2 ");
  free(ta);
}
}

void test_passbook_pos1(void) {
struct TransAction** tas = malloc(4 * sizeof(tas[0]));
struct TransAction** ptr = tas;
char source[] = 
  "10.11.07, 0, 1000, 12000, \"備考1\"\n"
  "10.11.17, 0, 2000, 12000, \"備考2\"\n"
  "10.11.17, 3000, 0, 13000, \"備考3\"\n"
  "10.12.17, 2000, 3000, 10000, \"備考4\"\n"
  ;
char* p = &source[0];
  p = transaction(ptr++, p);
  p = transaction(ptr++, p);
  p = transaction(ptr++, p);
  p = transaction(ptr, p);

  CheckTransAction(tas[0], "10.11.07", 0, 1000, 12000, "備考1");
  CheckTransAction(tas[1], "10.11.17", 0, 2000, 12000, "備考2");
  CheckTransAction(tas[2], "10.11.17", 3000, 0, 13000, "備考3");
  CheckTransAction(tas[3], "10.12.17", 2000, 3000, 10000, "備考4");
  time_t time;
  str_to_time(&time, "10.11.06", '\0');
  assert(0 == passbook_pos(tas, time, 4));

  str_to_time(&time, "10.11.08", '\0');
  assert(1 == passbook_pos(tas, time, 4));

  str_to_time(&time, "10.11.17", '\0');
  assert(3 == passbook_pos(tas, time, 4));

  str_to_time(&time, "10.11.18", '\0');
  assert(3 == passbook_pos(tas, time, 4));
  
  str_to_time(&time, "11.01.27", '\0');
  assert(4 == passbook_pos(tas, time, 4));

int i;
  for(i = 0; i < 4; i++)
    free(tas[i]);
  free(tas);
}

void test_passbook_pos2(void) {
struct TransAction** tas = malloc(4 * sizeof(tas[0]));
struct TransAction** ptr = tas;
char source[] = 
  "11.06.15,,,198000, \"繰越金\"\n"
  "11.06.16,1000,,199000, \"給料日1\"\n"
  "11.06.16,1000,,199000, \"給料日2\"\n"
  "11.06.17,,3000,196000, \"お米代1\"\n"
  ;
char* p = &source[0];
  while(*p != '\0')
    p = transaction(ptr++, p);
 time_t time;
  str_to_time(&time, "11.06.17", '\0');
  assert(4 == passbook_pos(tas, time, 4));
int i;
  for(i = 0; i < 4; i++)
    free(tas[i]);
  free(tas);
}

void test_passbook_add1(void) {
char source[] = 
  "10.12.17, 2000, 3000, 10000, \"備考1\"\n"
  "10.11.17, 3000, 0, 13000, \"備考2\"\n"
  "10.12.27, 0, 1000, 12000, \"備考3\"\n"
  "10.11.17, 0, 2000, 10000, \"備考4\"\n"
  ;
char* p = &source[0];
struct PassBook* pbook = passbook(3);
  p = passbook_add(pbook, p);

char buf[20];
  
  CheckTransAction(pbook->ta[0], "10.12.17", 2000, 3000, 10000, "備考1");
  p = passbook_add(pbook, p);
  
  CheckTransAction(pbook->ta[0], "10.11.17", 3000, 0, 13000, "備考2");
  CheckTransAction(pbook->ta[1], "10.12.17", 2000, 3000, 10000, "備考1"); 
  
  p = passbook_add(pbook, p);
  
  string_time(buf, localtime(&pbook->ta[0]->time));
  assert(!strcmp("10.11.17", buf));
  string_time(buf, localtime(&pbook->ta[1]->time));
  assert(!strcmp("10.12.17", buf));
  string_time(buf, localtime(&pbook->ta[2]->time));
  assert(!strcmp("10.12.27", buf));
 
  CheckTransAction(pbook->ta[0], "10.11.17", 3000, 0, 13000, "備考2");
  CheckTransAction(pbook->ta[1], "10.12.17", 2000, 3000, 10000, "備考1"); 
  CheckTransAction(pbook->ta[2], "10.12.27", 0, 1000, 12000, "備考3"); 
  
  // add slow 
  p = passbook_add(pbook, p);
  assert(*p == '\0'); 
  string_time(buf, localtime(&pbook->ta[0]->time));
  assert(!strcmp("10.11.17", buf));
  string_time(buf, localtime(&pbook->ta[1]->time));
  assert(!strcmp("10.11.17", buf));
 
  string_time(buf, localtime(&pbook->ta[2]->time));
  assert(!strcmp("10.12.17", buf));
  string_time(buf, localtime(&pbook->ta[3]->time));
  assert(!strcmp("10.12.27", buf));
 
  CheckTransAction(pbook->ta[0], "10.11.17", 3000, 0, 13000, "備考2");
  CheckTransAction(pbook->ta[1], "10.11.17", 0, 2000, 10000, "備考4");
  CheckTransAction(pbook->ta[2], "10.12.17", 2000, 3000, 10000, "備考1"); 
  CheckTransAction(pbook->ta[3], "10.12.27", 0, 1000, 12000, "備考3"); 
 
  passbook_free(pbook);
}

void test(void) {
  test_str_to_int();
  test_str_to_time();
  test_difftime();
  test_transaction();
  test_passbook_pos1();
  test_passbook_pos2();
  test_passbook_add1();
}