/*	
 *	clipio.c
 *
 *	S1C33 Family Cコンパイラパッケージの io.lib 関数を置き換えます。
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2015 Naoyuki Sawa
 *
 *	* Mon Nov 25 20:37:00 JST 2002 Naoyuki Sawa
 *	- 作成開始。
 *	* Mon Nov 30 08:06:00 JST 2002 Naoyuki Sawa
 *	- stderr_open()/close()/write()を追加。
 *	* Wed Dec 11 12:30:00 JST 2002 Naoyuki Sawa
 *	- キャッシュ用メンバ変数(cache_sct/len/ptr)と、キャッシュを介したシーク関数(io_seek())を追加。
 *	  連続したfread()呼び出しでセクタが変化しない場合は、pceFileReadSct()を使わずキャッシュ情報を使います。
 *	  この改良により、fgetc()やfgets()のような細かな単位で読み込みを行う処理が、2倍以上速くなりました。
 *	* Sat Nov 22 06:00:00 JST 2003 Naoyuki Sawa
 *	- stderr_open()はアプリケーションから明示的に呼ぶようにしたので、(→clippce.c参照)
 *	  stderr_open()が呼ばれた場合は常に出力モニタの接続を待つように変更しました。
 *	* Wed Jun 26 20:03:00 JST 2005 Naoyuki Sawa
 *	- stderr_open()内の不要なpceLCDDispStop()呼び出しを削除しました。
 *	* Tue Dec 13 04:35:00 JST 2005 Naoyuki Sawa
 *	- 標準出力への書き込みも、標準エラー出力と同様に、標準エラー出力モニタへ出力するように変更しました。
 *	  これまでは、標準出力(stdout)への書き込みは、エラー終了としていました。
 *	  この変更により、fprintf(stderr, fmt, ...)だけでなく、printf(fmt, ...)もモニタできるようになりました。
 *	* Sun Jan 15 04:02:00 JST 2005 Naoyuki Sawa
 *	- ungetc()にて、EOFが指定されたら、エラーとして扱い、何もせずにEOFを返す処理を追加しました。
 *	  これまでは、EOFが指定された場合にも、エラーとせずに、ストリームへ戻す処理を行っていました。
 *	* Tue Oct 30 00:38:57 JST 2012 Naoyuki Sawa
 *	- fileno(),isatty()を追加しました。宣言は、clippce.h にあります。	//{{2015/01/07追記:fileno(),isatty()の宣言を、clippce.h⇒include/stdio.hへ移動しました。実装はclipio.cにあります。isatty()は本当はio.hかunistd.hで宣言すべきなのですが、P/ECEにはio.hもunistd.hも無いのでinclude/stdio.hで宣言することにしました。}}
 *	* Tue Oct 30 22:43:07 JST 2012 Naoyuki Sawa
 *	- isatty()の生成コードが(僅かながら)小さくなるよう、条件式を変更しました。
 *	* Sat Mar 23 17:43:37 JST 2013 Naoyuki Sawa
 *	- stat(),fstat()を追加しました。宣言は、include/sys/stat.h にあります。
 *	* Sun Mar 09 14:07:14 JST 2014 Naoyuki Sawa
 *	- stdin_read()を追加しました。
 *	  この機能追加に伴い、tool/dstderr にも機能追加を行いました。
 *	  詳細は、tool/dstderr/dstderr.c の同日のコメントを参照してください。
 *	* Mon Mar 10 17:00:24 JST 2014 Naoyuki Sawa
 *	- puts()が改行を出力していなかったバグを修正しました。
 *	- getchar,getc,putchar,putcをマクロ化し、関数を削除しました。
 *	  /clip/include/stdio.h を参照してください。
 *	* Thu Mar 13 01:48:55 JST 2014 Naoyuki Sawa
 *	- 接続完了時のメッセージが解析の邪魔になる事が有るため削除しました。
 *	- tool/dstderr/dstdpipe を作成しました。
 *	  これまでの dstderr と同等の機能を持ち、より柔軟です。
 *	  キーボード以外からの入力も受け付けるので、ファイルの流し込みや、他プログラムとP/ECEの中継パイプとしても動作します。
 *	  今後は、dstderr よりも dstdpipe を使用するようにしてください。
 *	* Thu Aug 07 23:44:53 JST 2014 Naoyuki Sawa
 *	- fcloseall()を追加しました。
 *	* Sun Sep 07 20:09:27 JST 2014 Naoyuki Sawa
 *	- stat.st_blksize,stat.st_blocksを追加しました。
 *	- statvfs(),fstatvfs()を追加しました。宣言は、include/sys/statvfs.h にあります。
 *	* Thu Oct 23 18:26:54 JST 2014 Naoyuki Sawa
 *	- 関数プロトタイプのfscanf(),scanf()を削除し、clipscan.cにて実装しました。
 *	* Wed Dec 31 17:37:43 JST 2014 Naoyuki Sawa
 *	- cstart.cのpceAppInit00()でpceLCDDispStart()を呼び出し、液晶画面の表示を開始するようにしました。
 *	  この変更に伴い、stderr_open()内のpceLCDDispStart()呼び出しは不要になり、削除しました。
 *	* Wed Mar 11 21:52:17 JST 2015 Naoyuki Sawa
 *	- truncate(),ftruncate()を追加しました。
 *	  宣言はinclude/stdio.hに有ります。
 *	  本来はunistd.hで宣言すべきなのですがP/ECEにはunistd.hが無いので代りにinclude/stdio.hで宣言する事にしました。
 *	* Fri Mar 13 21:06:23 JST 2015 Naoyuki Sawa
 *	- fwrite()を実装しました。fwrite()を呼び出していたfprintf()やfputc()等も、動作するようになりました。
 *	  セクタ単位で書き込むので、極端にフラッシュメモリを傷める事は有りません。ログの記録等に利用できると思います。
 *	  ただし、頻繁にfflush()を呼び出すと、フラッシュメモリを傷める可能性が有ります。
 *	  なるべく明示的なfflush()呼び出しは行わず、fwrite()とfclose()での自動的な書き込みに任せてください。
 *	- 書き込みモードで開いたストリームに対するfseek()は未対応です。
 *	  現在の実装では、書き込みモードで開いたストリームに対してfseek()を使用すると、間違ったデータを書き込みます。
 *	- 今回の変更に際してキャッシュ用メンバ変数(cache_sct/len/ptr)を削除したので、変更前よりもfread()の性能が少し低下しました。
 *	  キャッシュ用メンバ変数を削除した理由は必然ではなく、複雑さを減らしてバグの危険を低減しようと思ったからです。
 *	  fread()の性能低下が大きく影響するケースは少ないと思いますが、もし問題が生じた場合は変更前のプロジェクトを参照してキャッシュ用メンバ変数を戻してください。
 *	- 今回は処理変更とソース整形を多く行ったため、変更前のコードをソース内に残していません。
 *	  変更前の処理を参照する場合は、前回までのアーカイプを参照してください。
 *	* Sat Mar 14 21:29:01 JST 2015 Naoyuki Sawa
 *	- truncate()の「pceFileCreate()利用版」を作成しました。
 *	* Fri May 01 00:17:04 JST 2015 Naoyuki Sawa
 *	- io_open()に追加書き込みモード('a')の処理を追加しました。
 *	  この変更によりfopen()に"a"が指定できるようになりました。
 *	* Fri May 01 22:43:26 JST 2015 Naoyuki Sawa
 *	- vfprintf()が_def_vbuff[]を利用するのをやめて、代りにvasprintf()を利用するように変更しました。
 *	  vfprintf()は元々ファイル書き込みが遅くて、速度が重要ではないので、毎回メモリ確保,開放を行っても影響は小さいと思います。
 *	- fprintf()やvfprintf()が_def_vbuff[]を破壊しなくなったので、これらの関数を割り込みルーチン内でも使えるようになりました。
 *	  例えば割り込みルーチンの開発中に、デバッグログをP/ECEファイルシステム上のファイルとして出力するような使い方が出来ます。
 *	* Sun Aug 02 16:45:48 JST 2015 Naoyuki Sawa
 *	- fprintf(),printf(),vfprintf(),vprintf()を、clipio.c⇒clipprnt.cへ移動しました。
 *	  Win32プロジェクトでも、doprnt()を利用したprintf()等を利用したいからです。
 *	  clipio.cはP/ECEに依存しているのでWin32プロジェクトに含められませんが、clipprnt.cならばWin32プロジェクトに含められます。
 */
