Related
We are working on a project where we want to communicate with a server.
This is our function to communicate with the server, but somehow it does not read the incoming messages correctly all the time.
Sometimes in the buffer there is something like:
(Server sends)"+ Client version acClient: ID 38ail6ii3s8jc"
instead of:
(Server sends)"+ Client version accepted - please send Game-ID to join"
(We send)"Client: ID 38ail6ii3s8jc"
So, I think the error is within the char *receiveAnswer(int sock) function.
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#define BUFFERSIZE 1024
#define bzeroNew(b,len) (memset((b), '\0', (len)), (void) 0) //buffer loeschen
#define VERSION "VERSION 3.4\n"
#include "functions.h"
char buffer[BUFFERSIZE];
int prologEnd = 0;
int proof;
//liest von Server eine Nachricht ein und speichert sie im buffer ab
char *receiveAnswer(int sock) {
bzeroNew(buffer, BUFFERSIZE);
if(recv(sock, buffer, sizeof(buffer), 0) < 0) {
perror("ERROR: Empfangen fehlgeschlagen\n");
}
printf("%s", buffer);
return buffer;
}
void sendResponse(int sock, char* message) {
bzeroNew(buffer, BUFFERSIZE);
strcpy(buffer, message);
proof = send(sock, buffer, strlen(buffer), 0);
if(proof < 0) {
perror("ERROR: Senden fehlgeschlagen\n");
}
printf("Client: %s\n", buffer);
receiveAnswer(sock);
}
int performConnection(int sock, char* gameID) {
bzeroNew(buffer, BUFFERSIZE);
receiveAnswer(sock);
while(strncmp(buffer, "+", 1) == 0 && prologEnd == 0) {
if(strncmp(buffer, "+ MNM Gameserver", 16) == 0) {
receiveAnswer(sock);
sendResponse(sock, VERSION);
}
else if(strncmp(buffer, "+ Client", 8) == 0) {
sendResponse(sock, gameID);
}
else if(strncmp(buffer, "+ PLAYING", 9) == 0) {
sendResponse(sock, "PLAYER\n");
receiveAnswer(sock);
}
else if(strncmp(buffer, "+ YOU", 5) == 0) {
receiveAnswer(sock);
printf("\n");
prologEnd = 1;
}
else if(strncmp(buffer, "+ TOTAL", 7) == 0) {
receiveAnswer(sock);
receiveAnswer(sock);
prologEnd = 1;
}
}
bzeroNew(buffer, BUFFERSIZE);
return 0;
}
This is our main() function, but I think the error is within the file above:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h> // für Warten auf Kindprozess
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
// für Shared Memory:
#include <sys/ipc.h>
#include <sys/shm.h>
#include "functions.h"
#include "sharedMemory.h"
// dublicat, brauchen wir das?
#define GAMEKINDNAME "NMMorris"
#define HOSTNAME "sysprak.priv.lab.nm.ifi.lmu.de"
#define PORTNUMBER 1357
int main (int argc, char *argv[]) {
char gamekindname[256] = "NMMorris";
char hostname[256] = "sysprak.priv.lab.nm.ifi.lmu.de";
int portnumber = 1357;
char* gameID = argv[2];
char playerNumber[256];
char configFile[256] = "client.conf" ;
int fd[2]; // TODO: fd und client_fd vereinen
//gameID formatieren
char bufferGameID[64];
strcpy(bufferGameID, "ID ");
strcat(bufferGameID, gameID);
strcpy(gameID, bufferGameID);
strcat(gameID, "\n");
int i;
char tmp[256];
//Argumente einlesen und an Variablen übergeben
for(i = 3; i < 7; i++) {
strcpy(tmp, argv[i]);
if (strcmp(tmp, "-p") == 0){
strcpy(playerNumber, argv[i+1]);
} else if (strcmp(tmp, "-conf") == 0){
strcpy(configFile, argv[i+1]);
}
}
config configMain = readConfig(configFile);
strcpy(gamekindname, configMain.gameKind);
strcpy(hostname, configMain.hostServerName);
portnumber = configMain.portNmbr;
printf("\n>>>Config File Data<<<\n");
printf("HostServerName: %s\n", hostname);
printf("PortNumber: %d\n", portnumber);
printf("GameKind: %s\n\n ", gamekindname);
//From here: sockets
int sock, client_fd;
struct sockaddr_in serv_addr;
struct hostent *server;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("\nERROR: Socket creation error \n");
return - 1;
}
//ipAdresse nachschauen
server = gethostbyname(hostname);
if (server == NULL)
{
perror("ERROR: no such host\n");
}
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(portnumber);
memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length);
if ((client_fd = connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))) < 0) {
perror("ERROR: Connection Failed \n");
return -1;
}
printf(">>> Mit Host : %s verbunden <<<\n", hostname);
if(performConnection(sock, gameID) != 0) {
perror("performConnection Failed\n");
} // TODO: verlagern
close(client_fd);
return 0;
// Shared Memory Segment erstellen
int shmid_game = shmget(KEY, sizeof(gameInfo), IPC_CREAT | SHM_R | SHM_W);
if (shmid_game == -1) {
perror("Error while creating shared memory segment");
exit(EXIT_FAILURE);
} else {
printf("Creation successful\n");
}
int shmid_player = shmget(KEY, sizeof(playerInfo), IPC_CREAT | SHM_R | SHM_W);
if (shmid_player == -1) {
perror("Error while creating shared memory segment");
exit(EXIT_FAILURE);
} else {
printf("Creation successful\n");
}
// Prozess mit SHM verbinden
void* shm_game = shmat(shmid_game, 0, 0);
if (shm_game == NULL) {
perror("Error while attaching shared memory segment");
exit(EXIT_FAILURE);
} else {
printf("Attachment successful\n");
}
void* shm_player = shmat(shmid_player, 0, 0);
if (shm_player == NULL) {
perror("Error while attaching shared memory segment");
exit(EXIT_FAILURE);
} else {
printf("Attachment successful\n");
}
// Kindprozess (Connector) erstellen
pid_t pid;
pid = fork();
if (pid < 0) {
fprintf(stderr, "Fehler bei Erstellung des Kindprozesses.\n");
} else if (pid == 0) { // Kindprozess (Connector)
close(fd[1]);
performConnection(sock, gameID);
} else { // Elternprozess (Thinker)
close(fd[0]);
}
return 0;
}
TCP is a stream-oriented protocol, not a message-oriented one. A message sent as 100 bytes can be received as 1 100-byte read or as 100 1-byte reads, or any combination in between. This means that you must keep looping on the recv() until you have the whole message. That, in turn, means you need to know when a message is completely received. Either prepend the message's length before the message, use a fixed-size message, or have a unique recognizable terminator at the end of the message.
recv() returns the number of bytes that it has written into your buffer -- which is to say, it returns the number of bytes that it currently has available to give to you at the time you called it. Importantly, that will often be less than the number of bytes you requested, so it is mandatory that you check the return value of recv() to find out how many bytes you actually received, and not just assume that the value returned by recv() is equal to sizeof(buffer).
OTOH if you want recv() to not return until sizeof(buffer) bytes have been successfully read, you can pass the MSG_WAITALL flag to recv() in the fourth argument.
From the recv man page:
This flag requests that the operation block until the full
request is satisfied. However, the call may still return
less data than requested if a signal is caught, an error
or disconnect occurs, or the next data to be received is
of a different type than that returned. This flag has no
effect for datagram sockets.
It is not guaranteed that recv() will get all of the bytes sent at once. The convention is to call it in a loop until you've read all the bytes.
NB that recv() returns 0 when the client is stalling or closed the connection, and -1 on a read error.
Handling partial send()s:
int sendall(int s, char *buf, int *len)
{
int total = 0; // how many bytes we've sent
int bytesleft = *len; // how many we have left to send
int n;
while(total < *len) {
n = send(s, buf+total, bytesleft, 0);
if (n == -1) { break; }
total += n;
bytesleft -= n;
}
*len = total; // return number actually sent here
return n==-1?-1:0; // return -1 on failure, 0 on success
}
— From Beej's guide to Network Programming
The above code snippet calls send() in a loop until all the data has been sent.
You can now write a similar recv_all function that calls recv() in a loop until it has read all the data.
Handling partial recv()s:
Perhaps something like this:
/* Synopsis: Calls recv() in a loop to ensure
* len bytes have been read. Stores
* the total number of bytes sent in len.
*
* Returns: 0 on failure, 1 otherwise.
*/
static int recv_all(int sockfd, char *buf, size_t *len)
{
size_t bytes_left = *len;
size_t total = 0;
ssize_t rv = 0;
errno = 0;
while (total < *len) {
rv = recv(sockfd, buf + total, bytes_left, 0);
if (rv == 0) { /* Client closed the connection or is stalling */
return 0;
} else if (rv == -1) { /* A read error */
perror("recv");
return 0;
}
total += rv;
bytes_left -= rv;
}
*len = total;
return 1;
}
recv() may also return 0 when 0 characters were read. In that case, len can be compared against the original len to see if the call to recv() was successful.
Note: The above recv_all function has not been tested, and hence is not guaranteed to be bug free. It's meant to be an example.
I am writing a small http proxy server(in C) on a linux machine, Ubuntu 18.04.1 to be specific, and I've been trying to find a way to get the pid of the process that is connecting to it.
It might be of use to mention that the proxy is intended to proxy connections only for processes running on the same machine, so I guess this should make this task possible.
The server uses AF_INET family sockets along with read/write operations in order to do it's job; I am mentioning this because after some research I did encounter threads about "ancillary data",for example: Is there a way to get the uid of the other end of a unix socket connection
Ancillary data contain credentials of the connecting socket(such as PID), but only work on AF_UNIX sockets, used for local IPC, and requires us to explicitly send/receive it on both sides(client/server). In my case, although, as I mentioned, the server will only proxy traffic on the same machine as the server, I need to use AF_INET sockets, so everyone(e.g. web browser) is able to connect to it.
Performance is not so critical; so any suggestions(including workarounds using system calls etc.) are very welcome.
We can use netstat -nptW output to see which local processes' TCP connections. As the output may be security sensitive, superuser privileges are required to see processes belonging to all users.
Since there is no reason to run a proxy service with elevated privileges (expect perhaps CAP_NET_BIND_SERVICE), a privileged helper program is needed.
I pondered a suitable security model for a bit, and came to the conclusion that a helper which examines the connected socket given to it (as say standard input), and outputs just the peer PID(s), would be safest: it would be extremely hard to misuse it, and even if possible, only the peer process ID is revealed.
Here is the example helper, tcp-peer-pids.c:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define EXITCODE_OK 0
#define EXITCODE_STDIN_INVALID 1
#define EXITCODE_UNKNOWN_ADDRESS 2
#define EXITCODE_NETSTAT 3
#define EXITCODE_NETSTAT_OUTPUT 4
#define EXITCODE_WRITE_ERROR 5
#define EXITCODE_PRIVILEGES 6
static pid_t *pids = NULL;
static size_t num_pids = 0;
static size_t max_pids = 0;
static int add_pid(const pid_t p)
{
size_t i;
/* Check if already listed. */
for (i = 0; i < num_pids; i++)
if (pids[i] == p)
return 0;
/* Ensure enough room in pids array. */
if (num_pids >= max_pids) {
const size_t max_temp = (num_pids | 1023) + 1025 - 8;
pid_t *temp;
temp = realloc(pids, max_temp * sizeof pids[0]);
if (!temp)
return ENOMEM;
pids = temp;
max_pids = max_temp;
}
pids[num_pids++] = p;
return 0;
}
int main(void)
{
struct sockaddr_storage sock_addr;
socklen_t sock_addrlen = sizeof sock_addr;
char sock_match[128], sock_host[64], sock_port[32];
struct sockaddr_storage peer_addr;
socklen_t peer_addrlen = sizeof peer_addr;
char peer_match[128], peer_host[64], peer_port[32];
FILE *cmd;
char *line = NULL;
size_t size = 0;
ssize_t len;
int status;
/* Socket address is *remote*, and peer address is *local*.
This is because the variables are named after their matching netstat lines. */
if (getsockname(STDIN_FILENO, (struct sockaddr *)&sock_addr, &sock_addrlen) == -1) {
fprintf(stderr, "Standard input is not a valid socket.\n");
exit(EXITCODE_STDIN_INVALID);
}
if (getpeername(STDIN_FILENO, (struct sockaddr *)&peer_addr, &peer_addrlen) == -1) {
fprintf(stderr, "Standard input is not a connected socket.\n");
exit(EXITCODE_STDIN_INVALID);
}
if ((sock_addr.ss_family != AF_INET && sock_addr.ss_family != AF_INET6) ||
(peer_addr.ss_family != AF_INET && peer_addr.ss_family != AF_INET6)) {
fprintf(stderr, "Standard input is not an IP socket.\n");
exit(EXITCODE_STDIN_INVALID);
}
/* For security, we close the standard input descriptor, */
close(STDIN_FILENO);
/* and redirect it from /dev/null, if possible. */
{
int fd = open("/dev/null", O_RDONLY);
if (fd != -1 && fd != STDIN_FILENO) {
dup2(fd, STDIN_FILENO);
close(fd);
}
}
/* Convert sockets to numerical host and port strings. */
if (getnameinfo((const struct sockaddr *)&sock_addr, sock_addrlen,
sock_host, sizeof sock_host, sock_port, sizeof sock_port,
NI_NUMERICHOST | NI_NUMERICSERV)) {
fprintf(stderr, "Unknown socket address.\n");
exit(EXITCODE_UNKNOWN_ADDRESS);
}
if (getnameinfo((const struct sockaddr *)&peer_addr, peer_addrlen,
peer_host, sizeof peer_host, peer_port, sizeof peer_port,
NI_NUMERICHOST | NI_NUMERICSERV)) {
fprintf(stderr, "Unknown peer address.\n");
exit(EXITCODE_UNKNOWN_ADDRESS);
}
/* Combine to the host:port format netstat uses. */
snprintf(sock_match, sizeof sock_match, "%s:%s", sock_host, sock_port);
snprintf(peer_match, sizeof peer_match, "%s:%s", peer_host, peer_port);
/* Switch to privileged user, if installed as setuid. */
{
uid_t real_uid = getuid();
gid_t real_gid = getgid();
uid_t effective_uid = geteuid();
gid_t effective_gid = getegid();
if (real_gid != effective_gid || real_uid != effective_uid) {
/* SetUID or SetGID in effect. Switch privileges. */
if (setresgid(effective_gid, effective_gid, effective_gid) == -1 ||
setresuid(effective_uid, effective_uid, effective_uid) == -1) {
fprintf(stderr, "Error in privileges: %s.\n", strerror(errno));
exit(EXITCODE_PRIVILEGES);
}
}
}
/* Run netstat to obtain the data; redirect standard error to standard output. */
cmd = popen("LANG=C LC_ALL=C /bin/netstat -nptW 2>&1", "r");
if (!cmd) {
fprintf(stderr, "Cannot run netstat.\n");
exit(EXITCODE_NETSTAT);
}
/* Input line loop. */
while (1) {
char *field[8], *ends;
long val;
pid_t p;
len = getline(&line, &size, cmd);
if (len < 1)
break;
/* Split each line into fields. */
field[0] = strtok(line, "\t\n\v\f\r "); /* Protocol */
/* We are only interested in tcp ("tcp" and "tcp6" protocols). */
if (strcmp(field[0], "tcp") && strcmp(field[0], "tcp6"))
continue;
field[1] = strtok(NULL, "\t\n\v\f\r "); /* Recv-Q */
field[2] = strtok(NULL, "\t\n\v\f\r "); /* Send-Q */
field[3] = strtok(NULL, "\t\n\v\f\r "); /* Local address (peer) */
field[4] = strtok(NULL, "\t\n\v\f\r "); /* Remote address (sock) */
field[5] = strtok(NULL, "\t\n\v\f\r "); /* State */
field[6] = strtok(NULL, "\t\n\v\f\r /"); /* PID */
field[7] = strtok(NULL, "\t\n\v\f\r "); /* Process name */
/* Local address must match peer_match, and foreign/remote sock_match. */
if (strcmp(field[3], peer_match) || strcmp(field[4], sock_match))
continue;
/* This line corresponds to the process we are looking for. */
/* Missing PID field is an error at this point. */
if (!field[6])
break;
/* Parse the PID. Parsing errors are fatal. */
ends = field[6];
errno = 0;
val = strtol(field[6], &ends, 10);
if (errno || ends == field[6] || *ends != '\0' || val < 1)
break;
p = (pid_t)val;
if ((long)p != val)
break;
/* Add the pid to the known pids list. */
if (add_pid(p))
break;
}
/* The line buffer is no longer needed. */
free(line);
/* I/O error? */
if (!feof(cmd) || ferror(cmd)) {
fprintf(stderr, "Error reading netstat output.\n");
exit(EXITCODE_NETSTAT_OUTPUT);
}
/* Reap the netstat process. */
status = pclose(cmd);
if (status == -1) {
fprintf(stderr, "Error reading netstat output: %s.\n", strerror(errno));
exit(EXITCODE_NETSTAT_OUTPUT);
}
if (!WIFEXITED(status)) {
fprintf(stderr, "Netstat died unexpectedly.\n");
exit(EXITCODE_NETSTAT_OUTPUT);
}
if (WEXITSTATUS(status)) {
fprintf(stderr, "Netstat failed with exit status %d.\n", WEXITSTATUS(status));
exit(EXITCODE_NETSTAT_OUTPUT);
}
/* Output the array of pids as binary data. */
if (num_pids > 0) {
const char *head = (const char *)pids;
const char *const ends = (const char *)(pids + num_pids);
ssize_t n;
while (head < ends) {
n = write(STDOUT_FILENO, head, (size_t)(ends - head));
if (n > 0)
head += n;
else
if (n != -1)
exit(EXITCODE_WRITE_ERROR);
else
if (errno != EINTR)
exit(EXITCODE_WRITE_ERROR);
}
}
/* Discard the pids array. */
free(pids);
exit(EXITCODE_OK);
}
It can be run using ordinary user privileges (in which case it'll only know about processes owned by that user), root privileges, or as setuid root.
If used with sudo, ensure you use rule proxyuser ALL = NOPASSWD: /path/to/helper, because sudo has no way of asking a password there. I would probably just install the helper as setuid root at /usr/lib/yourproxy/tcp-peer-pid, owner root, group your proxy service group, and no access to other users (root:proxygroup -r-sr-x---).
The helper is tightly coupled to netstat -nptW output format, but does explicitly set the C locale to avoid getting localized output.
The comparison address:port strings to match to "Local Address" and "Foreign Address" in netstat output are constructed from the addresses returned by getpeername() and getsockname(), respectively, using [getnameinfo()(http://man7.org/linux/man-pages/man3/getnameinfo.3.html) in numerical form (using NI_NUMERICHOST | NI_NUMERICSERV flags).
The helper provides the PIDs in binary form to the server, because the server code would have been too long to fit in a single post here otherwise.
Here is an example TCP service, server.c, which uses the above helper to find out the PID of the peer end of the socket on the local computer. (To avoid denial-of-service attacks, you should set an IP filter that rejects accesses to your proxy service port from outside the computer.)
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#ifndef HELPER_PATH
#define HELPER_PATH "./tcp-peer-pids"
#endif
#ifndef HELPER_NAME
#define HELPER_NAME "tcp-peer-pids"
#endif
#ifndef SUDO_PATH
#define SUDO_PATH "/usr/bin/sudo"
#endif
#ifndef SUDO_NAME
#define SUDO_NAME "sudo"
#endif
/*
* Signal handler, to detect INT (Ctrl+C), HUP, and TERM signals.
*/
static volatile sig_atomic_t done = 0;
static void handle_done(int signum)
{
/* In Linux, all signals have signum > 0. */
__atomic_store_n(&done, (sig_atomic_t)signum, __ATOMIC_SEQ_CST);
}
static int install_done(int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_flags = SA_RESTART; /* Do not interrupt slow syscalls. */
act.sa_handler = handle_done;
if (sigaction(signum, &act, NULL) == -1)
return -1; /* errno set by getpeername() */
return 0;
}
/* Helper function: Move descriptors away from STDIN/STDOUT/STDERR.
Returns 0 if successful, -1 with errno set if an error occurs. */
static inline int normalfds(int fd[], const size_t n)
{
unsigned int closemask = 0;
int err = 0;
size_t i;
int newfd;
for (i = 0; i < n; i++)
while (fd[i] == STDIN_FILENO || fd[i] == STDOUT_FILENO || fd[i] == STDERR_FILENO) {
newfd = dup(fd[i]);
if (newfd == -1) {
err = errno;
break;
}
closemask |= 1u << fd[i];
fd[i] = newfd;
}
/* Close temporary descriptors. */
if (closemask & (1u << STDIN_FILENO)) close(STDIN_FILENO);
if (closemask & (1u << STDOUT_FILENO)) close(STDOUT_FILENO);
if (closemask & (1u << STDERR_FILENO)) close(STDERR_FILENO);
/* Success? */
if (!err)
return 0;
/* Report error. */
errno = err;
return -1;
}
/* Return the number of peer processes.
If an error occurs, returns zero; examine errno. */
size_t peer_pids(const int connfd, pid_t *const pids, size_t maxpids)
{
char *in_data = NULL;
size_t in_size = 0;
size_t in_used = 0;
size_t n;
int binpipe[2], status;
pid_t child, p;
/* Sanity check. */
if (connfd == -1) {
errno = EBADF;
return 0;
}
/* Create a pipe to transfer the PIDs (in binary). */
if (pipe(binpipe) == -1)
return 0; /* errno set by pipe(). */
/* Make sure the binary pipe descriptors do not conflict with standard descriptors. */
if (normalfds(binpipe, 2) == -1) {
const int saved_errno = errno;
close(binpipe[0]);
close(binpipe[1]);
errno = saved_errno;
return 0;
}
/* Fork a child process. */
child = fork();
if (child == -1) {
const int saved_errno = errno;
close(binpipe[0]);
close(binpipe[1]);
errno = saved_errno;
return 0;
}
if (!child) {
/* This is the child process. */
#ifdef USE_SUDO
const char *cmd_path = SUDO_PATH;
char *const cmd_args[3] = { SUDO_NAME, HELPER_PATH, NULL };
#else
const char *cmd_path = HELPER_PATH;
char *const cmd_args[2] = { HELPER_NAME, NULL };
#endif
/* The child runs in its own process group, for easier management. */
setsid();
/* Close read end of pipe. */
close(binpipe[0]);
/* Move established connection to standard input. */
if (connfd != STDIN_FILENO) {
if (dup2(connfd, STDIN_FILENO) != STDIN_FILENO)
_Exit(99);
close(connfd);
}
/* Move write end of pipe to standard output. */
if (dup2(binpipe[1], STDOUT_FILENO) != STDOUT_FILENO)
_Exit(99);
else
close(binpipe[1]);
/* Execute helper. */
execv(cmd_path, cmd_args);
/* Failed to execute helper. */
_Exit(98);
}
/* Parent process. */
/* Close write end of pipe, so we detect when child exits. */
close(binpipe[1]);
/* Read all output from child. */
status = 0;
while (1) {
ssize_t bytes;
if (in_used >= in_size) {
const size_t size = (in_used | 1023) + 1025 - 8;
char *temp;
temp = realloc(in_data, in_size);
if (!temp) {
status = ENOMEM;
break;
}
in_data = temp;
in_size = size;
}
bytes = read(binpipe[0], in_data + in_used, in_size - in_used);
if (bytes > 0) {
in_used += bytes;
} else
if (bytes == 0) {
/* End of input condition. */
break;
} else
if (bytes != -1) {
status = EIO;
break;
} else
if (errno != EINTR) {
status = errno;
break;
}
}
/* Close the pipe. */
close(binpipe[0]);
/* Abort, if an error occurred. */
if (status) {
free(in_data);
kill(-child, SIGKILL);
do {
p = waitpid(child, NULL, 0);
} while (p == -1 && errno == EINTR);
errno = status;
return 0;
}
/* Reap the child process. */
do {
status = 0;
p = waitpid(child, &status, 0);
} while (p == -1 && errno == EINTR);
if (p == -1) {
const int saved_errno = errno;
free(in_data);
errno = saved_errno;
return 0;
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
free(in_data);
errno = ESRCH; /* The helper command failed, really. */
return 0;
}
/* We expect an integer number of pid_t's. Check. */
n = in_used / sizeof (pid_t);
if ((in_used % sizeof (pid_t)) != 0) {
free(in_data);
errno = EIO;
return 0;
}
/* None found? */
if (!n) {
free(in_data);
errno = ENOENT; /* Not found, really. */
return 0;
}
/* Be paranoid, and verify the pids look sane. */
{
const pid_t *const pid = (const pid_t *const)in_data;
size_t i;
for (i = 0; i < n; i++)
if (pid[i] < 2) {
free(in_data);
errno = ESRCH; /* Helper failed */
return 0;
}
}
/* Copy to user buffer, if specified. */
if (maxpids > n)
memcpy(pids, in_data, n * sizeof (pid_t));
else
if (maxpids > 0)
memcpy(pids, in_data, maxpids * sizeof (pid_t));
/* The pid buffer is no longer needed. */
free(in_data);
/* Return the number of pids we actually received. */
return n;
}
int main(int argc, char *argv[])
{
struct addrinfo hints, *list, *curr;
const char *node, *serv;
int service_fd, err;
struct sockaddr_storage client_addr;
socklen_t client_addrlen;
int client_fd;
if (argc != 3) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s HOST PORT\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
/* Install signal handers for Ctrl+C, HUP, and TERM. */
if (install_done(SIGINT) ||
install_done(SIGHUP) ||
install_done(SIGTERM)) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Empty or - or * is a wildcard host. */
if (argv[1][0] == '\0' || !strcmp(argv[1], "-") || !strcmp(argv[1], "*"))
node = NULL;
else
node = argv[1];
serv = argv[2];
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* TCP */
hints.ai_flags = AI_PASSIVE;
hints.ai_protocol = 0;
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
list = NULL;
err = getaddrinfo(node, serv, &hints, &list);
if (err) {
fprintf(stderr, "Invalid host and/or port: %s.\n", gai_strerror(err));
return EXIT_FAILURE;
}
service_fd = -1;
err = 0;
for (curr = list; curr != NULL; curr = curr->ai_next) {
service_fd = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
if (service_fd == -1)
continue;
errno = 0;
if (bind(service_fd, curr->ai_addr, curr->ai_addrlen) == -1) {
if (!err)
if (errno == EADDRINUSE || errno == EADDRNOTAVAIL || errno == EACCES)
err = errno;
close(service_fd);
service_fd = -1;
continue;
}
if (listen(service_fd, 5) == -1) {
if (!err)
if (errno == EADDRINUSE)
err = errno;
close(service_fd);
service_fd = -1;
continue;
}
/* This socket works. */
break;
}
freeaddrinfo(list);
list = curr = NULL;
if (service_fd == -1) {
if (err)
fprintf(stderr, "Cannot listen for incoming connections on the specified host and port: %s.\n", strerror(err));
else
fprintf(stderr, "Cannot listen for incoming connections on the specified host and port.\n");
return EXIT_FAILURE;
}
/* Do not leak the listening socket to child processes. */
fcntl(service_fd, F_SETFD, FD_CLOEXEC);
/* We also want the listening socket to be nonblocking. */
fcntl(service_fd, F_SETFL, O_NONBLOCK);
fprintf(stderr, "Process %ld is waiting for incoming TCP connections.\n", (long)getpid());
/* Incoming connection loop. */
while (!done) {
struct timeval t;
char client_host[64]; /* 64 for numeric, 1024 for non-numeric */
char client_port[32];
pid_t client_pid;
fd_set fds;
t.tv_sec = 0;
t.tv_usec = 100000; /* Max. 0.1s delay to react to done signal. */
FD_ZERO(&fds);
FD_SET(service_fd, &fds);
if (select(service_fd + 1, &fds, NULL, NULL, &t) < 1)
continue;
client_addrlen = sizeof client_addr;
client_fd = accept(service_fd, (struct sockaddr *)&client_addr, &client_addrlen);
if (client_fd == -1) {
if (errno == EINTR || errno == ECONNABORTED)
continue;
fprintf(stderr, "Error accepting an incoming connection: %s.\n", strerror(errno));
continue;
}
if (getnameinfo((const struct sockaddr *)&client_addr, client_addrlen,
client_host, sizeof client_host, client_port, sizeof client_port,
NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
fprintf(stderr, "Cannot resolve peer address for incoming connection, so dropping it.\n");
close(client_fd);
continue;
}
printf("Incoming connection from %s:%s", client_host, client_port);
fflush(stdout);
if (peer_pids(client_fd, &client_pid, 1) != 1) {
printf(", but cannot determine process ID. Dropped.\n");
close(client_fd);
continue;
}
printf(" from local process %ld.\n", (long)client_pid);
fflush(stdout);
/*
* Handle connection.
*/
printf("Closing connection.\n");
fflush(stdout);
close(client_fd);
}
/* Close service socket. */
close(service_fd);
switch (__atomic_load_n(&done, __ATOMIC_SEQ_CST)) {
case SIGINT:
fprintf(stderr, "Received INT signal.\n");
break;
case SIGHUP:
fprintf(stderr, "Received HUP signal.\n");
break;
case SIGTERM:
fprintf(stderr, "Received TERM signal.\n");
break;
}
return EXIT_SUCCESS;
}
The peer_pids() function communicates with the helper process. It is very straightforward, albeit careful to not return unreliable data: instead of ignoring errors or trying to recover from them, it reports failure. This allows the main program do if (peer_pids(client_fd, &pid, 1) != 1) /* Don't know! */ and drop any connection the server is unsure of -- an approach I consider the sane one here.
The normalfds() helper function is often ignored. It helps avoid issues if any of the standard streams are/get closed. It simply moves the set of descriptors away from the three standard streams, using at most three extra descriptors.
You can define USE_SUDO at compile time to have it use sudo when executing the helper. Define HELPER_PATH and HELPER_NAME to the absolute path to the helper and its file name, respectively. (As it is now, they default to ./tcp-peer-pid and tcp-peer-pid, for easier testing.)
The server does install a signal handler for INT (Ctrl+C), HUP (sent when the user closes the terminal), or TERM signals, which all cause it to stop accepting new connections and exit in a controlled manner. (Because the signal handler is installed using SA_RESTART flag, its delivery will not interrupt slow syscalls or cause errno == EINTR. This also means that accept() should not block, or the signal delivery will not be noticed. So, blocking in select() for 0.1s, and checking if a signal was delivered in between, is a good compromise, at least in an example server.)
On my machine, I compiled and tested the service in one terminal window using
gcc -Wall -O2 tcp-peer-pids.c -o tcp-peer-pids
gcc -Wall -O2 "-DHELPER_PATH=\"$PWD/tcp-peer-pids\"" server.c -o server
./server - 2400
That will report Process # is waiting for incoming TCP connections. In another window, using Bash or POSIX shell, I run one or more test netcat commands:
nc localhost 2400 & wait
It might look silly to run a command in the background, and immediately wait for it, but that way you can see the PID of the nc process.
On my system, all loopback (127.x.y.z), TCP/IPv4, and TCP/IPv6 (the addresses of my ethernet and WiFi interfaces) worked fine, and reliably reported the correct PID of the process connecting to the example server.
There are a number of cases where the number of PIDs reported might vary: For example, if the program has executed a child process, but left the connected descriptor open in the child as well. (This should be considered a bug.) Another typical case is the program having exited before the netstat command executes.
If you find any typos or errors or strange behaviour, let me know in a comment so I can verify and fix. I wrote both programs in one sitting, so they are quite likely to contain bugs. As I mentioned, I would not trust either in production before having a colleague (or myself a few times, later on, with fresh eyes) going through it with a critical/paranoid eye.
I would personally only use this approach for logging and statistics, not access control per se. By access control, I mean that you should configure an IP filter (the firewall built in to the Linux kernel) to limit access to only trusted hosts; and specifically allow no incoming proxy connections to the proxy service if only local applications are to be proxied, rather than rely on this detecting all remote connections.
For application-specific logging/limiting, use readlink() on the /proc/PID/exe pseudosymlink. This cannot be faked, but the call may fail if the executable is not accessible, or is too deep in the directory tree. (In those cases I'd reject the proxy connection altogether.)
Note that it is usually trivial for an user to copy an executable to any directory they own, and execute it from there. This means that for application-specific limiting to work at all, you should have tight limits for all applications by default, and relax the limits for specific executables.
I have a file with some data, which is also memory-mapped. So that I have both file descriptor and the pointer to the mapped pages. Mostly the data is only read from the mapping, but eventually it's also modified.
The modification consists of modifying some data within the file (sort of headers update), plus appending some new data (i.e. writing post the current end of the file).
This data structure is accessed from different threads, and to prevent collisions I synchronize access to it (mutex and friends).
During the modification I use both the file mapping and the file descriptor. Headers are updated implicitly by modifying the mapped memory, whereas the new data is written to the file by the appropriate API (WriteFile on windows, write on posix). Worth to note that the new data and the headers belong to different pages.
Since the modification changes the file size, the memory mapping is re-initialized after every such a modification. That is, it's unmapped, and then mapped again (with the new size).
I realize that writes to the mapped memory are "asynchronous" wrt file system, and order is not guaranteed, but I thought there was no problem because I explicitly close the file mapping, which should (IMHO) act as a sort of a flushing point.
Now this works without problem on windows, but on linux (android to be exact) eventually the mapped data turns-out to be inconsistent temporarily (i.e. data is ok when retrying). Seems like it doesn't reflect the newly-appended data.
Do I have to call some synchronization API to ensure the data if flushed properly? If so, which one should I use: sync, msync, syncfs or something different?
Thanks in advance.
EDIT:
This is a pseudo-code that illustrates the scenario I'm dealing with.
(The real code is more complex of course)
struct CompressedGrid
{
mutex m_Lock;
int m_FileHandle;
void* m_pMappedMemory;
Hdr* get_Hdr() { return /* the mapped memory with some offset*/; }
void SaveGridCell(int idx, const Cell& cCompressed)
{
AutoLock scope(m_Lock);
// Write to mapped memory
get_Hdr()->m_pCellOffset[Idx] = /* current end of file */;
// Append the data
lseek64(m_FileHandle, 0, FILE_END);
write(m_FileHandle, cCompressed.pPtr, cCompressed.nSize);
// re-map
munmap(...);
m_pMappedMemory = mmap(...); // specify the new file size of course
}
bool DecodeGridCell(int idx, Cell& cRaw)
{
AutoLock scope(m_Lock);
uint64_t nOffs = get_Hdr()->m_pCellOffset[Idx] = /* ;
if (!nOffs)
return false; // unavail
const uint8_t* p = m_pMappedMemory + nOffs;
cRaw.DecodeFrom(p); // This is where the problem appears!
return true;
}
Use addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, offset) to map the file.
If the size of the file changes, use newaddr = mremap(addr, len, newlen, MREMAP_MAYMOVE) to update the mapping to reflect it. To extend the file, use ftruncate(fd, newlen) before remapping the file.
You can use mprotect(addr, len, protflags) to change the protection (read/write) on any pages in the mapping (both must be aligned on a page boundary). You can also tell the kernel about your future accesses via madvise(), if the mapping is too large to fit in memory at once, but the kernel seems pretty darned good at managing readahead etc. even without those.
When you make changes to the mapping, use msync(partaddr, partlen, MS_SYNC | MS_INVALIDATE) or msync(partaddr, partlen, MS_ASYNC | MS_INVALIDATE) to ensure the changes int partlen chars from partaddr forward are visible to other mappings and file readers. If you use MS_SYNC, the call returns only when the update is complete. The MS_ASYNC call tells the kernel to do the update, but won't wait until it is done. If there are no other memory maps of the file, the MS_INVALIDATE does nothing; but if there are, that tells the kernel to ensure the changes are reflected in those too.
In Linux kernels since 2.6.19, MS_ASYNC does nothing, as the kernel tracks the changes properly anyway (no msync() is needed, except possibly before munmap()). I don't know if Android kernels have patches that change that behaviour; I suspect not. It is still a good idea to keep them in the code, for portability across POSIXy systems.
mapped data turns-out to be inconsistent temporarily
Well, unless you do use msync(partaddr, partlen, MS_SYNC | MS_INVALIDATE), the kernel will do the update when it sees best.
So, if you need some changes to be visible to file readers before proceeding, use msync(areaptr, arealen, MS_SYNC | MS_INVALIDATE) in the process doing those updates.
If you don't care about the exact moment, use msync(areaptr, arealen, MS_ASYNC | MS_INVALIDATE). It'll be a no-op on current Linux kernels, but it's a good idea to keep them for portability (perhaps commented out, if necessary for performance) and to remind developers about the (lack of) synchronization expectations.
As I commented to OP, I cannot observe the synchronization issues on Linux at all. (That does not mean it does not happen on Android, because Android kernels are derivatives of Linux kernels, not exactly the same.)
I do believe the msync() call is not needed on Linux kernels since 2.6.19 at all, as long as the mapping uses flags MAP_SHARED | MAP_NORESERVE, and the underlying file is not opened using the O_DIRECT flag. The reason for this belief is that in this case, both mapping and file accesses should use the exact same page cache pages.
Here are two test programs, that can be used to explore this on Linux. First, a single-process test, test-single.c:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
static inline int read_from(const int fd, void *const to, const size_t len, const off_t offset)
{
char *p = (char *)to;
char *const q = (char *)to + len;
ssize_t n;
if (lseek(fd, offset, SEEK_SET) != offset)
return errno = EIO;
while (p < q) {
n = read(fd, p, (size_t)(q - p));
if (n > 0)
p += n;
else
if (n != -1)
return errno = EIO;
else
if (errno != EINTR)
return errno;
}
return 0;
}
static inline int write_to(const int fd, const void *const from, const size_t len, const off_t offset)
{
const char *const q = (const char *)from + len;
const char *p = (const char *)from;
ssize_t n;
if (lseek(fd, offset, SEEK_SET) != offset)
return errno = EIO;
while (p < q) {
n = write(fd, p, (size_t)(q - p));
if (n > 0)
p += n;
else
if (n != -1)
return errno = EIO;
else
if (errno != EINTR)
return errno;
}
return 0;
}
int main(int argc, char *argv[])
{
unsigned long tests, n, merrs = 0, werrs = 0;
size_t page;
long *map, data[2];
int fd;
char dummy;
if (argc != 3) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s FILENAME COUNT\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This program will test synchronization between a memory map\n");
fprintf(stderr, "and reading/writing the underlying file, COUNT times.\n");
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
if (sscanf(argv[2], " %lu %c", &tests, &dummy) != 1 || tests < 1) {
fprintf(stderr, "%s: Invalid number of tests to run.\n", argv[2]);
return EXIT_FAILURE;
}
/* Create the file. */
page = sysconf(_SC_PAGESIZE);
fd = open(argv[1], O_RDWR | O_CREAT | O_EXCL, 0644);
if (fd == -1) {
fprintf(stderr, "%s: Cannot create file: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
if (ftruncate(fd, page) == -1) {
fprintf(stderr, "%s: Cannot resize file: %s.\n", argv[1], strerror(errno));
unlink(argv[1]);
return EXIT_FAILURE;
}
/* Map it. */
map = mmap(NULL, page, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, fd, 0);
if (map == MAP_FAILED) {
fprintf(stderr, "%s: Cannot map file: %s.\n", argv[1], strerror(errno));
unlink(argv[1]);
close(fd);
return EXIT_FAILURE;
}
/* Test loop. */
for (n = 0; n < tests; n++) {
/* Update map. */
map[0] = (long)(n + 1);
map[1] = (long)(~n);
/* msync(map, 2 * sizeof map[0], MAP_SYNC | MAP_INVALIDATE); */
/* Check the file contents. */
if (read_from(fd, data, sizeof data, 0)) {
fprintf(stderr, "read_from() failed: %s.\n", strerror(errno));
munmap(map, page);
unlink(argv[1]);
close(fd);
return EXIT_FAILURE;
}
werrs += (data[0] != (long)(n + 1) || data[1] != (long)(~n));
/* Update data. */
data[0] = (long)(n * 386131);
data[1] = (long)(n * -257);
if (write_to(fd, data, sizeof data, 0)) {
fprintf(stderr, "write_to() failed: %s.\n", strerror(errno));
munmap(map, page);
unlink(argv[1]);
close(fd);
return EXIT_FAILURE;
}
merrs += (map[0] != (long)(n * 386131) || map[1] != (long)(n * -257));
}
munmap(map, page);
unlink(argv[1]);
close(fd);
if (!werrs && !merrs)
printf("No errors detected.\n");
else {
if (!werrs)
printf("Detected %lu times (%.3f%%) when file contents were incorrect.\n",
werrs, 100.0 * (double)werrs / (double)tests);
if (!merrs)
printf("Detected %lu times (%.3f%%) when mapping was incorrect.\n",
merrs, 100.0 * (double)merrs / (double)tests);
}
return EXIT_SUCCESS;
}
Compile and run using e.g.
gcc -Wall -O2 test-single -o single
./single temp 1000000
to test a million times, whether the mapping and the file contents stay in sync, when both accesses are done in the same process. Note that the msync() call is commented out, because on my machine it is not needed: I never see any errors/desynchronization during testing even without it.
The test rate on my machine is about 550,000 tests per second. Note that each tests does it both ways, so includes a read and a write. I just cannot get this to detect any errors. It is written to be quite sensitive to errors, too.
The second test program uses two child processes and a POSIX realtime signal to tell the other process to check the contents. test-multi.c:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define NOTIFY_SIGNAL (SIGRTMIN+0)
int mapper_process(const int fd, const size_t len)
{
long value = 1, count[2] = { 0, 0 };
long *data;
siginfo_t info;
sigset_t sigs;
int signum;
if (fd == -1) {
fprintf(stderr, "mapper_process(): Invalid file descriptor.\n");
return EXIT_FAILURE;
}
data = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0);
if (data == MAP_FAILED) {
fprintf(stderr, "mapper_process(): Cannot map file.\n");
return EXIT_FAILURE;
}
sigemptyset(&sigs);
sigaddset(&sigs, NOTIFY_SIGNAL);
sigaddset(&sigs, SIGINT);
sigaddset(&sigs, SIGHUP);
sigaddset(&sigs, SIGTERM);
while (1) {
/* Wait for the notification. */
signum = sigwaitinfo(&sigs, &info);
if (signum == -1) {
if (errno == EINTR)
continue;
fprintf(stderr, "mapper_process(): sigwaitinfo() failed: %s.\n", strerror(errno));
munmap(data, len);
return EXIT_FAILURE;
}
if (signum != NOTIFY_SIGNAL)
break;
/* A notify signal was received. Check the write counter. */
count[ (data[0] == value) ]++;
/* Update. */
data[0] = value++;
data[1] = -(value++);
/* Synchronize */
/* msync(data, 2 * sizeof (data[0]), MS_SYNC | MS_INVALIDATE); */
/* And let the writer know. */
kill(info.si_pid, NOTIFY_SIGNAL);
}
/* Print statistics. */
printf("mapper_process(): %lu errors out of %lu cycles (%.3f%%)\n",
count[0], count[0] + count[1], 100.0 * (double)count[0] / (double)(count[0] + count[1]));
fflush(stdout);
munmap(data, len);
return EXIT_SUCCESS;
}
static inline int read_from(const int fd, void *const to, const size_t len, const off_t offset)
{
char *p = (char *)to;
char *const q = (char *)to + len;
ssize_t n;
if (lseek(fd, offset, SEEK_SET) != offset)
return errno = EIO;
while (p < q) {
n = read(fd, p, (size_t)(q - p));
if (n > 0)
p += n;
else
if (n != -1)
return errno = EIO;
else
if (errno != EINTR)
return errno;
}
return 0;
}
static inline int write_to(const int fd, const void *const from, const size_t len, const off_t offset)
{
const char *const q = (const char *)from + len;
const char *p = (const char *)from;
ssize_t n;
if (lseek(fd, offset, SEEK_SET) != offset)
return errno = EIO;
while (p < q) {
n = write(fd, p, (size_t)(q - p));
if (n > 0)
p += n;
else
if (n != -1)
return errno = EIO;
else
if (errno != EINTR)
return errno;
}
return 0;
}
int writer_process(const int fd, const size_t len, const pid_t other)
{
long data[2] = { 0, 0 }, count[2] = { 0, 0 };
long value = 0;
siginfo_t info;
sigset_t sigs;
int signum;
sigemptyset(&sigs);
sigaddset(&sigs, NOTIFY_SIGNAL);
sigaddset(&sigs, SIGINT);
sigaddset(&sigs, SIGHUP);
sigaddset(&sigs, SIGTERM);
while (1) {
/* Update. */
data[0] = ++value;
data[1] = -(value++);
/* then write the data. */
if (write_to(fd, data, sizeof data, 0)) {
fprintf(stderr, "writer_process(): write_to() failed: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Let the mapper know. */
kill(other, NOTIFY_SIGNAL);
/* Wait for the notification. */
signum = sigwaitinfo(&sigs, &info);
if (signum == -1) {
if (errno == EINTR)
continue;
fprintf(stderr, "writer_process(): sigwaitinfo() failed: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (signum != NOTIFY_SIGNAL || info.si_pid != other)
break;
/* Reread the file. */
if (read_from(fd, data, sizeof data, 0)) {
fprintf(stderr, "writer_process(): read_from() failed: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Check the read counter. */
count[ (data[1] == -value) ]++;
}
/* Print statistics. */
printf("writer_process(): %lu errors out of %lu cycles (%.3f%%)\n",
count[0], count[0] + count[1], 100.0 * (double)count[0] / (double)(count[0] + count[1]));
fflush(stdout);
return EXIT_SUCCESS;
}
int main(int argc, char *argv[])
{
struct timespec duration;
double seconds;
pid_t mapper, writer, p;
size_t page;
siginfo_t info;
sigset_t sigs;
int fd, status;
char dummy;
if (argc != 3) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s FILENAME SECONDS\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This program will test synchronization between a memory map\n");
fprintf(stderr, "and reading/writing the underlying file.\n");
fprintf(stderr, "The test will run for the specified time, or indefinitely\n");
fprintf(stderr, "if SECONDS is zero, but you can also interrupt it with\n");
fprintf(stderr, "Ctrl+C (INT signal).\n");
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
if (sscanf(argv[2], " %lf %c", &seconds, &dummy) != 1) {
fprintf(stderr, "%s: Invalid number of seconds to run.\n", argv[2]);
return EXIT_FAILURE;
}
if (seconds > 0) {
duration.tv_sec = (time_t)seconds;
duration.tv_nsec = (long)(1000000000 * (seconds - (double)(duration.tv_sec)));
} else {
duration.tv_sec = 0;
duration.tv_nsec = 0;
}
/* Block INT, HUP, CHLD, and the notification signal. */
sigemptyset(&sigs);
sigaddset(&sigs, SIGINT);
sigaddset(&sigs, SIGHUP);
sigaddset(&sigs, SIGCHLD);
sigaddset(&sigs, NOTIFY_SIGNAL);
if (sigprocmask(SIG_BLOCK, &sigs, NULL) == -1) {
fprintf(stderr, "Cannot block the necessary signals: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Create the file. */
page = sysconf(_SC_PAGESIZE);
fd = open(argv[1], O_RDWR | O_CREAT | O_EXCL, 0644);
if (fd == -1) {
fprintf(stderr, "%s: Cannot create file: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
if (ftruncate(fd, page) == -1) {
fprintf(stderr, "%s: Cannot resize file: %s.\n", argv[1], strerror(errno));
unlink(argv[1]);
return EXIT_FAILURE;
}
close(fd);
fd = -1;
/* Ensure streams are flushed before forking. They should be, we're just paranoid here. */
fflush(stdout);
fflush(stderr);
/* Fork the mapper child process. */
mapper = fork();
if (mapper == -1) {
fprintf(stderr, "Cannot fork mapper child process: %s.\n", strerror(errno));
unlink(argv[1]);
return EXIT_FAILURE;
}
if (!mapper) {
fd = open(argv[1], O_RDWR);
if (fd == -1) {
fprintf(stderr, "mapper_process(): %s: Cannot open file: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
status = mapper_process(fd, page);
close(fd);
return status;
}
/* For the writer child process. (mapper contains the PID of the mapper process.) */
writer = fork();
if (writer == -1) {
fprintf(stderr, "Cannot fork writer child process: %s.\n", strerror(errno));
unlink(argv[1]);
kill(mapper, SIGKILL);
return EXIT_FAILURE;
}
if (!writer) {
fd = open(argv[1], O_RDWR);
if (fd == -1) {
fprintf(stderr, "writer_process(): %s: Cannot open file: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
status = writer_process(fd, page, mapper);
close(fd);
return status;
}
/* Wait for a signal. */
if (duration.tv_sec || duration.tv_nsec)
status = sigtimedwait(&sigs, &info, &duration);
else
status = sigwaitinfo(&sigs, &info);
/* Whatever it was, we kill the child processes. */
kill(mapper, SIGHUP);
kill(writer, SIGHUP);
do {
p = waitpid(-1, NULL, 0);
} while (p != -1 || errno == EINTR);
/* Cleanup. */
unlink(argv[1]);
printf("Done.\n");
return EXIT_SUCCESS;
}
Note that the child processes open the temporary file separately. To compile and run, use e.g.
gcc -Wall -O2 test-multi.c -o multi
./multi temp 10
The second parameter is the duration of the test, in seconds. (You can interrupt the testing safely using SIGINT (Ctrl+C) or SIGHUP.)
On my machine, the test rate is roughly 120,000 tests per second; the msync() call is commented out here also, because I don't ever see any errors/desynchronization even without it. (Plus, msync(ptr, len, MS_SYNC) and msync(ptr, len, MS_SYNC | MS_INVALIDATE) are horribly slow; with either, I can get less than 1000 tests per second, with absolutely no difference in the results. That's a 100x slowdown.)
The MAP_NORESERVE flag to mmap tells it to use the file itself as backing storage when under memory pressure, rather than swap. If you compile the code on a system that does not recognize that flag, you can omit it. As long as the mapping is not evicted from RAM, the flag does not affect the operation at all.
I am actually trying to write a small program to catch global keyboard inputs from specific USB keyboards under linux.
I am testing with this piece of code :
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>
#include <string.h>
#include <stdio.h>
static const char *const evval[3] = {
"RELEASED",
"PRESSED ",
"REPEATED"
};
int main(void)
{
const char *dev = "/dev/input/event2";
struct input_event ev;
ssize_t n;
int fd;
char name[256]= "Unknown";
// int codes[2];
// codes[0] = 58; /* M keycap */
// codes[1] = 49; /* assign to N */
fd = open(dev, O_RDONLY);
if (fd == -1) {
fprintf(stderr, "Cannot open %s: %s.\n", dev, strerror(errno));
return EXIT_FAILURE;
}
if(ioctl(fd, EVIOCGNAME(sizeof(name)), name) > 0)
{
printf("The device on %s says its name is '%s'\n", dev, name);
}
/*
int err = ioctl(fd, EVIOCSKEYCODE, codes);
if (err) {
perror("evdev ioctl");
}*/
while (1) {
n = read(fd, &ev, sizeof ev);
if (n == (ssize_t)-1) {
if (errno == EINTR)
continue;
else
break;
} else
if (n != sizeof ev) {
errno = EIO;
break;
}
if (ev.type == EV_KEY && ev.value >= 0 && ev.value <= 2)
printf("%s 0x%04x (%d)\n", evval[ev.value], (int)ev.code, (int)ev.code);
}
fflush(stdout);
fprintf(stderr, "%s.\n", strerror(errno));
return EXIT_FAILURE;
}
Ths point is that I don't know how to change some input key by other. I tried by calling write() on currently red event by changing the event code, sent key was still previous one, and I tried to used ioctl with EVIOCSKEYCODE, but the call failed with an "invalid argument" error (and I'm not sure to call it correctly).
How can I change outputed key correctly ?
Use the EVIOCGRAB ioctl to grab the input device, so that by reading the events you consume them. Normally (not-grabbed) the events are not consumed when you read them. The ioctl takes an additional parameter, (int)1 for grabbing, (int)0 for releasing.
To re-inject any events, just write them to the uinput device. See eg. a mouse example here. (The event structures are the same type, you only need to write a struct uinput_user_dev structure to the uinput device first, to describe your new input device (which provides the mapped events).)
In other words, you don't remap: you consume and forward.
In the example from, "Advance Programming in the Unix Environment" the following sample program creates a file, then uses lseek to move the file pointer to a further address thus placing a "hole" in the file. The author says the space in between is filled with "0's". I wanted to see if those "0's" would print out. So I modified the program slightly. However I noticed that only the valid characters were writen to the file.
My question is how does the Unix/Linux filesystem manager know not to print the bytes in between?
#include "apue.h"
#include <fcntl.h>
#include <unistd.h>
char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";
char buf3[10];
int
main(void)
{
int fd;
if ((fd = creat("file.hole", FILE_MODE)) < 0) {
err_sys("creat error");
}
if (write(fd, buf1, 10) != 10) { /* offset is now = 10 */
err_sys("buf1 write error");
}
if (lseek(fd, 16380, SEEK_SET) == -1) { /* offset now = 16380 */
err_sys("lseek error");
}
if (write(fd, buf2, 10) != 10) { /* offset now = 16390 */
err_sys("buf2 write error");
}
close(fd);
if ((fd = open("file.hole", O_RDWR)) == -1) {
err_sys("failed to re-open file");
}
ssize_t n;
ssize_t m;
while ((n = read(fd, buf3, 10)) > 0) {
if ((m = write(STDOUT_FILENO, buf3, 10)) != 10) {
err_sys("stdout write error");
}
}
if (n == -1) {
err_sys("buf3 read error");
}
exit(0);
}
The character \000 has a null-width display representation. It is printed, but its printing is invisible. Not every codepoint is a character. In the same way, \n is printed as a newline, not as a character.