Unix C, Day03

====================
第三课  文件系统(上)
====================
一、系统调用
------------
            应用程序 -----------+
               |                |
               v                |如果应用
             各种库             |程序是采
(C/C++标准库、Shell命令和脚本、 |用C/C++语言
        X11图形程序及库)        |那么就可以直接进行系统调用,因为操作系统以C函数形式提供系统调用
               |                |
               v                |
            系统调用 <----------+
(内核提供给外界访问的接口函数,
调用这些函数将使进程进入内核态)
               |
               v
              内核
    (驱动程序、系统功能程序)
1. Unix/Linux大部分系统功能是通过系统调用实现的。 如open/close。
2. Unix/Linux的系统调用虽已被封装成C函数的形式, 但它们并不是标准C的一部分。
3. 标准库函数大部分时间运行在用户态, 但部分函数偶尔也会调用系统调用,进入内核态。 如malloc/free。
4. 程序员自己编写的代码也可以调用系统调用, 与操作系统内核交互,进入内核态。 如brk/sbrk/mmap/munmap。
5. 系统调用在内核中实现,其外部接口定义在C库中。 该接口的实现借助软中断进入内核。
两个实用的工具:
(1)time命令:测试运行时间
real : 总执行时间
user : 用户空间执行时间
sys  : 内核空间执行时间
举例:
# time ./a.out
fd1 = 3, fd2 = 4
real    0m0.001s
user    0m0.000s
sys    0m0.000s
(2)strace命令:跟踪系统调用
二、一切皆文件
--------------
1. Linux环境中的文件具有特别重要的意义, 因为它为操作系统服务和设备, 提供了一个简单而统一的接口。 在Linux中,(几乎)一切皆文件。
2. 程序完全可以象访问普通磁盘文件一样, 访问串行口、网络、打印机或其它设备。
3. 大多数情况下只需要使用五个基本系统调用: open/close/read/write/ioctl, 即可实现对各种设备的输入和输出。
4. Linux中的任何对象都可以被视为某种特定类型的文件, 可以访问文件的方式访问之。
5. 广义的文件
1) 目录文件
# vim .
2) 设备文件
A. 控制台:/dev/console
B. 声卡:/dev/audio
C. 标准输入输出:/dev/tty
D. 空设备:/dev/null
例如:
# cat /dev/tty
Hello, World !
Hello, World !
# echo Hello, World ! > /dev/tty
Hello, World !
# echo Hello, World ! > test.txt
# cat test.txt
Hello, World !
# cat /dev/null > test.txt
# cat test.txt
# find / -name perl 2> /dev/null
三、文件相关系统调用
--------------------
open   - 打开/创建文件
creat  - 创建空文件
close  - 关闭文件
read   - 读取文件
write  - 写入文件
lseek  - 设置读写位置
fcntl  - 修改文件属性
unlink - 删除硬链接
rmdir  - 删除空目录
remove - 删除硬链接(unlink)或空目录(rmdir)
注意:
1. 如果被unlink/remove删除的是文件的最后一个硬链接, 并且没有进程正打开该文件, 那么该文件在磁盘上的存储区域将被立即标记为自由。 反之,如果有进程正打开该文件, 那么该文件在磁盘上的存储区域, 将在所有进程关闭该文件之后被标记为自由。
  a -> +-----+
X b -> | ... |
X c -> +-----+
2. 如果被unlink/remove删除的是一个软链接文件, 那么仅软链接文件本身被删除,其目标不受影响。
       +-----+
  a -> | ... |
       +-----+
       +-----+
X b -> |  a  |
       +-----+
       +-----+
X c -> |  a  |
       +-----+