#include "clip.h"
//※2002/11/26現在の実装では、いずれの関数もエラー時にerrnoを設定しません。
//│errnoを見てエラーを判断しているプログラムは、正しく動かない可能性があります。
//│→TODO:errnoを設定するよう修正せよ。
/****************************************************************************
 *	関数プロトタイプ
 ****************************************************************************/
//□以下は『S5U1C33000C Manual (S1C33 Family Cコンパイラパッケージ) (Ver.4)』(S5U1C33000C_J.pdf) p.97 8.2.1 入出力関数(io.lib) 表8.2.1.1 入出力関数一覧 の並びと同じ順番です。各々の関数を実装したら、コメントアウトして行ってください。
//FILE* fopen(const char* filename, const char* mode) { DIE(); }
//FILE* freopen(const char* filename, const char* mode, FILE* stream) { DIE(); }
//int fclose(FILE* stream) { DIE(); }
//int fflush(FILE* stream) { DIE(); }
//int fseek(FILE* stream, long offset, int origin) { DIE(); }
//long ftell(FILE* stream) { DIE(); }
//void rewind(FILE* stream) { DIE(); }
//int fgetpos(FILE* stream, fpos_t* pos) { DIE(); }
//int fsetpos(FILE* stream, const fpos_t* pos) { DIE(); }
//size_t fread(void* buffer, size_t size, size_t count, FILE* stream) { DIE(); }
//size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream) { DIE(); }
//int fgetc(FILE* stream) { DIE(); }
//int getc(FILE* stream) { DIE(); }
//int getchar() { DIE(); }
//int ungetc(int c, FILE* stream) { DIE(); }
//char* fgets(char* string, int n, FILE* stream) { DIE(); }
//char* gets(char* buffer) { DIE(); }
//int fputc(int c, FILE* stream) { DIE(); }
//int putc(int c, FILE* stream) { DIE(); }
//int putchar(int c) { DIE(); }
//int fputs(const char* string, FILE* stream) { DIE(); }
//int puts(const char* string) { DIE(); }
//int remove(const char* filename) { DIE(); }
int rename(const char* oldname, const char* newname) { DIE(); }
void setbuf(FILE* stream, char* buffer) { DIE(); }
int setvbuf(FILE* stream, char* buffer, int mode, size_t size) { DIE(); }
FILE* tmpfile() { DIE(); }
char* tmpnam(char* string) { DIE(); }
//int feof(FILE* stream) { DIE(); }
//int ferror(FILE* stream) { DIE(); }
//void clearerr(FILE* stream) { DIE(); }
//void perror(const char* string) { DIE(); }
//int fscanf(FILE* stream, const char* format, ...) { DIE(); }	-> 2014/10/23 clipscan.c
//int scanf(const char* format, ...) { DIE(); }			-> 2014/10/23 clipscan.c
//int sscanf(char* buffer, const char* format, ...); -> io.lib(そのまま使用)
//int fprintf(FILE* stream, const char* format, ...) { DIE(); }
//int printf(const char* format, ...) { DIE(); }
//int sprintf(char* buffer, const char* format, ...); -> P/ECE Kernel(pcesprintf) -> 2003/05/20 clipprnt.c
//int vfprintf(FILE* stream, const char* format, va_list argptr) { DIE(); }
//int vprintf(const char* format, va_list argptr) { DIE(); }
//int vsprintf(char* buffer, const char* format, va_list argptr); -> P/ECE Kernel(pcevsprintf) -> 2003/05/20 clipprnt.c
/****************************************************************************
 *	ローカル変数・関数
 ****************************************************************************/
//ダミーの標準入出力用アドレス定義。
//偶数アドレスにしておかないとio_file()でエラーを検出する前にアドレスエラーで落ちるので注意。
#define FILE_STDIN	((FILE*)(- 4))	//ダミー。io_file()でエラー扱い。fread() のみ特別処理
#define FILE_STDOUT	((FILE*)(- 8))	//ダミー。io_file()でエラー扱い。fwrite()のみ特別処理
#define FILE_STDERR	((FILE*)(-12))	//ダミー。io_file()でエラー扱い。fwrite()のみ特別処理
/*--------------------------------------------------------------------------*/
#define MAX_IO		(3+3)		//同時に開ける最大ファイル数	標準入出力(3)+通常ファイル(3)	調整可
#define VALIDFILEACC	0x9CE6		//-> fa.valid	\usr\PIECE\sysdev\pcekn\file.c 参照
#define IO_WRT		(1<<0)		//-> IO.flag	書き込みモード
#define IO_EOF		(1<<1)		//-> IO.flag	EOF指示子
#define IO_ERR		(1<<2)		//-> IO.flag	エラー指示子
typedef struct _IO {
	FILEACC		fa;				//+ 0,   16  ファイルアクセス構造体
	int		pos;				//+16,    4  次回の読み込み/書き込み位置
	unsigned char	flag;				//+20,    1  IO_EOF,IO_ERRの組み合わせ
	char		filename[26/*MAXFILENAME*/+1];	//+21,   27  ファイル名
	unsigned char	sctbuf[0/*BLOCKSIZE*/];		//+48,(4096) セクタバッファ	書き込みモードで開いた時だけメモリを確保する。保持しているバイト数は(pos%BLOCKSIZE)で求められる。ちょうどBLOCKSIZEバイトを保持する事は無い。fwrite()にてBLOCKSIZEバイト単位でフラッシュメモリへ書き込むから。
} IO;							//=48+(4096)
static IO* ios[MAX_IO] = {	//ファイルI/O構造体ポインタ配列
	(IO*)FILE_STDIN,	//0(STDIN_FILENO ) 標準入力		┐
	(IO*)FILE_STDOUT,	//1(STDOUT_FILENO) 標準出力		├固定
	(IO*)FILE_STDERR,	//2(STDERR_FILENO) 標準エラー出力	┘
	//これ以降が通常ファイル用。初期値はNULL。
};
/*--------------------------------------------------------------------------*/
//指定されたストリームポインタが、通常ファイルのストリームポインタである事を確認した上で、ファイルI/O構造体ポインタにキャストして返す。
//NULLポインタや、標準入出力のストリームポインタや、壊れたポインタやメモリを指定した場合、当関数の中でエラー停止する。
static IO* io_file(FILE* stream) {
	IO* io = (IO*)stream;
	if(io->fa.valid != VALIDFILEACC) { DIE(); } //ファイルが開かれていることを確認する。
	return io;
}
/*--------------------------------------------------------------------------*/
static IO* io_open(const char* filename, const char* mode) {
	IO* io;
	switch(mode[0]) { //処理を簡略化して、一文字目だけで判定する事にした。
	case 'r':
		//ファイルI/O構造体のメモリを確保する。読み込みモードの場合はセクタバッファ不要。
		io = calloc(1, sizeof(IO));
		if(!io) { DIE(); }	//メモリ不足
		//ファイルを開きます。
		if(pceFileOpen(&io->fa, filename, FOMD_RD)) {	//pceFileOpen()が成功したら(io->fa.valid==VALIDFILEACC)になっています。
			free(io);	//失敗したらすぐに、ファイルI/O構造体のメモリも開放する。
			return NULL;	//ファイルを開けなかった。
		}
		break;
	case 'w':
		//ファイルI/O構造体のメモリを確保する。書き込みモードの場合はセクタバッファ必要。
		io = calloc(1, sizeof(IO) + BLOCKSIZE);
		if(!io) { DIE(); }	//メモリ不足
		//ファイルを作成します。
		if(pceFileCreate(filename, 0) ||	//ファイルが無ければ新規作成する。ファイルが有れば0バイトに切り詰める。
		   pceFileOpen(&io->fa, filename, FOMD_WR)) {	//pceFileOpen()が成功したら(io->fa.valid==VALIDFILEACC)になっています。
			free(io);	//失敗したらすぐに、ファイルI/O構造体のメモリも開放する。
			return NULL;	//ファイルを作成できなかった。
		}
		io->flag |= IO_WRT;	//書き込みモード
		break;
	case 'a':
		//ファイルI/O構造体のメモリを確保する。書き込みモードの場合はセクタバッファ必要。
		io = calloc(1, sizeof(IO) + BLOCKSIZE);
		if(!io) { DIE(); }	//メモリ不足
		//ファイルを開きます。
		if(pceFileOpen(&io->fa, filename, FOMD_WR) &&	//ファイルが有れば開く。
		  (pceFileCreate(filename, 0) ||		//ファイルが無ければ新規作成する。
		   pceFileOpen(&io->fa, filename, FOMD_WR))) {	//pceFileOpen()が成功したら(io->fa.valid==VALIDFILEACC)になっています。
			free(io);	//失敗したらすぐに、ファイルI/O構造体のメモリも開放する。
			return NULL;	//ファイルを作成できなかった。
		}
		io->flag |= IO_WRT;	//書き込みモード
		//書き込み位置をファイルの終端とします。
		io->pos = io->fa.fsize;
		//書き込み位置を含むセクタの既存のデータをセクタバッファへ読み込みます。
		{
			int sct = io->pos / BLOCKSIZE;					//sct=書き込み位置を含むセクタ番号。
			int sln = pceFileReadSct(&io->fa, NULL, sct, BLOCKSIZE);	//sln=このセクタのデータサイズ。(==io->pos%BLOCKSIZE)
			memcpy(io->sctbuf, io->fa.aptr, sln);				//セクタからバッファへ読み込みます。
		}
		break;
	default:
		DIE();/*return NULL;*/	//アプリケーションのバグの可能性が高いので、エラー値を返すのではなくエラー停止する事にした。
	}
	//ファイル名を格納する。
	strcpy(io->filename, filename);
	return io;
}
/*--------------------------------------------------------------------------*/
static void io_close(FILE* stream) {
	IO* io = io_file(stream);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
	//ファイルを閉じる。
	pceFileClose(&io->fa);
	//ファイルI/O構造体のメモリを開放する。
	free(io);
}
/****************************************************************************
 *	グローバル変数・関数
 ****************************************************************************/
