`

终端规范模式和非规范模式

阅读更多
    终端规范模式很简单:发一个读请求,当一行已经输入后,终端驱动程序立即返回。以下几个条件都会造成读返回。
    1、所请求的字节数已读到时。此时无需读一个完整的行,如果读了部分行,也不会丢失任何信息,下一次读会从前一次读的停止处开始。
    2、当读到一个行定界符时。在终端特殊输入字符一节中已了解到,下列字符在规范模式中会被解释为“行结束”:NL、EOL、EOL2 和 EOF,还有如若已设置 ICRNL,但未设置 IGNCR,则 CR 字符的作用与 NL 字符一样,也会终止一行。
    3、如果捕捉到信号,并且该函数不再自动重启,则读也会返回。
    下面来看看 getpass 函数在 UNIX 系统中的一个典型实现。此函数由 login(1) 和 crypt(1) 程序调用。它读入用户在终端上键入的口令,它关闭了回显,但仍以规范模式工作,因为不管键入什么口令都能构成一个完整行。
#include <stdio.h>
#include <signal.h>
#include <termios.h>

#define MAX_PASS_LEN	8		// max #chars for user to enter

char *getpass(const char *prompt){
	static char buf[MAX_PASS_LEN+1];	// null	byte at end

	sigset_t	set, oset;
	sigemptyset(&set);
	sigaddset(&set, SIGINT);		// block SIGSET
	sigaddset(&set, SIGTSTP);		// block SIGTSTP
	sigprocmask(SIG_BLOCK, &set, &oset);	// and save mask

	FILE *fp;
	if((fp=fopen(ctermid(NULL), "r+")) == NULL)
		return NULL;
	setbuf(fp, NULL);				// close I/O buffer
	struct termios	termst, otermst;
	tcgetattr(fileno(fp), &termst);			// save tty state
	otermst = termst;				// structure copy
	termst.c_lflag &= ~(ECHO |ECHOE |ECHOK | ECHONL);	// disable echo
	tcsetattr(fileno(fp), TCSAFLUSH, &termst);

	fputs(prompt, fp);
	char *str = buf;
	int ch;
	while((ch=getc(fp))!=EOF && ch != '\n'){
		if(str < &buf[MAX_PASS_LEN])
			*str++ = ch;
	}
	*str = 0;			// null terminate
	putc('\n', fp);		// echo a newline

	tcsetattr(fileno(fp), TCSAFLUSH, &otermst);	// restore tty state
	sigprocmask(SIG_SETMASK, &oset, NULL);		// restore signal mask
	fclose(fp);
	return buf;
}

int main(void){
	char *ptr;
	if((ptr=getpass("Enter password: ")) == NULL){
		printf("getpass error\n");
		return 1;
	}
	printf("password: %s\n", ptr);
	while(*ptr != 0)
		*ptr++ = 0;		// zero it out when we're done with it
	return 0;
}

    关于本程序,需要注意以下几个方面。
    1、阻塞了信号 SIGINT 和 SIGTSTP。否则在输入 INTR 字符 或 SUSP 字符时都会使程序终止,然后在禁止回显状态下返回到终端。但没有一个 getpass 版本捕捉、忽略或阻塞 SIGQUIT 信号,所以输入 QUIT 字符就会使程序异常终止,并且很可能使终端保持在禁止回显状态。
    2、关闭了标准 I/O 流的缓冲,以免在读、写之间产生某些交叉(这样就需要多次调用 fflush)。也可使用不带缓冲的 I/O,但这样就需要使用 read 来模仿 getc 函数。
    3、由于 getpass 中使用静态存储区来存储输入的明文口令,所以为了安全起见,在程序完成后应在内存中消除它。因为要是该程序会产生其他用户可能读取的 core 文件(core 的系统默认许可权使每个用户都能读它),或者如果某个其他进程能够设法读该进程的存储空间,则它们就可能会读到这个口令。当然也可以在 getpass 中将该口令加密存储。

    可以通过关闭 termios 结构中 c_lflag 字段的 ICANON 标志来指定非规范模式。在非规范模式中,输入数据不装配成行,不处理下列特殊字符:ERASE、KILL、EOF、NL、EOL、EOL2、CR、REPRINT、STATUS 和 WERASE(见终端特殊输入字符)。当读取了指定量的数据,或者已经超过了给定量的时间后,就会通知系统返回数据。这使用了 termios 结构中 c_cc 数组的两个下标为 VMIN 和 VTIME 的两个变量:MIN 和 TIME。MIN 指定一个 read 返回前的最小字节数,TIME 指定等待数据到达的分秒数(分秒为秒的 1/10)。有下列 4 种情形。
    1、MIN > 0,TIME > 0:TIME 指定一个字节间定时器(interbyte timer),它只在第一个字节被接收时启动(这意味着在定时器超时时,read 至少会返回一个字节)。在定时器超时之前,若已接收到 MIN 个字节,则 read 返回 MIN 个字节。如果在接到 MIN 个字节之前,该定时器已超时,则 read 返回已接收到的字节。
    2、MIN > 0,TIME = 0:read 在接收到 MIN 个字节之前不返回。这可能会使 read 长期阻塞。
    3、MIN = 0,TIME > 0:这里的 TIME 指定的是一个在调用 read 时就会立即启动的读定时器。在接到一个字节或者该定时器超时时,read 即返回。如果是定时器超时,则 read 返回 0。
    4、MIN = 0,TIME = 0:如果有数据可用,则 read 最多返回所要求的字节数。否则立即返回 0。
    注意,为了兼容性,POSIX.1 允许下标 VMIN 和 VTIME 的值可分别与 VEOF 和 VEOL 的相同,但这同时也带来了可移植性问题。从非规范模式转换为规范模式时,必须恢复 VEOF 和 VEOL。例如,如果 VMIN 等于 VEOF,且不恢复它们的值,那么当把 VMIN 的典型值设置为 1 时,文件结束符就变成了 Ctrl+A。解决这一问题最简单的方法是:在要转入非规范模式时,将整个 termios 结构保存起来,以后再要转会规范模式时恢复它。
    下面这个程序中的函数 tty_cbreak 和 tty_raw 分别将终端设置为 cbreak 模式(cbreak mode)和原始模式(raw mode)。在这两种模式之间转换时,需要先调用 tty_reset 函数,它将终端恢复到调用 tty_cbreak 或 tty_raw 之前的工作状态,以减少出错时终端处于不可用状态的机会。程序中还提供了另外两个函数 tty_atexit 和 tty_termios。tty_atexit 可被登记为退出处理程序,以保证 exit 时恢复终端工作模式。tty_termios 则返回一个指向原来规范模式下 termios 结构的指针。不过在查看代码之前,先看一下 cbreak 模式和 raw 模式要满足的要求,其中涉及到的终端标志介绍可见终端 I/O 综述
    cbreak 模式要满足的要求如下。
    1、非规范模式。如上文所述,这种模式关闭了对某些输入字符的处理,但没有关闭对信号的处理。调用者一般应当捕捉这些信号,以免使程序终止,导致终端保持在 cbreak 模式。
    2、关闭回显。
    3、每次输入一个字节。为此,可将 MIN 和 TIME 分别设置为 1 和 0。
    raw 模式要满足的要求如下。
    1、非规范模式,同时还关闭了对信号产生字符 ISIG 和扩充输入字符 IEXTEN 的处理,另外还禁用了 BRKINT 字符,使 BREAK 字符不再产生信号。
    2、关闭回显。
    3、禁止输入中的 CR 到 NL 映射 ICRNL、输入奇偶检测 INPCK、剥离输入字节的第 8 位 ISTRIP 以及输出流控制 IXON。
    4、8 位字符 CS8,且禁用奇偶校验 PARENB。
    5、禁止所有输出处理 OPOST。
    6、每次输入一个字节(MIN=1, TIME=0)。
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>

static struct termios	save_termios;
static int				ttysavefd = -1;
static enum{RESET, RAW, CBREAK}	ttystate = RESET;

int tty_cbreak(int fd){		// put terminal into a cbreak mode
	if(ttystate != RESET){
		errno = EINVAL;
		return -1;
	}
	struct termios	buf;
	if(tcgetattr(fd, &buf) < 0)
		return -1;
	save_termios = buf;		// structure copy
	buf.c_lflag &= ~(ECHO | ICANON);	// echo off, canonical mode off
	buf.c_cc[VMIN] = 1;		// 1 byte at a time,
	buf.c_cc[VTIME] = 0;	// and no timer
	if(tcsetattr(fd, TCSAFLUSH, &buf) < 0)
		return -1;
	//Varify that the changes stuck. tcsetattr can return 0 on partial success
	int err;
	if(tcgetattr(fd, &buf) < 0){
		err = errno;
		tcsetattr(fd, TCSAFLUSH, &save_termios);
		errno = err;
		return -1;
	}
	if((buf.c_lflag & (ECHO |ICANON)) || buf.c_cc[VMIN]!=1 || buf.c_cc[VTIME
]!=0){
		//only some of the changes were made, restore the original settings
		tcsetattr(fd, TCSAFLUSH, &save_termios);
		errno = EINVAL;
		return -1;
	}
	ttystate = CBREAK;
	ttysavefd = fd;
	return 0;
}

int tty_raw(int fd){	// put terminal into a raw mode
	if(ttystate != RESET){
		errno = EINVAL;
		return -1;
	}
	struct termios	buf;
	if(tcgetattr(fd, &buf) < 0)
		return -1;
	save_termios = buf;		// structure copy
	buf.c_lflag &= ~(ECHO |ICANON |IEXTEN |ISIG);
	buf.c_iflag &= ~(BRKINT |ICRNL |INPCK |ISTRIP | IXON);
	buf.c_cflag &= ~(CSIZE |PARENB);
	buf.c_cflag |= CS8;		// set 8 bits/char
	buf.c_oflag &= ~(OPOST);
	buf.c_cc[VMIN] = 1;
	buf.c_cc[VTIME] = 0;
	if(tcsetattr(fd, TCSAFLUSH, &buf) < 0)
		return -1;
	int err;
	if(tcgetattr(fd, &buf) < 0){
		err = errno;
		tcsetattr(fd, TCSAFLUSH, &save_termios);
		errno = err;
		return -1;
	}
	if((buf.c_lflag & (ECHO |ICANON |IEXTEN |ISIG)) ||
		(buf.c_iflag & (BRKINT |ICRNL |INPCK |ISTRIP |IXON)) ||
		(buf.c_cflag & (CSIZE |PARENB |CS8)) != CS8 ||
		(buf.c_oflag & OPOST) || buf.c_cc[VMIN]!=1 || buf.c_cc[VTIME]!=0
)
	{
		tcsetattr(fd, TCSAFLUSH, &save_termios);	
		errno = EINVAL;
		return -1;
	}
	ttystate = RAW;
	ttysavefd = fd;
	return 0;
}

int tty_reset(int fd){		// restore terminal's mode
	if(ttystate == RESET)
		return 0;
	if(tcsetattr(fd, TCSAFLUSH, &save_termios) < 0)
		return -1;
	ttystate = RESET;
	return 0;
}

void tty_atexit(void){		// can be set up by atexit(tty_atexit);
	if(ttysavefd >= 0)
		tty_reset(ttysavefd);
}

struct termios *tty_termios(void){	// let caller see original tty state
	return &save_termios;
}

/* ======================= test ============================ */
static void sig_catch(int signo){
	printf("signal caught\n");
	tty_reset(STDIN_FILENO);
	exit(0);
}

int main(void){
	atexit(tty_atexit);
	signal(SIGINT, sig_catch);		// catch SIGINT
	signal(SIGQUIT, sig_catch);		// catch SIGQUIT
	signal(SIGTERM, sig_catch);		// catch SIGTERM

	int	i;
	char c;
	if(tty_raw(STDIN_FILENO) < 0){
		printf("tty_raw error\n");
		return 1;
	}
	printf("Enter raw mode characters, terminate with Backspace\n");
	while((i=read(STDIN_FILENO, &c, 1)) == 1){
		if((c &= 255) == 0177)		// 0177 = ASCII Backspace
			break;
		printf("%#o\n", c);
	}
	if(i <= 0){
		printf("read error\n");
		return 1;
	}

	tty_reset(STDIN_FILENO);

	if(tty_cbreak(STDIN_FILENO) < 0){
		printf("tty_cbreak error\n");
		return 1;
	}
	printf("\nEnter cbreak mode characters, terminate with SIGINT\n");
	while((i=read(STDIN_FILENO, &c, 1)) == 1)
		printf("%#o\n", c & 255);
	if(i <= 0){
		printf("read error\n");
		return 1;
	}

	tty_reset(STDIN_FILENO);
	return 0;
}

    编译后运行该程序,可以观察到两种终端模式的工作情况如下。
$ ./termmode.out 
Enter raw mode characters, terminate with Backspace
                                                   04            # Ctrl+D
                                                     03          # Ctrl+C
                                                       033       # F7
                                                          0133
                                                              061
                                                                 070
                                                                    0176
                                                        # Backspace
Enter cbreak mode characters, terminate with SIGINT
01                     # Ctrl+A
0177                   # Backspace
signal caught          # Ctrl+C

    这里,在原始模式中,输入的字符是文件结束符 Ctrl+D(04)、中断符 Ctrl+C(03)和特殊功能键 F7。该功能键在本人所用的终端上产生 5 个字符:ESC(033)、[(0133)、字符 1(061)、字符 8(070)和 ~(0176)。注意,因为在原始模式下关闭了输出处理(~OPOST),所以在每个字符后没有得到回车符。在 cbreak 模式下,不对特殊输入字符进行处理,但是仍对终端产生的信号进行处理。
分享到:
评论

相关推荐

    中国移动A-GPS终端技术规范V4.0.0.doc

    中国移动A-GPS终端技术规范V4.0.0引用了多个国际标准和技术规范,如3GPP、RRLP、GPS等,这些规范提供了A-GPS技术的技术基础和实现方法。 5. 术语、定义和缩略语 中国移动A-GPS终端技术规范V4.0.0中定义了一系列...

    TCESA 1160-2021 移动智能终端未成年人保护通用规范.pdf

    根据TCESA 1160-2021标准,移动智能终端的未成年人保护模式设置应包括未成年人保护模式入口、未成年人保护模式退出和未成年人保护模式显见性三个方面。其中,未成年人保护模式入口是指用户可以通过特定的操作进入未...

    AGPS规范 终端A-GPS技术规范

    终端A-GPS技术规范 前 言 III 1. 范围 1 2. 规范性引用文件 1 3. 术语、定义和缩略语 2 4. 概述 3 4.1 业务简介 3 4.2 国际规范要求 4 4.3 3GPP (RRLP And Performance Request) 4 5. 功能要求 5 5.1 定位模式要求...

    《中国联通5g行业终端总体技术要求》.pdf

    4.6. 调制模式要求 4.7. MIMO要求 4.8. 功率等级要求 4.9. IP协议栈要求 5. 5G行业终端务能力要求 5.1. 语音业务要求 5.2. 短信业务要求 5.3. 定位能力要求 5.4. eSIM能力要求 6. 5G行业终端性能要求 6.1. 速率性能...

    最新中国电信CRM规范2.0

    本规范是中国电信CTG-MBOSS 2.0 规范体系的一个分册,规范了客户关系管理(CRM)系统的业务功能,为中国电信CTG-MBOSS CRM2.0系统的规划和建设提供基本的功能规范要求。 本规范包含中国电信CRM2.0系统建设所涉及的...

    定制4GLTE硬件车规级OBD车联网标准终端T-Box传输协议开发API-V2.5.pdf

    6. 终端模式切换:EST627汽车OBD智能信息终端V2.5支持多种工作模式,例如在线模式、离线模式、休眠模式等。不同的工作模式对应不同的数据传输和处理机制。 7. 指令列表:EST627汽车OBD智能信息终端V2.5支持多种指令...

    3GPP协议R16版本-5G无线接入网38系列规范.rar

    包括了119个规范的R16版本,部分文档目录:TS23.501系统总体介绍、TS38.300无线侧总体介绍、TS38.401无线侧架构、TS38.211物理信道结构、TS38.104基站射频要求、TS38.304 用户终端(UE)在空闲模式以及RRC非激活状态...

    Q/CUP 047.1—2013 中国银联IC卡技术规范——产品规范 第1部分 非接触式读写器规范

    最新版非接触式读写器规范,银联企业标准 目 次 .............................................................................. I 前 言 .......................................................................

    中国移动PON网络工程施工及验收规范借鉴.pdf

    这些知识点涵盖了PON网络工程的施工和验收的各个方面,包括机房环境与安全检查、设备与器材检验、安装工艺检验、设备安装、线缆敷设、系统检测、术语、定义和缩略语、规范性引用文件、总则和实施QB等方面。

    电子钱包业务规范+技术规范

    电子钱包应用是电信行业为用户提供的一种基于近场通信技术实现的新的手机支付形式,建立了一种新型的用户消费的业务模式,使用户的支付行为和用户手机终端有机结合,真正做到随时随地便捷消费。 电子钱包应用是脱机...

    内网终端安全管理解决方案白皮书.docx

    2. 精细化桌面管理能力天珣具备真正的安全内核,能够实现对终端和终端用户行为细粒度的控制,保证终端用户行为按照预定规范执行,并有效杜绝各种潜在攻击和违规行为所带来的安全风险,保证终端桌面合规安全运行。...

    基于ARM技术的电能采集终端设计.pdf

    该设计需要包括标准表、功率源、信号源、载波通信系统和检测单元等组件,以确保设计的电能采集终端的规范性和安全性。 知识点4:基于ARM技术的电能采集终端设计的优势 基于ARM技术的电能采集终端设计具有多种优势...

    GSMt通信搜索

    该模式适合于非智能终端 、终端仿真器等。 PDU模式相当于计算机网络中的分组交换接口协议。这种传送方式能够很平稳地过渡到GPRS,因此GSM规范要求用户尽可能地使用PDU模式处理短消息。 PDU格式分为收短消息和发短...

    智能家居安全监控

    该模式适合于非智能终端 、终端仿真器等。 PDU模式相当于计算机网络中的分组交换接口协议。这种传送方式能够很平稳地过渡到GPRS,因此GSM规范要求用户尽可能地使用PDU模式处理短消息。 PDU格式分为收短消息和发短...

    单端、差分常见接口标准规范

    电路设计中常用接口标准规范,电气特性电平标准,数据速率、数据格式、电缆长度、传输模式、终端、总线公共模式范围、连接器类型和系统配置

    ISO7816传输协议详尽中文版终稿.pdf

    "ISO7816传输协议详尽中文版终稿.pdf" 本文档详细介绍了ISO7816...本文档详细介绍了ISO7816传输协议的详细规范,涉及卡的电气特性、终端的电气特性和卡的触点分配,为卡片和终端之间的数据传输提供了详细的技术规范。

    论文研究-装备保障模拟训练平台集成联盟模式研究.pdf

    提出了基于联盟模式的装备保障模拟训练平台集成方案, 构建了平台集成框架, 设计了基于可变客户端的协同运行支撑环境多层体系结构, 规范了基于角色的任务系统动态部署过程和训练终端运行过程。通过本研究能够屏蔽...

    长沙软库快速开发平台介绍

    我公司自行研发的开发平台同时支持Browser/Server(浏览器模式)和Client /Server(客户端模式) 两种模式,在办公室场景采用Browser模式,在工控机终端采用Client模式,两种模式并行进行。 客户端模式、浏览器模式...

Global site tag (gtag.js) - Google Analytics