diff --git a/opengl/README.md b/opengl/README.md index 2bee729..f20d56b 100644 --- a/opengl/README.md +++ b/opengl/README.md @@ -18,11 +18,11 @@ Requirement of freeglut. GLUT is a higher level interface built on top of GL, and adds things like: -- mouse/keyboard input. +- mouse/keyboard input. This requires callback functions to be called from an event loop. -- higher level geometric objects like spheres, cubes and teapots. +- higher level geometric objects like spheres, cubes and teapots. The original GLUT implementation is proprietary, but open source implementations exist such as Freeglut. diff --git a/posix/Makefile b/posix/Makefile new file mode 120000 index 0000000..cbd0d3e --- /dev/null +++ b/posix/Makefile @@ -0,0 +1 @@ +../Makefile_many \ No newline at end of file diff --git a/posix/Makefile_params b/posix/Makefile_params new file mode 100644 index 0000000..3fe046e --- /dev/null +++ b/posix/Makefile_params @@ -0,0 +1 @@ +LIBS := -lm -pthread diff --git a/posix/README.md b/posix/README.md new file mode 100644 index 0000000..75a6b6f --- /dev/null +++ b/posix/README.md @@ -0,0 +1 @@ +POSIX C API. diff --git a/posix/main.c b/posix/main.c new file mode 100644 index 0000000..148d617 --- /dev/null +++ b/posix/main.c @@ -0,0 +1,3197 @@ +/* +Cheat on the POSIX C API. + +The POSIX C API is mostly an extension of the ANSI C API. +ANSI C features shall not be discussed here. + +#implementations + + On most Linux systems as of 2013, the POSIX C API is implemented by the GNU C library: + . + + The GNU documentation states that POSIX compliance + is a design goal of the GNU C library. + + #windows + + TODO Is there a Windows implementation for the POSIX C API? Official? + +#headers + + List of all headers: + + POSIX defines certain things *inside* + headers with the same name as the ANSI stdlib ones + which are only activated if you add the defines *before + including those files*! + + GCC: if you want to access them with the `-ansi -c99` flags, + you need to define `XXX_XOPEN_SOURCE` + + There are other preprocessor defines which may expose posix functions such as `_POSIX_C_SOURCE` and `POSIX_SOURCE` + For the GNU C library, see `man feature_test_macros` for an explanaition. + + The actual value of the preprocessor refers to the POSIX version. For example: + + - 500: issue 5, 1995 + - 600: issue 6, 2004 + - 700: issue 7, 2008 + +#TODO + + #ptrace + + one process (tracer) observes another process' (tracee) memory directly + + +*/ + +#define _XOPEN_SOURCE 700 +//#define _POSIX_C_SOURCE 200112L +//#define POSIX_SOURCE + +//ansi headers in which POSIX places extensions + +#include +#include //NZERO +#include //M_PI, M_PI_2, M_PI_4: +#include +#include //popen(), perror() +#include +#include +#include //strerror + +//posix only headers: + +#include +#include +#include +#include //creat, O_CREAT +#include +#include //strfmon +#include //gethostbyname +#include +#include +#include //getpwuid, getpwnam, getpwent +#include +#include +#include //mmap, munmap +#include //rusage, getrusage, rlimit, getrlimit +#include //select, FD_ZERO, FD_SET +#include //shmget, shmat, etc. +#include +#include //S_IRUSR and family, +#include //lots of posix realted typedef types +#include //uname, struct utsname +#include //wait, sleep +#include //syslog +#include //major posix header. Anything that is not elsewhere is here. + +extern char **environ; + +/* pthreads related */ + +#define NUM_THREADS 5 + + pthread_mutex_t main_thread_mutex = PTHREAD_MUTEX_INITIALIZER; + + void* main_thread(void* vargument) + { + int argument; + + argument = *((int*)vargument); + + pthread_mutex_lock(&main_thread_mutex); + printf("tid = %d\n", argument); + //all threads of a process have the same PID + printf(" getpid() = %llu\n", (uintmax_t)getpid()); + printf(" pthread_self() = %llu\n", (uintmax_t)pthread_self()); + pthread_mutex_unlock(&main_thread_mutex); + + return NULL; + } + +int main(int argc, char** argv) +{ + /* + #Namespace + + POSIX adds many further per header reserved names which it would be wise to follow even on ANSI C: + section "The Name Space". + */ + + /* + #errors + + Typical error dealing conventions POSIX are: + + - if the return value is not needed, functions return 0 on successs and either -1 on failure + or an integer which indicates failure cause + + - if the return value is strictly positive, return -1 on error + + - if the return value is a pointer, return `NULL` on error + + - if the return value can be any integer (`ptrace` for example), return `-1`, but force the user to + clear `errno` before making the call, and check if `errno != -` after the call. + + Whenever there is an error, set `errno` accordingly to determine what was the cause of the erro + + #errno.h + + Is defined by ANSI C, but more predefined error constants are added extended in POSIX, + + Only differences from ANSI C shall be commented here. Note that errno, + perror and strerror for example are all in ANSI C. + + Some of the POSIX specific errors and their error messages are: + + - EPERM: Operation not permitted + + when users try to do something which requires previledges that they don't have, + such as being the root user. + + - ENOENT: No such file or directory + - EINTR: Interrupted system call + - EIO: I/O Error + - EBUSY: Device or resource busy + - EEXIST: File exists + - EINVAL: Invalid argument + - EMFILE: Too many open files + - ENODEV: No such device + - EISDIR: Is a directory + - ENOTDIR: Isn’t a directory + + Functions that modify errno document that. The convention is that only functions which fail modify + errno, not those that succeed. + + errno can be modified as `errno = 0` for example. + */ + { + /* + #errno + + errno can be modified by functions to contain a description of certain + standard errors. + + errno is a lvalue and users can explicitly modify it. + This is necessary in certain error checking situations, when the error can only be decided + by changes in errno and not by the return value. + + 0 indicates no error (ANSI C) + + Since many function may change errno, you must use the functions that + depend on errno immediatelly after the function that generates the error + */ + { + char *dirname = "i_dont_exist"; + + //assure that dirname does not exist + if(access(dirname, F_OK) == 0 ) + assert(rmdir(dirname) != -1 ); + errno = 0; + + rmdir(dirname); + assert(errno != 0); + + //sucessful calls do *not* set errno to 0 + mkdir(dirname, 0777); + rmdir(dirname); + assert(errno != 0); + } + } + + /* + #printf + + This discusses `printf` and `sprintf` POSIX extensions to ANSI. + */ + { + /* + #dollar + + `%2$d` means: use second argument. Treat the following arguments as if this one did not exist. + + Has been incorporated in POSIX, but may break ANCI C code! (unlikely). + + For that reason, compiling this generates warnings on gcc, and you should avoid this feature as: + + - it is unlikely to be incorporated in ANSI C since it is a breaking change + + - if you ever decide to increase portability to ANSI C + (in case some other key functions you were using someday get ANSI C alternatives), + you will have to correct this + */ + { + //char buf[256]; + //sprintf(buf, "%2$d %d %d", 0, 1); + //assert(strcmp(buf, "1 0 1") == 0); + } + } + + /* + #string functions + + POSIX offers some extra convenience functions to common string operations which are not present in ANSI C. + */ + { + /* + str + */ + + /* + #strfmon + + Monetary string formatting. + */ + { + const int size = 16; + char out[size]; + strfmon(out, size, "%n", 1234.567); + printf("%s", out); + assert(strcmp(out, "1234.57") == 0 ); + } + } + + /* + #syslog + + writes error messages to standard system files + + interface: + + void syslog(int priority, const char *message, arguments...); + + error levels: + + - LOG_EMERG Description + - LOG_ALERT An emergency situation + - LOG_CRIT High-priority problem, such as database corruption + - LOG_ERR Critical error, such as hardware failure + - LOG_WARNING Errors + - LOG_NOTICE Warning + - LOG_INFO Special conditions requiring attention + - LOG_DEBUG Informational messages + + error source: + + - LOG_USER: a user space application + - LOG_LOCAL[0-7]: left for admins to specify + + message: accepts format strings similar to printf with extensions + + - %m: errno message string + */ + { + //TODO0 this breaks my poor program, why? needs root? + + //syslog(LOG_ERR | LOG_USER, "syslog test: %m\n"); + } + + /* + #environment variables + + each process includes a list of its environment variables + + those can be modified for the process + + child processes inherit those variables, so this is a way + for processes to communicate + */ + { + /* + #getenv + + specified by ANSI C + + #setenv + + not specified by ANSI C TODO check + + #putenv + + don't use, just use `setenv` instead. POSIX 7 itself says this. + */ + { + assert(setenv("HOME", "asdf", true) != -1 ); + assert(strcmp(getenv("HOME"), "asdf" ) == 0 ); + + //with overwrite false, if existing is not overwritten + //but error is not returned: + + assert(setenv("HOME", "qwer", false) != -1 ); + assert(strcmp(getenv("HOME"), "asdf" ) == 0 ); + } + + /* + #environ + + automatically set by POSIX libraries linked to + + is a list of strings of type `VAR=val` + */ + if (0) { + //print entire environment + char **env = environ; + puts("environ:"); + while (*env) { + printf(" %s\n", *env); + env++; + } + } + } + + /* + #math.h + + the `M_PI` constants are defined by POSIX inside of math.h + */ + { + //#constants + { + //ansi c way of calculating some constants: + const float PI = acos(-1); + const float E = exp(1); + + //POSIX provides macros that expand to those constants: + assert(fabs(M_E - E) < 1e-6 ); + assert(fabs(M_PI - PI) < 1e-6 ); + assert(fabs(M_PI/2.0 - M_PI_2) < 1e-6 ); + assert(fabs(M_PI/4.0 - M_PI_4) < 1e-6 ); + } + + /* + #bessel + + As of POSIX 7, the only major function addition to the math library + seems to be bessel functions. + + TODO understand, specially why is it so important to be here? + + + */ + { + //double j0(double); + //double j1(double); + //double jn(int, double); + } + } + + /* + #sleep + + Non busy sleep, that is, stop program execution for a given time, + and let other programs run in the meantime. + + There is no portable standard way of doing this. + */ + if (0) { + printf("sleep:\n"); + for(int i=0; i<3; i++) + { + printf("%d\n", i); + sleep(1); + } + printf("\n"); + } + + /* + #times + + Get real time, user time and system time. + */ + { + //TODO0 example + } + + /* + #file descriptors + + `int` identifier to a data stream. + + Many file descriptors can point to a single file. + + One very important property of file descriptors is the current position from which read and write shall operate. + Reads and writes move the current position forward. + + #file descriptors vs ANSI C FILE objects + + ANSI C supports only the concept of file pointers via the `FILE` macro. + + POSIX extends ANSI C and contains both function that manipulate ANSI C `FILE` objects + and file descriptors, which are integers that identify a file descriptor for the OS. + + Operations that use file desriptors: + + - open, close, write + + Operations that use FILE pointers: + + - ANSI C `fopen`, `fclose`, `ftell`, etc. functions + + Since they are specific to POSIX, functions that use file pointers often give more options + than ANSI C functions that use `FILE*` objects. + + If you don't need that greater level of control, + just use the ANSI C functions for greater portability. + + It is possible to convert freely to and from `FILE*` via fdopen and fileno. + + #fdopen + + Convert file descriptor to `FILE*`. + + #fileno + + Convert `FILE*` to file descriptor. + + #open + + man 2 open + + open file descriptors such as files + + returns an `int` (file descriptor number) instead of a file + + Interfaces: + + int open(const char *pathname, int flags); + int open(const char *pathname, int flags, mode_t mode); + + Flags. Must specify one and only of the following: + + - O_WRONLY: write only + - O_RDONLY: read only. + + Undefined behaviour with O_TRUNC. + + TODO0 can be used with O_CREAT? + + - O_RDWR: read and write + + Other important flags. + + - O_APPEND: If the file exists, open fd at the end of the file. + + - O_TRUNC: If the file exists, open fd at the end of the file, + set its length to zero, discarding existing contents. + + Undefined behaviour with O_RDONLY. + + - O_CREAT: If the file does not exit, creat it, with permissions given in mode. + + Mode must be specified when this flag is set, and is ignored if this is not set. + + - O_EXCL: Used with O_CREAT, ensures that the caller creates the file. + + The open is atomic; that is, no two opens can open at the same time. + + If the file already exists, open will fail. + + Useful if multiple programs may try to create the same file. + + Mode can be specified via oring predefine permission values of type: + + - S_IRWXU 00700 user (file owner) has read, write and execute permission + - S_IRUSR 00400 user has read permission + - S_IWUSR 00200 user has write permission + - S_IXUSR 00100 user has execute permission + + For group and other: G and GRP, O and OTH. + + Return value: `-1` on error and set perror. + + #creat + + Same as: + + open(path, O_WRONLY|O_CREAT|O_TRUNC, mode); + + #close + + File descriptors occupy memory and are thus a finite resource. + + When you are done with one, release it with a close call. + + #write + + Write to file descriptor, such as one representing a file gotten via `open` + or one representing a pipe obtained with `pipe`. + + Increments the current position of the file descriptor. + For regular files, if this becomes greater than the current file size, + then the file size is increased as needed. + + #return value + + Returns number of bytes writen. + + For regular files, POSIX does not say much when the number of bytes writen is less than the ammount required + and that usually only happens in bad cases: + + - signal received in the middle of a write + + If it receives a signal before writing anything, returns -1. + + - no more space in the filesystem + + - no permission to write such large files + + For pipes, this may occur in less bad scenarios, for example if the pipe buffer is filled, + the write may either: + + - be partial if `nbytes > PIPE_BUF` + + - block until more space is available depending on the `O_NONBLOCK` state. + + On error, return -1 and set errno. + + It seems that POSIX does not say if zero return values on non-zero size write requests. + It is unlikely that an implementation will return 0, since that would make no progress, + and it should return `-1` and set errno instead to report error cases. + + #atomicity of simultaneous writes + + Writes of less than `PIPE_BUF` bytes cannot be interleaved with other writes. + + Larger writes can. + + TODO0 are writes to seekable files atomic? Seems not: + for pipes we know yes for writes smaller than PIPE_BUF. + + #pwrite + + Same as write, but writes to a given offset and does not update fd position. + + It cannot be done on non seekable files. + + #read + + POSIX 7 docs: + + Read bytes from a file descriptor. + + Interface: + + ssize_t read(int fildes, void *buf, size_t nbyte); + + The behavior of multiple concurrent reads on the same pipe, FIFO, or terminal device is unspecified. + + If the starting position is at or after the end-of-file, 0 shall be returned. + + If the value of `nbyte` is larger than {SSIZE_MAX}, the result is implementation-defined. + In practice this is rarely the case, because `SSIZE_MAX` is the size of a `size_t` type, + which is usually an integer, giving around 2Gb. + + The exact behaviour of read depends on the fd type: pipes and regular files have slightly different rules. + + #read pipe + + When attempting to read from an empty pipe or FIFO: + + - If no process has the pipe open for writing, read() shall return 0 to indicate end-of-file. + + - If some process has the pipe open for writing and O_NONBLOCK is set, + read() shall return -1 and set errno to [EAGAIN]. + + - If some process has the pipe open for writing and O_NONBLOCK is clear, + read() shall block the calling thread until some data is written or + the pipe is closed by all processes that had the pipe open for writing. + + #return value + + Returns number of bytes read. + + Quoting POSIX: + + The value returned may be less than nbyte if: + + - the number of bytes left in the file is less than nbyte + + - the read() request was interrupted by a signal + + - if the file is a pipe or FIFO or special file and has fewer + than nbyte bytes immediately available for reading. + + Therefore on a regular file, this is how the end of file can be recognized. + + On error return -1 and set errno. + + #lseek + + Like ANSI C fseek for file descriptors. + + lseek cannot be done on certain file descriptor types which are not seekable, + for example on pipes. + + #lseek after eof + + If data is writen with offset after file size, file size is increased and data skipped reads `(int)0` (`'\0'`). + + This contrasts with ANSI C fseek, in which this is undefined behaviour. + #fcntl + + Manipulate a file descriptor. + + Check if a fd is open: + + #dup + + Duplicate file descriptor. + + Same as: + + fcntl(fildes, F_DUPFD, 0); + */ + { + int fd; + char in[] = "abcd"; + int nbytes = strlen(in); + int nbytes_total, nbytes_last; + char *out = malloc (nbytes + 1); + char *fname = "open.tmp"; + + /* + write + + A robust write usage that either outputs all its bytes, + or gives an error. + */ + { + fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } else { + nbytes_total = 0; + while (nbytes_total < nbytes) { + nbytes_last = write(fd, in + nbytes_total, nbytes - nbytes_total); + if (nbytes_last == -1) { + perror("write"); + exit(EXIT_FAILURE); + } + nbytes_total += nbytes_last; + } + + if (close(fd) == -1 ) { + perror("close"); + exit(EXIT_FAILURE); + } + } + } + + //read + { + fd = open(fname, O_RDONLY); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } else { + nbytes_total = 0; + while ((nbytes_last = read(fd, out, nbytes) ) > 0 ) { + //compare output as it comes out, even if less than nbytes comes + assert(memcmp(in + nbytes_total, out, nbytes_last) == 0 ); + nbytes_total += nbytes_last; + } + if (nbytes_last == -1) { + perror("read"); + exit(EXIT_FAILURE); + } + + if (close(fd) == -1 ) { + perror("close"); + exit(EXIT_FAILURE); + } + close(fd); + } + } + + //BAD forget O_CREAT on non-existent file gives ENOENT + { + fd = open("/i/do/not/exist", O_RDONLY, S_IRWXU); + if (fd == -1) { + assert(errno == ENOENT); + } + else { + assert(false); + } + } + + //BAD write on a O_RDONLY fd gives errno EBADF + { + int fd; + char *fname = "write_rdonly.tmp"; + + fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + else { + if (close(fd) == -1 ) { + perror("close"); + exit(EXIT_FAILURE); + } + } + + fd = open(fname, O_RDONLY); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + else { + if (write(fd, "a", 1) == -1 ) { + assert(errno == EBADF); + } + else { + assert(false); + } + + if (close(fd) == -1 ) { + perror("close"); + exit(EXIT_FAILURE); + } + } + } + + /* + open and write without truncate + + after write 1: abcd + after write 2: 01cd + */ + { + //set file to abc + { + fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } else { + if (write(fd, in, nbytes) != nbytes ) { + perror("write"); + exit(EXIT_FAILURE); + } + if (close(fd) == -1 ) { + perror("close"); + exit(EXIT_FAILURE); + } + } + } + + //open and write to it without truncating + { + fd = open(fname, O_RDWR); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + else { + if (write(fd, "01", 2) != 2 ) { + perror("write"); + exit(EXIT_FAILURE); + } + } + } + + //check the new result + { + if (lseek(fd, 0, SEEK_SET) != 0 ) { + perror("lseek"); + exit(EXIT_FAILURE); + } + if (read(fd, out, nbytes) != nbytes ) { + perror("read"); + exit(EXIT_FAILURE); + } + else { + //the first two bytes were overwriten + assert(memcmp(out, "01cd", nbytes) == 0 ); + } + if (close(fd) == -1 ) { + perror("close"); + exit(EXIT_FAILURE); + } + close(fd); + } + } + + /* + lseek after eof + */ + { + int fd; + char out[2]; + + fd = open("lseek.tmp", O_RDWR | O_CREAT | O_TRUNC, S_IRWXU); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + else { + if (lseek(fd, 1, SEEK_SET) != 1 ) { + perror("lseek"); + exit(EXIT_FAILURE); + } + if (write(fd, "a", 1) != 1 ) { + perror("write"); + exit(EXIT_FAILURE); + } + //read after eof, return 0 and read nothing + if (read(fd, out, 1) != 0 ) { + assert(false); + } + if (lseek(fd, 0, SEEK_SET) != 0 ) { + perror("lseek"); + exit(EXIT_FAILURE); + } + //byte 0 was never writen, so reading it returns (int)0 + if (read(fd, out, 2) != 2 ) { + perror("read"); + exit(EXIT_FAILURE); + } else { + assert(memcmp(out, "\0a", 2) == 0 ); + } + if (close(fd) == -1 ) { + perror("close"); + exit(EXIT_FAILURE); + } + } + } + + /* + File descriptors open by default to all processes. + + Analogous to ANSI C `stdout`, `stdin` and `stderr`, except that the ANSI C `FILE*` objects. + + #STDIN_FILENO + + stdin + + #STDOUT_FILENO + + stdout + + #STDERR_FILENO + + stderr + */ + { + printf("STDIN_FILENO = %d\n", STDIN_FILENO); + printf("STDOUT_FILENO = %d\n", STDOUT_FILENO); + printf("STDERR_FILENO = %d\n", STDERR_FILENO); + } + } + + /* + #link + + Create hardlink to file. + + If newfile exists, the link is not created, returns -1 and sets `errno = EEXIST` + + #unlink + + Delete file. + + Is called unlink because what you are doing is not to directly remove a file from disk + but instead remove one hardlink for the data. + + if the number of hardlinks to a data equals 0, it the data gets deleted. + + If the given path does not exist `errno = ENOENT`. + */ + { + int fd; + char in[] = "a"; + char in_new[] = "b"; + int nbytes = strlen(in); + char *out = malloc(nbytes); + char *oldpath = "link_old.tmp"; + char *newpath = "link_new.tmp"; + + //create old + fd = open(oldpath, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } else { + if (write(fd, in, nbytes) != nbytes ) { + perror("write"); + exit(EXIT_FAILURE); + } + if (close(fd) == -1 ) { + perror("close"); + exit(EXIT_FAILURE); + } + } + + /* ensure that the new path does not exist + ENOENT is ok since the path may not exist */ + if (unlink(newpath) == -1 && errno != ENOENT ) { + perror("link"); + exit(EXIT_FAILURE); + } + + //make the hardlink + if (link(oldpath, newpath) == -1 ) { + perror("link"); + exit(EXIT_FAILURE); + } + + //write to new + fd = open(newpath, O_WRONLY); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } else { + + if (write(fd, in_new, nbytes) != nbytes ) { + perror("write"); + exit(EXIT_FAILURE); + } + + if (close(fd) == -1 ) { + perror("close"); + exit(EXIT_FAILURE); + } + } + + //assert that it reflected on old + fd = open(oldpath, O_RDONLY); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } else { + if (read(fd, out, nbytes) != nbytes ) { + perror("read"); + exit(EXIT_FAILURE); + } + + assert(memcmp(out, in_new, nbytes) == 0 ); + + if (close(fd) == -1 ) { + perror("close"); + exit(EXIT_FAILURE); + } + } + + free(out); + } + + /* + #symlink + + create symbolic link + + TODO0 example + */ + { + } + + /* + #select + + Wait for one of multiple file descriptors to become available for some operation. + + Sounds like server implementation! + */ + + /* + #mmap + + Good man page: + + man mmap + + Initial code source: + + Map RAM memory to a file. + + TODO application? + */ + { + char *filepath = "mmap.tmp"; + int numints = 4; + int filesize = numints * sizeof(int); + + int i; + int fd; + int result; + int *map; /* mmapped array of int's */ + + //write to file with mmap + { + /* `O_WRONLY` is not sufficient when mmaping, need `O_RDWR`.*/ + fd = open(filepath, O_RDWR | O_CREAT | O_TRUNC, (mode_t)0600); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + + /* set fd position and write to it to strech the file */ + result = lseek(fd, filesize - 1, SEEK_SET); + if (result == -1) { + close(fd); + perror("lseek"); + exit(EXIT_FAILURE); + } + + /* write something to the file to actually strech it */ + result = write(fd, "", 1); + if (result != 1) { + close(fd); + perror("write"); + exit(EXIT_FAILURE); + } + + /* do the actual mapping call */ + map = mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) { + close(fd); + perror("mmap"); + exit(EXIT_FAILURE); + } + + /* write int's to the file as if it were memory because MAP_SHARED was used */ + for (i = 0; i < numints; ++i) { + map[i] = i; + } + + /* free mmapped memory */ + if (munmap(map, filesize) == -1) { + perror("munmap"); + exit(EXIT_FAILURE); + } + + /* un-mmaping doesn't close the file, so we still need to do that. */ + if (close(fd) == -1) { + perror("close"); + exit(EXIT_FAILURE); + } + } + + //read result back in + { + fd = open(filepath, O_RDONLY, 0); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + + map = mmap(0, filesize, PROT_READ, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) { + close(fd); + perror("mmap"); + exit(EXIT_FAILURE); + } + + assert(map[1] == 1); + + //segmentation fault because no PROT_WRITE: + { + //map[1] = 2; + } + + if (munmap(map, filesize) == -1) { + perror("munmap"); + exit(EXIT_FAILURE); + } + + if (close(fd) == -1) { + perror("close"); + exit(EXIT_FAILURE); + } + } + + /* + MAP_PRIVATE + + Creates a copy-on-write version of file in memory. + + Changes do not reflect on the file. + + TODO0 application? + */ + { + //private write + { + fd = open(filepath, O_RDONLY, 0); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + + map = mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + close(fd); + perror("mmap"); + exit(EXIT_FAILURE); + } + + map[0] = 1; + + if (munmap(map, filesize) == -1) { + perror("munmap"); + exit(EXIT_FAILURE); + } + + if (close(fd) == -1) { + perror("close"); + exit(EXIT_FAILURE); + } + } + + //read + { + fd = open(filepath, O_RDONLY, 0); + if (fd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + + map = mmap(0, filesize, PROT_READ, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) { + close(fd); + perror("mmap"); + exit(EXIT_FAILURE); + } + + //did not change! + assert(map[0] == 0); + + if (munmap(map, filesize) == -1) { + perror("munmap"); + exit(EXIT_FAILURE); + } + + if (close(fd) == -1) { + perror("close"); + exit(EXIT_FAILURE); + } + } + } + } + + //#pathname operations + { + /* + #realpath + + Return: + + - absolute path + - cannonical: does not contain `.` nor `..`. + + Interface: + + char *realpath(const char *restrict file_name, + char *restrict resolved_name); + + The function does completelly different things depending if resolved_name is NULL or not: + + - `resolved_name == NULL`: realpath mallocs the path for you and returns it. + + You have to free it in the future. + + This is a good options if you don't already have a buffer of the right size, since calculating the required buffer size + would be annoying (would require calling `pathconf`). + + - `resolved_name == NULL`: the pathname is copied to `resolved_name`. + + You must make sure that you have enough space there. + + This is a good option if you already have a large enough buffer laying around, + since it does not require dynamic allocation. + + Of course, ensuring that your buffer is large enough means doing messy `pathconfs` at some point. + */ + { + char *rp = realpath(".", NULL); + if (rp) { + printf("realpath(\".\") = %s\n", rp); + } else { + perror("realpath"); + exit(EXIT_FAILURE); + } + free(rp); + } + + /* + #dirname #basename + + p may be modified memory is statically allocated + and may change on next dirname/basename call. + TODO what is p + + behaviour: + + path dirname basename + ---------- -------- --------- + "/usr/lib" "/usr" "lib" + "/usr/" "/" "usr" + "usr" "." "usr" + "/" "/" "/" + "." "." "." + ".." "." ".." + */ + { + + char p[1024]; + char* res; + + strcpy(p, "a/b"); + res = dirname(p); + assert(strcmp(res, "a") == 0 ); + + strcpy(p, "a/b"); + res = basename(p); + assert(strcmp(res, "b") == 0 ); + } + } + + /* + #file and directory operations + + there is no standard portable way of doing most them: + + + posix alternatives: + + - portable semi heavyweight: booost: #include + - portable lightweight: dirent.h + */ + { + + /* + #stat family + + get info on paths + + return value: 0 on success, other constants on errors. + + If you get a 0, you know the file exists! + This is not however the best way to check if a file exists since it incurs the large overhead + of getting the parameters. Use `access` for that instead. + + This fills in the `struct stat` given by pointer. + + The family contains the following variants: + + - stat: takes string path. Grouped under fstatat. + - lstat: does not dereference symbolic links. Grouped under fstatat. + - fstat: takes fd. + - fstatat: can't understand, does not seem important. + + #struct stat + + fields: + + - dev_t st_dev Device ID of device containing file. + - ino_t st_ino File serial number. + - mode_t st_mode Mode of file + - nlink_t st_nlink Number of hard links to the file. + - uid_t st_uid User ID of file. + - gid_t st_gid Group ID of file. + - dev_t st_rdev Device ID (if file is character or block special). + - off_t st_size For regular files, the file size in bytes. + + For symbolic links, the length in bytes of the + pathname contained in the symbolic link. + + For a typed memory object, the length in bytes. + + For other file types, the use of this field is + unspecified. + + - struct timespec st_atim Last data access timestamp. + - struct timespec st_mtim Last data modification timestamp. + - struct timespec st_ctim Last file status change timestamp. + + - blksize_t st_blksize A file system-specific preferred I/O block size + for this object. In some file system types, this + may vary from file to file. + - blkcnt_t st_blocks Number of blocks allocated for this object. + + #find if a file exists + + in *nix, you often cannot be sure if a file or directory exists, + because to do that you must have permission to list all of its parent dirs. + + the only thing you can say is that a path is accessible or not. + + using stat is a good way to do that. + */ + { + char in[] = "123\n"; + char fname[] = "stat.tmp"; + struct stat s; + + //create the file + int fd = open(fname, O_WRONLY | O_CREAT, S_IRWXU); + int nbytes = strlen(in); + if (fd != -1) { + if (write(fd, in, nbytes) != nbytes ) + assert(false); + else { + //assert that file exists: + assert(stat(fname, &s) == 0 ); + + //view/assert the fields of the stat struct: + assert(s.st_size == nbytes); + } + close(fd); + } + else { + assert(false); + } + } + + /* + #access + + Check if file or directory exists and or has a given permission (rwx): + + - R_OK + - W_OK + - X_OK + - F_OK: file exists + + If the access is not permitted, errno is still set even if this call did not give an error. + */ + { + char *exist = realpath(".", NULL); + if(access(exist, F_OK) == -1 ) { + perror("access"); + assert(false); + } + free(exist); + + char *dont_exist = "/i/dont/canot/must/not/exist.asdf"; + if(access(dont_exist, F_OK) == -1 ) { + perror("access(dont_exist, F_OK)" ); + } else { + assert(false); + } + } + + //#mkdir + { + struct stat s; + char fname[] = "mkdir"; + + //remove the file if it exists: + if(stat(fname, &s) == 0 ) + rmdir(fname); + + //make the dir and check for error: + if(mkdir(fname, 0777) == -1 ) + assert(false); + } + + //#rmdir + { + mkdir("rmdir", 0777); + if(rmdir("rmdir") == -1 ) + assert(false); + } + + /* + #ls + + Opendir is the basis for `ls`. + + #opendir + + Open a directory for reading. + + #readdir + + Get next directory entry, or NULL if over. + + #dirent + + + */ + { + DIR* dp; + struct dirent* entry; + + dp = opendir("."); + if (dp == NULL) { + perror("opendir"); + } else { + printf("opendir:\n"); + while ((entry = readdir(dp) ) != NULL ) { + printf(" %s\n", entry->d_name); + } + } + } + } + + /* + #getrusage + + rusage stands for Resource usage + + the kernel allows processes to use a certain ammount of ressources such as + memory or processor time + + exceeding those limits may lead the kernel to kill a processes + + returns the total time usage of current process (non sleeping/waiting) + + int getrusage(int who, struct rusage *r_usage); + + - who: + + - RUSAGE_SELF: only get information about current process + - RUSAGE_CHILDREN: information includes both current process and chidren who are dead + and are just waiting for the parent to call `wait()` on them. + + This makes sense here because the only thing that keeps their memory + used up is the existance of the parent process. + + - r_usage: is the main return valu, and is set to contain a struct: + + struct rusage { + struct timeval ru_utime; // user time used + struct timeval ru_stime; // system time used + }; + + and `timeval` is of type: + + struct timeval { + time_t tv_sec Seconds. + suseconds_t tv_usec Microseconds. + }; + + - return: 0 on success, -1 on error + errno set to exact error + */ + { + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) == -1 ) { + perror("getrusage failed"); + exit(EXIT_FAILURE); + } else { + printf( + "user time = %llu s %llu micro secs\n", + (uintmax_t)usage.ru_utime.tv_sec, + (uintmax_t)usage.ru_utime.tv_usec + ); + printf( + "system time = %llu s %llu micro secs\n", + (uintmax_t)usage.ru_stime.tv_sec, + (uintmax_t)usage.ru_stime.tv_usec + ); + } + } + + /* + #getrlimit + + returns the maximum value for a given resource + + there are two types of limits: + + - soft: can be increased by any process to any value lower than the hard maximum + - hard: only processes with special privileges may increase the hard limit + + if a resource goes over the soft limit, the kernel may choose to kill the process + + interfaces: + + int getrlimit(int resource, struct rlimit *rlp); + int setrlimit(int resource, const struct rlimit *rlp); + + - resource: see the docs for a description of each possible value + + - rlp: struct of type: + + struct rlimit { + rlim_t rlim_cur // The current (soft) limit. + rlim_t rlim_max // The hard limit. + } + + where `rlim_t` is an unsigned integer + */ + { + struct rlimit limit; + if (getrlimit(RLIMIT_DATA, &limit) == -1 ) { + perror("getrlimit(RLIMIT_DATA, ...) failed" ); + exit(EXIT_FAILURE); + } else { + //maximum process memory in bytes + if (limit.rlim_max == RLIM_INFINITY) { + //RLIM_INFINITY means that no limit is imposed on the resource + puts("RLIMIT_DATA: no limit imposed"); + } else { + printf( + "RLIMIT_DATA\n soft = %llu\n hard = %llu\n", + (uintmax_t)limit.rlim_cur, + (uintmax_t)limit.rlim_max + ); + } + } + + //ok, enough of error checking from now on + + printf("RLIM_INFINITY = %llu\n", (uintmax_t)RLIM_INFINITY); + + //maximum total cpu usage in seconds + getrlimit(RLIMIT_CPU, &limit); + printf( + "RLIMIT_CPU\n soft = %llu\n hard = %llu\n", + (uintmax_t)limit.rlim_cur, + (uintmax_t)limit.rlim_max + ); + + //maximum file size in bytes + getrlimit(RLIMIT_FSIZE, &limit); + printf( + "RLIMIT_FSIZE\n soft = %llu\n hard = %llu\n", + (uintmax_t)limit.rlim_cur, + (uintmax_t)limit.rlim_max + ); + + //number of file descriptors: + getrlimit(RLIMIT_NOFILE, &limit); + printf( + "RLIMIT_NOFILE\n soft = %llu\n hard = %llu\n", + (uintmax_t)limit.rlim_cur, + (uintmax_t)limit.rlim_max + ); + } + + /* + #limits.h + + This header exists in ANSI C, and POSIX extends it with several values. + + Defines current and possible maximuns, minimums for several resources. + + Some resources cannot cannot be evaluated statically. + + For example the maximum path length depends on which directory we are talking about, + since diferent directories can be on differnet mount points. + + Also some resources can change maximum values at anytime while the program is executing. + + In those cases, limits defines a KEY value which can be passed to a function that gets + the actual values for a given key, for example pathconf or sysconf. + + For resources that have fixed values, this header furnishes them directly. + */ + { + //static macros + { + //TODO0 + printf("NL_ARGMAX = %d\n", NL_ARGMAX); + + //maximum value that fits into a `size_t`. + printf("SSIZE_MAX (Mib) = %ju\n", (uintmax_t)SSIZE_MAX / (1 << 20) ); + + } + + /* + #sysconf + + Get lots of info on the system configuration + + Meanings for the constants can be found under + the `limits.h` and `unistd.h` corresponding variables + + If the value can be negative, it is necessary to check errno changes for error. + + It seems + + #maximum path length + + This is needed often when you need to deal with paths names. + + Sources: + + - + + Keep in mind that this value can vary even while a program is running, + and depends of the underlying filesystem, and therefore of the direcotory you are in. + + As a consequence of this, it does not make sense to have a macro constant and use it to create + fixed variable arrays: a function is needed, and memory must be allocated with malloc. + */ + { + //number of processors: + + printf("_SC_NPROCESSORS_ONLN = %ld\n", sysconf(_SC_NPROCESSORS_ONLN) ); + + //maximum lengh of command line arguments + environment variables: + + printf("_SC_ARG_MAX (MiB) = %ld\n", sysconf(_SC_ARG_MAX) / (1 << 20 ) ); + + //TODO0 find the number of processors / cpus / cores: not possible without glibc extension? + // + } + + /* + #pathconf + + Similar to sysconf, but for parameters that depend on a path, such as maxium filename lengths. + */ + { + //max basename in given dir including trailling null: + + printf("pathconf(\".\", _PC_NAME_MAX) = %ld\n", pathconf(".", _PC_NAME_MAX)); + + //max pathname in (TODO this is per device?) + + printf("pathconf(\".\", _PC_PATH_MAX) = %ld\n", pathconf(".", _PC_PATH_MAX)); + } + } + + /* + #user information + + once use have uids for processes, you can querry standard user information + which was traditionally stored in the `/etc/passwd` file. + */ + { + /* + #getpwuid + + you can get those information either by username or by uid: + + #include + + struct passwd *getpwuid(uid_t uid); + struct passwd *getpwnam(const char *name); + + the struct returned is: + + struct passwd { + passwd Member // Description + char *pw_name // The user’s login name + uid_t pw_uid // The UID number + gid_t pw_gid // The GID number + char *pw_dir // The user’s home directory + char *pw_gecos // The user’s full name + char *pw_shell // The user’s default shell + } + + which contains all the required user metadata specified by POSIX + */ + { + uid_t uid = getuid(); + struct passwd* info = getpwuid(uid); + if (info == NULL) { + perror("getpwuid failed"); + exit(EXIT_FAILURE); + } else { + puts("getpwuid"); + printf(" pw_name = %s\n", info->pw_name ); + printf(" pw_uid = %d\n", info->pw_uid ); + printf(" pw_gid = %d\n", info->pw_gid ); + printf(" pw_dir = %s\n", info->pw_dir ); + printf(" pw_gecos = %s\n", info->pw_gecos); + printf(" pw_shell = %s\n", info->pw_shell); + } + } + + /* + #getpwuid + + iterate a list of all passwd structures + + first call gets the first, every call gets the next + + to start from beginning again do: + + void setpwent(void); + + when you are done, free any associated resources with: + + endpwent() + */ + { + puts("all users:"); + struct passwd* info; + + info = getpwent(); + while (info != NULL) { + printf(" %s\n", info->pw_name ); + info = getpwent(); + } + endpwent(); + } + } + + /* + #uname + + you can get information about the current computer using `uname` + + unsurprisingly, it is the same information given by the POSIX utility `uname` + */ + { + struct utsname info; + if (uname(&info) == -1 ) { + perror("uname failed"); + exit(EXIT_FAILURE); + } else { + puts("uname"); + printf(" sysname = %s\n", info.sysname ); + printf(" nodename = %s\n", info.nodename ); + printf(" release = %s\n", info.release ); + printf(" version = %s\n", info.version ); + printf(" machine = %s\n", info.machine ); + } + } + + /* + #process info + + #getpid + + each process has an unique identifying integer called pid + + #getuid + + each process has user information associated to it + which determine what the process can or not + + there are two types of uid and gid: real and effective: + + - real is always of who executes the program + + - effective may be different depending on the suid and sgid bits + + #getguid + + like getuid but for user group + + #setuid + + sets the user id if you have the priviledges + + #getppid + + Gets paren't pid. + + It seems that it is not possible to list all children of a process in POSIX: + + */ + { + uid_t uid = getuid(); + uid_t euid = geteuid(); + gid_t gid = getgid(); + gid_t egid = getegid(); + pid_t pid = getpid(); + printf("getpid() = %llu\n", (uintmax_t)pid ); + printf("getuid() = %llu\n", (uintmax_t)uid ); + printf("geteuid() = %llu\n", (uintmax_t)euid ); + printf("getgid() = %llu\n", (uintmax_t)gid ); + printf("getegid() = %llu\n", (uintmax_t)egid ); + printf("getppid() = %llu\n", (uintmax_t)getppid()); + + /* + #getcwd + + pwd + + #root directory + + As of POSIX 7, this concept is not available. + + It is implemented as a Glibc extension under `_BSD_SOURCE`. + */ + { + const int n = 1 << 10; + char buf[n]; + if (getcwd(buf, n) == NULL ) { + perror("getcwd"); + } else { + printf("getcwd() = %s\n", buf); + } + } + } + + /* + #getpriority + + Each process, user and group has a priority associated to it. + + Those priorities are commonly called *nice* values on UNIX, since + the higher the nice, the less time it takes (it is nicer to other processes) + + Any process can reduce its priority. + Only a priviledged process can increaes its priority, + even if the process just decreased it himself. + + POSIX does not fix the nice range, but it does specify that: + + - lower values are more favorable + - the values must be between `-{NZERO}` and x{NZERO}-1`. + + where `NZERO` is implementation defined parameter. + + The minimum value for NZERO if 20, it is also the most common. + + The actual effect of priority is undefined. + + Nice: + + int nice(int incr) + + - incr: how much to increase the nice value + - return: the new nice value after the increase + + getpriority: + + int getpriority(int which, id_t who); + + - which: + + - PRIO_PROCESS: + - PRIO_PGRP: TODO + - PRIO_USER: TODO + + - who: pid, uid or gid depending on which. `0` means current. + + #error checking + + On error, returns `-1` and errno set to indicate the error. + + Therefore simply checking the return value is not enough to detect an error + since `-1` is a valid return value. + + Therefore, to do error checking one *must* check `errno != 0`: + + - set `errno = 0` + - make the call + - there is an error iff ret = -1 errno != 0. + + #setpriority + + Return value is the same as getpriority after the modification. + + #nice + + Same as setpriority, but only for `PRIO_PROCESS` but increments + (or decrements) the value instead of setting it to an absolute value. + + Return value is the same as getpriority after the modification. + + Can only be negative if we have `sudo`, since a negative value means to increase the priority. + */ + { + int prio; + + printf("NZERO = %d\n", NZERO); + + errno = 0; + prio = getpriority(PRIO_PROCESS, 0); + if (prio == -1 && errno != 0) { + perror("getpriority(PRIO_PROCESS, 0)" ); + } else { + printf("getpriority(PRIO_PROCESS, 0) = %d\n", prio ); + } + + errno = 0; + prio = getpriority(PRIO_PGRP, 0); + if (prio == -1 && errno != 0) { + perror("getpriority(PRIO_PGRP, 0)" ); + } else { + printf("getpriority(PRIO_PGRP, 0) = %d\n", prio ); + } + + errno = 0; + prio = getpriority(PRIO_USER, 0); + if (prio == -1 && errno != 0) { + perror("getpriority(PRIO_USER, 0)" ); + } else { + printf("getpriority(PRIO_USER, 0) = %d\n", prio ); + } + + errno = 0; + prio = nice(0); + if (prio == -1 && errno != 0) { + perror("nice(0)" ); + } else { + printf("nice(0) = %d\n", nice(0 ) ); + } + + //ok, tired of errno checking: + printf("nice(0) = %d\n", nice(0 ) ); + printf("nice(1) = %d\n", nice(1 ) ); + printf("nice(0) = %d\n", nice(0 ) ); + + errno = 0; + prio = nice(-1); + if (prio == -1 && errno != 0) { + //if not root we end up here + perror("nice(-1)" ); + } else { + printf("nice(-1) = %d\n", prio ); + } + } + + /* + #sched.h + + Get or set scheduler information + + POSIX 7 specifies four scheduling policies, more can be defined by the implementation + + The precise decription of those policies can be found under System interfaces > Scheduling policies. + + TODO0 what happens if there is both a FIFO and a RR process running? + + - SCHED_FIFO: + + First in first out. Process runs untill it finishes, blocking the CPU while it runs. + + An infinite loop could be catastrophic. + + In linux, applied to realtime process. + + - SCHED_RR: + + Round robin. + + Assign time slices proportional to prio and turn around the pie. + + In linux, applied to realtime process. + + - SCHED_SPORADIC: + + TODO0 + + Optional, so don't rely on it. + + - SCHED_OTHER: + + Any other type of scheduling. + + In linux, includes SCHED_NORMAL, the normal scheduling policy. + + POSIX explicitly says that it is implementation defined what happens + when there are both process of `SCHED_OTHER` and another type at the same time. + + #sched_getscheduler + + pid_t for given pid, 0 for current process + + #sched_get_priority_max + + Get maximum possible priority for a given policy. + + #sched_get_priority_min + */ + { + printf("SCHED_FIFO = %d\n", SCHED_FIFO ); + printf("sched_get_priority_max(SCHED_FIFO) = %d\n", sched_get_priority_max(SCHED_FIFO)); + printf("sched_get_priority_min(SCHED_FIFO) = %d\n", sched_get_priority_min(SCHED_FIFO)); + + printf("SCHED_RR = %d\n", SCHED_RR ); + printf("sched_get_priority_max(SCHED_RR) = %d\n", sched_get_priority_max(SCHED_RR)); + printf("sched_get_priority_min(SCHED_RR) = %d\n", sched_get_priority_min(SCHED_RR)); + + //printf("SCHED_SPORADIC = %d\n", SCHED_SPORADIC ); + + printf("SCHED_OTHER = %d\n", SCHED_OTHER ); + printf("sched_get_priority_max(SCHED_OTHER) = %d\n", sched_get_priority_max(SCHED_OTHER)); + printf("sched_get_priority_min(SCHED_OTHER) = %d\n", sched_get_priority_min(SCHED_OTHER)); + + printf("sched_getscheduler(0) = %d\n", sched_getscheduler(0 ) ); + + /* + #sched_setscheduler() + + You need root permissions to change to higher priority modes such as from SCHED_NORMAL to SCHED_FIFO or SCHED_RR. + */ + { + int policy = SCHED_FIFO; + struct sched_param sched_param = { + .sched_priority = 99 + }; + + if (sched_setscheduler(0, policy, &sched_param) == -1 ) { + perror("sched_setscheduler"); + //exit(EXIT_FAILURE); + } else { + assert(sched_getscheduler(0) == policy ); + } + } + + /* + Bogging the computer down. + + Time to have some destructive fun by making your computer bog down because of infinite loops on real time processes. + + This will generate an infinite number of `SCHED_FIFO` classes with maximum priority. + + *SAVE ALL DATA BEFORE DOING THIS!!!* + + Needs root to work. + + I recommend running this with music on the background to see the music break up. + The mouse also becomes very irresponsive. + + However userspace system does not halt. It seems that POSIX does not specify how operating systems + should deal with different simultaneous scheduling policies, and my Linux does not let SCHED_FIFO run 100% of the time. + maybe as explained here: . + + I could therefore kill the parent and all children with a C-C on the starting terminal. + */ + if (0) { + int policy = SCHED_FIFO; + struct sched_param sched_param = { + .sched_priority = sched_get_priority_max(policy) + }; + + if (sched_setscheduler(0, policy, &sched_param) == -1 ) { + perror("sched_setscheduler"); + } else { + while (1) { + pid_t pid = fork(); + if (pid == -1) { + perror("fork"); + assert(false); + } else { + if (pid == 0) + break; + } + } + while (1); + } + } + } + + //#execl, execlp, execsle, execv, execvp, execvpe + { + /* + interfaces for ``execve`` system call + + execute and *leave*, ends current process!! + + common combo: fork + execl + + this is effective because of COW implemented on some systems: + memory will only be copied to new process if needed, and in this case it is no needed. + + takes variable number or args + + must end null terminated + + versions: + + - char 'p': path, uses PATH var to find executable + - TODO: char 'v', char 'e'? what's the difference? + + sample calls: + + execl("/bin/ls", "-l", "-h", NULL); + + execlp("ls", "-l", "-h", NULL); + + execlp("cprogram", "cprogram", "arg0", NULL); + + don't forget that in a c program the first arg is the program name + */ + } + + /* + #fork + + Makes a copy of this process. + + This includes open file descriptors. + + Global memory space (`.DATA` and `.BSD`) is copied to current value but separated + (unlike threads, which share memory space). + + #fork and buffering + + + + There are three buffering methods: + + - unbuffered + - fully buffered + - line buffered + + When you fork, the streams get forked too, + with unflushed data still inside + + stdout and stderr flush at newlines + if you don't put newlines, if does not flush, + and fork copies the buffers + + This will print everything twice. + + #vfork + + fork but keep same address space. POSIX 7 discourages its use, + and says that it may be deprecated in the future + + #wait + + Wait for any child to terminate and then wake up. + + Same as: + + waitpid(-1, &status, 0); + + #waitpid + + Wait for child with given PID to terminate. + + #copy on write #COW + + often the fork is followed by an operation which does not use the old memory + such as `exec`, making copying the data useless. + + some operating systems may at first not copy memory from old process, + but wait util memory is written to do that. + + this has page granularity (physical RAM parameter, larger than most variables) + */ + { + //this variable will be duplicated on the parent and on the child + int i = 0; + int ppid; /* parent pid */ + + ppid = getpid(); + if (ppid == -1) { + perror("getpid"); + exit(EXIT_FAILURE); + } + + //this variable is visible only by the parent + //TODO0 is the compiler smart enough not to duplicate this to the child? + { + //int i = 0; + } + + puts("fork parent only before child"); + + //flush before fork so that existing output won't be repeated twice: + fflush(stdout); + + //in case of success, pid is set differently on parent and child + //so you can distinguish between them. For the child, `pid = 0`. + pid_t pid = fork(); + if (pid == -1) { + perror("fork"); + assert(false); + } else { + puts("fork child and parent"); + + //happens on child only: + if (pid == 0) { + /* + this puts is assynchonous with the process stdout + + so it might not be in the line program order + + but they both go to the same terminal + */ + puts("fork child only"); + + //child has a different pid from its parent + int pid = getpid(); + if (pid == -1) { + perror("getpid"); + exit(EXIT_FAILURE); + } + assert(pid != ppid); + + //this shall only change the child's `i` because memory was cloned (unlike threads) + i++; + + //the child exits here: + exit(EXIT_SUCCESS); + } + + //happens on parent only, before or after child. + puts("fork parent only"); + + //parent waits for the child to end. + //only the parent reaches this point because of the exit call + //done on the child above + int status; + wait(&status); + assert(status == EXIT_SUCCESS); + + //happens on parent and only after child: + puts("fork parent only after child"); + + //memory was cloned, parent i was only modified in child memory + assert(i == 0); + } + } + + /* + Forking too many processes + + Time to have some destructive fun and test the limits on how many processes the system can have. + + This will generate an infinite number of processes. Each one will sleep and terminate. + + **SAVE ALL DATA BEFORE DOING THIS!!!** + + My computer bogged down and I could do nothing about it except turn off the power. + */ + if (0) { + while (1) { + pid_t pid = fork(); + if (pid == -1) { + perror("fork"); + assert(false); + } + } + } + + /* + #IPC + + inter process communication + + the basic ways are: + + at startup: + + - command line arguments + - environment variables + + during execution: + + - pipes + - regular files + - signals + - shared memory + - sockets + */ + + /* + #pipe + + Must read man page: + + man 7 pipe + + Unidirectional data transfer from the write side to the read side. + + The write side in on one process (producer) + and the read side on another one (consumer). + + The consumer and producer can be the same process, but in that case pipes are useless. + + There are two types of pipes: + + - unnamed pipes + - FIFOs + + Both pipe sides are represented by either a file descriptor int or by a `FILE*`, + and many operations on them are the same as operation on disk files using + functions such as `write` and `read`. + + #pipes are RAM only + + Pipes are meant to be implementable on RAM only without using the filesystem. + + This means that reading and writing to pipes is potentially much faster + than reading and writing to files. + + The price to pay is that some operations + such as `ftell` and `fseek` are not possible. + There are also fcntl flags which cannot be applied to pipes. + + #read and write operations on pipes + + Read and write operations may be slightly differnt depending on the file descriptor type. + + This is specially the case for pipes vs regular files with respect to blocking. + + For example, pipes usually block if there is no more data available, and wait for data to become available. + + See the documentation of read and write for the details. + + #forbidden pipe operations + + Since pipes are meant to be implemented on RAM only, pipes cannot be rewinded. + + For example, using `ftell` on a pipe generate `errno=ESPIPE`. + + Therefore, once the data has been read from the read side it can be discareded by the OS, + preventing all its RAM from being used up easily. + + It seems that the only way to ask for forgiveness rather than permission and just do a `ftell`. + + + #advantages over using files + + Potentially faster because possible to be RAM only. + + If the read happens before the write it blocks untill the write is done. + This lets the OS manage all the synchronization. + + #pipe capacity + + There is a maximum data ammount that can be writen to a pipe. + + If a process tries to write more data than the maximum to the pipe, it blocks until + part of the data is read. + + This is a good source of deadlocks! + + It seems that it is not possible to get the maximum pipe capacity: + . + */ + { + /* + #unnamed pipe + + Can be created either via `popen` or `pipe` + + A single process must start two children process: + the data source and the data consumer and connect them + + Advantages over named pipes: + + - simple: no need to agree on a filename to communicate over + - secure: other process cannot se the data as they could in a file + + i think it is not possible to know if a file pointer + is open for reading or writtin besides looking at how + it was created + + workflow: + + - child fills the buffer, then parent takes control + - child fills ... + */ + + /* + #popen + + man popen + + Creates unnamed pipes. + + Consider using ansi c `system` instead of this. + + Opens a new process running the given command. + + Given string runs inside `sh` in a separated process and therefore it is: + + - slow + - does magic stuff like pathname expansion (`*.txt`) + - harder to port to non posix systems if one day you decide to do that + + It seems from the POSIX docs that the interpreter `sh` cannot be changed. + + Unlike the ANSI C `system` function, + does not automatically wait for the command to return: see `pclose` for that. + + For that reason, `popen` is slightly more general than `system`, + as you can do more work in the current program begore getting the shell output. + + This is not however extremely useful since you usually need the shell output + to continue working anyways. + + It is possible to either: + + - read output from stdout of a command + - write to stdin of a command + + TODO0 possible to both set stdin *and* get stdout? + + The output of popen is put on an unnamed pipe, which is accessible via + ANSI C FILE type returned by the function, instead of POSIX file descriptor (integers) + Therefore you must use ANSI C file io functions like `fopen` or `fclose` with it, + instead of the more low level POSIX `open` or `write` family. + + errno may or not be set depending on what caused the error. + + #pclose + + waits for child generated process. + + returns child exit status. + + if child was already waited for, returns -1 to indicate an error. + + errno may or not be set depending on what caused the error. + */ + { + //read from stdout of command and get its exit status + { + //popen uses ANSI C fread which uses ANSI C FILE type: + FILE* read_fp; + char buffer[BUFSIZ + 1]; + char cmd[1024]; + int chars_read; + int exit_status; + int read_cycles = 0; + int desired_read_cycles = 3; + int desired_last_char_read = 1; + assert(desired_last_char_read < BUFSIZ); + + sprintf( + cmd, + "for i in `seq %ju`; do echo -n a; done", + (uintmax_t)(desired_read_cycles-1) * BUFSIZ + desired_last_char_read + ); + read_fp = popen(cmd, "r"); + if (read_fp == NULL) { + printf("popen failed\n"); + exit(EXIT_FAILURE); + } else { + do + { + chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp ); + buffer[chars_read] = '\0'; + printf("======== n bytes read: %d\n", chars_read); + //printf("%s\n", buffer); //if you want to see a bunch of 'a's... + read_cycles++; + } while (chars_read == BUFSIZ); + + exit_status = pclose(read_fp); + if (exit_status == -1) { + printf("pclose failed\n"); + exit(EXIT_FAILURE); + } + + assert(read_cycles == desired_read_cycles); + assert(chars_read == desired_last_char_read); + assert(exit_status == 0); + } + } + + //write to stdin of command, its stdout goes to current stdout + { + FILE *write_fp; + char buf[BUFSIZ]; + int exit_status; + + memset(buf, 'b', BUFSIZ); + + //counts how many charaters were given to stdin + //using many sh features to make it clear that a sh interpreter is called + write_fp = popen("read A; printf 'popen write = '; printf $A | wc -c", "w"); + + if (write_fp == NULL) { + assert(false); + } else { + //TODO0 is safe to write twice BUFSIZ without a newline in the middle? + fwrite(buf, sizeof(char), BUFSIZ, write_fp ); + fwrite(buf, sizeof(char), BUFSIZ, write_fp ); + + //prints 2 * BUFSIZ + //command waits until pipe close + exit_status = pclose(write_fp); + assert(exit_status == 0); + } + } + } + + /* + #pipe + + Create unnamed pipes. + + Very close to the linux pipe system call. + + Differences from popen: + + - does not use a shell, avoiding many of its problems + + - uses two integer file descriptors, one for each end of the pipe, + instead of ANSI C `FILE` typedef. + + Therefore you manipulate pipes with file descriptor functions + like `open` and `write` instead of ANSI C `fopen` family. + + This potentially gives you more control over the operations. + + It may however be a bit harder to setup. + + Typically used with fork + exec. + */ + { + /* + Producer and consumer are the same process. + + Useuless but simple illustration of a pipe call. + */ + { + int nbytes; + int pipes[2]; + char data[] = "123"; + char buf[BUFSIZ + 1]; + + if (pipe(pipes) == -1 ) { + perror("pipe"); + exit(EXIT_FAILURE); + } else { + nbytes = write(pipes[1], data, strlen(data)); + assert(nbytes == strlen(data)); + nbytes = read(pipes[0], buf, BUFSIZ); + assert(nbytes == strlen(data)); + buf[nbytes] = '\0'; + assert(strcmp(buf, data) == 0 ); + } + } + + /* + Parent writes to child. + + This works because if ever read happens before, it blocks. + */ + { + int nbytes; + int file_pipes[2]; + const char data[] = "123"; + char buf[BUFSIZ + 1]; + pid_t pid; + + if (pipe(file_pipes) == -1 ) { + perror("pipe"); + exit(EXIT_FAILURE); + } else { + fflush(stdout); + pid = fork(); + if (pid == -1) { + perror("fork"); + exit(EXIT_FAILURE); + } else { + if (pid == 0) { + //child only + + //if read happens before write, it blocks because there is no data + nbytes = read(file_pipes[0], buf, BUFSIZ); + + printf("pipe child. data: %s\n", buf); + exit(EXIT_SUCCESS); + } + + //parent only + nbytes = write(file_pipes[1], data, strlen(data)); + assert(nbytes == strlen(data)); + + int status; + wait(&status); + + //parent only after child + assert(status == EXIT_SUCCESS); + } + } + } + } + + /* + #FIFO + + aka named pipes + + appear on the filesystem + + therefore can be accessed as by any process who sees it + and has enough priviledge level + + are however faster than writting to files, + since everything happens on RAM + + cannot open for rw + + application: simple client/servers! + + created with mkfifo + + #mkfifo + + Create a FIFO. + + #mknod + + Create a FIFO, character device, block device, directory or file. + + int mknod(const char *path, mode_t mode, dev_t dev); + + POSIX only demands that this work for FIFOs, all other are optional suggested interfaces. + + The type is ored into mode and can be: + + S_IFIFO + S_IFCHR + S_IFDIR + S_IFBLK + S_IFREG //regular file + + however implementation need only support `S_IFIFO`. + + `dev` represents the device corresponding to the file, and must be `0` in the case of `S_IFIFO`, + since FIFOs have no associated devices. + + Linux implements all the options except `S_IFDIR` via the `sys_mknod` syscall. + It also adds `S_IFSOCK` for UNIX domain sockets. Glibc provides a wrapper for all the extended linux + functionality. + */ + { + } + + /* + #PIPE_BUF + + Maximum size that guarantees that a pipe write operation will be atomic. + + PIPE_BUF is guaranteed to be at least 512. + + See `man 7 pipe` for an explanation. + + Quoting POSIX 7: + + If path refers to a FIFO, or fildes refers to a pipe or FIFO, + the value returned shall apply to the referenced object. + + If path or fildes refers to a directory, the value returned shall apply to any FIFO that exists + or can be created within the directory. + + If path or fildes refers to any other type of file, + it is unspecified whether an implementation supports an association of the variable name with the specified file. + + Conclusions: + + - the PIPE_BUF of an unnamed pipe can only be determined after it is created via `fpathconf`. + - the PIPE_BUF of FIFOs is the same for all fifos on given directory. + + It can be retreived with `pathconf`. + */ + { + long pipe_buf; + int pipes[2]; + + //unnamed pipe + if (pipe(pipes) == -1 ) { + perror("pipe"); + exit(EXIT_FAILURE); + } else { + pipe_buf = fpathconf(pipes[0], _PC_PIPE_BUF); + printf("PIPE_BUF pipes[0] = %ld\n", pipe_buf); + assert(pipe_buf >= 512); + + pipe_buf = fpathconf(pipes[1], _PC_PIPE_BUF); + printf("PIPE_BUF pipes[1] = %ld\n", pipe_buf); + assert(pipe_buf >= 512); + } + + //directory + pipe_buf = pathconf(".", _PC_PIPE_BUF); + printf("PIPE_BUF \".\" = %ld\n", pipe_buf); + assert(pipe_buf >= 512); + } + } + + /* + #shared memory + + Memory that can be accessed by multiple separate processes. + + In this example, both parent and child processes access the same shared memory. + */ + { + int shmid; + int *shmem; + + /* + #shmget + + Allocate shared memory + + int shmget(key_t key, size_t size, int shmflg); + + - key: TODO0 what is this + - size: num of bytes + - shmflg: permission flags like for files + other specific flags + IPC_CREAT is required to allocate the memory + + Return: + + - negative if error + - unique identifier of memory if positive + */ + { + shmid = shmget((key_t)1234, sizeof(int) * 2, IPC_CREAT | S_IRWXU | S_IRWXO ); + assert(shmid >= 0); + } + + /* + #shmat + + Attach shared memory to current process so it can be used afterwards + + void *shmat(int shm_id, const void *shm_addr, int shmflg); + + - shm_id: memory id given to shmget + - shm_id: where to map to. Usual choice: `NULL` so the system choses on its own. + - shm_flg: + */ + { + shmem = shmat(shmid, NULL, 0); + if (shmem == (void*)-1) { + assert(false); + } else { + shmem[0] = 1; + fflush(stdout); + pid_t pid = fork(); + if (pid < 0) { + assert(false); + } + else { + + //child only + if (pid == 0) { + + //child inherits attached memory + shmem[0]++; + + //detach from child: + assert(shmdt(shmem) == 0 ); + + exit(EXIT_SUCCESS); + } + + int status; + wait(&status); + //parent after child + assert(status == EXIT_SUCCESS); + assert(shmem[0] == 2); + + /* + #shmdt + + detach shared memory from current process + + int shmdt(void* shmem); + + each process should detach it separatelly before deleting the memory + */ + { + //detach from parent: + assert(shmdt(shmem) == 0 ); + } + + /* + #shmctl + + controls the shared memory, doing amongst other things its deletion + + int shmctl(int shm_id, int command, struct shmid_ds *buf); + + - shm_id returned by shmget + - command one of the following: + + - IPC_STAT: get parameters of memory into buf + - IPC_SET: set parameters of memory to match buf + - IPC_RMID: delete memory. + + It must be detached from all processes, or you get unspecified behaviour. + + - buf the following struct: + + struct shmid_ds { + uid_t shm_perm.uid; + uid_t shm_perm.gid; + mode_t shm_perm.mode; + } + + it can be `NULL` for `PIC_RMID` + + - return: 0 on success, -1 on failure + */ + { + assert(shmctl(shmid, IPC_RMID, NULL) == 0 ); + } + } + } + } + } + + /* + #threads + + See pthread. + + #pthread + + Sources: + + - + + Posix threads. + + Complex model with around 100 functions prefixed by `pthread`. + + c11 will introduce a standard threading model, + so in time this may become less important + + Each thread has its own stack, but unlike process, global memory is shared. + + Quicker to start than a process because less resource copy is needed. + + In Linux, based on the `clone` system call. + + In gcc you must compile with `-pthread`. + + #thread synchronization mechanisms + + - mutexes - Mutual exclusion lock: Block access to variables by other threads. + This enforces exclusive access by a thread to a variable or set of variables. + + - joins - Make a thread wait till others are complete (terminated). + + - condition variables - data type pthread_cond_t + + Good tutorial: + + #pthread_create + + Create a new thread. + + int pthread_create( + pthread_t *restrict thread, + const pthread_attr_t *restrict attr, + void *(*start_routine)(void*), + void *restrict arg + ) + + - thread: + + unique id of the created thread + + can be retreived from the thread with `pthread_self()` + + In POSIX all threads of a process have the same PID. TODO confirm with reference + + - attr: + - start_routine: function that runs the thread code + - arg: argument to start_routine + + #pthread_join + + Wait for a given thread to terminated. + + If it has already terminated, does not wait. + + #pthread_self + + Get thread id of current running thread. + + vs linux gettid: + + #pthread_mutex methods + + Allows a single thread to enter some code region. + + #PTHREAD_MUTEX_INITIALIZER + + New pthread_mutex_t should be initialized to it. + + #pthread_mutex_lock + + Acquire mutex: from now one no one else can enter. + + #pthread_mutex_unlock + + Release mutex: from now one others can enter. + */ + { + pthread_t threads[NUM_THREADS]; + int thread_args[NUM_THREADS]; + int rc, i; + + /* create all threads */ + for (i = 0; i < NUM_THREADS; ++i) + { + thread_args[i] = i; + rc = pthread_create(&threads[i], NULL, main_thread, (void *) &thread_args[i]); + assert(rc == 0); + printf("created thread: %ju\n", (uintmax_t)threads[i]); + } + + /* wait for all threads to complete */ + for (i = 0; i < NUM_THREADS; ++i) + { + rc = pthread_join(threads[i], NULL); + if(rc != 0) { + printf("%s\n", strerror(rc) ); + exit(EXIT_FAILURE); + } + } + } + + /* + #thread synchronization + + Threads can be synchronized via: + + - semaphores + + - mutexes + + Threads have the specific synchronization mechanisms: + + #semaphore + + #mutex + */ + { + /* + #sched_yield + + excplicitly tell scheduler to schedule another process + */ + { + sched_yield(); + } + } + + /* + #netdb.h + + network information (NETwork DataBase) + + */ + { + /* + #gethostname + + Copies name of current host on given string: + + int gethostname(char* hostname, int namelength); + + Name is truncated to namelength if too large + */ + { + const int namelength = 256; + char hostname[namelength]; + gethostname(hostname, namelength); + printf("gethostname = %s\n", hostname); + } + + /* + #gethostbyname + + Give a hostname string ("localhost", "john") and get info on that host, + including its IP. + + struct hostent *gethostbyname(const char *name); + + Return value: + + struct hostent { + char *h_name; // name of the host + char **h_aliases; // list of aliases (nicknames) + int h_addrtype; // address type + int h_length; // length in bytes of the address + char **h_addr_list // list of address (network order) + }; + + `NULL` on error + + #gethostbyaddr + + Same as gethostbyname but by address. + */ + { + const int namelength = 256; + char **names; + char **addrs; + struct hostent* hostent; + char hostnames[2][namelength]; + + if (gethostname(hostnames[0], namelength) == -1 ) { + perror("gethostname"); + exit(EXIT_FAILURE); + } + + strcpy(hostnames[1], "www.google.com"); + + for (int i = 0; i < 2; i++) { + hostent = gethostbyname(hostnames[i]); + //hostent = gethostbyname("www.google.com"); + if (hostent == NULL) { + fprintf(stderr, "gethostbyname failed for hostname = %s\n", hostnames[i]); + } else { + printf("gethostbyname\n"); + printf(" name: %s\n", hostent -> h_name); + printf(" aliases:\n"); + names = hostent -> h_aliases; + while (*names) { + printf(" %s\n", *names); + names++; + } + //assert that it is an inet address + if (hostent -> h_addrtype != AF_INET) { + printf("host is not AF_INET\n"); + exit(EXIT_FAILURE); + } + + //show addresses + printf(" IPs:\n"); + addrs = hostent->h_addr_list; + while (*addrs) { + /* + #inet_ntoa + + Converts integer representation of ip (4 bytes) to a string. + + Takes network byte ordering into consideration. + */ + printf(" %s", inet_ntoa(*(struct in_addr*)*addrs) ); + addrs++; + } + printf("\n"); + } + } + } + + /* + POSIX requires that systems must keep a database that given a protocol links: + service name (strings) to their ports and vice versa. + + #servent struct + + Represents a service. + + Must contain at least the following fields: + + char *s_name Official name of the service. + char **s_aliases A pointer to an array of pointers to + alternative service names, terminated by + a null pointer. + int s_port The port number at which the service + resides, in network byte order. + char *s_proto The name of the protocol to use when + contacting the service. + + #getservbyport + + struct servent *getservbyport(int port, const char *proto); + + - protocol: protocol name to look for. + + If `NULL`, match the first service found on that port for any protocol. + + #getservbyname + + Same as getservbyport but using the service name itself + + struct servent *getservbyname(const char *name, const char *proto); + */ + { + struct servent *servent; + int port; + char proto[] = "tcp"; + char name[] = "http"; + + /* + TODO what is a service? PING, HTTP, etc. application layer stuff? + How to gete those working then? + */ + + port = 80; + servent = getservbyport(port, proto); + if (servent == NULL) { + fprintf(stderr, "getservbyport(%d, %s) failed\n", port, proto); + } else { + printf("getservbyport\n"); + printf(" s_name = %s\n", servent->s_name); + printf(" s_proto = %s\n", servent->s_proto); + } + + servent = getservbyname(name, proto); + if (servent == NULL) { + fprintf(stderr, "getservbyname(%s, %s) failed\n", name, proto); + } else { + printf("getservbyname\n"); + printf(" s_name = %s\n", servent->s_name); + printf(" s_port = %d\n", servent->s_port); + printf(" s_proto = %s\n", servent->s_proto); + } + } + + /* + #getprotobyname + + The system must implement a database that links protocol names to protocol identifier numbers. + + Interface: + + struct protoent *getprotobyname(const char *name); + + `protoent` must contain at least the following fields: + + char *p_name Official name of the protocol. + char **p_aliases A pointer to an array of pointers to + alternative protocol names, terminated by + a null pointer. + int p_proto The protocol number. + */ + } + + /* + #sync + + Makes all cached writes to all filesystems. + + OSes may keep disk writes for later for efficienty, grouping several writes into one. + + This explicitly tells the OS to write everything down. + + TODO what is an application for this, except before shutting down the system? + + #fsync + + Same as sync, but only for filesystem containing given fd. + */ + { + sync(); + + int fd = open(__FILE__, O_RDONLY); + fsync(fd); + close(fd); + } + + /* + #termios.h + + Terminal management + */ + { + //TODO0 + } + + /* + #terminal + + some POSIX functions deal with the controlling terminal which called the program if any + + #getlogin + + gets login name of controlling terminal + + note that this is different from getuid, since it looks at the controlling terminal, + and not at processes specific information. + */ + { + char* login = getlogin(); + if (login == NULL) { + perror("getlogin failed"); + } else { + printf("getlogin() = %s\n", getlogin()); + } + } + + return EXIT_SUCCESS; +} diff --git a/posix/signal.c b/posix/signal.c new file mode 100644 index 0000000..7bf93ac --- /dev/null +++ b/posix/signal.c @@ -0,0 +1,237 @@ +/* +In short: + +- signals are a simple way for processes to communicate +- signals are limited to passing a single byte between two processes + +To send arbitrary signals to a process from a terminal, consider using the `kill` utility +(it does more than killing via a SIGTERM, as in general it sends any signal to a process) + +Also consider the convenient non POSIX stadardized VT100 control characters such as `` +which generate certain signals such as `SIGTERM`. + +#ANSI + +ANSI C supports the concept of signals, and only POSIX specific features shall be discussed here. + +Please look for ANSI C info for any feature used but not explained here. + +Linux extends POSIX by adding new signals, Those shall not be discussed here. + +#POSIX + +Docs here: . + +POSIX defines several signals in addition to the ANSI C signals. + +As in ANSI C, each signal has the following attributes: + +- general description of which conditions generate the signal + +- the signal can or cannot be handled. + + Most signals can be handled, but there are a few exceptions such as: + + - `SIGKILL`: always kill processes, cannot be handled. + - `SIGKSTOP` and `SIGCONT` + +- default action to take + + For signals that can be handled, you can change those behavious by creating your own handlers. + + The possible default behaviours are: + + - T: + + Abnormal termination of the process. + + The process is terminated with all the consequences of `_exit()` except that the status made available to + `wait()` and `waitpid()` indicates abnormal termination by the specified signal. + + Default action for most signals. + + - A :Abnormal termination of the process. + + [XSI] [Option Start] Additionally, implementation-defined abnormal termination actions, + such as creation of a core file, may occur. [Option End] + + Linux implements concept of core dumps on those cases. + Note however that those may be turned on or off depending on the system configuration. + + - I: Ignore the signal. + + An important example is `SIGCHLD`, which is generated when a child terminates, + but has no effect by default, since in general killing the parent is not what + should happen on most programs. + + - S: Stop the process. + + Mainly `SIGSTOP`. + + - C: Continue the process, if it is stopped; otherwise, ignore the signal. + + Mainly `SIGCONT`. + +POSIX specific signals include: + +- SIGKILL + + Kills program. + + Cannot be handled unlike to `SIGINT` and `SIGTERM`. + +- SIGQUIT + + Quit program. + + Used in case of abnormal termination (`A`), unlike `SIGINT` and `SIGTERM`. + + May therefore generate a core dump on certain systems. + + On Linux, you must first enable core dumps on the current terminal via `ulimit -c unlimited`, + since coredumps may be turned off by default. + + Can be generated on VT100 with ``. + +- SIGSTOP + + Freezes program. + + `ctrl+z`, in linux terminals. + + Cannot be handle. + +- SIGCONT + + Continues a process that + +- SIGHUP + + Controlling terminal was killed. + + This is why killing the terminal kills most process by default unless those process implement a handler. + +- SIGPIPE + + Process write to a pipe with no readers on other side + +- SIGCHLD + + Child terminated, stopped or continued. + + Ignored by default. + +- SIGALRM + + Received after the alarm call after given no of secs. + +- SIGUSR1 and SIGUSR2: left to users to do whatever they want with + +#parent death signal + + In POSIX, no signal needs to be sent to the child if the parent exits: + + In Linux, this can be achieved via the `prctl` syscall. + This may seem surprising considering that: + + - parents can wait for children + + - children get a NOHUP when controling process is killed + This is mentioned at: + + TODO what is a controlling process? + +#sources + +- + + good intro + +- man 7 signal + + man pages + +# TODO + +- determine where specification barriers betwwen ANSIC / POSIX / linux +*/ + +#define _XOPEN_SOURCE 700 + +#include +#include +#include +#include + +#include + +void signal_handler( int sig ) +{ + //sig arg allows us to use a single function for several different signals: + //just look at it and decide which action to take based on the signal number + + printf( "sig: %d\n", sig ); + + //after the signal is dealt with, the handler is then changed to its default action + //if you want to continue using this handler for future signals, you have to reregister + //it here: TODO confirm. If I remove this it does not work. + + signal( sig, signal_handler ); + + //you can change the action handler at any time + //for example, if you uncomment this line, only the first signal will be ignored + //and but the second will be dealt with the default action: + + //(void) signal( sig, SIG_DFL ); +} + +int main() +{ + + /* + #signal + + POSIX recommends `sigaction` instead of `signal` + */ + + signal( SIGALRM, signal_handler ); + + /* + #send signal + + this is done via the kill function: + + int kill(pid_t pid, int sig); + + returns 0 on success + + you must have permissions to send a signal to a program + + TODO 0 which? same uid? + */ + + //send a SIGINT to ourselves: + assert( kill( getpid(), SIGALRM ) == 0 ); + + int i = 0; + while ( i < 3 ) + { + /* + #alarm + + sends a SIGALRM to ourselves in n seconds + */ + assert( alarm( 1 ) == 0 ); + printf( "%d\n", i ); + i++; + sleep( 2 ); + } + + /* + #pause + + Stop program until it receives a signal. + */ + + return EXIT_SUCCESS; +} diff --git a/posix/socket/Makefile b/posix/socket/Makefile new file mode 120000 index 0000000..0c2ecfc --- /dev/null +++ b/posix/socket/Makefile @@ -0,0 +1 @@ +../../Makefile_many \ No newline at end of file diff --git a/posix/socket/README.md b/posix/socket/README.md new file mode 100644 index 0000000..2e3ea2b --- /dev/null +++ b/posix/socket/README.md @@ -0,0 +1,92 @@ +Sockets are similar to pipes but: + +- allow communication across different systems and are thus a base for networks local sockets also exist. + +- allow bidirection communication + +- allow multiple clients to connet to a single server (the concepts of client and server are clearly defined) + +#What posix sockets can do + +POSIX sockets allows to implement any Application layer program, and thus to implement things like web browsers, crawlers or wget like utilities. + +It seems however that POSIX does not support lower level layer control, for exapmle making an ICMP echo + +For those functionalities it seems that Linux specific functionalities must be used for example raw sockets: + +#Socket params + +sockets are characterized by three parameters: + +- domain +- type +- protocol + +this are exactly the 3 parameters that the `socket` call receives. + +##Domain + +- `AF_UNIX`: local sockets for single machine usage + + UNIX domain sockets are uniquelly identified on the filesystem like pipes or other special files + +- `AF_INET`: internet IP protocol, regular local networks or the internet + + this is one of the few stream like resources that are not put into the filesystem because TODO + +- `AF_INET6`: IPv6 + +##Type + +- `SOCK_STREAM`: connexion works like a file stream to the program + + in `AF_INET` this is automatically done via TCP/IP + + delivery and ordering is guaranteed by TCP/IP + + a connection is maintained while data is being sent + +- `SOCK_DGRAM`: datagram + + lower level protocol + + does not establish connection + + no automatic delivery guarantee + + data must be manually split into packages of a maximum width + + in `AF_INET` this is UDP + +certain domains may have differnt types + +`AF_UNIX` has a single type: `SOCK_STREAM` + +`AF_INET` has the following types: + +##Protocol + +sometimes it is possible to choose different protocols for a given type + +`0` uses the default protocol + +#Testing + +- run the server: + + ./server & + +- run as many clients as you want: + + ./client && ./client + ./client + +- kill the server: + + fg + + and then hit Ctrl-C + +#Local socket + +Is inserted into the filesystem. diff --git a/posix/socket/inet/Makefile b/posix/socket/inet/Makefile new file mode 100644 index 0000000..e47822b --- /dev/null +++ b/posix/socket/inet/Makefile @@ -0,0 +1,34 @@ +# make all files with one of the given extensions by IN_EXTS +# separatelly, possibly using a different rule per extension + +IN_DIR ?= ./ +IN_EXTS ?= .c +OUT_DIR ?= _out/ +OUT_EXT ?= +RUN ?= main + +INS := $(foreach IN_EXT, $(IN_EXTS), $(wildcard $(IN_DIR)*$(IN_EXT)) ) +INS_NODIR := $(notdir $(INS)) +OUTS_NODIR := $(basename $(INS_NODIR)) +OUTS_NODIR := $(addsuffix $(OUT_EXT), $(OUTS_NODIR)) +OUTS := $(addprefix $(OUT_DIR), $(OUTS_NODIR)) + +.PHONY: all clean mkdir run + +all: mkdir $(OUTS) + +# rule to make the c files +$(OUT_DIR)%$(OUT_EXT): %.c + gcc -Wall -std=c99 -pedantic-errors -o "$@" "$<" + +clean: + rm -rf $(OUT_DIR) + +mkdir: + mkdir -p $(OUT_DIR) + +run: all + cd $(OUT_DIR) && ./server$(OUT_EXT) & + cd $(OUT_DIR) && ./client$(OUT_EXT) & + cd $(OUT_DIR) && ./client$(OUT_EXT) & + cd $(OUT_DIR) && ./client$(OUT_EXT) & diff --git a/posix/socket/inet/README.md b/posix/socket/inet/README.md new file mode 100644 index 0000000..b24f18e --- /dev/null +++ b/posix/socket/inet/README.md @@ -0,0 +1,6 @@ +inet sockets, can be used across different computers for networking + +will only comment on differences from the unix sockets + +once a connection has been made to a port, the port stays blocked, +so be sure to take a port that is not being used diff --git a/posix/socket/inet/client.c b/posix/socket/inet/client.c new file mode 100644 index 0000000..9241f23 --- /dev/null +++ b/posix/socket/inet/client.c @@ -0,0 +1,106 @@ +/** +@file + +Inet server example +*/ + +#define _XOPEN_SOURCE 700 + +#include "assert.h" +#include "stdbool.h" +#include "stdio.h" +#include "stdlib.h" + +#include +#include //getprotobyname +#include +#include +#include "unistd.h" + +int main( int argc, char** argv ) +{ + char server_ip[] = "127.0.0.1"; + unsigned short server_port = 12345; + in_addr_t server_addr; + int sockfd; + //this is the struct used by INet addresses: + struct sockaddr_in address; + char ch_init = 'a'; + char ch = ch_init; + struct protoent *protoent; + char protoname[] = "tcp"; + //char protoname[] = "udp"; + + protoent = getprotobyname( protoname ); + if ( protoent == NULL ) { + perror( "getprotobyname" ); + exit(EXIT_FAILURE); + } + + sockfd = socket( AF_INET, SOCK_STREAM, protoent->p_proto ); + if ( sockfd == -1 ) { + perror( "socket" ); + exit(EXIT_FAILURE); + } + + /* + #inet_addr + + converts the text representation to a representation that can be used on the network + + #s_addr + + server address + */ + server_addr = inet_addr( server_ip ); + if ( server_addr == (in_addr_t)-1 ) { + fprintf( stderr, "inet_addr(\"%s\") failed\n", server_ip ); + return EXIT_FAILURE; + } + address.sin_addr.s_addr = server_addr; + + address.sin_family = AF_INET; + + /* + #htons + + Host TO Network Short + + takes a short (2 bytes), and converts it to the correct byte ordering expected by the network + + you must do this, or else the network won't look at the right port + + versions: + + - htons + - htonl (long, 4 bytes) + - ntohs (inverse) + - ntohl + + #sin_port + + port at which to contact server + */ + + address.sin_port = htons( server_port ); + + if ( connect( sockfd, ( struct sockaddr* )&address, sizeof( address ) ) == -1 ) { + perror( "connect" ); + return EXIT_FAILURE; + } + + if ( write( sockfd, &ch, 1 ) == -1 ) { + perror( "write" ); + exit(EXIT_FAILURE); + } + + /* according to wireshark, the response is received from some random port that the OS assigns to us */ + + if ( read( sockfd, &ch, 1 ) == -1 ) { + perror( "read" ); + exit(EXIT_FAILURE); + } + close( sockfd ); + assert( ch == ch_init + 1 ); + exit( EXIT_SUCCESS ); +} diff --git a/posix/socket/inet/server.c b/posix/socket/inet/server.c new file mode 100644 index 0000000..376b60f --- /dev/null +++ b/posix/socket/inet/server.c @@ -0,0 +1,81 @@ +#define _XOPEN_SOURCE 700 + +#include "stdbool.h" +#include "stdio.h" +#include "stdlib.h" +#include "string.h" + +#include +#include //getprotobyname +#include +#include +#include "unistd.h" + +int main(int argc, char** argv) { + unsigned short server_port = 12345; + char ch; + size_t client_len; + int server_sockfd, client_sockfd; + struct sockaddr_in client_address, server_address; + struct protoent *protoent; + char protoname[] = "tcp"; + //char protoname[] = "udp"; + + protoent = getprotobyname(protoname); + if (protoent == NULL) { + perror("getprotobyname"); + exit(EXIT_FAILURE); + } + + server_sockfd = socket( + AF_INET, + SOCK_STREAM, + protoent->p_proto + //0 + ); + if (server_sockfd == -1) { + perror("socket"); + exit(EXIT_FAILURE); + } + + /* + #s_addr server + + on server, this is which addresses it will accept connections from + + #INADDR_ANY + + special value that tells server to accept connections from anyone + */ + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = htonl(INADDR_ANY); + server_address.sin_port = htons(server_port); + if (bind( + server_sockfd, + (struct sockaddr*)&server_address, + sizeof(server_address) + ) == -1 + ) { + perror("bind"); + exit(EXIT_FAILURE); + } + + if (listen(server_sockfd, 5) == -1) { + perror("listen"); + exit(EXIT_FAILURE); + } + + while (1) { + client_len = sizeof(client_address); + client_sockfd = accept( + server_sockfd, + (struct sockaddr*)&client_address, + &client_len + ); + read(client_sockfd, &ch, 1); + ch++; + write(client_sockfd, &ch, 1); + close(client_sockfd); + } + return EXIT_SUCCESS; +} diff --git a/posix/socket/unix/Makefile b/posix/socket/unix/Makefile new file mode 100644 index 0000000..e47822b --- /dev/null +++ b/posix/socket/unix/Makefile @@ -0,0 +1,34 @@ +# make all files with one of the given extensions by IN_EXTS +# separatelly, possibly using a different rule per extension + +IN_DIR ?= ./ +IN_EXTS ?= .c +OUT_DIR ?= _out/ +OUT_EXT ?= +RUN ?= main + +INS := $(foreach IN_EXT, $(IN_EXTS), $(wildcard $(IN_DIR)*$(IN_EXT)) ) +INS_NODIR := $(notdir $(INS)) +OUTS_NODIR := $(basename $(INS_NODIR)) +OUTS_NODIR := $(addsuffix $(OUT_EXT), $(OUTS_NODIR)) +OUTS := $(addprefix $(OUT_DIR), $(OUTS_NODIR)) + +.PHONY: all clean mkdir run + +all: mkdir $(OUTS) + +# rule to make the c files +$(OUT_DIR)%$(OUT_EXT): %.c + gcc -Wall -std=c99 -pedantic-errors -o "$@" "$<" + +clean: + rm -rf $(OUT_DIR) + +mkdir: + mkdir -p $(OUT_DIR) + +run: all + cd $(OUT_DIR) && ./server$(OUT_EXT) & + cd $(OUT_DIR) && ./client$(OUT_EXT) & + cd $(OUT_DIR) && ./client$(OUT_EXT) & + cd $(OUT_DIR) && ./client$(OUT_EXT) & diff --git a/posix/socket/unix/README.md b/posix/socket/unix/README.md new file mode 100644 index 0000000..88cb9b7 --- /dev/null +++ b/posix/socket/unix/README.md @@ -0,0 +1 @@ +unix sockets, meant to be used locally on a single computer diff --git a/posix/socket/unix/client.c b/posix/socket/unix/client.c new file mode 100644 index 0000000..f40e212 --- /dev/null +++ b/posix/socket/unix/client.c @@ -0,0 +1,95 @@ +#define _XOPEN_SOURCE 700 + +#include "assert.h" +#include "stdbool.h" +#include "stdio.h" +#include "stdlib.h" +#include "string.h" + +#include +#include //sockaddr_un +#include "unistd.h" + +int main( int argc, char** argv ) +{ + //name of the socket file + //server and client must agree on it + char name[] = "server_socket"; + + //sockets are accessible via file descriptors + int sockfd; + int len; + + //this is the struct used by UNix addresses + struct sockaddr_un address; + char ch_init = 'a'; + char ch = ch_init; + + /* + #socket + + Create the socket, and get a file descrpitor to it. + + This must be done by both clients and servers. + + int socket(int domain, int type, int protocol); + + - protocol: + + For a given domain, select which protocol id to use. + + `0` uses a default protocol for the domain. + + Many domains have a single protocol. + + Other do not, for example `AF_INET` has both `tcp` adn `udp`. + + To get a protocol id, use `struct protoent *protoent = getprotobyname('tcp')`, + and then extract `protoent->p_proto`. + */ + + sockfd = socket( AF_UNIX, SOCK_STREAM, 0 ); + if ( sockfd == -1 ) { + perror( "socket" ); + exit(EXIT_FAILURE); + } + + /* + #connect + + request connection to the socket on the given address + + if the socket file does not exist fails + */ + + //type of socket + address.sun_family = AF_UNIX; + + //give a name to the socket + strcpy( address.sun_path, name ); + + len = sizeof( address ); + + if ( connect( sockfd, ( struct sockaddr* )&address, len ) == -1 ) { + perror( "connect" ); + exit( EXIT_FAILURE ); + } + + if ( write( sockfd, &ch, 1 ) == -1 ) { + perror( "write" ); + exit(EXIT_FAILURE); + } + if ( read( sockfd, &ch, 1 ) == -1 ) { + perror( "read" ); + exit(EXIT_FAILURE); + } + + //you should close the connection on both client and server + + close( sockfd ); + + //assert that the server did its job of increasing the char we gave it + assert( ch == ch_init + 1 ); + + exit( EXIT_SUCCESS ); +} diff --git a/posix/socket/unix/server.c b/posix/socket/unix/server.c new file mode 100644 index 0000000..cdf5f9d --- /dev/null +++ b/posix/socket/unix/server.c @@ -0,0 +1,170 @@ +#define _XOPEN_SOURCE 700 + +#include "assert.h" +#include "stdbool.h" +#include "stdio.h" +#include "stdlib.h" +#include "string.h" + +#include +#include +#include //sockaddr_un +#include "unistd.h" + +/** +Simple server that takes one char per connection, +increases it, and returns it to the client. +*/ +int main( int argc, char** argv ) +{ + //name of the socket file + //server and client must agree on it + char ch; + char name[] = "server_socket"; + size_t client_len; + int server_sockfd, client_sockfd; + struct sockaddr_un client_address, server_address; + + //remove any existing socket file and create a new one + if ( unlink( name ) == -1 && errno != ENOENT ) { + perror( "unlink" ); + exit(EXIT_FAILURE); + } + + /* + #setsockopt + + Set several socket options + + int setsockopt(int sockfd, int level, int optname, + const void *optval, socklen_t optlen) + + TODO it seems that setting SO_REUSEADDR allows to immediately reuse this socket. + Otherwise, bind fails because the address is already used. + + Discussion on the python interface: + */ + + /* + if ( setsockopt( + int sockfd, + int level, + int optname, + const void *optval, + socklen_t optlen + ) == -1 ) + { + } + */ + + server_sockfd = socket( AF_UNIX, SOCK_STREAM, 0 ); + if ( server_sockfd == -1 ) { + perror( "socket" ); + exit(EXIT_FAILURE); + } + + /* + #bind + + Bind an address to the socket file. + + May take different address formats depending on the socket parameters. + + #AF_INET + + In the AF_INET domain, the address is specified using a structure called sockaddr_in, defined in + netinet/in.h, which contains at least these members: + + struct sockaddr_in { + short int; //AF_INET + unsigned short int; //port number + struct in_addr; //IP address + }; + + The IP address structure, in_addr, is defined as follows: + + struct in_addr { + unsigned long int + }; + + The four bytes of an IP address constitute a single 32-bit value + */ + server_address.sun_family = AF_UNIX; + strcpy( server_address.sun_path, name ); + if ( bind( + server_sockfd, + (struct sockaddr*)&server_address, + sizeof( server_address ) + ) == -1 ) + { + perror( "bind" ); + exit(EXIT_FAILURE); + } + + /* + #listen + + Create a connection queue + + int listen(int socket, int backlog); + + Backlog is the max queue size. + + If overflows TODO. + */ + + if ( listen( server_sockfd, 5 ) == -1 ) { + perror( "listen" ); + exit(EXIT_FAILURE); + } + + //run server + while ( 1 ) + { + /* + #accept + + Accept connect from client. + + For each accept a new file descriptor is created to communicate with the client. + + Blocks until a connexion is requested by a client via `connect` + */ + client_len = sizeof( client_address ); + client_sockfd = accept( + server_sockfd, + (struct sockaddr*)&client_address, + &client_len + ); + if ( client_sockfd == -1 ) { + perror( "accept" ); + exit(EXIT_FAILURE); + } + + if ( read( client_sockfd, &ch, 1 ) == -1 ) { + perror( "read" ); + exit(EXIT_FAILURE); + } + + ch++; + + if ( write( client_sockfd, &ch, 1 ) == -1 ) { + perror( "write" ); + exit(EXIT_FAILURE); + } + + /* + #send + + Could be used instead of write for sockets. + + `write` is the same as a send without flags, so send has more options. + */ + + //you should close the connection on both client and server + + close( client_sockfd ); + } + + return EXIT_SUCCESS; +} diff --git a/posix/socket/wget.c b/posix/socket/wget.c new file mode 100644 index 0000000..9cb777f --- /dev/null +++ b/posix/socket/wget.c @@ -0,0 +1,90 @@ +/** +@file + +Minimalistic error checked program that fetches a web page and prints it to stdout. +*/ + +#define _XOPEN_SOURCE 700 + +#include "assert.h" +#include "stdbool.h" +#include "stdio.h" +#include "stdlib.h" +#include "string.h" + +#include +#include //getprotobyname +#include +#include +#include "unistd.h" + +int main(int argc, char** argv) { + char hostname[] = "www.google.com"; + unsigned short server_port = 80; + char protoname[] = "tcp"; + char request[] = "GET / HTTP/1.0\n\n"; + int request_len = strlen(request); + in_addr_t server_addr; + int sockfd; + struct sockaddr_in address; + struct protoent *protoent; + char buff[BUFSIZ]; + struct hostent* hostent; + int nbytes_total, nbytes_last; + + // Build the socket. + protoent = getprotobyname(protoname); + if (protoent == NULL) { + perror("getprotobyname"); + exit(EXIT_FAILURE); + } + + sockfd = socket(AF_INET, SOCK_STREAM, protoent->p_proto); + if (sockfd == -1) { + perror("socket"); + exit(EXIT_FAILURE); + } + + // Build the address. + hostent = gethostbyname(hostname); + if (hostent == NULL) { + fprintf(stderr, "error: gethostbyname(\"%s\")\n", hostname); + exit(EXIT_FAILURE); + } + + server_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent->h_addr_list))); + if (server_addr == (in_addr_t)-1) { + fprintf(stderr, "error: inet_addr(\"%s\")\n", *(hostent->h_addr_list)); + exit(EXIT_FAILURE); + } + address.sin_addr.s_addr = server_addr; + address.sin_family = AF_INET; + address.sin_port = htons(server_port); + + if (connect(sockfd, (struct sockaddr*)&address, sizeof(address)) == -1) { + perror("connect"); + exit(EXIT_FAILURE); + } + + nbytes_total = 0; + while (nbytes_total < request_len) { + nbytes_last = write(sockfd, request + nbytes_total, request_len - nbytes_total); + if (nbytes_last == -1) { + perror("write"); + exit(EXIT_FAILURE); + } + nbytes_total += nbytes_last; + } + + while ((nbytes_total = read(sockfd, buff, BUFSIZ)) > 0) { + write(STDOUT_FILENO, buff, nbytes_total); + } + + if (nbytes_total == -1) { + perror("read"); + exit(EXIT_FAILURE); + } + + close(sockfd); + exit(EXIT_SUCCESS); +}