FILE* stdin  = FILE_STDIN;
FILE* stdout = FILE_STDOUT;
FILE* stderr = FILE_STDERR;
/*--------------------------------------------------------------------------*/
FILE* fopen(const char* filename, const char* mode) {
	int fd;
	//ファイル記述子の空きを検索する。
	for(fd = 0; fd < MAX_IO; fd++) {
		//ファイル記述子の空きが有れば…
		if(!ios[fd]) {
			//ファイルを開く。
			ios[fd] = io_open(filename, mode);	//失敗ならば(ios[fd]=NULL)のまま。
			return (FILE*)ios[fd];			//失敗ならば(ios[fd]=NULL)を返す。
		}
	}
	return NULL; //ファイル記述子の空きが無かった。
}
/*--------------------------------------------------------------------------*/
FILE* freopen(const char* filename, const char* mode, FILE* stream) {
	//このストリームのファイル記述子を取得する。
	int fd = fileno(stream);
	//このストリームを閉じる。
	fclose(stream);		//標準入出力が指定された場合はこの中でエラー停止する。
	if(ios[fd]) { DIE(); }	//ファイル記述子が開放された事を確認する。もし開放されていなければfclose()のバグである。
	//ファイルを開き、元と同じファイル記述子の位置に格納する。
	ios[fd] = io_open(filename, mode);	//失敗ならば(ios[fd]=NULL)のまま。
	return (FILE*)ios[fd];			//失敗ならば(ios[fd]=NULL)を返す。
}
/*--------------------------------------------------------------------------*/
int fclose(FILE* stream) {
	//このストリームのファイル記述子を取得する。
	int fd = fileno(stream);
	//書き込みモードならばセクタバッファをフラッシュする。読み込みモードならばダミー処理となる。
	int result = fflush(stream);	//標準入出力が指定された場合はこの中でエラー停止する。
	//ファイルを閉じる。
	io_close(stream);
	//ファイル記述子を開放する。
	ios[fd] = NULL;	//この処理はfflush()よりも後で行う事。fflush()⇒truncate()の中でios[]を参照して'現在開いているファイルI/O構造体にも反映する処理'を行うから。
	//fflush()の結果を、fclose()の戻り値とする。
	return result;
}
/*--------------------------------------------------------------------------*/
//{{2015/03/14メモ:pceAppExit()の中にfcloseall()の呼び出しを追加しました。
// * Sat Mar 14 21:29:01 JST 2015 Naoyuki Sawa
// - pceAppExit()の中にfcloseall()の呼び出しを追加しました。
//   fcloseall()の中でDIE()すると異常動作になるので注意してください。バグの検出ならば仕方が無いが起こり得るエラーに対してはfcloseall()はDIE()でなくエラー値を返すようにしてください。
//   pceAppExit()はfcloseall()が返したエラー値を無視して終了処理を継続します。
//}}2015/03/14メモ:pceAppExit()の中にfcloseall()の呼び出しを追加しました。
int fcloseall() {
	//fcloseall()の戻り値には、二つの流儀があります。
	//│GNU C: 全てのファイルが正常に閉じられた場合(0)を返す。エラーの場合、EOFを返す。
	//│Visual C++: クローズしたストリームの総数を返す。エラーが発生すると、EOFを返す。
	//本モジュールの実装は、GNU Cの流儀に従いました。
	int fd, result = 0;
    //	for(fd =                 0; fd < MAX_IO; fd++) { //正しくはこうだが…
	for(fd = STDERR_FILENO + 1; fd < MAX_IO; fd++) { //当実装では標準入出力は閉じない事にした。
		FILE* stream = (FILE*)ios[fd];
		//ストリームが開かれていたら閉じる。1個でもエラーが発生したら戻り値をEOFにする。エラーが発生してもループを継続する。
		if(stream && fclose(stream)) { result = EOF; }
	}
	return  result;
}
/*--------------------------------------------------------------------------*/
int fflush(FILE* stream) {
	IO* io = io_file(stream);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
	//書き込みモードならば…
	if(io->flag & IO_WRT) {
		int sct = io->pos / BLOCKSIZE;	//現在の書き込み位置を含むセクタ番号
		int len = io->pos % BLOCKSIZE;	//セクタバッファに残っているバイト数
		//セクタバッファに残っているバイト数が有れば…
		if(len) {
			//ファイルサイズを伸張する。
			if(truncate(io->filename, io->pos)) { return EOF; }	//容量不足
			//セクタバッファに残っているデータを書き込む。
			if(pceFileWriteSct(&io->fa, io->sctbuf, sct, len) != len) { DIE(); }	//バグ
		}
	}
	return 0;
}
/*--------------------------------------------------------------------------*/
int fseek(FILE* stream, long offset, int origin) {
	IO* io = io_file(stream);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
	clearerr(stream); //EOF指示子とエラー指示子をクリアする。
	switch(origin) {
	case SEEK_SET:
		io->pos = offset;
		break;
	case SEEK_CUR:
		io->pos += offset;
		break;
	case SEEK_END:
		io->pos = io->fa.fsize - offset;
		break;
	default:
		DIE();/*return -1;*/	//アプリケーションのバグの可能性が高いので、エラー値を返すのではなくエラー停止する事にした。
	}
	return 0;
}
/*--------------------------------------------------------------------------*/
long ftell(FILE* stream) {
	IO* io = io_file(stream);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
	return io->pos;
}
/*--------------------------------------------------------------------------*/
void rewind(FILE* stream) {
	fseek(stream, 0, SEEK_SET); //fseek()の中でEOF指示子とエラー指示子がクリアされます。
}
/*--------------------------------------------------------------------------*/
int fgetpos(FILE* stream, fpos_t* pos) {
	*pos = ftell(stream);
	return 0;
}
/*--------------------------------------------------------------------------*/
int fsetpos(FILE* stream, const fpos_t* pos) {
	return fseek(stream, *pos, SEEK_SET); //fseek()の中でEOF指示子とエラー指示子がクリアされます。
}
/*--------------------------------------------------------------------------*/
size_t fread(void* buffer, size_t size, size_t count, FILE* stream) {
	if(stream == FILE_STDIN) { //標準入力は特別処理で、標準エラー出力モニタから入力します。
		//厳密には、この処理は誤りである。
		//本当は、標準入力に文字が残っていたとしても、sizeの倍数に満たないブロックは読み込んではいけない。
		//しかし、P/ECEプログラムではほぼsize=1で使用すると思うので、この誤りは実質問題にはならないと判断し、このままにすることにした。
		return stdin_read(buffer, size * count) / size;
	} else {
		IO* io = io_file(stream);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
		char* p = (char*)buffer;
		int len = size * count;
		int ofs, sct, sof, sln;
		//現在の実装では、最後の項目が中途半端に読み込める場合、その分もバッファに読み込んでしまいます。
		//例えば、150バイトのファイルの前半100バイトをバッファに読み込む場合、本当のfread()ならば：
		//│char buf[100];
		//│retval = fread(buf, 100, 2, fp); -> 戻り値は1で、buf[0〜99]までにデータが読み込まれます
		//となりますが、この実装では：
		//│char buf[100];
		//│retval = fread(buf, 100, 2, fp); -> 戻り値は1ですが、buf[0〜149]までデータが読み込まれます
		//となり、バッファオーバーランを起こしてしまいます。
		//そもそもこのような危険な呼び出し方をすることはあまりないと思いますが、念のためご注意ください。
		ofs = 0;
		while(ofs < len) {
			//セクタのフラッシュメモリアドレスを取得します。
			sct = (io->pos + ofs) / BLOCKSIZE;			//sct=現在の読み込みオフセットを含むセクタ番号。
			sln = pceFileReadSct(&io->fa, NULL, sct, BLOCKSIZE);	//sln=このセクタのデータサイズ。最終セクタのみ(sln<BLOCKSIZE)になり得る。
			if(sln == 0) { break; }					//たぶんオフセット超過。
			//このセクタから読み込むサイズを決定します。
			sof = (io->pos + ofs) % BLOCKSIZE;			//セクタ内オフセット。
			sln -= sof;						//セクタ内オフセット以降から読み込み可能な最大データサイズ。
			if(sln > len - ofs) { sln = len - ofs; }		//読み込み可能な最大データサイズが残り読み込みサイズ以上なら、切り詰めます。
			if(sln <= 0) { break; }					//たぶんオフセット超過、または読み込みサイズに0以下が指定された(len<=0)。
			//セクタからバッファへ読み込みます。
			memcpy(p + ofs, io->fa.aptr + sof, sln);
			ofs += sln;
		}
		count = ofs / size;						//完全に読み込めた項目数。
		io->pos += count * size;					//完全に読み込めた項目の分だけ、ファイルポインタの現在位置を進めます。
		if(count * size < len) { io->flag |= IO_EOF; }			//完全に読み込めたバイト数が、要求サイズ*未満*だったら、EOFをマークする。
		return count;							//完全に読み込めた項目数を返します。
	}
}
/*--------------------------------------------------------------------------*/
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream) {
	if((stream == FILE_STDOUT) || (stream == FILE_STDERR)) { //標準出力と標準エラー出力は特別処理で、標準エラー出力モニタへ出力します。
		stderr_write(buffer, size * count);
		return count;
	} else {
		IO* io = io_file(stream);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
		char* p = (char*)buffer;
		int len = size * count;
		//ファイルサイズを、データを追加した後のサイズを超えない,かつ,セクタサイズの倍数に伸張する。
		if(truncate(io->filename, (io->pos + len) & ~(BLOCKSIZE - 1))) { return 0; }	//容量不足
		//データの残りバイト数が無くなるまで…
		while(len) {
			int sct = io->pos / BLOCKSIZE;		//現在の書き込み位置を含むセクタ番号
			int sof = io->pos % BLOCKSIZE;		//セクタバッファに追加するオフセット
			int sln = BLOCKSIZE - sof;		//セクタバッファに追加できるバイト数
			if(sln > len) { sln = len; }		//セクタバッファに追加するバイト数を求める。
			memcpy(&io->sctbuf[sof], p, sln);	//セクタバッファに追加する。
			p       += sln;				//データアドレスを進める。
			len     -= sln;				//データの残りバイト数を減らす。
			io->pos += sln;				//書き込み位置を進める。
			if(io->pos % BLOCKSIZE) { break; }	//セクタバッファが一杯になる前にデータが尽きたら、フラッシュメモリに書き込まずに抜ける。
			if(pceFileWriteSct(&io->fa, io->sctbuf, sct, BLOCKSIZE) != BLOCKSIZE) { DIE(); }	//バグ
		}
		if(len) { DIE(); }	//バグ
		return count;
	}
}
/*--------------------------------------------------------------------------*/
int fgetc(FILE* stream) {
	unsigned char c; //fgetc(),getc(),getchar()は、文字をunsigned charとして読んでintにキャストして返す。(「Linux Programmer's Manual - FGETC」より)
	if(fread(&c, 1, 1, stream) != 1) { return EOF; }
	return c;
}
/*--------------------------------------------------------------------------*/
//{{2014/03/10削除:getchar,getc,putchar,putcをマクロ化し、関数を削除しました。/clip/include/stdio.h を参照してください。
//int getc(FILE* stream) {
//	return fgetc(stream);
//}
//int getchar() {
//	return getc(stdin);
//}
//}}2014/03/10削除:getchar,getc,putchar,putcをマクロ化し、関数を削除しました。/clip/include/stdio.h を参照してください。
/*--------------------------------------------------------------------------*/
int ungetc(int c, FILE* stream) {
	IO* io = io_file(stream);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
	//EOFを返そうとしたら、ストリームへ戻す処理を行わずに、エラーを示すEOFを返します。
	//それ以外ならば、ストリームへ戻す処理を行い、戻した文字コードをそのまま返します。
	if(c != EOF) {
		io->pos--; //※適当な実装！本当のungetc()とはぜんぜん違います。
		//もしも呼び出し元が、'\x80'〜'\xFF'を符号拡張して、'\xFF80'〜'\xFFFF'を渡した場合でも、ungetc()の戻り値は、'\x80'〜'\xFF'となります。
		//実際には、上位8bitが\xFFかどうかにかかわらず、常に上位8bitをクリアします。
		//この仕様は、Visual C++ 6.0 SP6の、ランタイムライブラリのソースの実装に合わせませした。
		c = (unsigned char)c;
	}
	return c;
}
/*--------------------------------------------------------------------------*/
char* fgets(char* string, int n, FILE* stream) {
	char* p = string;
	if(n <= 0) { return NULL; } //stringの容量が0以下ならば何もしない。(*p='\0')も行ってはいけない。
	while(--n) { //(n-1)文字までしか読み込まないのでプレデクリメントして0判定する。
		if(fread(p, 1, 1, stream) != 1) {
			if(p == string) { return NULL; } //一文字も読めなかった。
			break;
		}
		if(*p++ == '\n') { break; } //'\n'を\0に置き換えないので'\n'の場合もp++を行う。
	}
	*p = '\0';
	return string;
}
/*--------------------------------------------------------------------------*/
char* gets(char* buffer) {
	char* p = buffer;
	for(;;) {
		if(fread(p, 1, 1, stdin) != 1) {
			if(p == buffer) { return NULL; } //一文字も読めなかった。
			break;
		}
		if(*p == '\n') { break; } //'\n'を'\0'に置き換えるので'\n'ならばp++を行わない。
		p++;
	}
	*p = '\0';
	return buffer;
}
/*--------------------------------------------------------------------------*/
int fputc(int c, FILE* stream) {
	if(fwrite(&c, 1, 1, stream) != 1) { return EOF; }
	return (unsigned char)c; //fputc(),putc(),putchar()はunsigned charとして書き込まれた文字をintにキャストして返す。(「Linux Programmer's Manual - PUTS」より)
}
/*--------------------------------------------------------------------------*/
//{{2014/03/10削除:getchar,getc,putchar,putcをマクロ化し、関数を削除しました。/clip/include/stdio.h を参照してください。
//int putc(int c, FILE* stream) {
//	return fputc(c, stream);
//}
//int putchar(int c) {
//	return putc(c, stdout);
//}
//}}2014/03/10削除:getchar,getc,putchar,putcをマクロ化し、関数を削除しました。/clip/include/stdio.h を参照してください。
/*--------------------------------------------------------------------------*/
int fputs(const char* string, FILE* stream) {
	int c;
	while((c = *string++)) {
		if(fwrite(&c, 1, 1, stream) != 1) { return EOF; }
	}
	return 0; //成功時は書き込んだバイト数ではなく0を返す。
}
/*--------------------------------------------------------------------------*/
int puts(const char* string) {
	if((fputs(string, stdout) < 0) ||
	   (fputc('\n'  , stdout) < 0)) { return EOF; }
	return 0; //成功時は書き込んだバイト数ではなく0を返す。
}
/*--------------------------------------------------------------------------*/
int remove(const char* filename) {
	if(pceFileDelete(filename) != 0) { return -1; }
	return 0;
}
/*--------------------------------------------------------------------------*/
int feof(FILE* stream) {
	IO* io = io_file(stream);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
	return io->flag & IO_EOF;
}
/*--------------------------------------------------------------------------*/
int ferror(FILE* stream) {
	IO* io = io_file(stream);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
	return io->flag & IO_ERR;
}
/*--------------------------------------------------------------------------*/
void clearerr(FILE* stream) {
	IO* io = io_file(stream);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
	io->flag &= ~(IO_EOF|IO_ERR);
}
/*--------------------------------------------------------------------------*/
void perror(const char* string) {
	//□「MSDN ライブラリ Microsoft Visual Studio 6.0 - perror」より:
	//│最初にstringが表示され、次にコロン、さらにエラーを起こした最後のライブラリ呼び出しのシステムエラーメッセージと続き、最後に改行文字が出力されます。
	//│stringをNULLポインタまたはNULL文字列へのポインタにすると、perror関数はシステムエラーメッセージだけを表示します。
	if(string && string[0]) { fprintf(stderr, "%s: ", string); }
	fprintf(stderr, "%s\n", strerror(errno));
}
/*--------------------------------------------------------------------------*/
//{{2015/08/02削除:fprintf(),printf(),vfprintf(),vprintf()を、clipio.c⇒clipprnt.cへ移動しました。Win32プロジェクトでも、doprnt()を利用したprintf()等を利用したいからです。clipio.cはP/ECEに依存しているのでWin32プロジェクトに含められませんが、clipprnt.cならばWin32プロジェクトに含められます。
//int fprintf(FILE* stream, const char* format, ...) {
//	int retval;
//	va_list argptr;
//	va_start(argptr, format);
//	retval = vfprintf(stream, format, argptr);
//	va_end(argptr);
//	return retval;
//}
///*--------------------------------------------------------------------------*/
//int printf(const char* format, ...) {
//	int retval;
//	va_list argptr;
//	va_start(argptr, format);
//	retval = vprintf(format, argptr);
//	va_end(argptr);
//	return retval;
//}
///*--------------------------------------------------------------------------*/
//int vfprintf(FILE* stream, const char* format, va_list argptr) {
//   //{{2015/05/01変更:vfprintf()が_def_vbuff[]を利用するのをやめて、代りにvasprintf()を利用するように変更しました。
//   //	//文字列展開バッファとして_def_vbuff[]を利用しています。
//   //	return fwrite(_def_vbuff, 1, vsprintf(_def_vbuff, format, argptr), stream);
//   //↓2015/05/01変更:vfprintf()が_def_vbuff[]を利用するのをやめて、代りにvasprintf()を利用するように変更しました。
//	char* s;
//	int n = vasprintf(&s, format, argptr);
//	if(n < 0) { DIE(); }
//	n = fwrite(s, 1, n, stream);
//	free(s);
//	return n;
//   //}}2015/05/01変更:vfprintf()が_def_vbuff[]を利用するのをやめて、代りにvasprintf()を利用するように変更しました。
//}
///*--------------------------------------------------------------------------*/
//int vprintf(const char* format, va_list argptr) {
//	return vfprintf(stdout, format, argptr);
//}
//}}2015/08/02削除:fprintf(),printf(),vfprintf(),vprintf()を、clipio.c⇒clipprnt.cへ移動しました。Win32プロジェクトでも、doprnt()を利用したprintf()等を利用したいからです。clipio.cはP/ECEに依存しているのでWin32プロジェクトに含められませんが、clipprnt.cならばWin32プロジェクトに含められます。
/****************************************************************************
 *	標準Ｃライブラリの補助 -> clippce.h					//{{2015/01/07追記:fileno(),isatty()の宣言を、clippce.h⇒include/stdio.hへ移動しました。実装はclipio.cにあります。isatty()は本当はio.hかunistd.hで宣言すべきなのですが、P/ECEにはio.hもunistd.hも無いのでinclude/stdio.hで宣言することにしました。}}
 ****************************************************************************/
