Virtual key-press to function-key of a gaming keyboard - keyboard

How to virtually activate special function keys of a "MadCatz Strike 7" keyboard? Sending its key code should have MadCatz7 software act as if registering a physical key press.
Are there key codes for these function keys (or is there a better way)? I am working on an AutoIt script that should let MadCatz software do the rest by activating corresponding special function keys.

Try to figure out the key with this code. Normally your specail function keys have a alternative hotkey as ctrl+alt+F10 or something.
Opt('GUICloseOnESC', 0)
Global $tState = DllStructCreate('byte[256]')
GUICreate('MyGUI', 200, 200)
$Label = GUICtrlCreateLabel('', 20, 72, 160, 52, 0x01)
GUICtrlSetFont(-1, 32, 800, 0, 'Tahoma')
GUISetState()
$Prev = -1
While 1
Sleep(10)
If GUIGetMsg() = -3 Then
ExitLoop
EndIf
$Key = 0
$Ret = DllCall('user32.dll', 'int', 'GetKeyboardState', 'ptr', DllStructGetPtr($tState))
For $i = 0x08 To 0xFF
Switch $i
Case 0x0A, 0x0B, 0x0E To 0x0F, 0x16, 0x1A, 0x1C To 0x1F, 0x3A To 0x40, 0x5E, 0x88 To 0x8F, 0x97 To 0x9F, 0xB8 To 0xB9, 0xC1 To 0xDA, 0xE0, 0xE8
ContinueLoop
Case Else
If BitAND(DllStructGetData($tState, 1, $i + 1), 0xF0) Then
$Key = $i
ExitLoop
EndIf
EndSwitch
Next
If $Key <> $Prev Then
GUICtrlSetData($Label, '0x' & Hex($Key, 2))
$Prev = $Key
EndIf
WEnd

There is no way to do that without telling the MadCatz software directly that the special key was pressed, so that the software would launch the programmed template.
I solved the problem using an AutoIt Script and will post the code when it's done.

Related

File descriptor is in blocking mode, but read() is not blocking

I am writing some software to handle serial port read/writing for a Beaglebone system. The OS is Debian 9. I am writing code in C with --std=gnu99.
Here is my code:
// reference
// https://www.cmrr.umn.edu/~strupp/serial.html
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>
int open_port(void)
{
int fd;
fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NDELAY);
// removing O_NDELAY no difference
if(fd == -1)
{
perror("open_port: Unable to open /dev/ttyS1 - ");
}
else
{
fcntl(fd, F_SETFL, 0);
}
return fd;
}
int main()
{
// open fd for serial port
int fd = open_port();
// set baud rate
struct termios options;
// get current options
tcgetattr(fd, &options);
// set input and output baud
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
// enable reciever and set local mode
// CLOCAL: ignore modem control lines
// CREAD: enable reciever
options.c_cflag |= (CLOCAL | CREAD);
// set partity bit
//options.c_cflag &= PARENB;
options.c_cflag &= ~PARENB;
// use even parity
//options.c_cflag &= ~PARODD;
// use only 1 stop bit
options.c_cflag &= ~CSTOPB;
// set character size to 8 bit
options.c_cflag &= ~CSIZE;
options.c_cflag &= CS8;
// disable flow control
//options.c_cflag &= ~CNEW_RTSCTS; // does not work?
// note: check local options, some may be required
// disable canonical input (use raw)
// disable echoing of characters
// disable echoing of erase characters
// disable signals SIGINTR SIGSUSP SIGDSUSP, SIGQUIT
// input characters are passed through exactly as recieved
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// disable parity check
options.c_iflag &= IGNPAR;
// disable flow control (software)
options.c_iflag &= ~(IXON | IXOFF | IXANY);
// set raw output, no output processing
options.c_oflag &= ~OPOST;
// set options
tcsetattr(fd, TCSANOW, &options);
char data[] = {0x01, 0x03, 0x00, 0x00};
int n = write(fd, data, 4);
if(n =! 4)
{
printf("write fail %d\n", n);
}
char buffer[40];
n = read(fd, buffer, 2);
if(n != 2)
{
printf("read fail %d\n", n);
}
if(buffer[0] == 0x03 && buffer[1] == 0x00)
{
}
else
{
printf("uart error\n");
}
char data2[] = {0x00, 0x00, 0x00, 0x01};
n = write(fd, data2, 4);
if(n != 4)
{
printf("write fail %d\n", n);
}
n = read(fd, buffer, 2);
if(n != 2)
{
printf("read fail %d\n", n);
}
if(buffer[0] == 0x03 && buffer[1] == 0x00)
{
}
else
{
printf("uart error\n");
}
printf("process complete\n");
// to close
close(fd);
}
The issue I have is calls to read() do not block. This is not the desired behaviour. These calls should block until 2 bytes of data are received.
My guess would be I have misconfigured an option somewhere. However I don't know where the mistake is, and from what I have researched, this should be reading in blocking mode. (fcntl(fd, F_SETFL, 0);)
The issue I have is calls to read() do not block.
That's actually a conclusion, rather than an observation.
Presumably your program is reporting zero bytes read, which technically is not an error.
... from what I have researched, this should be reading in blocking mode. (fcntl(fd, F_SETFL, 0);)
Correct, you have the file descriptor configured for blocking mode.
My guess would be I have misconfigured an option somewhere.
Yes, your termios configuration is incomplete. (It uses proper bitwise operations, but unfortunately is incomplete and has errors, see ADDENDUM.)
When configured for non-canonical mode, you must also specify the VMIN and VTIME parameters.
When left unspecified (as in your code), the default values of VMIN = 0 and VTIME = 0 are likely to be in effect, which is equivalent to a non-blocking read.
Note that the byte length in the read() syscall has minimal effect on completion behavior (other than capping the number of bytes to be returned).
From the man page for read(2):
read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
Therefore the count parameter is effectively a maximum.
The actual number of bytes read can be between zero and count depending on the values configured for VMIN and VTIME (and the actual data available).
See Linux Blocking vs. non Blocking Serial Read for more details.
To read at least two bytes per syscall (with no time limit), include the following in the termios configuration:
options.c_cc[VMIN] = 2;
options.c_cc[VTIME] = 0;
The above two statements inserted into your program before the tcsetattr() syscall would cause your program to block forever until exactly two bytes can be returned in each read(fd, buffer, 2) statement.
A read(fd, buffer, 4) statement would also block forever, and return either 2, 3, or 4 bytes of data (depending on program timing with respect to data reception & buffering).
ADDENDUM
Turns out there are a few bugs in your termios initialization.
As posted your program neither transmits or receives any data correctly, and you failed to detect these errors or neglect to mention them.
The following statement in your program obliterates all the previous c_cflag operations, and unintentionally reconfigures the baudrate to B0 and the character size to CS5:
options.c_cflag &= CS8;
The correct syntax is:
options.c_cflag |= CS8;
The statement
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
should be expanded with more attributes to match cfmakeraw():
options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
The following statement in your program disables every attribute in c_iflag except IGNPAR, and leaves the IGNPAR attribute in an unknown/ambiguous state:
// disable parity check
options.c_iflag &= IGNPAR;
To match cfmakeraw(), it should be changed to:
options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL);
With all the corrections mentioned, I can get your program to execute as expected.