四、文件描述符(file descriptor)
--------------
1. 文件描述符是一个非负的整数。
2. 文件描述符可以唯一表示一个打开的文件。
3. 文件描述符由系统调用(open)返回, 被内核空间(后续系统调用)引用。
4. 内核缺省为每个进程打开三个文件描述符:
0 - 标准输入
1 - 标准输出
2 - 标准出错
在标准C的stdio.h中定义了三个FILE*来指向三个FILE结构体,三个结构体分别维护着三个文件描述符0、1、2。三个FILE*他们是
0 - 标准输入 : stdin
1 - 标准输出 : stdout
2 - 标准出错 : stderr
在unistd.h中被定义为如下三个宏:
#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
/*
 * stdin stdout stderr练习
 * 都stdin stdout stderr都是FILE*类型
 * */
#include <stdio.h>

int main (void) {
	int data;
	fscanf (stdin, "%d", &data);//stdin stdout stderr都是FILE*类型的
	fprintf (stdout, "标准输出:%d\n", data);//标准输出设置有缓冲
	fprintf (stderr, "标准错误:%d\n", data);//标准错误没有设置缓冲
	return 0;
}


# a.out 0<i.txt 1>o.txt 2>e.txt



5. 文件描述符的范围介于0到OPEN_MAX之间, 传统Unix中OPEN_MAX宏被定义为63, 现代Linux使用更大的上限。
6、打开(open)一个文件涉及的内核数据结构

p.s.   linux系统中可以通过ls -i可查看文件的i节点号。 i节点记录了文件的属性和数据在磁盘上的存储位置。 目录也是文件,存放路径和i节点号的映射表。



 
五、open/creat/close
--------------------
#include <fcntl.h>
int open (/*open函数的原型: int open(const char*, int, ...)*/
    const char* pathname, // 路径
    int         flags,    // 模式
    mode_t      mode      // 权限(仅创建文件有效)
);  // 创建/读写文件时都可用此函数
int open (
    const char* pathname, // 路径
    int         flags     // 模式
);  // 常用于读写文件
int creat (
    const char* pathname, // 路径
    mode_t      mode      // 权限
);  // 常用于创建文件
成功返回文件描述符,失败返回-1。
flags为以下值的位或:
O_RDONLY   - 只读。\
                    |
O_WRONLY   - 只写。 > 只选一个
                    |
O_RDWR     - 读写。/
O_APPEND   - 追加。
O_CREAT    - 创建,不存在即创建(已存在即直接打开,
             并保留原内容,除非...),
             有此位mode参数才有效。
O_EXCL     - 排斥,已存在即失败。\
                                  > 只选一个,
O_TRUNC    - 清空,已存在即清空  /  配合O_CREAT使用
             (有O_WRONLY/O_RDWR)。
O_NOCTTY   - 非控,若pathname指向控制终端,
             则不将该终端作为控制终端。
O_NONBLOCK - 非阻,若pathname指向FIFO/块/字符文件,
             则该文件的打开及后续操作均为非阻塞模式。
O_SYNC     - 同步,write等待数据和属性,
             被物理地写入底层硬件后再返回。
O_DSYNC    - 数同,write等待数据,
             被物理地写入底层硬件后再返回。
O_RSYNC    - 读同,read等待对所访问区域的所有写操作,
             全部完成后再读取并返回。
O_ASYNC    - 异步,当文件描述符可读/写时,
             向调用进程发送SIGIO信号。
open/creat所返回的一定是当前未被使用的, 最小文件描述符。 一个进程可以同时打开的文件描述符个数, 受limits.h中定义的OPEN_MAX宏的限制, POSIX要求不低于16,传统Unix是63,现代Linux是256。
#include <unistd.h>
int close (
    int fd // 文件描述符
);
成功返回0,失败返回-1。
范例:open.c
/*
 * open creat close文件控制函数练习
 *
 * */
#include <stdio.h>
#include <fcntl.h>//声明了open creat close