int fileno(FILE* stream) {
    //{{2015/03/14修正:fileno()には標準入出力も指定可能とするためにio_file()を使わず明示的なキャストを行うように修正した。
    //	IO* io = io_file(stream);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
    //↓2015/03/14修正:fileno()には標準入出力も指定可能とするためにio_file()を使わず明示的なキャストを行うように修正した。
	IO* io = ((IO*)stream ? /*GCC拡張機能*/ : (IO*)-1);	//ストリームポインタをファイルI/O構造体ポインタにキャストする。標準入出力も指定可能とするためにio_file()は使用しない。ただしNULLポインタは(-1)に変換しておく。ここで変換しておかないとこの後の検索でios[]のNULLエントリに一致してしまうから。
    //}}2015/03/14修正:fileno()には標準入出力も指定可能とするためにio_file()を使わず明示的なキャストを行うように修正した。
	int fd;
	//ファイルI/O構造体ポインタ配列を検索して…
	for(fd = 0; fd < MAX_IO; fd++) {
		//指定されたストリームに一致するファイル記述子が見つかったら、指定されたストリームに一致したファイル記述子を返します。
		if(ios[fd] == io) { return fd; }
	}
	//指定されたストリームに一致するファイル記述子が見つからなければ…
	DIE();/*return -1;*/	//アプリケーションのバグの可能性が高いので、エラー値を返すのではなくエラー停止する事にした。
}
/*--------------------------------------------------------------------------*/
int isatty(int fd) {
	//指定されたファイル記述子が、端末を参照するファイル記述子ならば、1を返します。
	//そうでなければ、0を返します。
//{{2012/10/30:生成コードが(僅かながら)小さくなるよう、条件式を変更しました。
//	return (fd == STDIN_FILENO ) ||	//標準入力
//	       (fd == STDOUT_FILENO) ||	//標準出力
//	       (fd == STDERR_FILENO);	//標準エラー出力
//↓2012/10/30:生成コードが(僅かながら)小さくなるよう、条件式を変更しました。
	return (unsigned)fd <= STDERR_FILENO;	//標準入力,標準出力,標準エラー出力
//}}2012/10/30:生成コードが(僅かながら)小さくなるよう、条件式を変更しました。
}
/****************************************************************************
 *	標準Ｃライブラリの補助 -> include/sys/stat.h
 ****************************************************************************/