stty serial port settings for parity not persistent

I have a netcore app that opens the serial port, and writes "parity error" on the console once a parity error gets detected. It works fine in Windows 10 but can't get it to work under Linux.
My assumption is that the OS is not passing the parity error to netcore.
For checking the port settings I run:
stty -D /dev/ttyS0 -ignpar inpck
then I run:
stty -D /dev/ttyS0 -a
and the settings seem to be properly set (-ignpar inpck) as expected.
Then I run my netcore 3 app but parity error is not detected.
So I run
stty -D /dev/ttyS0 -a
for validating the settings, but these seem to be reset (-ignpar -inpck)
How can I force my app to run with the inpck attribute enabled?
Is there a way to make inpck to be enabled by default?
Thanks.
UPDATE: The netcore 3 app parity error detection works fine in windows 10, but it does not work under linux. My assumptions are that either:
A) netcore runtime is not passing the parity settings to the driver
(not likely)
B) the OS is ignoring the instructions.
The stty command is simply a method from the shell to utilize the termios API.
Application programs are expected to use the termios API to configure the serial terminal to the exact requirements of the situation (rather than rely on an expected configuration on startup).
If the app environment that you're using does not permit access to the termios API, then you may be using an inappropriate method.
Do you know any linux app that could explicitly react somehow to a parity error?
The following C program reads lines (i.e. canonical mode) from a serial terminal, and is configured to detect a Mark (or 1) as the parity bit with an 8-bit character frame.
#define SERIALTERMINAL "/dev/ttyS0"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
int set_interface_attribs(int fd, int speed)
{
struct termios tty;
if (tcgetattr(fd, &tty) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
cfsetospeed(&tty, (speed_t)speed);
cfsetispeed(&tty, (speed_t)speed);
tty.c_cflag |= CLOCAL | CREAD;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; /* 8-bit characters */
tty.c_cflag |= PARENB; /* enable parity */
tty.c_cflag &= ~PARODD; /* Even parity */
tty.c_cflag |= CMSPAR; /* force Even parity to SPACE */
tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
tty.c_lflag |= ICANON | ISIG; /* canonical input */
tty.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);
tty.c_iflag &= ~IGNCR; /* preserve carriage return */
tty.c_iflag &= ~(INLCR | ICRNL | IUCLC | IMAXBEL);
tty.c_iflag &= ~(IXON | IXOFF | IXANY); /* no SW flowcontrol */
tty.c_iflag |= IGNBRK; /* ignore breaks */
tty.c_iflag &= ~ISTRIP;
tty.c_iflag &= ~IGNPAR; /* report error */
tty.c_iflag |= INPCK; /* test parity */
tty.c_iflag |= PARMRK; /* verbose parity err */
tty.c_oflag &= ~OPOST;
tty.c_cc[VEOL] = 0;
tty.c_cc[VEOL2] = 0;
tty.c_cc[VEOF] = 0x04;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
return 0;
}
int main(void)
{
char *portname = SERIALTERMINAL;
int fd;
int wlen;
fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0) {
printf("Error opening %s: %s\n", portname, strerror(errno));
return -1;
}
/*baudrate 115200, 8 bits, Space for parity, 1 stop bit */
set_interface_attribs(fd, B115200);
/* simple output */
wlen = write(fd, "Hello!\n", 7);
if (wlen != 7) {
printf("Error from write: %d, %d\n", wlen, errno);
}
tcdrain(fd); /* delay for output */
/* simple canonical input, read lines */
do {
unsigned char buf[81];
unsigned char *p;
int rdlen;
rdlen = read(fd, buf, sizeof(buf) - 1);
if (rdlen > 0) {
buf[rdlen] = 0;
printf("Read %d:", rdlen);
/* first display as hex numbers then ASCII */
for (p = buf; rdlen-- > 0; p++) {
printf(" 0x%x", *p);
if (*p < ' ')
*p = '.'; /* replace any control chars */
}
printf("\n \"%s\"\n\n", buf);
} else if (rdlen < 0) {
printf("Error from read: %d: %s\n", rdlen, strerror(errno));
} else { /* rdlen == 0 */
printf("Nothing read. EOF?\n");
}
/* repeat read */
} while (1);
}
The program was executed on an old Linux (Ubuntu 14.04.2 LTS) PC that has a built-in 16550A serial port.
[ 2.656593] 00:08: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
This serial port does not seem capable of transmitting 8-bits with parity (an 11-bit frame), but does seem capable of reading 8-bits with parity.
The serial data is generated from a SBC that has a 9-bit capable UART. An oscilloscope was used to capture frames to confirm that the 8S1 and 8E1 frames were 11 bits long.
(An FTDI USB-to-RS232 converter was not reliable in generating all parity configurations with 8-bit characters.)
When the sender is configured for 8-bits and Space for parity (which matches the program), the PC program reads "ABCDEFG\n" as:
Read 8: 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0xa
"ABCDEFG."
The data is read correctly.
When the sender is configured for 8-bits and Even parity, the PC program reads "ABCDEFG\n" as:
Read 14: 0x41 0x42 0xff 0x0 0x43 0x44 0xff 0x0 0x45 0xff 0x0 0x46 0x47 0xa
"AB�.CD�.E�.FG."
The read (correctly) identifies three characters that have Mark instead of Space as the parity bit.
Each character with "parity error" is preceded by bytes of 0xFF 0x00 (i.e. a total of three bytes).
Note that when the actual received datum is 0xFF (with no parity error), termios will expand that datum to two bytes of 0xFF 0xFF. So beware that when the next datum is 0x00, then this is not the error indication. IOW reading 0xFF 0xFF 0x00 converts to actual data 0xFF 0x00.
But when the actual received datum is 0xFF with a parity error, then read returns 0xFF 0x00 0xFF (i.e. there is no expansion combined with the error indication).