int main (void) {
	int fd1 = open ("open.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);//八进制数0666表示文件权限属性
	if (fd1 == -1) {
		perror ("open");
		return -1;
	}

	printf ("fd1 = %d\n", fd1);

	int fd2 = open ("open.txt", O_RDONLY);
	if (fd2 == -1) {
		perror ("open");
		return -1;
	}

	printf ("fd2 = %d\n", fd2);

	close (fd2);
	close (fd1);

	return 0;
}

操作系统可通过权限掩码umask(当前为0022), 屏蔽程序中open函数所创建文件的某些权限位。如: 
0666 (rw-rw-rw-) & ~0022 = 0644 (rw-r--r--)
creat函数是通过调用open实现的。
int creat (const char* pathname, mode_t mode) {
    return open (pathname,
        O_WRONLY | O_CREAT | O_TRUNC, mode);
}



六、write
---------
#include <unistd.h>
ssize_t write (
    int         fd,   // 文件描述符
    const void* buf,  // 缓冲区
    size_t      count // 期望写入的字节数
);
成功返回实际写入的字节数(0表示未写入),失败返回-1。
size_t: unsigned int,无符号整数
ssize_t: int,有符号整数
范例:write.c
/*
 * write()练习
 * */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

int main (void) {
	int fd = open ("write.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	const char* text = "Hello, World !";
	printf ("写入内容:%s\n", text);
	size_t towrite = strlen (text) * sizeof (text[0]);

	ssize_t written = write (fd, text, towrite);
	if (written == -1) {
		perror ("write");
		return -1;
	}

	printf ("期望写入%d字节,实际写入%d字节。\n", towrite, written);

	close (fd);

	return 0;
}


给write传递一个无效的文件描述符实参,程序运行期报无效的文件描述符。

/*
 * (1)给write传一个无效的文件描述符,程序运行期报错:write: Bad file descriptor
 *  (2) open/creat返回的一定是当前未使用的最小的文件描述符 
 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main (void) {
	const char* text = "Hello, World !";
	/*
	if (write (3, text, strlen (text)) == -1) {
	//给write传一个无效的文件描述符
	//程序运行期报错:write: Bad file descriptor
		perror ("write");
		return -1;
	}
	*/
	if (close (STDOUT_FILENO) == -1) {//关闭文件描述符STDOUT_FILENO对应的文件
		perror ("close");
		return -1;
	}

	if (creat ("bad.txt", 0644) == -1) {//打开bad.txt文件,返回当前未使用的最小的文件描述符
		perror ("creat");	    //这里会返回文件描述符1 , 恰好是上面刚弃用的文件描述符STDOUT_FILENO
		return -1;
	}

	printf ("%s", text);//此时将会把test输出在文件描述符为1的文件里面,也就是bad.txt

	return 0;
}
 


七、read
--------
#include <unistd.h>
ssize_t read (
    int    fd,   // 文件描述符
    void*  buf,  // 缓冲区
    size_t count // 期望读取的字节数
);
成功返回实际读取的字节数(0表示读取到文件尾),失败返回-1。
范例:read.c
/*
 * unistd.h read()函数练习
 * */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

int main (void) {
	int fd = open ("read.txt", O_RDONLY);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	char text[256];
	size_t toread = sizeof (text);

	ssize_t readed = read (fd, text, toread);
	if (readed == -1) {
		perror ("read");
		return -1;
	}

	printf ("期望读取%d字节,实际读取%d字节。\n", toread, readed);
	text[readed / sizeof (text[0])] = '\0';
	printf ("读取内容:%s\n", text);

	close (fd);

	return 0;
}


二进制读写和文本读写在Unix类系统上没有区别。
   类unix系统在写文本文件或读文本文件时候的换行符总为0a(0a是换行的ascii码,也就是<LF>),dos/Windows等系统写文本文件或者读文本文件时的换行符为 0d 0a(0d是回车的ascii码,0a是换行的ascii码,也就是<CR><LF>)。所以在类Unix系统上二进制读写和文本读写没有区别,因为文本文件的换行符号总被处理成一个字符0a。
范例:binary.c、text.c
/*文件名:text.c
 */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main (void) {
	int fd = open ("text.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	char name[256] = "张飞";
	unsigned int age = 38;
	double salary = 20000;

	char buf[1024];
	sprintf (buf, "%s %u %.2lf\n", name, age, salary);

	if (write (fd, buf, strlen (buf) * sizeof (buf[0])) == -1) {
		perror ("write");
		return -1;
	}

	struct Employee {
		char name[256];
		unsigned int age;
		double salary;
	}	employee = {"赵云", 25, 8000};

	sprintf (buf, "%s %u %.2lf", employee.name, employee.age,
		employee.salary);

	if (write (fd, buf, strlen (buf) * sizeof (buf[0])) == -1) {
		perror ("write");
		return -1;
	}

	close (fd);

	if ((fd = open ("text.txt", O_RDONLY)) == -1) {
		perror ("open");
		return -1;
	}

	memset (buf, 0, sizeof (buf));

	if (read (fd, buf, sizeof (buf)) == -1) {
		perror ("read");
		return -1;
	}

	sscanf (buf, "%s%u%lf%s%u%lf", name, &age, &salary,
		employee.name, &employee.age, &employee.salary);

	printf ("姓名:%s\n", name);
	printf ("年龄:%u\n", age);
	printf ("工资:%.2lf\n", salary);
	printf ("员工:%s %u %.2lf\n", employee.name, employee.age,
		employee.salary);

	close (fd);

	return 0;
}


/*文件名:binary.c
 */
#include <stdio.h>
#include <fcntl.h>

int main (void) {
	int fd = open ("binary.dat", O_WRONLY | O_CREAT | O_TRUNC, 0644);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	char name[256] = "张飞";

	if (write (fd, name, sizeof (name)) == -1) {
		perror ("write");
		return -1;
	}

	unsigned int age = 38;

	if (write (fd, &age, sizeof (age)) == -1) {
		perror ("write");
		return -1;
	}

	double salary = 20000;

	if (write (fd, &salary, sizeof (salary)) == -1) {
		perror ("write");
		return -1;
	}

	struct Employee {
		char name[256];
		unsigned int age;
		double salary;
	}	employee = {"赵云", 25, 8000};

	if (write (fd, &employee, sizeof (employee)) == -1) {
		perror ("write");
		return -1;
	}

	close (fd);

	if ((fd = open ("binary.dat", O_RDONLY)) == -1) {
		perror ("open");
		return -1;
	}

	if (read (fd, name, sizeof (name)) == -1) {
		perror ("read");
		return -1;
	}

	printf ("姓名:%s\n", name);

	if (read (fd, &age, sizeof (age)) == -1) {
		perror ("read");
		return -1;
	}

	printf ("年龄:%u\n", age);

	if (read (fd, &salary, sizeof (salary)) == -1) {
		perror ("read");
		return -1;
	}

	printf ("工资:%.2lf\n", salary);

	if (read (fd, &employee, sizeof (employee)) == -1) {
		perror ("read");
		return -1;
	}

	printf ("员工:%s %u %.2lf\n", employee.name, employee.age,
		employee.salary);

	close (fd);

	return 0;
}


 
练习:带覆盖检查的文件复制
/*
 * 实现命令cp的同样功能,复制文件
 * */
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

int main (int argc, char* argv[]) {
	if (argc < 3) {
		fprintf (stderr, "用法:%s <源文件> <目的文件>\n", argv[0]);
		return -1;
	}

	int src = open (argv[1], O_RDONLY);
	if (src == -1) {
		perror ("open");
		return -1;
	}

	struct stat st;
	if (fstat (src, &st) == -1) {
		perror ("fstat");
		return -1;
	}

	int dst = open (argv[2], O_WRONLY | O_CREAT | O_EXCL, st.st_mode);
	if (dst == -1) {
		if (errno != EEXIST) {
			perror ("open");
			return -1;
		}

		printf ("文件%s已存在,是否覆盖?(y/n) ", argv[2]);
		int ch = getchar ();
		if (ch != 'y' && ch != 'Y')
			return 0;

		if ((dst = open (argv[2], O_WRONLY | O_CREAT | O_TRUNC,
			st.st_mode)) == -1) {
			perror ("open");
			return -1;
		}
	}

	unsigned char buf[1024];
	ssize_t bytes;

	while ((bytes = read (src, buf, sizeof (buf))) > 0)
		if (write (dst, buf, bytes) == -1) {
			perror ("write");
			return -1;
		}

	if (bytes == -1) {
		perror ("read");
		return -1;
	}

	close (dst);
	close (src);

	return 0;
}


八、系统I/O与标准I/O
--------------------
1. 当系统调用函数被执行时,需要切换用户态和内核态,
   频繁调用会导致性能损失。
2. 标准库做了必要的优化,内部维护一个缓冲区,
   只在满足特定条件时才将缓冲区与系统内核同步,
   借此降低执行系统调用的频率,
   减少进程在用户态和内核态之间来回切换的次数,
   提高运行性能。
范例:sysio.c、stdio.c
# time ./sysio
real    0m17.442s
user    0m0.000s
sys     0m0.284s
# time ./stdio
real    0m0.056s
user    0m0.000s
sys     0m0.009s
九、lseek
---------
1. 每个打开的文件都有一个与其相关的“文件位置”。
2. 文件位置通常是一个非负整数,
   用以度量从文件头开始计算的字节数。
3. 读写操作都从当前文件位置开始,
   并根据所读写的字节数,增加文件位置。
4. 打开一个文件时,除非指定了O_APPEND,
   否则文件位置一律被设为0。
5. lseek函数仅将文件位置记录在内核中,
   并不引发任何I/O动作。
6. 在超越文件尾的文件位置写入数据,
   将在文件中形成空洞。
7. 文件空洞不占用磁盘空间,但被算在文件大小内。
#include <sys/types.h>
#include <unistd.h>
off_t lseek (
    int   fd,     // 文件描述符
    off_t offset, // 偏移量
    int   whence  // 起始位置
);
成功返回当前文件位置,失败返回-1。
whence取值:
SEEK_SET - 从文件头
           (文件的第一个字节)。
SEEK_CUR - 从当前位置
           (上一次读写的最后一个字节的下一个位置)。
SEEK_END - 从文件尾
           (文件的最后一个字节的下一个位置)。
范例:seek.c
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main (void) {
	int fd = open ("seek.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	const char* text = "Hello, World !";
	if (write (fd, text, strlen (text) * sizeof (text[0])) == -1) {
		perror ("write");
		return -1;
	}

	if (lseek (fd, -7, SEEK_CUR) == -1) {
		perror ("lseek");
		return -1;
	}

	off_t pos = lseek (fd, 0, SEEK_CUR);
	if (pos == -1) {
		perror ("lseek");
		return -1;
	}

	printf ("当前文件位置:%d\n", pos);

	text = "Linux";
	if (write (fd, text, strlen (text) * sizeof (text[0])) == -1) {
		perror ("write");
		return -1;
	}

	if (lseek (fd, 8, SEEK_END) == -1) {
		perror ("lseek");
		return -1;
	}

	text = "<-这里有个洞洞!";
	if (write (fd, text, strlen (text) * sizeof (text[0])) == -1) {
		perror ("write");
		return -1;
	}

	off_t size = lseek (fd, 0, SEEK_END);
	if (size == -1) {
		perror ("lseek");
		return -1;
	}

	printf ("文件大小:%d字节\n", size);

	close (fd);

	return 0;
}


思考一:既然lseek系统调用相当于标C库函数fseek,
那么是否存在与标C库函数ftell相对应的系统调用?
不存在,因为通过lseek(fd,0,SEEK_CUR)就可以获得当前文件位置。
思考二:如何获取文件的大小?
通过lseek(fd,0,SEEK_END)可以获得文件的大小。



 
 
十、dup/dup2
--------------
#include <unistd.h>
int dup (int oldfd);
int dup2 (int oldfd, int newfd);
成功返回文件描述符oldfd的副本,失败返回-1。
1. 复制一个已打开的文件描述符。
2. 返回的一定是当前未被使用的最小文件描述符。
3. dup2可由第二个参数指定描述符的值。 若指定描述符已打开,则先关闭之。
4. 所返回的文件描述符副本, 与源文件描述符,对应同一个文件表。
 
范例:dup.c
/*
 * dup/dup2函数练习
 *
 * dup/dup2可以实现文件描述符复制,复制之后就可以实现两个不同的文件描述符代表同一个文件表结构,代表同一文件。
 * 
 *
 *注意区分通过dup获得的文件描述符副本, 和两次open同一个文件的区别:
 *dup只复制文件描述符,不复制文件表。
 *
 *fd1 \
 *     > 文件表 -> v节点 -> i节点
 *fd2 /
 *
 *open创建新文件表,并为其分配新文件描述符。
 *
 *fd1 -> 文件表1 \
 *                > v节点 -> i节点
 *fd2 -> 文件表2 /
 */

#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main (void) {
	int fd1 = open ("dup.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd1 == -1) {
		perror ("open");
		return -1;
	}

	printf ("fd1 = %d\n", fd1);

	int fd2 = dup (fd1);
	if (fd2 == -1) {
		perror ("dup");
		return -1;
	}

	printf ("fd2 = %d\n", fd2);

	int fd3 = dup2 (fd2, 100);
	if (fd3 == -1) {
		perror ("dup2");
		return -1;
	}

	printf ("fd3 = %d\n", fd3);

	const char* text = "Hello, World !";
	if (write (fd1, text, strlen (text) * sizeof (text[0])) == -1) {
		perror ("write");
		return -1;
	}

	if (lseek (fd2, -7, SEEK_END) == -1) {
		perror ("lseek");
		return -1;
	}

	text = "Linux";
	if (write (fd3, text, strlen (text) * sizeof (text[0])) == -1) {
		perror ("write");
		return -1;
	}

	close (fd3);
	close (fd2);
	close (fd1);

	return 0;
}


注意区分通过dup获得的文件描述符副本, 和两次open同一个文件的区别:


dup只复制文件描述符,不复制文件表。
fd1 \
     > 文件表 -> v节点 -> i节点
fd2 /
open,即使open的是同一个文件,也会创建新文件表,并为其分配新文件描述符。
fd1 -> 文件表1 \
                > v节点 -> i节点
fd2 -> 文件表2 /
范例:open()即使open的是同一个文件,也会创建新文件表,并为其分配新文件描述符

/*
 * open()练习
 */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main (void) {
	int fd1 = open ("same.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd1 == -1) {
		perror ("open");
		return -1;
	}

	printf ("fd1 = %d\n", fd1);

	int fd2 = open ("same.txt", O_RDWR);
	if (fd2 == -1) {
		perror ("open");
		return -1;
	}

	printf ("fd2 = %d\n", fd2);

	int fd3 = open ("same.txt", O_RDWR);
	if (fd3 == -1) {
		perror ("open");
		return -1;
	}

	printf ("fd3 = %d\n", fd3);

	const char* text = "Hello, World !";
	if (write (fd1, text, strlen (text) * sizeof (text[0])) == -1) {
		perror ("write");
		return -1;
	}

	if (lseek (fd2, -7, SEEK_END) == -1) {
		perror ("lseek");
		return -1;
	}

	text = "Linux";
	if (write (fd3, text, strlen (text) * sizeof (text[0])) == -1) {
		perror ("write");
		return -1;
	}

	close (fd3);
	close (fd2);
	close (fd1);

	return 0;
}









/*
 *文件名: mis.c 
 *描述:学生管理系统登录模块练习
 *注册 - 增加用户名和密码
 *登录 - 验证用户名和密码
 *用户信息保存在文件中
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

#define USER_FILE "user.dat"

typedef int (*MENU) (void);
typedef int (*ONMENU) (void);

typedef struct tag_User {
	char name[256];
	char passwd[256];
}	USER;

void menuLoop (MENU menu, ONMENU onMenu[], size_t menus) {
	for (;;) {
		int idMenu = menu ();
		if (idMenu < 0 || menus <= idMenu)
			printf ("无效选择!\n");
		else if (onMenu[idMenu] () < 0)
			break;
	}
}

int menuLogin (void) {
	printf ("------------\n");
	printf ("学生管理系统\n");
	printf ("------------\n");
	printf ("[1] 注册    \n");
	printf ("[2] 登录    \n");
	printf ("[0] 退出    \n");
	printf ("------------\n");
	printf ("请选择:");

	int idMenu = -1;
	if (scanf ("%d", &idMenu) != 1)
		scanf ("%*[^\n]");

	return idMenu;
}

int menuStudent (void) {
	printf ("------------\n");
	printf ("学生管理系统\n");
	printf ("------------\n");
	printf ("[1] 增加学生\n");
	printf ("[2] 删除学生\n");
	printf ("[3] 浏览学生\n");
	printf ("[0] 注销    \n");
	printf ("------------\n");
	printf ("请选择:");

	int idMenu = -1;
	if (scanf ("%d", &idMenu) != 1)
		scanf ("%*[^\n]");

	return idMenu;
}

int onRegister (void) {
	USER userNew;
	printf ("用户名:");
	scanf ("%s", userNew.name);
	printf ("密码:");
	scanf ("%s", userNew.passwd);

	int fd = open (USER_FILE, O_RDWR | O_CREAT, 0644);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	USER userOld;
	ssize_t bytes;

	while ((bytes = read (fd, &userOld, sizeof (userOld))) > 0)
		if (! strcmp (userOld.name, userNew.name)) {
			printf ("用户名已存在,注册失败!\n");
			close (fd);
			return 0;
		}

	if (bytes == -1) {
		perror ("read");
		close (fd);
		return -1;
	}

	if (write (fd, &userNew, sizeof (userNew)) == -1) {
		perror ("write");
		close (fd);
		return -1;
	}

	close (fd);

	printf ("注册成功!\n");

	return 0;
}

int onAdd (void) {
	printf ("录入学生信息...\n");
	return 0;
}

int onDel (void) {
	printf ("删除学生信息...\n");
	return 0;
}

int onBrowse (void) {
	printf ("浏览学生信息...\n");
	return 0;
}

int onLogout (void) {
	return -1;
}

int onLogin (void) {
	USER userLog;
	printf ("用户名:");
	scanf ("%s", userLog.name);
	printf ("密码:");
	scanf ("%s", userLog.passwd);

	int fd = open (USER_FILE, O_RDONLY | O_CREAT, 0644);
	if (fd == -1) {
		perror ("open");
		return -1;
	}

	USER userOld;
	ssize_t bytes;

	while ((bytes = read (fd, &userOld, sizeof (userOld))) > 0)
		if (! strcmp (userOld.name, userLog.name))
			if (strcmp (userOld.passwd, userLog.passwd)) {
				printf ("密码错误,登录失败!\n");
				close (fd);
				return 0;
			}
			else
				break;

	if (bytes == -1) {
		perror ("read");
		close (fd);
		return -1;
	}

	if (bytes == 0) {
		printf ("用户名错误,登录失败!\n");
		close (fd);
		return 0;
	}

	close (fd);

	ONMENU onMenu[] = {onLogout, onAdd, onDel, onBrowse};
	menuLoop (menuStudent, onMenu, sizeof (onMenu) / sizeof (onMenu[0]));

	return 0;
}

int onQuit (void) {
	return -1;
}

int main (void) {
	ONMENU onMenu[] = {onQuit, onRegister, onLogin};
	menuLoop (menuLogin, onMenu, sizeof (onMenu) / sizeof (onMenu[0]));
	return 0;
}


 
 

                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值