int stat(const char* path, struct stat* buf) {
	FILEACC fa;
	if(pceFileOpen(&fa, path, FOMD_RD)) {
		errno = ENOENT;	//No such file or directory
		return -1;	//stat()はファイルの存在チェックに利用する事が多く、失敗してもアプリケーションバグの可能性は低いので、DIE()せずにエラー値を返すのが良いだろう。
	}
	memset(buf, 0, sizeof(struct stat));
	buf->st_mode    = S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
	buf->st_nlink   = 1;
	buf->st_size    = fa.fsize;
	buf->st_blksize = BLOCKSIZE;			/*{{2014/09/07変更:stat.st_blksize,stat.st_blocksを追加しました。}}*/
	buf->st_blocks  = (buf->st_size + 511) >> 9;	/*{{2014/09/07変更:stat.st_blksize,stat.st_blocksを追加しました。}}*/
	pceFileClose(&fa);
	return 0;
}
/*--------------------------------------------------------------------------*/
int fstat(int fd, struct stat* buf) {
	memset(buf, 0, sizeof(struct stat));
	buf->st_nlink   = 1;
	buf->st_blksize = BLOCKSIZE;				/*{{2014/09/07変更:stat.st_blksize,stat.st_blocksを追加しました。}}*/	//標準入出力もst_blksizeを格納する仕様のようです。(Web上のLinuxの情報より)
	if(isatty(fd)) {	//標準入出力ならば…
		buf->st_mode    = S_IFCHR;
	//不要	buf->st_blocks  = (buf->st_size + 511) >> 9;	/*{{2014/09/07変更:stat.st_blksize,stat.st_blocksを追加しました。}}*/	//標準入出力はst_blocks=0。既にクリア済みなので明示的な格納処理は不要です。
	} else {		//通常ファイルならば…
		IO* io = io_file((FILE*)ios[fd]);	//不正なファイル記述子が指定された場合はこの中でエラー停止する。
		buf->st_mode    = S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
		buf->st_size    = io->fa.fsize;
		buf->st_blocks  = (buf->st_size + 511) >> 9;	/*{{2014/09/07変更:stat.st_blksize,stat.st_blocksを追加しました。}}*/
	}
	return 0;
}
/****************************************************************************
 *	標準Ｃライブラリの補助 -> include/sys/statvfs.h
 ****************************************************************************/