How do I wait for a keystroke interrupt with a syscall on Linux?

I want to receive an interrupt when the user presses a special keystroke like F1-12 in my program, which is written in nasm. I simply need to wait for a function keystroke at the start of my main function. I know that this is possible with the BIOS's int 16h, which returns a scancode. How can I do this under Linux?
The necessary code for this is rather complicated; I eventually figured out how to check for F1 in C with raw ioctl, read, and write. The translation to nasm should be straightforward if you're familiar with assembly and Linux syscalls.
It's not exactly what you want, in that it only checks for F1, not the rest of them. F1's sequence is 0x1b, 0x4f, 0x50. You can find other sequences with od -t x1 and pressing the key. For example, F2 is 0x1b, 0x4f, 0x51.
The basic idea is that we get the current terminal attributes, update them to be raw (cfmakeraw), and then set them back. The ioctl syscall is used for this.
On a terminal in raw mode, read() will get any character(s) the user has typed, unlike the "cooked" mode where the kernel does line-editing with backspace and control-u until the user submits the line by pressing enter or control-d (EOF).
#include <unistd.h>
#include <sys/ioctl.h>
#include <termios.h>
struct ktermios {
tcflag_t c_iflag;
tcflag_t c_oflag;
tcflag_t c_cflag;
tcflag_t c_lflag;
cc_t c_line;
cc_t c_cc[19];
};
int getch() {
unsigned char c;
read(0, &c, sizeof(c));
return c;
}
int main(int argc, char *argv[]) {
struct ktermios orig, new;
ioctl(0, TCGETS, &orig);
ioctl(0, TCGETS, &new); // or more simply new = orig;
// from cfmakeraw documentation
new.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
new.c_oflag &= ~OPOST;
new.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
new.c_cflag &= ~(CSIZE | PARENB);
new.c_cflag |= CS8;
ioctl(0, TCSETS, &new);
while (1) {
if (getch() == 0x1b && getch() == 0x4f && getch() == 0x50) {
break;
}
}
write(1, "Got F1!\n", 8);
ioctl(0, TCSETS, &orig); // restore original settings before exiting!
return 0;
}
I based this on this answer, which was very helpful.

serial port not responding