int statvfs(const char* path, struct statvfs* buf) {
	memset(buf, 0, sizeof(struct statvfs));
	{
		const pffsMASTERBLOCK* msb = (pffsMASTERBLOCK*)pceSystemGetInfo()->pffs_top;
		{
			const DIRECTORY *pdir = msb->dir, *pdir_end = msb->dir + MAXDIR;
			do {
				if((pdir->name[0] ==  0) ||	//┐\usr\PIECE\sysdev\pcekn\file.c
				   (pdir->name[0] == -1)) {	//┘Search() 参照
					buf->f_ffree++;
				}
			} while(++pdir < pdir_end);
		}
		{
			const FAT *pfat = msb->fat, *pfat_end = msb->fat + MAXFAT;
			do {
				switch(pfat->chain) {
				case FAT_INVALID:		//無効セクタ(512KB版と2MB版の差分)
				case FAT_SYSTEM:		//マスターブロック
					/** no job **/
					break;
				case FAT_FREE:			//空きセクタ
					buf->f_bfree++;
					/*FALLTHRU*/
				case FAT_END:			//チェインの終端
				default:			//チェインの途中
					buf->f_blocks++;
					break;
				}
			} while(++pfat < pfat_end);
		}
	}
	buf->f_bsize	= BLOCKSIZE;
	buf->f_frsize	= BLOCKSIZE;
//	buf->f_blocks	= 上の処理で設定済み
//	buf->f_bfree	= 上の処理で設定済み
	buf->f_bavail	= buf->f_bfree;
	buf->f_files	= MAXDIR;
//	buf->f_ffree	= 上の処理で設定済み
	buf->f_favail	= buf->f_ffree;
//不要	buf->f_fsid	= 0;
//不要	buf->f_flag	= 0;
	buf->f_namemax	= FIELD_SIZEOF(DIRECTORY, name) - 1;
	return 0;
}
/*--------------------------------------------------------------------------*/
int fstatvfs(int fd, struct statvfs* buf) {
	return statvfs("/", buf);
}
/****************************************************************************
 *	標準Ｃライブラリの補助 -> include/stdio.h
 ****************************************************************************/
//{{2015/03/13削除:clipdfrg.cを変更して整列前のセクタ並びが昇順でなくても整列出来るようにしたのでこの注意事項は不要になりました。truncate()やftruncate()とfile_defrag()を併用しても安全です。
////■重要■━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
////┃truncate()やftruncate()を使って伸張したファイルは、FATレイアウトが以下の条件に反する可能性が有ります。　　　　　　　　　　　　┃
////┃⇒clipdfrg.c⇒defrag_sub()⇒『自分自身のチェインに含まれている場合、それは後方であるはずです。（前方は整列済みのはずだから）』┃
////┃truncate()やftruncate()を使って伸張したファイルに対しては、file_defrag()を使用しないようにして下さい。　　　　　　　　　　　　┃
////┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
//}}2015/03/13削除:clipdfrg.cを変更して整列前のセクタ並びが昇順でなくても整列出来るようにしたのでこの注意事項は不要になりました。truncate()やftruncate()とfile_defrag()を併用しても安全です。
//■「Linux Programmer's Manual - TRUNCATE」(http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/truncate.2.html)より:
//□説明
//│truncate()はpathで指定されるファイルを、ftruncate()はfdで参照されるファイルを、lengthバイトの長さになるように延長する、もしくは切り詰める。
//│もし切り詰める前のファイルがlengthより長ければ、lengthバイトを越える部分のデータは失われる。
//│もし切り詰める前のファイルがlengthより短かければ、伸張される。
//│伸張された部分を読んだ場合はヌルバイト('\0')の列が返される。							←当実装ではこの処理を省略した。伸張した部分は不定値となる。
//│ファイルオフセットは変更されない。
//│大きさが変更されると、ファイルのst_ctimeとst_mtimeフィールド(それぞれ最終状態変更時刻、最終修正時刻)が更新される。	←P/ECEでは関係無い。
//│また、set-user-IDとset-group-IDの許可ビットがクリアされるかもしれない。						←P/ECEでは関係無い。
//│ftruncate()の場合、ファイルは書き込み用に開いていなければならない。							←当実装ではこの検査を省略した。読み込み用に開いていても動作する。
//│truncate()の場合、ファイルは書き込み可能でなければならない。							←P/ECEでは関係無い。
//□返り値
//│成功した場合は0が返される。
//│エラーの場合は-1が返され、errnoが適切に設定される。									←当実装はエラーが発生した場合はエラー停止し処理を返さない。
#if 0 //「フルスクラッチ版」
int truncate(const char* path, /*off_t*/long length) {
	const SYSTEMINFO* const sip = pceSystemGetInfo();
	pffsMASTERBLOCK *msb, *pmb;
	DIRECTORY* pdir;
	int i, sct, sctcnt, fatcnt;
	unsigned short* psct;
	//ファイル名が空文字列でなく長さが0未満でない事を最初に検査しておく。最初に検査しておく事でこの後の処理をいくらか単純にできる。
	if((*path == 0) || (length < 0)) { DIE(); }
	//管理ブロックのコピーを作成する。以下の処理はこのコピーを変更し、全て成功した後でフラッシュメモリ上の管理ブロックへ書き戻す。
	msb = (pffsMASTERBLOCK*)sip->pffs_top;
	pmb = malloc(BLOCKSIZE);
	if(!pmb) { DIE(); }	//メモリ不足
	memcpy(pmb, msb, BLOCKSIZE);
	//指定されたファイル名のディレクトリエントリを検索する。
	for(i = 0; i < MAXDIR; i++) {
		pdir = &pmb->dir[i];
		if(//(*pdir->name != '\x00') &&		//0x00で塗りつぶされた無効エントリ。最初にpathが空文字列で無い事を確認済みなので偶然一致する危険は無くここで検査する必要は無い。
		   //(*pdir->name != '\xFF') &&		//0xFFで塗りつぶされた無効エントリ。'\xFF'x26文字のpathが渡されたら一致する可能性も有るが意図的に行わない限り有り得ないので検査しなくても良いだろう。
		   !strcmp(pdir->name, path)) { break; }
	}
	if(i == MAXDIR) { DIE(); }	//指定されたファイル名のディレクトリエントリが存在しない。
	//変更前のサイズと変更後のサイズが異なるならば…
	if(pdir->size != length) {
		//変更後のサイズを格納する。
		pdir->size = length;
		//変更後の必要セクタ数を求める。
		sctcnt = (length + (BLOCKSIZE - 1)) / BLOCKSIZE;
		//FATの数を求める。以下のようになるはずです:
		//│512KB版P/ECE (0xC80000-0xC28000)/0x1000= 88個
		//│  2MB版P/ECE (0xE00000-0xC28000)/0x1000=472個
		fatcnt = (sip->pffs_end - sip->pffs_top) / BLOCKSIZE;
		//ディレクトリエントリからFATチェインのフィールドをたどって…
		psct = &pdir->chain;
		while(--sctcnt >= 0) {	//必要セクタ数が無くなったら抜ける。もし必要セクタ数=0(サイズ=0)だったならば一度もループ内を通らない。
			//次のセクタ番号を取得する。
			sct = *psct;
			//次のセクタ番号が無ければ…
			if(sct == FAT_END) {	//変更前のFATチェインの終端,又は,変更後のFATチェインを延長中
				//空きFATエントリを探す。
				for(sct = 0; sct < fatcnt; sct++) {
					if(pmb->fat[sct].chain == FAT_FREE) { break; }
				}
				if(sct == fatcnt) { DIE(); }	//空きFATエントリが無い。
				//FATエントリが空きでなくなった事をマークする。このセクタが変更後のFATチェインの一時的な終端となる。さらに延長した場合は次のループのこの下の処理で上書きされる。
				pmb->fat[sct].chain = FAT_END;
				//変更後のFATチェインを延長する。
				*psct = sct;
			}
			//次のFATチェインのフィールドへのポインタを取得する。
			psct = &pmb->fat[sct].chain;
		}
		//最後のFATチェインの次のセクタ番号を取り出しておく。
		sct = *psct;
		//変更後のFATチェインを終端する。変更後より変更前のFATチェインの方が短いか同じだった場合は既に(*psct==FAT_END)になっているのでダミー処理となる。
		*psct = FAT_END;
		//変更後のFATチェインよりも変更前のFATチェインの方が長かったならば…
		while(sct != FAT_END) {	//初回の判定は「!(変更後より変更前のFATチェインの方が短いか同じ)」。二回目以降の判定は「!(変更前のFATチェインの終端)」。
			//次のFATチェインのフィールドへのポインタを取得する。
			psct = &pmb->fat[sct].chain;
			//次のセクタ番号を取り出しておく。
			sct = *psct;
			//次のセクタを開放する。
			*psct = FAT_FREE;
		}
		//変更が完了した管理ブロックのコピーを、フラッシュメモリ上の管理ブロックへ書き戻す。
		i = turbo(0);	//pceFlashErase/Write()は48MHzでは動作しないので、確実に通常モードにしておく。
		if(pceFlashErase(msb) || pceFlashWrite(msb, pmb, BLOCKSIZE)) { DIE(); }
		turbo(i);	//元のスピードに戻す。
		//'現在開いているファイルI/O構造体にも反映する処理'を行う。
		//fwrite()やfflush()経由で呼び出されるpceFileWriteSct()が、io->fa.fsizeやio->fa.chainを参照するので、この処理は必須である。	←←←←■重要■
		for(i = 0; i < MAX_IO; i++) {
			IO* io = ios[i];
			if(io && !strcmp(io->filename, pdir->name)) {
				io->fa.fsize = pdir->size;
				io->fa.chain = pdir->chain;
				//同じファイルを複数開いている可能性が有るので処理を継続する。(稀だとは思うが)
			}
		}
	}
	//管理ブロックのコピーを開放する。
	free(pmb);
	return 0;
}
#else //「pceFileCreate()利用版」
// * Sat Mar 14 21:29:01 JST 2015 Naoyuki Sawa
// - truncate()の「pceFileCreate()利用版」を作成しました。
//   「フルスクラッチ版」を作った後に気付いたのですが、処理内容がほとんどpceFileCreate()と同じで、pceFileCreate()を利用して作る方がコードサイズを節約できました。
// - 「フルスクラッチ版」と「pceFileCreate()利用版」のどちらを使用しても構いません。現在のところ「pceFileCreate()利用版」を選択しておく事にしました。
//   「フルスクラッチ版」も動作検証済みです。今後の参考として、「フルスクラッチ版」のコードも残しておく事にしました。
int truncate(const char* path, /*off_t*/long length) {
	FILEACC fa;
	int i;
	if(pceFileOpen(&fa, path, FOMD_WR) ||			//1. ファイルが存在する事を確認する。truncate()は存在しないファイルを伸縮する事はできない仕様である。いきなりpceFileCreate()を呼び出すとファイルが存在しない場合に作成してしまうのでtruncate()の仕様に反してしまう。
	   pceFileClose(&fa) ||					//2. 1.で開いたファイルをすぐに閉じる。
	   pceFileCreate(path, length) ||			//3. ファイルを伸縮する。
	   pceFileOpen(&fa, path, FOMD_WR)) { return -1; }	//4. ファイルを伸縮した後の情報を取得するためにファイルを開く。
	//'現在開いているファイルI/O構造体にも反映する処理'を行う。
	//fwrite()やfflush()経由で呼び出されるpceFileWriteSct()が、io->fa.fsizeやio->fa.chainを参照するので、この処理は必須である。	←←←←■重要■
	for(i = 0; i < MAX_IO; i++) {
		IO* io = ios[i];
		if(io && !strcmp(io->filename, path)) {
			io->fa.fsize = fa.fsize;
			io->fa.chain = fa.chain;
			//同じファイルを複数開いている可能性が有るので処理を継続する。(稀だとは思うが)
		}
	}
	return pceFileClose(&fa);				//5. 4.で開いたファイルを閉じる。
}
#endif//
/*--------------------------------------------------------------------------*/
int ftruncate(int fd, /*off_t*/long length) {
	IO* io = io_file((FILE*)ios[fd]);	//標準入出力,又は,不正なファイル記述子が指定された場合はこの中でエラー停止する。
	return truncate(io->filename, length);
}
/****************************************************************************
 *	標準エラー出力,標準入力
 ****************************************************************************/