I wrote a program that communicate with serial port, using termios, this program will read serial port in non-blocking mode and write response to serial port once it read data. If there is no data read from serial port, the program will do other thing, on next loop, the program read serial port again.
now the question is, after sometimes gone, maybe several minutes, or maybe several hours, the serial port don't respond to my program any more. Even I execute echo 'HB\n' > /dev/ttyUSB0(then the serial port should respond 'HACK'), it doesn't respond any more..
I even don't known when the serial port is 'dead', I don't have any clue.. it 'dead' untimed.
here is my configuration:
/// set local mode options
//tminfo.c_lflag |= ICANON;
tminfo.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/// set control mode options
tminfo.c_cflag |= (CLOCAL | CREAD);
tminfo.c_cflag |= HUPCL;
// set hardware flow control
tminfo.c_cflag &= ~CRTSCTS;
// set how many bits in a character
tminfo.c_cflag &= ~CSIZE;
tminfo.c_cflag |= CS8;
// set parity mode (default to odd validation), this option (PARENB) will both enable input and output parity checking
tminfo.c_cflag &= ~PARENB; // we don't need prity checking now
/// set input mode options
// set input parity checking
tminfo.c_iflag &= ~INPCK;
tminfo.c_cflag &= ~CSTOPB;
/// set output mode options
tminfo.c_oflag &= ~OPOST;
tminfo.c_cc[VMIN] = 1;
tminfo.c_cc[VTIME] = 1;
/// set line speed, defaults to 38400bps, both for input and output
// this call will set both input and output speed
cfsetspeed(&tminfo, B38400);
It's hard to debug the serial in this situation. I really can't figure out what cause the serial port 'dead' on earth. I'm nearly crazy...
what the possible reason? Any help will be appreciated!
update:
when the serial port "dead", its configuration is:
speed 38400 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 1;
-parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel
-iutf8
-opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-isig -icanon -iexten -echo -echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
update 2
/proc/tty/driver/ar933x-uart
I have noticed that this tx and rx field value does not change during my program running, even if I write to my serial manually.
serinfo:1.0 driver revision:
0: uart:AR933X UART mmio:0x18020000 irq:11 tx:169 rx:0 RTS|DTR|CD
/proc/tty/driver/serial
serinfo:1.0 driver revision:
0: uart:unknown port:00000000 irq:0
1: uart:unknown port:00000000 irq:0
2: uart:unknown port:00000000 irq:0
3: uart:unknown port:00000000 irq:0
4: uart:unknown port:00000000 irq:0
5: uart:unknown port:00000000 irq:0
6: uart:unknown port:00000000 irq:0
7: uart:unknown port:00000000 irq:0
8: uart:unknown port:00000000 irq:0
9: uart:unknown port:00000000 irq:0
10: uart:unknown port:00000000 irq:0
11: uart:unknown port:00000000 irq:0
12: uart:unknown port:00000000 irq:0
13: uart:unknown port:00000000 irq:0
14: uart:unknown port:00000000 irq:0
15: uart:unknown port:00000000 irq:0
/proc/tty/driver/usbserial
usbserinfo:1.0 driver:2.0
0: module:pl2303 name:"pl2303" vendor:067b product:2303 num_ports:1 port:1 path:usb-ehci-platform-1
and, below is the more detailed code...
int Serial::openup(const char *devfile) {
if(-1 == (devfds = open(devfile, O_RDWR | O_NOCTTY ))) {
perror(strerror(errno));
return -1;
}
// set device file io mode to nonblock
//int oldflags = fcntl(devfds, F_GETFL);
//fcntl(devfds, F_SETFL, oldflags | O_NONBLOCK);
// get terminal's attributes
tcgetattr(devfds, &tminfo);
memset(&tminfo, 0, sizeof(struct termios));
/// set local mode options ///
//tminfo.c_lflag |= ICANON;
tminfo.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG | IEXTEN);
/// set control mode options ///
tminfo.c_cflag |= (CLOCAL | CREAD);
// disable hardware flow control
tminfo.c_cflag &= ~CRTSCTS;
// set how many bits in a character
tminfo.c_cflag &= ~CSIZE;
tminfo.c_cflag |= CS8;
// we don't need prity checking
tminfo.c_cflag &= ~PARENB;
tminfo.c_cflag &= ~CSTOPB;
/// set input mode options ///
// disable input parity checking, this
tminfo.c_iflag &= ~(INPCK | PARMRK | IGNBRK | BRKINT | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
/// set output mode options ///
//tminfo.c_oflag |= (OPOST | ONLCR);
tminfo.c_oflag &= ~OPOST; // ***
tminfo.c_cc[VMIN] = 0; // ***
tminfo.c_cc[VTIME] = 1; // ***
/// set line speed, defaults to 38400bps, both for input and output ///
// this call will set both input and output speed
cfsetspeed(&tminfo, B38400);
if(-1 == tcsetattr(devfds, TCSANOW, &tminfo)) {
perror(strerror(errno));
return -1;
}
return 0;
}
int Serial::serve() {
char buffer[256] = {0};
/*
struct timeval timeo;
timeo.tv_sec = 0;
timeo.tv_usec = 2 * 1000;
select(0, NULL, NULL, NULL, &timeo);
*/
//print_trace("ready to read data from serial port.\n");
int read_count = 0;
if((read_count = read_line(devfds, buffer, 256))) {
print_trace("read line: %d bytes, %s\n", read_count, buffer);
if(0 == strncmp(buffer, "S", 1)) {
// do some operation
} else if(0 == strncmp(buffer, "N", 1)) {
// do some operation
}
} else {
//print_trace("read no data.\n");
}
// TODO: test only, for find out the reason of serial port 'dead' problem
tcflush(devfds, TCIFLUSH);
}
there is another function for other module to write to serial port
int Serial::write_to_zigbee_co(const char *msg) {
int write_count = 0;
int len = strlen(msg);
struct timeval timeo;
timeo.tv_sec = 0;
timeo.tv_usec = 20 * 1000;
select(0, NULL, NULL, NULL, &timeo);
tcflush(devfds, TCOFLUSH);
if(len == (write_count = write(devfds, msg, len))) {
} else {
tcflush(devfds, TCOFLUSH);
}
return write_count;
}
Serial ports do not just suddenly "die".
The typical reason for a suddenly "dead" or nonresponsive serial link is unwanted flow control. You seem to have HW flow-control disabled, but software flow-control has not been disabled and raw mode has not been properly configured.
Your initialization needs
tminfo.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF);
Also clear the IEXTEN in c_lflag,
and clear the PARENB in c_cflag.
Consider using the cfmakeraw() function to simplify the initialization for raw mode.
Raw mode
cfmakeraw() sets the terminal to something like the "raw" mode of the old Version 7
terminal driver: input is available character by character, echoing is disabled, and
all special processing of terminal input and output characters is disabled. The
terminal attributes are set as follows:
termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;
ADDENDUM
Your revised termios settings look okay.
The next step I would try would be to determine which side of the serial link is at fault. Is one end not receiving or is one end not transmitting?
You could try using the metrics maintained by the serial port drivers. If both sides are running Linux, then you should inspect the files in /proc/tty/drivers. The serial port divers will report the receive and transmit byte counts on each port. Compare the Rx and Tx counts before the test and then after the failure.
If you cannot get any stats from the CC2530 side, then a serial link monitor may be necessary. Beside a dedicated test instrument, you could make/set-up one using a PC with two serial ports. Connect Port A to the host and Port B to the CC2530, so that this PC is a "man in the middle". You would then have to write a program to retransmit the received data of Port A over to Port B, and Port B's RxD to Port A's TxD.
This data that is retransmitted (both channels) also has to be displayed or logged. The purpose is to determine which side of the serial link is failing. Once that has been established, then you have to figure out if it is a receive or transmit issue.
OR
you could post more of your code (the complete open(), initialization routines and read & write logic) for everyone to desk check it.
Addendum 2
The code you have posted has some issues.
Initialization code
// get terminal's attributes
tcgetattr(devfds, &tminfo);
memset(&tminfo, 0, sizeof(struct termios));
This code obtains the termios data, and then zeroes it out!
You need to remove the memset() statement.
Read code
if((read_count = read_line(devfds, buffer, 256))) {
The serial port has been initialized for non-canonical (aka raw) mode, but here is a read_line() which is a canonical input operation.
I don't know exactly what happens when you setup raw mode and try to read lines, but if the read operation ever hangs, I would not be surprised.
You need to evaluate the type of data that will be exchanged between these two devices over this serial link.
Is every message composed of ASCII text with each line terminated by a newline character?
If "yes", then you can use canonical mode and the read_line().
Otherwise you should use non-canonical mode and the read() syscall, and write code to parse the received data.
if((read_count = read_line(devfds, buffer, 256))) {
...
} else {
//print_trace("read no data.\n");
}
When read_line() returns an error (-1), this code will treat it as a good return, and try to process stale or garbage data in the receive buffer. If there have been any read errors they have been undetected and never reported.
tcflush(devfds, TCIFLUSH);
IMO you are misusing tcflush(). There may be some rare cases, but normally you should not throw away any data until you have actually parsed it and know that it is garbage data. You should delete this tcflush() statement.
Write code
select(0, NULL, NULL, NULL, &timeo);
Performing a time delay prior to the write is a questionable operation in userspace. In a multitasking environment with scheduling and preemption to disrupt actual execution time, userspace programs rarely need to add such a fixed delay to every write() syscall.
tcflush(devfds, TCOFLUSH);
Another questionable (mis)use of tcflush().
This should be removed.
if(len == (write_count = write(devfds, msg, len))) {
} else {
tcflush(devfds, TCOFLUSH);
}
Another questionable/improper use of tcflush().
This should be replaced with better recovery code. It's unlikely that a short write will occur; most likely to be returned is either the full write count or an error return (-1). You need to check for an error return (-1), and the errno variable. (You need to do this for other syscalls too, such as tcgetattr(). You need to read the man page of each syscall you use to learn what can be returned.)

How to load LUKS passphrase from USB, falling back to keyboard?

I want to set up a headless Linux (Debian Wheezy) PC with whole disk encryption, with the ability to unlock the disk either with a USB drive, or by entering a passphrase by keyboard. My starting point is a fresh install using the basic whole disk encryption option in the Debian Installer, which manages everything besides /boot as a LUKS-encrypted logical volume group and gives me the keyboard option. I will describe my current solution in an answer, in hopes that it will be useful and that others can improve on it.
Here are some of the issues I had:
Setting up a passphrase and putting it on the USB drive.
Loading the USB modules in time.
Waiting for the USB drive to recognized by Linux before trying to read from it.
Identifying the correct USB drive (not some other drive that happens to be inserted).
Writing a "keyscript" to pull a passphrase off the USB drive.
Ensuring that the fall-back to keyboard kicks in in all USB failure cases.
I will accept an answer with significant improvements and upvote answers that offer contributions.
A lot of my solution is derived from the post, Using A USB Key For The LUKS Passphrase.
Create a random passphrase:
dd if=/dev/urandom bs=1 count=256 > passphrase
Insert a USB drive. dmesg output will show the device name; assume /dev/sdd. Figure out its size:
blockdev --getsize64 /dev/sdd
I decided to install the passphrase at the end of the raw device, figuring it might survive any accidental use of the USB drive.
dd if=passphrase of=/dev/sdd bs=1 seek=<size-256>
Add the passphrase to the LUKS volume:
cryptsetup luksAddKey /dev/sda5 passphrase
This does not affect the existing hand-entered passphrase from the installer. The passphrase file can be deleted:
rm passphrase
Find a unique name for the USB stick, so we can identify it when present:
ls -l /dev/disk/by-id | grep -w sdd
You should see one symlink. I will call it /dev/disk/by-id/<ID>.
Edit /etc/crypttab. You should see a line like:
sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 none luks
Modify it to:
sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 /dev/disk/by-id/<ID> luks,keyscript=/bin/passphrase-from-usb
The keyscript referred to above will need to read the passphrase from the USB device. However, it needs to do more than that. To understand how it is used, check /usr/share/initramfs-tools/scripts/local-top/cryptroot, the script that runs at boot time to unlock the root device. Note when a keyscript is set, it is simply run and the output piped to luksOpen with no other checking. There is no way to signal an error (USB drive not present) or fall back to keyboard input. If the passphrase fails, the keyscript is run again in a loop, up to some number of times; however we are not told which iteration we are on. Also, we have no control over when the keyscript is run, so we can't be sure Linux has recognized the USB drive.
I addressed this with some hacks:
Poll on the USB drive and wait 3 seconds for it to appear. This works for me, but I would love to know a better way.
Create a dummy file /passphrase-from-usb-tried on first run to indicate that we have been run at least once.
If we have been run at least once, or the USB drive cannot be found, run the askpass program used by cryptroot for keyboard input.
The final script:
#!/bin/sh
set -e
if ! [ -e /passphrase-from-usb-tried ]; then
touch /passphrase-from-usb-tried
if ! [ -e "$CRYPTTAB_KEY" ]; then
echo "Waiting for USB stick to be recognized..." >&2
sleep 3
fi
if [ -e "$CRYPTTAB_KEY" ]; then
echo "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME) from USB key" >&2
dd if="$CRYPTTAB_KEY" bs=1 skip=129498880 count=256 2>/dev/null
exit
else
echo "Can't find $CRYPTTAB_KEY; USB stick not present?" >&2
fi
fi
/lib/cryptsetup/askpass "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME)\nEnter passphrase: "
Finally, we need to ensure that this script is available in the initramfs. Create /etc/initramfs-tools/hooks/passphrase-from-usb containing:
#!/bin/sh
PREREQ=""
prereqs() {
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
. "${CONFDIR}/initramfs.conf"
. /usr/share/initramfs-tools/hook-functions
copy_exec /bin/passphrase-from-usb /bin
The USB drivers were not present in my initramfs. (It appears they are by default in later versions of Debian.) I had to add them by adding to /etc/initramfs-tools/modules:
uhci_hcd
ehci_hcd
usb_storage
When all is done, update the initramfs:
update-initramfs -u
It would be ideal to me if I could simply have a small USB stick containing a passphrase that will
unlock the disk. Not only would that be handy for servers (where you could leave the USB stick in the
server - the goal is to be able to return broken harddisks without having to worry about confidential data), it would also be great for my laptop: Insert the USB stick when booting and remove it after
unlocking the cryptodisk.
I have now written a patch that will search the root dir of all devices for the file 'cryptkey.txt' and try decrypting
with each line as a key. If that fails: Revert to typing in the pass phrase.
It does mean the key cannot contain \n, but that would apply to any typed in key, too. The good part is that you can use the same USB disk to store the key for multiple machines: You do not need a separate USB disk for each. So if you have a USB drive in your physical key ring, you can use the same drive for all the machines you boot when being physically close.
You add the key with:
cryptsetup luksAddKey /dev/sda5
And then put the same key as a line in a file on the USB/MMC disk called 'cryptkey.txt'. The patch is here:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=864647
If the USB drivers, MMC drivers or the filesystems are not present in your initramfs, you need to add them by adding to /etc/initramfs-tools/modules:
uhci_hcd
ehci_hcd
usb_storage
nls_utf8
nls_cp437
vfat
fat
sd_mod
mmc_block
tifm_sd
tifm_core
mmc_core
tifm_7xx1
sdhci
sdhci_pci
When all is done, update the initramfs:
update-initramfs -u
It can be found as patch and file at: https://gitlab.com/ole.tange/tangetools/tree/master/decrypt-root-with-usb
despite the great answer from #Andrew which works in previous versions. The solution actually is outdated and needs lots of tuning for ubuntu 18.04 and 19.10. So I want to share my research on this.
There are several catches about crypttab. The sepcs actually changed a lot from 14.04 to 18.04 and to 19.10. It starts to support more parameters for cryptsetup. For example keyfile-offset, keyfile-size, etc. Some of the options e.g. nobootwait are gone. Some parameters supported in other distro already but is not supported in ubuntu yet (for example very nice parameter keyfile-timeout. This can eliminate the entire keyscript since it will automatically fallback to keyboard input after the keyfile-timeout.)
The major pit-fall for crypttab on ubuntu is that it actually processed by 2 different processes. One is the traditionally initramfs and another is the modern systemd. Systemd is supposed to be more advanced and flexiable in many aspect. However, systemd has poor support for crypptab, there are many options such as keyscript just silently ignored. so I have no idea what is going on, until I spotted this post. Almost all the posts online about crypttab settings is for initramfs not for systemd. So we need to add initramfs to all the entries in crypttab to avoid problems.
I also discovered a nice way to debug our keyscript and crypttab without VM or repeatedly rebooting. It is cryptdisks_start. Before we actually propagate our changes to initramfs, we should always test it with this nice command. Otherwise, you have to end-up locked out from your system and can only recover it through chroot environment.
#andrew posted a nice way to use data hide in the raw area of a file system. However, I found it is very annoying when we want to automatically create partitions and dd the raw data to lots of usbkeys, we have to calculate the offset for all different file systems and different partition sizes. Moreover, if a user accidentally write onto the FS, there is some risk that the key got overritten. A raw partition without any FS on it makes more sense in this case. However raw partition does not have UUID which is not very useful for automatic unlocking. Thus, I would like introduce a way just use normal passphrase files on the usbkey filesystem. The major issue of passdev is it does not seek/stop during reading the file. Thus we cannot use the keyfile-offset and keyfile-size option when we want to fallback to keyboard input. Because cryptsetup will actually try to skip in the input content and if the content is shorter than keyfile-size, it raises an error. This also means if there is large offset, passdev can be very slow since it always read from beginning. However, there is no point to implement offset and keyfile size for a actual file on file system. I believe those are created for raw device.
The crypttab
luks-part UUID="<uuid>" /dev/disk/by-uuid/<keyfile FS uuid>:/<keyfile path relative to usbkey root>:<timeout in sec> luks,keyfile-offset=<seek to the key>,keyfile-size=<>,keyscript=/bin/passphrase-from-usbfs.sh,tries=<number of times to try>,initramfs
the keyscript passphrase-from-usbfs.sh utilized the /lib/cryptsetup/scripts/passdev which will wait the usb device and mount the fs then pipe out the file content. It supports the CRYPTTAB_KEY in format of /device-path/<keyfile FS uuid>:/<keyfile path relative to usbkey root>:<timeout in sec>.
#!/bin/sh
#all message need to echo to stderr, the stdout is used for passphrase
# TODO: we may need to do something about the plymouth
echo "CRYPTTAB_KEY=$CRYPTTAB_KEY" >&2
echo "CRYPTTAB_OPTION_keyfile_offset=$CRYPTTAB_OPTION_keyfile_offset" >&2
#set your offset and file size here if your system does not support those paramters
#CRYPTTAB_OPTION_keyfile_offset=
#CRYPTTAB_OPTION_keyfile_size=
echo "timeout=$CRYPTTAB_OPTION_keyfile_timeout" >&2
CRYPTTAB_OPTION_keyfile_timeout=10 # keyfile-timeout is not supported yet
pass=$(/lib/cryptsetup/scripts/passdev $CRYPTTAB_KEY)
rc=$?
if ! [ $rc -eq 0 ]; then
echo "Can't find $CRYPTTAB_KEY; USB stick not present?" >&2
/lib/cryptsetup/askpass "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME) Enter passphrase: "
else
echo "successfully load passphrase." >&2
echo -n $pass
fi
The hook tell update-initramfs to copy our scripts.
#!/bin/sh
PREREQ=""
prereqs() {
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
. "${CONFDIR}/initramfs.conf"
. /usr/share/initramfs-tools/hook-functions
copy_exec /bin/passphrase-from-usbfs.sh
copy_exec /bin/passphrase-from-usb.sh
#when using passdev we need to hook additionaly FS and binary
copy_exec /lib/cryptsetup/scripts/passdev
manual_add_modules ext4 ext3 ext2 vfat btrfs reiserfs xfs jfs ntfs iso9660 udf
Finally I posted the updated version of passphrase-from-usb.sh which can use the new parameters in crypttab:
To accompany excellent answers above please see C routines you could use to write/generate and read raw block device key. The "readkey.c" extracts key of given size from block device and "writekey.c" can generate or write existing key to raw device. The "readkey.c" once compiled can be used in custom script to extract key of known size from raw block device like so:
readkey </path/to/device> <keysize>
To see usage for "writekey", after compiled run it with no flags.
To compile just use:
gcc readkey.c -o readkey
gcc writekey.c -o writekey
I tested both on Verbatim 16GB USB 2.0 USB flash drive with custom "keyscript=" in crypttab also published below. The idea for "crypto-usb.sh" is from "debian etch" cryptsetup guide.
crypto-usb.sh:
#!/bin/sh
echo ">>> Trying to get the key from agreed space <<<" >&2
modprobe usb-storage >/dev/null 2>&1
sleep 4
OPENED=0
disk="/sys/block/sdb"
boot_dir="/boot"
readkey="/boot/key/readkey"
echo ">>> Trying device: $disk <<<" >&2
F=$disk/dev
if [ 0`cat $disk/removable` -eq 1 -a -f $F ]; then
mkdir -p $boot_dir
mount /dev/sda1 $boot_dir -t ext2 >&2
echo ">>> Attempting key extraction <<<" >&2
if [ -f $readkey ]; then
# prints key array to the caller
$readkey /dev/sdb 4096
OPENED=1
fi
umount $boot_dir >&2
fi
if [ $OPENED -eq 0 ]; then
echo "!!! FAILED to find suitable key !!!" >&2
echo -n ">>> Try to enter your password: " >&2
read -s -r A
echo -n "$A"
else
echo ">>> Success loading key <<<" >&2
fi
When generating the key size of the key has to be provided, generated key is saved to ".tmpckey" file with file permissions 0600 for later use. When writing existing key, size is determined by measuring the existing key size. This looks like complex approach however once compiled with simple "gcc" it can provide easy way of manipulating the raw key content.
readkey.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main(int argc, char *argv[])
{
int blockSize = 512;
int keySize = 2048;
FILE *device;
if ( argc == 3
&& (sizeof(argv[1]) / sizeof(char)) > 1
&& (sizeof(argv[2]) / sizeof(char)) > 1
&& (atoi(argv[2]) % 512) == 0
) {
device = fopen(argv[1], "r");
if(device == NULL) {
printf("\nI got trouble opening the device %s\n", argv[1]);
exit(EXIT_FAILURE);
}
keySize = atoi(argv[2]);
}
else if ( argc == 2
&& (sizeof(argv[1]) / sizeof(char)) > 1
) {
device = fopen(argv[1], "r");
if(device == NULL) {
printf("\nI got trouble opening the device %s\n", argv[1]);
exit(EXIT_FAILURE);
}
}
else {
printf("\nUsage: \n");
printf("\nKey Size Provided: \n");
printf("\n\t\treadkey </path/to/device> <keysize> \n");
printf("\nDefault key size: %d\n", keySize);
printf("\n\t\treadkey </path/to/device>\n");
exit(1);
}
int count;
char *block;
/* Verify if key is multiple of blocks */
int numBlocks = 0;
if (keySize % 512 != 0) {
printf("\nSory but key size is not multiple of block size, try again. TA.\n");
exit(1);
}
/* Seek till the end to get disk size and position to start */
fseek(device, 0, SEEK_END);
/* Determine where is the end */
long endOfDisk = ftell(device);
/* Make sure we start again */
rewind(device); // Do I need it ???
/* Get the required amount minus block size */
long startFrom = endOfDisk - blockSize - keySize;
/* Allocate space for bloc */
block = calloc(keySize, sizeof(char));
/* Start reading from specified block */
fseek(device, startFrom, SEEK_SET);
fread(block, 1, keySize, device);
/* Do something with the data */
for(count = 0; count < keySize/*sizeof(block)*/; count++){
printf("%c", block[count]);
}
/* Close file */
fclose(device);
/* Make sure freed array is zeroed */
memset(block, 0, keySize);
free(block);
}
writekey.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int blockSize = 512;
int keySize = 2048;
int count;
unsigned char *block;
/*
Thing to always remember that argv starts from 0 - the name of the program, and argc starts from 1 i.e. 1 is the name of the program.
*/
if ( argc == 3
&& strcmp(argv[1], "genwrite") != 0
&& (sizeof(argv[2]) / sizeof(char)) > 2
) {
char ch;
FILE *keyF;
keyF = fopen(argv[1], "r");
if (keyF == NULL) exit(EXIT_FAILURE);
/* Tell key Size */
fseek(keyF, 0, SEEK_END);
keySize = ftell(keyF);
rewind(keyF);
printf("\nKey Size: %d\n", keySize);
block = calloc(keySize, sizeof(char));
printf("\n-- Start Key --:\n");
for(count = 0; count < keySize/*sizeof(block)*/; count++){
char ch = fgetc(keyF);
block[count] = ch;
/*
Uncomment below to see your key on screen
*/
// printf("%c",ch);
}
printf("\n-- End Key --:\n");
fclose(keyF);
}
else if ( argc == 3
&& strcmp(argv[1], "genwrite") == 0
&& (sizeof(argv[2]) / sizeof(char)) > 2
)
{
printf("\n-- Attempting to create random key(ish --) of size: %d\n", keySize);
block = calloc(keySize, sizeof(char));
int count;
for(count = 0; count < keySize/*sizeof(block)*/; count++){
block[count] = (char) rand();
}
FILE *tmpfile;
tmpfile = fopen(".tmpckey", "w");
if(tmpfile == NULL) exit(EXIT_FAILURE);
fwrite(block, 1, keySize, tmpfile);
fclose(tmpfile);
chmod(".tmpckey", 0600);
}
else if ( argc == 4
&& strcmp(argv[1], "genwrite") == 0
&& (sizeof(argv[2]) / sizeof(char)) > 2
&& ((atoi(argv[3]) % 512) == 0)
)
{
keySize = atoi(argv[3]);
printf("\n-- Attempting to create random key(ish --) of size: %d\n", keySize);
block = calloc(keySize, sizeof(char));
int count;
for(count = 0; count < keySize/*sizeof(block)*/; count++){
block[count] = (char) rand();
}
FILE *tmpfile;
tmpfile = fopen(".tmpckey", "w");
if(tmpfile == NULL) exit(EXIT_FAILURE);
fwrite(block, 1, keySize, tmpfile);
fclose(tmpfile);
chmod(".tmpckey", 0600);
}
else {
printf("\n");
printf("################################################################################\n");
printf("# #\n");
printf("# Usage: #\n");
printf("# #\n");
printf("################################################################################\n");
printf("#> To write existing key to device: #\n");
printf("# #\n");
printf("# writekey </path/to/keyfile> </path/to/removable/sd*> #\n");
printf("# #\n");
printf("#> To generate and write pseudo random key, #\n");
printf("#> key will be saved to temporary file .tmpckey #\n");
printf("# #\n");
printf("# writekey genwrite </path/to/removable/sd*> <keysize in multiples of 512> #\n");
printf("# #\n");
printf("#> When keysize is not provided default size is set to %d. #\n", keySize);
printf("# #\n");
printf("################################################################################\n");
exit(1);
}
/*
Some printf debugging below, uncomment when needed to see what is going on.
*/
/*
printf("\nNumber of Args: %d\n", argc);
printf("\nCurrently block array contains: \n");
for(count = 0; count < keySize; count++){
printf("%c", block[count]);
}
printf("\n-- End block -- \n");
*/
/* Open Device itp... */
FILE *device = fopen(argv[2], "a");
if(device == NULL) exit(EXIT_FAILURE);
printf("\nDevice to write: %s\n", argv[2]);
fseek(device, 0, SEEK_END);
/* Determine where is the end */
long endOfDisk = ftell(device);
printf("\nDevice Size: %ld\n", endOfDisk);
/* Verify if key is multiple of blocks */
int numBlocks = 0;
if (keySize % 512 != 0 || endOfDisk < (blockSize + keySize) ) {
printf("\nSorry but key size is not multiple of block size or device you trying to write to is too small, try again. TA.\n");
fclose(device);
exit(1);
}
/* Make sure we start again */
rewind(device);
/* Get the required amount sunbstracting block size */
long startFrom = endOfDisk - blockSize - keySize;
/* Write some data to the disk */
printf("\nWriting data starting from: %ld\n", startFrom);
fseek(device, startFrom, SEEK_SET);
fwrite(block, 1, keySize, device);
printf("\nBlock Position after data write procedure : %ld\n", ftell(device));
/*
Below is just for convenience, to read what was written,
can aid in debugging hence left commented for later.
*/
/*
printf("\nAmount of Data written : %ld\n", ftell(device) - startFrom);
// Start reading from specified block
printf("\n>>>>>>>> DEBUGGING SECTION <<<<<<<<<\n");
rewind(device); //
fseek(device, startFrom, SEEK_SET);
printf("\nBlock Position before read attempted: %d\n", ftell(device));
printf("\nKey size: %d\n", keySize);
fread(block, 1, keySize, device);
// Do something with the data
printf("\nBlock Position startFrom: %ld\n", startFrom);
printf("\nBlock Position after read: %d\n", ftell(device));
printf("\n-- Buffer Read: --\n");
for(count = 0; count < keySize; count++){
printf("%c", block[count]);
}
printf("\n-- End block -- \n");
printf("\n-- -- \n");
printf("\n-- -- \n");
*/
/* Close file */
fclose(device);
/* Make sure freed array is zeroed */
memset(block, 0, keySize);
free(block);
/* Return success, might change it to be useful return not place holder */
return 0;
}
To verify key written to raw device is the same as the one in file(below will output nothing if keys are identical):
diff -B <(./readkey </path/to/device> 4096) <(cat .tmpckey)
Or for existing key generated using own means:
diff -B <(./readkey </path/to/device> <generated elsewhere key size>) <(cat </path/to/keyfile>)
Thank You
Here is a solution similar to the one by Andrew, but
using CRYPTTAB_TRIED described in the Debian crypttab man page to distinguish tries, and
calling the existing standard keyscript /lib/cryptsetup/scripts/passdev on the first try.
Create your keyfile or keypartition as usual for the passdev script.
Create the following file /usr/local/bin/key-from-usb and make it executable.
#!/bin/sh
set -e
if [ $CRYPTTAB_TRIED -ge 1 ]; then
/lib/cryptsetup/askpass "Second try to unlock $CRYPTTAB_SOURCE ($CRYPTTAB_NAME). Please enter passphrase: "
else
/lib/cryptsetup/scripts/passdev $CRYPTTAB_KEY
fi
In /etc/crypttab use the parameter keyscript=/usr/local/bin/key-from-usb.
Create /etc/initramfs-tools/hooks/key-from-usb with this content:
#!/bin/sh
PREREQ=""
prereqs() {
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
. "${CONFDIR}/initramfs.conf"
. /usr/share/initramfs-tools/hook-functions
manual_add_modules vfat
copy_exec /usr/lib/cryptsetup/scripts/passdev /usr/lib/cryptsetup/scripts/passdev
copy_exec /usr/local/bin/key-from-usb /usr/local/bin/key-from-usb
The first copy_exec line here is needed because passdev is not copied if it is not mentioned in crypttab. Similarly, manual_add_modules vfat will ensure that a vfat usb disk can still be used.
Hint: Use lsinitramfs /boot/initrd.img-... and diff/compare the results to check that the script and all its dependencies are included.

Resources