//UCS_RXDONEとUCS_TXDONEは、pceUSBCOMGetStat()を呼ぶとクリアされてしまうので、蓄積するための変数が必要である。
//別の目的で呼び出したpceUSBCOMGetStat()によって、UCS_RXDONEとUCS_TXDONEがクリアされてしまうのを回避するため、
//pceUSBCOMGetStat()を呼び出すたびに、ucs変数にUCS_RX*とUCS_TX*を蓄積するようにした。
static int ucs;	//UCS_PCSTATとUCS_PISTATは即反映、UCS_RX*とUCS_TX*は蓄積
/*--------------------------------------------------------------------------*/
//void stderr_open() {
//	USBCOMINFO uci = { "stderr" };
//	//標準エラー出力を開きます。
//	pceUSBCOMSetup(&uci);
//	//Bボタンが押されていたら、標準エラー出力モニタの接続を待ちます。
//	if(pcePadGetDirect() & PAD_B) {
//		unsigned char* old_vbuff;
//		pceLCDDispStop();
//		old_vbuff = pceLCDSetBuffer(_def_vbuff);
//		pceLCDDispStart();
//		pceFontSetType(0x80);
//		pceFontSetTxColor(3);
//		pceFontSetBkColor(0);
//		pceFontSetPos(0, (DISP_Y - 10 * 5) / 2);
//		//             ０１２３４５６７８９０１２
//		pceFontPutStr(" +----------------------+\n"
//		              " |標準エラー出力モニタの|\n"
//		              " | 接続を待っています… |\n"
//		              " |(Ａボタンでキャンセル)|\n"
//		              " +----------------------+");
//		pceLCDTrans();
//		while(pcePadGetDirect() & PAD_A) { /*開放待ち*/ }
//		for(;;) {
//			if(pceUSBCOMGetStat() & UCS_PCSTAT) { break; } //接続
//			if(pcePadGetDirect() & PAD_A) { break; } //キャンセル
//		}
//		while(pcePadGetDirect() & PAD_A) { /*開放待ち*/ }
//		pceLCDSetBuffer(old_vbuff);
//		stderr_write("接続完了\n", 9); //標準エラー出力モニタに表示
//	}
//}
//↓2003/11/22 stderr_open()はアプリケーションから明示的に呼ぶようにしたので、stderr_open()が呼ばれた場合は常に出力モニタの接続を待つように変更しました。
void stderr_open(int timeout) {
	USBCOMINFO uci = { "stderr" };
	unsigned char* old_vbuff;
	int T0, T1;
	//標準エラー出力を開きます。
	pceUSBCOMSetup(&uci);
	//標準エラー出力モニタの接続待ち。
//不要	pceLCDDispStop(); {{2005/06/26 Naoyuki Sawa 不要なので削除しました}}
	old_vbuff = pceLCDSetBuffer(_def_vbuff);
//不要	pceLCDDispStart();	//{{2014/12/31削除:cstart.cのpceAppInit00()でpceLCDDispStart()を呼び出し、液晶画面の表示を開始するようにしました。}}
	pceFontSetType(0x80);
	pceFontSetTxColor(3);
	pceFontSetBkColor(0);
	pceFontSetPos(0, (DISP_Y - 10 * 5) / 2);
	//             ０１２３４５６７８９０１２
	pceFontPutStr(" +----------------------+\n"
	              " |標準エラー出力モニタの|\n"
	              " | 接続を待っています… |\n"
	              " |(Ａボタンでキャンセル)|\n"
	              " +----------------------+");
	pceLCDTrans();
	while(pcePadGetDirect() & PAD_A) { /*開放待ち*/ }
	T0 = pceTimerGetCount();
	for(;;) {
		if(pceUSBCOMGetStat() & UCS_PCSTAT) { break; } //接続
		if(pcePadGetDirect() & PAD_A) { break; } //キャンセル
		if(timeout >= 0) {
			T1 = timeout * 1000 - (pceTimerGetCount() - T0);
			if(T1 <= 0) { break; } //タイムアウト
			pceFontSetPos((DISP_X - 5 * 9) / 2, DISP_Y - 16);
			//             012345678
			pceFontPrintf("あと%3d秒", (T1 + 999) / 1000);
			pceLCDTrans();
		}
	}
	while(pcePadGetDirect() & PAD_A) { /*開放待ち*/ }
	pceLCDSetBuffer(old_vbuff);
//	stderr_write("接続完了\n", 9); //標準エラー出力モニタに表示(キャンセル時は/dev/nulへ)	⇒{{2014/03/13削除:接続完了時のメッセージが解析の邪魔になる事が有るため削除しました。}}
	//標準入力の、初回の受信要求を発行します。
	stdin_read(NULL, 0);
}
/*--------------------------------------------------------------------------*/
void stderr_close() {
	USBCOMINFO uci = { "" };
	//標準エラー出力を閉じます。
	// - 単にpceUSBCOMStop()を呼んだだけでは、USBCOMが切断できません。
	//   PCとの接続が確立していた場合、pceUSBCOMStop()は切断を行わないからです。
	//   また、シグネチャもクリアされません。(以上、BIOS 1.18で確認)
	// - この問題を回避するには、pceUSBCOMSetup()を使ってUSBCOMを切断します。
	//   pceUSBCOMSetup()ならば、無条件にPC側の接続フラグをクリアするので、
	//   いったんpceUSBCOMSetup()を使って：
	//   │mystat(P/ECE側の接続フラグ) -> 1
	//   │pcstat(PC側の接続フラグ)    -> 0
	//   │シグネチャ                  -> ""
	//   としておき、あらためてpceUSBCOMStop()を呼んで：
	//   │mystat(P/ECE側の接続フラグ) -> 0
	//   とします。
	pceUSBCOMSetup(&uci);
	pceUSBCOMStop();
	ucs = 0;	//蓄積変数をクリア
}
/*--------------------------------------------------------------------------*/
void stderr_write(const void* ptr, int len) {
	//0バイトで送信要求を発行すると、絶対にUCS_TXDONEが立たないので、(usbcom.c: txsub() 参照)
	//あらかじめ除外しておきます。
	if(len <= 0) { return; }
	//PCとの接続が確立していたら…
	// - PISTATが1でなければPCSTATも1になりません。(usbcom.c: usbcom() case13: (USBCOM Open) 参照)
	//   PCSTATが1である間PISTATは0に戻りません。(usbcom.c: pceUSBCOMStop() 参照)
	//   従って、PCSTATが1ならPISTATも必ず1です。PISTATを検査する必要はありません。
	ucs = pceUSBCOMGetStat() | (ucs & (UCS_RXSTAT|UCS_TXSTAT));	//UCS_PCSTATとUCS_PISTATは即反映、UCS_RX*とUCS_TX*は蓄積
	if(ucs & UCS_PCSTAT) {
		//送信要求を発行します。
		pceUSBCOMStartTx(ptr, len);
		ucs &= ~UCS_TXSTAT;	//UCS_TX*をクリア
		//送信が完了するまで待ちます。
		for(;;) {
			ucs = pceUSBCOMGetStat() | (ucs & (UCS_RXSTAT|UCS_TXSTAT));	//UCS_PCSTATとUCS_PISTATは即反映、UCS_RX*とUCS_TX*は蓄積
			if(!(ucs & UCS_PCSTAT)) { break; }	//PCとの接続が切れた。
			if(  ucs & UCS_TXDONE ) { break; }	//送信が完了した。
		}
	}
}
/*--------------------------------------------------------------------------*/
int stdin_read(void* _buf, int bufsize) {
#define STDIN_RXSIZE (127/*調整可*/ + 1/*nul*/)
	static char*   rxbuf/*[STDIN_RXSIZE]*/;
	static char* keepbuf/*[STDIN_RXSIZE]*/;
	static int i_keepbuf;
	//
	char* buf = _buf;
	int i_buf = 0, c;
	//PCとの接続が確立していたら…
	// - PISTATが1でなければPCSTATも1になりません。(usbcom.c: usbcom() case13: (USBCOM Open) 参照)
	//   PCSTATが1である間PISTATは0に戻りません。(usbcom.c: pceUSBCOMStop() 参照)
	//   従って、PCSTATが1ならPISTATも必ず1です。PISTATを検査する必要はありません。
	ucs = pceUSBCOMGetStat() | (ucs & (UCS_RXSTAT|UCS_TXSTAT));	//UCS_PCSTATとUCS_PISTATは即反映、UCS_RX*とUCS_TX*は蓄積
	if(ucs & UCS_PCSTAT) {
		//初回に受信バッファと保持バッファを確保します。こうすれば、stdin_read()が呼ばれなければ余分なメモリを消費しません。
		if(!rxbuf) {
			rxbuf = calloc(2, STDIN_RXSIZE);
			if(!rxbuf) { DIE(); }	//メモリ不足
			keepbuf = rxbuf + STDIN_RXSIZE;
		}
		//書き込みポインタが、書き込みバッファのバイト数に達するまで…
		while(i_buf < bufsize) {
			//保持バッファの文字が、まだ残っていたら…
			if((c = keepbuf[i_keepbuf])) {
				//保持バッファから読み出した文字を、書き込みバッファに格納します。
				buf[i_buf++] = c;
				//保持バッファの読み出し位置を進めます。
				i_keepbuf++;
			//保持バッファの文字が、もう残っていなければ…
			} else {
				//前回の受信要求が完了していなければ…
				if(!(ucs & UCS_RXDONE)) { break; }	//受信要求が未発行ならば、この下で初回の受信要求を発行する。
				//前回の受信要求が完了していたら…
				ucs &= ~UCS_RXSTAT;	//UCS_RX*をクリア			//この下で次回の受信要求を発行する。
				//受信内容を保持バッファに保持します。受信バッファは、この後の受信要求発行で破壊されるからです。
				strcpy(keepbuf, rxbuf);
				//保持バッファの読み出し位置を、先頭に戻します。
				i_keepbuf = 0;
			}
		}
		//受信要求が発行されていなければ…
		if(!(ucs & (UCS_RXWAIT|UCS_RXING))) { //受信待機中,又は,受信中
			//次回のために、受信バッファへの受信要求を発行しておく。
			pceUSBCOMStartRx(rxbuf, STDIN_RXSIZE - 1/*nul*/);
		}
	}
	//書き込みバッファに格納したバイト数を返します。
	return i_buf;
#undef  STDIN_RXSIZE
}
