├── .gitignore ├── src └── bin │ ├── 01-overview │ ├── f06-getpid.rs │ ├── f09-userid-groupid.rs │ ├── f05-copy-stdin-stdout2.rs │ ├── f04-copy-stdin-stdout.rs │ ├── f08-strerror-perror.rs │ ├── f03-list-files.rs │ ├── f07-read-execute.rs │ └── f10-read-execute2.rs │ ├── 04-files-and-directories │ ├── f23-chdir.rs │ ├── f24-getcwd.rs │ ├── f09-umask.rs │ ├── e17-unlink-fd1.rs │ ├── f08-access.rs │ ├── f25-st_dev.rs │ ├── f12-chmod.rs │ ├── f03-file-type.rs │ ├── f16-unlink.rs │ ├── e16-dir-tree-depth.rs │ ├── f21-futimens.rs │ └── e06-cp-sparse.rs │ ├── 08-process-cntl │ ├── f17-echo-all.rs │ ├── f25-userid-print.rs │ ├── e06-zombie.rs │ ├── f24-userid-system.rs │ ├── f23-system.rs │ ├── f12-race-condition.rs │ ├── f28-accounting.rs │ ├── e02-vfork-stack.rs │ ├── f08-avoid-zombie.rs │ ├── f03-vfork.rs │ ├── e07-exec-opendir.rs │ ├── f01-fork.rs │ ├── f20-exec-interpreter.rs │ ├── f16-exec.rs │ ├── f31-times.rs │ ├── f06-exit-status.rs │ ├── f30-nice.rs │ ├── e03-waitid.rs │ └── f29-acdata.rs │ ├── 10-signals │ ├── e02-sig2str.rs │ ├── f07-sleep-impl.rs │ ├── f14-print-signal-mask.rs │ ├── f02-sigusr.rs │ ├── f10-read-timeout.rs │ ├── e10-sleep60.rs │ ├── f26-system-ed.rs │ ├── f06-sigcld-systemv.rs │ ├── f05-nonreentrant.rs │ ├── f25-abort.rs │ ├── f22-signal-protection.rs │ ├── f31-sigtstp-handler.rs │ ├── e12-fwrite-1gb.rs │ ├── f29-sleep.rs │ ├── f12-sigset-impl.rs │ ├── f15-sigpending.rs │ ├── e06-sync-parent-child.rs │ ├── e11-stdin-stdout-100.rs │ ├── e05-timers.rs │ ├── f23-sigsuspend-global.rs │ └── e09-all-signals.rs │ ├── 03-fileio │ ├── f01-seek-stdio.rs │ ├── e02-dup2.rs │ ├── e06-read-write-seek.rs │ ├── f11-fcntl.rs │ ├── f12-setfl.rs │ ├── f02-file-with-hole.rs │ └── f05-copy-stdin-stdout.rs │ ├── 05-stdio │ ├── f04-getc.rs │ ├── e01-setbuf-setvbuf.rs │ ├── f05-fgets.rs │ ├── f12-tmpnam-tmpfile.rs │ ├── e02-fgets-4.rs │ ├── f13-mkstemp.rs │ └── f15-fmemopen.rs │ ├── 07-process-env │ ├── f04-echo.rs │ ├── e05-atexit-type.rs │ ├── f03-atexit.rs │ ├── f01-main.rs │ └── f16-rlimits.rs │ ├── 09-process-relations │ ├── e02-child-session.rs │ └── f12-orphan-process-grp.rs │ ├── 11-threads │ ├── thread-cleanup.c │ ├── f02-thread-id.rs │ ├── f03-thread-exit.rs │ ├── thread-barrier.c │ ├── e01-pass-struct.rs │ ├── f15-conditional-var.rs │ ├── f10-mutex.rs │ ├── f13-timedlock.rs │ ├── f04-bogus-pthread-exit.rs │ ├── f05-thread-cleanup.rs │ ├── f14-reader-writer-lock.rs │ ├── f12-simplified-locking.rs │ ├── f11-two-mutexes.rs │ └── f16-barrier.rs │ ├── 12-thread-control │ ├── f11-getenv-nonreentrant.rs │ ├── f04-detached-thread.rs │ ├── f12-getenv-reentrant.rs │ ├── f16-signal-handling.rs │ ├── f13-getenv-setspecific.rs │ └── f08-recursive-mutex.rs │ ├── 06-system-data-files │ ├── e04-time_t-wrap.rs │ ├── e05-strftime.rs │ ├── f11-strftime.rs │ ├── f02-getpwnam.rs │ ├── e03-uname.rs │ └── e01-shadow-pw.rs │ └── 02-unix-standards │ ├── f17-open-max.rs │ ├── f16-pathname-alloc-space.rs │ └── f14-limits.rs ├── my_build.rs ├── LICENSE ├── README.md └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | __pycache__/ 3 | Cargo.lock 4 | *.bk 5 | .idea 6 | .DS_Store 7 | apue.iml 8 | -------------------------------------------------------------------------------- /src/bin/01-overview/f06-getpid.rs: -------------------------------------------------------------------------------- 1 | /// Figure 1.6 Print the process ID 2 | /// 3 | /// $ f06-getpid > /dev/null 4 | 5 | extern crate libc; 6 | 7 | fn main() { 8 | println!("hello world from process ID {}", unsafe { libc::getpid() }); 9 | } 10 | -------------------------------------------------------------------------------- /src/bin/01-overview/f09-userid-groupid.rs: -------------------------------------------------------------------------------- 1 | /// Figure 1.9: Print user ID and group ID 2 | /// 3 | /// $ f09-userid-groupid > /dev/null 4 | 5 | extern crate libc; 6 | 7 | use libc::{getuid, getgid}; 8 | 9 | fn main() { 10 | unsafe { 11 | println!("uid={:?}, gid={:?}", getuid(), getgid()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /my_build.rs: -------------------------------------------------------------------------------- 1 | extern crate gcc; 2 | 3 | fn main() { 4 | gcc::compile_library("libthread-cleanup.a", &["src/bin/11-threads/thread-cleanup.c"]); 5 | gcc::compile_library("libthread-barrier.a", &["src/bin/11-threads/thread-barrier.c"]); 6 | println!("cargo:rerun-if-changed=src/bin/11-threads/thread-cleanup.c"); 7 | println!("cargo:rerun-if-changed=src/bin/11-threads/thread-barrier.c"); 8 | } -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/f23-chdir.rs: -------------------------------------------------------------------------------- 1 | /// Figure 4.23 Example of chdir function 2 | /// 3 | /// $ f23-chdir 4 | /// chdir to /tmp succeeded 5 | 6 | extern crate libc; 7 | #[macro_use(cstr)] 8 | extern crate apue; 9 | 10 | use libc::chdir; 11 | use apue::LibcResult; 12 | 13 | fn main() { 14 | unsafe { chdir(cstr!("/tmp")) }.check_not_negative().expect("chdir failed"); 15 | println!("chdir to /tmp succeeded"); 16 | } 17 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f17-echo-all.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.17 Echo all command-line arguments and all environment strings 2 | /// 3 | /// no libc, only std as it is only a helper program 4 | 5 | use std::env; 6 | 7 | fn main() { 8 | for (i, arg) in env::args().enumerate() { 9 | println!("argv[{}] = {}", i, arg); 10 | } 11 | for (key, value) in env::vars() { 12 | println!("{}={}", key, value); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f25-userid-print.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.25 Print real and effective user IDs 2 | /// 3 | /// $ f25-userid-print | sed 's/[0-9]//g' # remove ids as they're different on every setup 4 | /// real uid = , effective uid = 5 | 6 | extern crate libc; 7 | 8 | use libc::{getuid, geteuid}; 9 | 10 | fn main() { 11 | unsafe { 12 | println!("real uid = {}, effective uid = {}", getuid(), geteuid()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/bin/01-overview/f05-copy-stdin-stdout2.rs: -------------------------------------------------------------------------------- 1 | /// Figure 1.5 Copy standard input to standard output, using standard I/O 2 | /// 3 | /// $ echo asdf | f05-copy-stdin-stdout2 4 | /// asdf 5 | 6 | extern crate libc; 7 | extern crate apue; 8 | 9 | use libc::ferror; 10 | use apue::LibcResult; 11 | use apue::my_libc::{putc, getc, stdin, stdout}; 12 | 13 | 14 | fn main() { 15 | unsafe { 16 | while let Ok(c) = getc(stdin).check_not_negative() { 17 | assert!(putc(c, stdout) >= 0, "output error"); 18 | } 19 | assert!(ferror(stdin) == 0, "input error"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/bin/10-signals/e02-sig2str.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 10.2: Implement the sig2str function described in Section 10.22. 2 | /// 3 | /// $ e02-sig2str 4 | /// Signal: Interrupt 5 | 6 | extern crate apue; 7 | extern crate libc; 8 | 9 | use libc::{SIGINT, c_char}; 10 | 11 | extern "C" { 12 | static sys_siglist: [*const c_char; 65usize]; 13 | } 14 | 15 | fn sig2str(signo: i32) -> &'static str { 16 | unsafe { 17 | std::ffi::CStr::from_ptr(sys_siglist[signo as usize]).to_str().expect("invalid utf8 string") 18 | } 19 | } 20 | 21 | fn main() { 22 | println!("Signal: {}", sig2str(SIGINT)); 23 | } 24 | -------------------------------------------------------------------------------- /src/bin/03-fileio/f01-seek-stdio.rs: -------------------------------------------------------------------------------- 1 | /// Figure 3.1: Test whether standard input is capable of seeking 2 | /// 3 | /// Takaway: yea, you cannot 4 | /// 5 | /// $ f01-seek-stdio < /etc/passwd 6 | /// seek OK 7 | /// $ cat < /etc/passwd | f01-seek-stdio 8 | /// cannot seek 9 | 10 | extern crate libc; 11 | extern crate apue; 12 | 13 | use libc::{STDIN_FILENO, SEEK_CUR, lseek}; 14 | use apue::LibcResult; 15 | 16 | fn main() { 17 | if unsafe { lseek(STDIN_FILENO, 0, SEEK_CUR).check_not_negative() }.is_ok() { 18 | println!("seek OK"); 19 | } else { 20 | println!("cannot seek"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/bin/05-stdio/f04-getc.rs: -------------------------------------------------------------------------------- 1 | /// Figure 5.4: Copy standard input to standard output using getc and putc 2 | /// 3 | /// $ echo hans | f04-getc 4 | /// hans 5 | 6 | extern crate libc; 7 | extern crate apue; 8 | use apue::LibcResult; 9 | use apue::my_libc::{stdout, stdin}; 10 | 11 | 12 | fn main() { 13 | unsafe { 14 | while let Ok(c) = libc::fgetc(stdin).check_not_negative() { 15 | if libc::fputc(c, stdout) == libc::EOF { 16 | panic!("output error"); 17 | } 18 | } 19 | if libc::ferror(stdin) != 0 { 20 | panic!("input error"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/bin/05-stdio/e01-setbuf-setvbuf.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 5.1: Implement setbuf using setvbuf 2 | /// 3 | /// $ e01-setbuf-setvbuf 4 | 5 | extern crate libc; 6 | #[macro_use(cstr)] 7 | extern crate apue; 8 | 9 | use libc::{c_char, FILE, _IOFBF, _IONBF, setvbuf, fopen, strlen}; 10 | 11 | unsafe fn setbuf(stream: *mut libc::FILE, buf: *mut c_char) { 12 | if buf.is_null() { 13 | // turn off buffering 14 | setvbuf(stream as *mut FILE, buf, _IONBF, 0); 15 | } else { 16 | // set full buffering 17 | setvbuf(stream as *mut FILE, buf, _IOFBF, strlen(buf)); 18 | } 19 | } 20 | 21 | fn main() { 22 | unsafe { 23 | let stream = fopen(cstr!("/etc/passwd"), cstr!("r")); 24 | setbuf(stream, std::ptr::null_mut()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/bin/10-signals/f07-sleep-impl.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.7: simple, incomplete implementation of sleep 2 | 3 | extern crate apue; 4 | extern crate libc; 5 | 6 | use libc::{c_int, SIGALRM, SIG_ERR}; 7 | use libc::{alarm, signal, pause}; 8 | 9 | fn sig_alrm(_: c_int) { 10 | // nothing to do, just return to wake up the pause 11 | } 12 | 13 | fn sleep1(seconds: u32) -> u32 { 14 | unsafe { 15 | if signal(SIGALRM, sig_alrm as usize) == SIG_ERR { 16 | return seconds; 17 | } 18 | alarm(seconds); // start timer 19 | pause(); // next caught signal wakes up up 20 | alarm(0) // turn off timer, return unslept time 21 | } 22 | } 23 | 24 | fn main() { 25 | println!("good night, diriding-ding!"); 26 | sleep1(1); 27 | println!("good morning, boo!"); 28 | } 29 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/e06-zombie.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 8.6: Write a program that creates a zombie, and then call system 2 | /// to execute the ps(1) command to verify that the process is a zombie. 3 | /// 4 | /// Takeaway: does only work on Linux, on MacOs the child process is somehow 5 | /// reaped automatically, at least ps doesn't show it 6 | /// More details here: http://stackoverflow.com/questions/41427982 7 | 8 | extern crate libc; 9 | #[macro_use(cstr)] 10 | extern crate apue; 11 | 12 | use libc::{fork, sleep, exit, system}; 13 | use apue::LibcResult; 14 | 15 | fn main() { 16 | unsafe { 17 | let pid = fork().check_not_negative().expect("fork error"); 18 | if pid == 0 { 19 | exit(0); 20 | } 21 | sleep(99); 22 | system(cstr!("ps -fo pid,ppid,state,tty,command")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/bin/01-overview/f04-copy-stdin-stdout.rs: -------------------------------------------------------------------------------- 1 | /// Figure 1.4 Copy standard input to standard output 2 | /// 3 | /// $ echo asdf | f04-copy-stdin-stdout 4 | /// asdf 5 | 6 | extern crate libc; 7 | #[macro_use(as_void)] 8 | extern crate apue; 9 | extern crate errno; 10 | 11 | use libc::{STDIN_FILENO, STDOUT_FILENO, read, write}; 12 | use apue::LibcResult; 13 | 14 | const BUFSIZE: usize = 4096; 15 | 16 | fn main() { 17 | unsafe { 18 | let buf: [u8; BUFSIZE] = std::mem::uninitialized(); 19 | while let Ok(n) = read(STDIN_FILENO, as_void!(buf), BUFSIZE).check_positive() { 20 | if write(STDOUT_FILENO, as_void!(buf), n as _) != n as _ { 21 | panic!("write error"); 22 | } 23 | } 24 | if errno::errno().0 != 0 { 25 | println!("read error"); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/bin/05-stdio/f05-fgets.rs: -------------------------------------------------------------------------------- 1 | /// Figure 5.5: Copy standard input to standard output using fgets and fputs 2 | /// 3 | /// $ echo kurt | f05-fgets 4 | /// kurt 5 | 6 | extern crate libc; 7 | 8 | static MAXLINE: libc::c_int = 10; 9 | 10 | fn main() { 11 | unsafe { 12 | let stdin = libc::fdopen(libc::STDIN_FILENO, &('r' as libc::c_char)); 13 | let stdout = libc::fdopen(libc::STDOUT_FILENO, &('w' as libc::c_char)); 14 | let mut buf = Vec::with_capacity(MAXLINE as usize); 15 | let ptr = buf.as_mut_ptr() as *mut libc::c_char; 16 | while !libc::fgets(ptr, MAXLINE, stdin).is_null() { 17 | if libc::fputs(ptr, stdout) == libc::EOF { 18 | panic!("output error"); 19 | } 20 | } 21 | if libc::ferror(stdin) != 0 { 22 | panic!("input error"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/bin/07-process-env/f04-echo.rs: -------------------------------------------------------------------------------- 1 | #![feature(lang_items)] 2 | #![feature(start)] 3 | #![no_std] 4 | #![no_main] 5 | 6 | /// Figure 7.4: Echo all command-line arguments to standard output 7 | /// 8 | /// Of course you would never do it this "raw" way (but rather with env::args()) 9 | /// but it was a nice exercise of dereferencing C pointers 10 | /// 11 | // $ f04-echo arg1 TEST foo | cat 12 | // argv[0]: f04-echo 13 | // argv[1]: arg1 14 | // argv[2]: TEST 15 | // argv[3]: foo 16 | 17 | extern crate libc; 18 | 19 | use libc::printf; 20 | 21 | 22 | #[no_mangle] // ensure that this symbol is called `main` in the output 23 | pub extern "C" fn main(_argc: i32, _argv: *const *const u8) { 24 | unsafe { 25 | for i in 0.._argc { 26 | printf("argv[%d]: %s\n\0".as_ptr() as *const i8, 27 | i, 28 | *_argv.offset(i as _) as *const i8); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f24-userid-system.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.24: Execute the command-line argument using system 2 | /// 3 | /// Takeaway: the security hole as described in the book does 4 | /// not apply for Mac, there: the forked process has the effective 5 | /// userid reset 6 | /// 7 | /// $ f24-userid-system "echo hans" 8 | /// hans 9 | /// normal termination, exit status = 0 10 | 11 | extern crate libc; 12 | #[macro_use(cstr)] 13 | extern crate apue; 14 | 15 | use libc::{system, exit}; 16 | use std::env; 17 | use apue::{LibcResult, pr_exit}; 18 | 19 | fn main() { 20 | let mut args = env::args(); 21 | if args.len() < 2 { 22 | println!("command-line argument required"); 23 | unsafe { exit(1) }; 24 | } 25 | args.next(); // skip exe-name 26 | let status = 27 | unsafe { system(cstr!(args.next().unwrap())).check_not_negative().expect("system() error") }; 28 | pr_exit(status); 29 | } 30 | -------------------------------------------------------------------------------- /src/bin/10-signals/f14-print-signal-mask.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.14 Print the signal mask for the process 2 | /// 3 | /// Takeaway: I assumed that SIG_UNBLOCK should set the bit for getting a signal mask bit, 4 | /// but I needed to use SIG_SETMASK to set the SIGINT flag 5 | /// 6 | /// $ f14-print-signal-mask 7 | /// after setting mask: SIGINT 8 | 9 | extern crate apue; 10 | extern crate libc; 11 | extern crate errno; 12 | 13 | use libc::{sigset_t, SIGINT, SIG_SETMASK, sigaddset, sigemptyset}; 14 | use apue::{LibcResult, pr_mask}; 15 | use apue::my_libc::sigprocmask; 16 | 17 | fn main() { 18 | unsafe { 19 | let mut sigs: sigset_t = std::mem::uninitialized(); 20 | sigemptyset(&mut sigs); 21 | sigaddset(&mut sigs, SIGINT); 22 | sigprocmask(SIG_SETMASK, &sigs, std::ptr::null_mut()) 23 | .check_not_negative() 24 | .expect("couldn't set signals"); 25 | pr_mask("after setting mask:"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/bin/09-process-relations/e02-child-session.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 9.2: Write a small program that calls fork and 2 | /// has the child create a new session. Verify that the 3 | /// child becomes a process group leader and that the child 4 | /// no longer has a controlling terminal. 5 | /// 6 | /// $ e02-child-session 7 | /// that was easy.. 8 | 9 | extern crate libc; 10 | extern crate apue; 11 | 12 | use libc::{STDIN_FILENO, fork, getpid, setsid, tcgetpgrp}; 13 | use apue::LibcResult; 14 | 15 | fn main() { 16 | unsafe { 17 | let pid = fork().check_not_negative().expect("fork error"); 18 | if pid == 0 { 19 | // child: create new session 20 | let sid = setsid(); 21 | // verify that I am the new process group leader 22 | assert!(sid == getpid()); 23 | // verify that we 24 | assert!(tcgetpgrp(STDIN_FILENO) == -1); 25 | println!("that was easy.."); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/f24-getcwd.rs: -------------------------------------------------------------------------------- 1 | /// Figure 4.24 Example of getcwd function 2 | /// 3 | /// mac only: 4 | /// $ f24-getcwd /var/tmp 5 | /// cwd = "/private/var/tmp" 6 | /// 7 | /// linux only: 8 | /// $ f24-getcwd /var/run/ 9 | /// cwd = "/run" 10 | 11 | extern crate libc; 12 | #[macro_use(cstr)] 13 | extern crate apue; 14 | extern crate clap; 15 | 16 | use libc::{chdir, getcwd}; 17 | use std::ffi::CString; 18 | use apue::{LibcResult, LibcPtrResult, path_alloc}; 19 | use clap::App; 20 | 21 | fn main() { 22 | unsafe { 23 | let matches = App::new("fcntl").args_from_usage(" path/to/cd/to").get_matches(); 24 | let path = matches.value_of("path").unwrap(); 25 | chdir(cstr!(path)).check_not_negative().expect("chdir failed"); 26 | let mut buf = path_alloc(); 27 | getcwd(buf.as_mut_ptr(), buf.capacity()).check_not_null().expect("getcwd failed"); 28 | println!("cwd = {:?}", CString::from_raw(buf.as_mut_ptr())); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/bin/11-threads/thread-cleanup.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void cleanup(void *arg); 5 | 6 | void * thr_fn1(void *arg) { 7 | printf("thread 1 start\n"); 8 | fflush(stdout); 9 | pthread_cleanup_push(cleanup, "thread 1 first handler"); 10 | pthread_cleanup_push(cleanup, "thread 1 second handler"); 11 | printf("thread 1 push complete\n"); 12 | fflush(stdout); 13 | // if (arg) 14 | // return((void *)1); 15 | pthread_cleanup_pop(0); 16 | pthread_cleanup_pop(0); 17 | return((void *)1); 18 | } 19 | void * thr_fn2(void *arg) { 20 | printf("thread 2 start\n"); 21 | fflush(stdout); 22 | pthread_cleanup_push(cleanup, "thread 2 first handler"); 23 | pthread_cleanup_push(cleanup, "thread 2 second handler"); 24 | printf("thread 2 push complete\n"); 25 | fflush(stdout); 26 | if (arg) 27 | pthread_exit((void *)2); 28 | pthread_cleanup_pop(0); 29 | pthread_cleanup_pop(0); 30 | pthread_exit((void *)2); 31 | } -------------------------------------------------------------------------------- /src/bin/03-fileio/e02-dup2.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 3.2: Write your own dup2 function that behaves the same way as the dup2 function 2 | /// described in Section 3.12, without calling the fcntl function. Be sure to handle errors 3 | /// correctly. 4 | /// 5 | /// $ e02-dup2 6 | /// registered 3 7 | /// registered 4 8 | /// registered 5 9 | 10 | extern crate libc; 11 | extern crate apue; 12 | 13 | use libc::{dup, close}; 14 | use apue::LibcResult; 15 | 16 | unsafe fn dup2(fd1: i32, fd2: i32) { 17 | if fd2 < fd1 { 18 | panic!("the fd you want is already taken"); 19 | } 20 | loop { 21 | let fd = dup(fd1).check_not_negative().expect("error calling dup"); 22 | println!("registered {}", fd); 23 | if fd >= fd2 { 24 | break; 25 | } 26 | } 27 | } 28 | 29 | fn main() { 30 | unsafe { 31 | // 3 is stderr, but why are 4 and 5 are already taken..? 32 | close(3); 33 | close(4); 34 | close(5); 35 | dup2(2, 5); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f23-system.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.22 The system function, without signal handling and 2 | /// Figure 8.23 Calling the system function 3 | /// 4 | /// mac only: 5 | /// $ f23-system 2>&1 | grep -Ev "[0-9]{2}:" # remove all lines with times 6 | /// normal termination, exit status = 0 7 | /// sh: nosuchcommand: command not found 8 | /// normal termination, exit status = 127 9 | /// normal termination, exit status = 44 10 | /// 11 | /// linux only: 12 | /// $ f23-system 2>&1 | grep -Ev "[0-9]{2}:" # remove all lines with times 13 | /// normal termination, exit status = 0 14 | /// sh: 1: nosuchcommand: not found 15 | /// normal termination, exit status = 127 16 | /// normal termination, exit status = 44 17 | 18 | 19 | extern crate libc; 20 | extern crate apue; 21 | extern crate errno; 22 | 23 | use apue::{pr_exit, system}; 24 | 25 | 26 | fn main() { 27 | for cmd in ["date", "nosuchcommand", "who; exit 44"].into_iter() { 28 | let status = system(cmd).expect("system error"); 29 | pr_exit(status); 30 | } 31 | } -------------------------------------------------------------------------------- /src/bin/03-fileio/e06-read-write-seek.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 3.6: If you open a file for read–write with the append flag, can you still read 2 | /// from anywhere in the file using lseek? Can you use lseek to replace existing data in the 3 | /// file? Write a program to verify this. 4 | /// 5 | /// Answer: yes you can 6 | /// 7 | /// $ echo "123456789" > /tmp/e06.txt 8 | /// $ e06-read-write-seek /tmp/e06.txt 9 | /// $ cat /tmp/e06.txt 10 | /// 123hansaplast! 11 | 12 | extern crate libc; 13 | #[macro_use(cstr)] 14 | extern crate apue; 15 | 16 | use libc::{SEEK_SET, fopen, fseek, fputs}; 17 | use apue::{LibcResult, LibcPtrResult}; 18 | 19 | fn main() { 20 | unsafe { 21 | let file = std::env::args() 22 | .next_back() 23 | .expect("specify path to file to be opened for read/write/seek"); 24 | let f = fopen(cstr!(file), cstr!("r+")).check_not_null().expect("fopen failed"); 25 | fseek(f, 3, SEEK_SET).check_not_negative().expect("fseek failed"); 26 | fputs(cstr!("hansaplast!") as _, f); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/bin/11-threads/f02-thread-id.rs: -------------------------------------------------------------------------------- 1 | /// Figure 11.2 Printing thread IDs 2 | /// 3 | /// $ f02-thread-id | sed 's/[0-9]//g; s/x[a-f]*//g' 4 | /// main thread: pid tid () 5 | /// new thread: pid tid () 6 | 7 | extern crate libc; 8 | extern crate apue; 9 | 10 | use libc::{c_void, pthread_t}; 11 | use libc::{pthread_create, getpid, pthread_self, usleep}; 12 | use std::ptr::null_mut; 13 | use apue::LibcResult; 14 | 15 | fn printids(s: &str) { 16 | unsafe { 17 | let pid = getpid(); 18 | let tid = pthread_self(); 19 | println!("{} pid {} tid {} (0x{:x})", s, pid, tid, tid); 20 | } 21 | } 22 | 23 | extern "C" fn thr_fn(_: *mut c_void) -> *mut c_void { 24 | printids("new thread:"); 25 | 0 as _ 26 | } 27 | 28 | fn main() { 29 | unsafe { 30 | let mut ntid: pthread_t = std::mem::zeroed(); 31 | pthread_create(&mut ntid, null_mut(), thr_fn as _, null_mut()).check_zero() 32 | .expect("can't create thread"); 33 | printids("main thread:"); 34 | usleep(100); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f12-race-condition.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.12 Program with a race condition 2 | /// 3 | /// Takeaway: on OSX it needed at least usleep(20) in order 4 | /// to experience the race condition 5 | /// 6 | /// $ f12-race-condition | awk 'END{print NR}' 7 | /// 2 8 | 9 | extern crate libc; 10 | extern crate apue; 11 | 12 | use libc::{c_char, FILE, STDOUT_FILENO, fork, setbuf, fdopen, usleep}; 13 | use apue::LibcResult; 14 | use apue::my_libc::putc; 15 | 16 | 17 | unsafe fn charatatime(out: *mut FILE, s: &str) { 18 | for c in s.chars() { 19 | putc(c as i32, out); 20 | usleep(20); 21 | } 22 | } 23 | 24 | fn main() { 25 | unsafe { 26 | // set unbuffered 27 | let stdout = fdopen(STDOUT_FILENO, &('w' as c_char)); 28 | setbuf(stdout, std::ptr::null_mut()); 29 | let pid = fork().check_not_negative().expect("fork error"); 30 | match pid { 31 | 0 => charatatime(stdout, "output from child \n"), 32 | _ => charatatime(stdout, "output from parent \n"), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/bin/10-signals/f02-sigusr.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.2 Simple program to catch SIGUSR1 and SIGUSR2 2 | /// 3 | /// Example session: 4 | /// (doesn't work with test.py, that's why only put in // comments 5 | /// 6 | // $ f02-sigusr & 7 | // $ kill -USR1 $! 8 | // received SIGUSR1 9 | // $ kill -USR1 $! 10 | // received SIGUSR2 11 | 12 | extern crate libc; 13 | extern crate apue; 14 | 15 | use libc::{c_int, SIGUSR1, SIGUSR2, SIG_ERR}; 16 | use libc::{signal, pause}; 17 | 18 | extern "C" fn sig_usr(signo: c_int) { 19 | match signo { 20 | SIGUSR1 => println!("received SIGUSR1"), 21 | SIGUSR2 => println!("received SIGUSR2"), 22 | _ => { 23 | panic!(format!("received signal {}", signo)); 24 | } 25 | } 26 | } 27 | 28 | fn main() { 29 | unsafe { 30 | if signal(SIGUSR1, sig_usr as usize) == SIG_ERR { 31 | panic!("can't catch SIGUSR1"); 32 | } 33 | if signal(SIGUSR2, sig_usr as usize) == SIG_ERR { 34 | panic!("can't catch SIGUSR2"); 35 | } 36 | loop { 37 | pause(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/bin/01-overview/f08-strerror-perror.rs: -------------------------------------------------------------------------------- 1 | /// Figure 1.8 Demonstrate strerror and perror 2 | /// 3 | /// Takeaways: 4 | /// 5 | /// - I was somehow surprised that I cannot access errno with only libc 6 | /// [this discussion](https://github.com/rust-lang/rfcs/pull/1571) explains 7 | /// why it is in the separate errno crate. 8 | /// - Without the extra fflush, on OSX the perror() output is printed first, only then the fprintf. 9 | /// On linux it's the "right" way around. 10 | /// 11 | /// $ f08-strerror-perror 2>&1 | grep EACCES 12 | /// EACCES: Permission denied 13 | 14 | 15 | extern crate libc; 16 | #[macro_use(cstr)] 17 | extern crate apue; 18 | extern crate errno; 19 | use errno::{Errno, set_errno}; 20 | 21 | use libc::{EACCES, ENOENT, STDERR_FILENO, c_char, fdopen, fprintf, strerror, perror, fflush}; 22 | 23 | fn main() { 24 | unsafe { 25 | let stderr = fdopen(STDERR_FILENO, &('w' as c_char)); 26 | fprintf(stderr, cstr!("EACCES: %s\n"), strerror(EACCES)); 27 | fflush(stderr); 28 | set_errno(Errno(ENOENT)); 29 | perror(cstr!(std::env::current_exe().unwrap().to_str().unwrap())); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/f09-umask.rs: -------------------------------------------------------------------------------- 1 | /// Figure 4.9 Example of umask function 2 | /// 3 | /// Takeaway: formatting stat output is totally different on linux and on macos 4 | /// 5 | /// $ rm /tmp/{foo,bar} 6 | /// 7 | /// linux only: 8 | /// $ f09-umask 9 | /// $ stat -c %A /tmp/{foo,bar} 10 | /// -rw-rw-rw- 11 | /// -rw------- 12 | /// $ rm /tmp/{foo,bar} 13 | /// 14 | /// mac only: 15 | /// $ f09-umask 16 | /// $ stat -f "%Sp" /tmp/{foo,bar} 17 | /// -rw-rw-rw- 18 | /// -rw------- 19 | /// $ rm /tmp/{foo,bar} 20 | 21 | extern crate libc; 22 | #[macro_use(cstr)] 23 | extern crate apue; 24 | 25 | use libc::{mode_t, S_IRUSR, S_IWUSR, S_IRGRP, S_IWGRP, S_IROTH, S_IWOTH, umask, creat}; 26 | use apue::LibcResult; 27 | 28 | const RWRWRW: mode_t = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); 29 | 30 | fn main() { 31 | unsafe { 32 | umask(0); 33 | creat(cstr!("/tmp/foo"), RWRWRW).check_not_negative().expect("creat error for /tmp/foo"); 34 | umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); 35 | creat(cstr!("/tmp/bar"), RWRWRW).check_not_negative().expect("creat error for /tmp/bar"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Philipp Keller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/bin/01-overview/f03-list-files.rs: -------------------------------------------------------------------------------- 1 | /// Figure 1.3 List all the files in a directory 2 | /// 3 | /// Takeaways: Look more closely on the definition in the 4 | /// headerfile. I missed the `$INODE64` bit first. 5 | /// See [issue on rust-lang](https://github.com/rust-lang/libc/issues/414) 6 | /// for details 7 | /// 8 | /// $ mkdir -p /tmp/apue 9 | /// $ touch /tmp/apue/hans 10 | /// $ f03-list-files /tmp/apue | grep hans 11 | /// hans 12 | /// $ rm /tmp/apue/hans 13 | /// $ rm -d /tmp/apue 14 | 15 | extern crate libc; 16 | #[macro_use(cstr)] 17 | extern crate apue; 18 | 19 | use apue::{array_to_string, LibcResult, LibcPtrResult}; 20 | use apue::my_libc::readdir; 21 | use libc::{opendir, closedir}; 22 | 23 | fn main() { 24 | let dir = std::env::args().nth(1).expect("please specify a path"); 25 | unsafe { 26 | let dp = opendir(cstr!(dir.clone())); 27 | assert!(!dp.is_null(), format!("can't open directory {:?}", dir)); 28 | while let Ok(dirp) = readdir(dp).check_not_null() { 29 | println!("{}", array_to_string(&(*dirp).d_name)); 30 | } 31 | closedir(dp).check_not_negative().expect("closedir"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/bin/07-process-env/e05-atexit-type.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 7.5: Use the typedef facility of C to define a new data type Exitfunc for an exit 2 | /// handler. Redo the prototype for atexit using this data type. 3 | /// 4 | /// $ e05-atexit-type 5 | /// main is done 6 | /// first exit handler 7 | /// first exit handler 8 | /// second exit handler 9 | 10 | extern crate libc; 11 | #[macro_use(cstr)] 12 | extern crate apue; 13 | extern crate errno; 14 | 15 | use apue::LibcResult; 16 | use libc::{atexit, printf}; 17 | 18 | type Exitfunc = extern "C" fn(); 19 | 20 | fn my_atexit(f: Exitfunc) -> std::io::Result { 21 | unsafe { atexit(f).check_not_negative() } 22 | } 23 | 24 | extern "C" fn my_exit1() { 25 | unsafe { printf(cstr!("first exit handler\n")) }; 26 | } 27 | 28 | extern "C" fn my_exit2() { 29 | unsafe { printf(cstr!("second exit handler\n")) }; 30 | } 31 | 32 | fn main() { 33 | my_atexit(my_exit2).expect(&format!("can't register my_exit2: {}", errno::errno())); 34 | my_atexit(my_exit1).expect(&format!("can't register my_exit1: {}", errno::errno())); 35 | my_atexit(my_exit1).expect(&format!("can't register my_exit1: {}", errno::errno())); 36 | println!("main is done"); 37 | } 38 | -------------------------------------------------------------------------------- /src/bin/07-process-env/f03-atexit.rs: -------------------------------------------------------------------------------- 1 | /// Figure 7.3 Example of exit handlers 2 | /// 3 | /// Takeaway: it seems that at the point of running the exit handlers, rusts destructors 4 | /// have already run and things like println!() are no longer available (calling them results in 5 | /// `pointer being freed was not allocated`), see http://stackoverflow.com/questions/35980148 6 | /// 7 | /// $ f03-atexit 8 | /// main is done 9 | /// first exit handler 10 | /// first exit handler 11 | /// second exit handler 12 | 13 | extern crate libc; 14 | #[macro_use(cstr)] 15 | extern crate apue; 16 | extern crate errno; 17 | 18 | use apue::LibcResult; 19 | use libc::{atexit, printf}; 20 | 21 | extern "C" fn my_exit1() { 22 | unsafe { printf(cstr!("first exit handler\n")) }; 23 | } 24 | 25 | extern "C" fn my_exit2() { 26 | unsafe { printf(cstr!("second exit handler\n")) }; 27 | } 28 | 29 | fn main() { 30 | unsafe { 31 | atexit(my_exit2).check_not_negative().expect("can't register my_exit2"); 32 | atexit(my_exit1).check_not_negative().expect("can't register my_exit1"); 33 | atexit(my_exit1).check_not_negative().expect("can't register my_exit1"); 34 | println!("main is done"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/bin/07-process-env/f01-main.rs: -------------------------------------------------------------------------------- 1 | #![feature(lang_items)] 2 | #![feature(start)] 3 | #![no_std] 4 | #![no_main] 5 | 6 | /// Figure 7.1: Classic C program 7 | /// 8 | /// Takeaway: per default rust creates a shim around the main. There's e.g. no way 9 | /// to return an int from the main method. Luckily it's quite easy to remove it via 10 | /// no-stdlib: https://doc.rust-lang.org/book/no-stdlib.html 11 | /// I didn't need all the options, e.g. I didn't need to specify default-features = false 12 | /// for libc 13 | /// 14 | /// fyi: Exit code 13 comes from the return code of printf (see answer to Exercise 7.1) 15 | /// update: before the exit code seemed stable, now it is somehow random. Got 10 on OSX 16 | /// and 126 on Linux 17 | /// 18 | // $ f01-main | cat # cat is needed so it "swallows" the exit code from f01-main 19 | // Hello World! 20 | 21 | extern crate libc; 22 | 23 | use libc::printf; 24 | 25 | #[no_mangle] // ensure that this symbol is called `main` in the output 26 | pub extern "C" fn main(_argc: i32, _argv: *const *const u8) { 27 | unsafe { 28 | printf("Hello World!\n\0".as_ptr() as *const i8); 29 | } 30 | } 31 | 32 | 33 | // This is needed for Linux but not for Mac 34 | #[lang = "eh_unwind_resume"] 35 | #[no_mangle] 36 | pub extern "C" fn rust_eh_unwind_resume() {} 37 | -------------------------------------------------------------------------------- /src/bin/12-thread-control/f11-getenv-nonreentrant.rs: -------------------------------------------------------------------------------- 1 | /// Figure 12.11: A nonreentrant version of getenv 2 | /// 3 | /// Finding: some fun with pointers and 0 terminated strings :-) 4 | 5 | extern crate libc; 6 | #[macro_use(cstr)] 7 | extern crate apue; 8 | 9 | use libc::{c_char, printf}; 10 | use std::ffi::CStr; 11 | 12 | extern "C" { 13 | pub static environ: *const *const c_char; 14 | } 15 | 16 | const MAXSTRINGSZ:usize = 4096; 17 | static mut BUF:[c_char;MAXSTRINGSZ] = [0;MAXSTRINGSZ]; 18 | 19 | fn getenv(name: &str) { 20 | unsafe { 21 | let mut cmp = name.to_owned(); 22 | cmp.push_str("="); 23 | let mut i = 0isize; 24 | loop { 25 | if *environ.offset(i) == std::ptr::null() { 26 | break 27 | } 28 | let s = CStr::from_ptr(*(environ.offset(i as _))).to_str().expect("no valid string"); 29 | if s.starts_with(&cmp) { 30 | for (j, c) in s.chars().enumerate() { 31 | BUF[j] = c as _; 32 | } 33 | BUF[s.len()] = 0; 34 | break 35 | } 36 | i += 1; 37 | } 38 | } 39 | } 40 | 41 | fn main() { 42 | getenv("PATH"); 43 | unsafe { 44 | printf(cstr!("%s\n"), BUF.as_ptr()); 45 | } 46 | } -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f28-accounting.rs: -------------------------------------------------------------------------------- 1 | /// Figure 2.28: Program to generate accounting data 2 | #[macro_use(cstr)] 3 | extern crate apue; 4 | extern crate libc; 5 | 6 | use libc::{sleep, fork, exit, abort, c_char, kill, getpid, SIGKILL}; 7 | use apue::LibcResult; 8 | use apue::my_libc::execl; 9 | 10 | unsafe fn _fork() -> i32 { 11 | fork().check_not_negative().expect("fork error") 12 | } 13 | 14 | fn main() { 15 | unsafe { 16 | // parent 17 | if _fork() > 0 { 18 | sleep(2); 19 | exit(2); 20 | } 21 | // 1st child 22 | if _fork() > 0 { 23 | // first child 24 | sleep(4); 25 | abort(); // terminate with core dump 26 | } 27 | // 2nd child 28 | if _fork() > 0 { 29 | execl(cstr!("/bin/dd"), 30 | cstr!("dd"), 31 | cstr!("if=/etc/passwd"), 32 | cstr!("of=/dev/stdout"), 33 | 0 as *const c_char) 34 | .check_not_negative() 35 | .expect("execl error"); 36 | exit(7); 37 | } 38 | // 3rd child 39 | if _fork() > 0 { 40 | sleep(8); 41 | exit(0); 42 | } 43 | // 4th child 44 | sleep(6); 45 | kill(getpid(), SIGKILL); 46 | exit(6); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/bin/06-system-data-files/e04-time_t-wrap.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 6.4 Calculate the latest time that can be represented by the time_t data type. 2 | /// After it wraps around, what happens? 3 | 4 | extern crate libc; 5 | #[macro_use(cstr, as_char)] 6 | extern crate apue; 7 | 8 | use libc::{tm, time_t, c_char, size_t, printf}; 9 | use std::mem::uninitialized; 10 | 11 | extern "C" { 12 | fn localtime(time: *const time_t) -> *mut tm; 13 | fn strftime(s: *mut c_char, 14 | maxsize: size_t, 15 | format: *const c_char, 16 | timeptr: *const tm) 17 | -> size_t; 18 | } 19 | 20 | fn main() { 21 | unsafe { 22 | let buf: [c_char; 1024] = uninitialized(); 23 | let mut t: time_t = 1; 24 | loop { 25 | println!("{:?}", t - 1); 26 | strftime(as_char!(buf), 27 | 1024, 28 | cstr!("%a %b %d, %Y"), 29 | localtime(&mut (t - 1))); 30 | printf(cstr!("%s\n"), buf.as_ptr()); 31 | t *= 2; 32 | } 33 | } 34 | } 35 | 36 | // Answer: there's a Segmentation fault on strftime. This probably happens when the year 37 | // within tm is bigger than 2147483647 (max i32 value). There's no "wrap around". Either 38 | // the question is intentionally misleading or there are some systems where it wraps 39 | // around. On OSX or Linux it doesn't. 40 | -------------------------------------------------------------------------------- /src/bin/06-system-data-files/e05-strftime.rs: -------------------------------------------------------------------------------- 1 | /// Exercise: Write a program to obtain the current time and print it using strftime, 2 | /// so that it looks like the default output from date(1). Set the TZ environment 3 | /// variable to different values and see what happens. 4 | 5 | extern crate libc; 6 | #[macro_use(cstr)] 7 | extern crate apue; 8 | 9 | use libc::{tm, time_t, c_char, size_t, printf}; 10 | use std::mem::uninitialized; 11 | 12 | extern "C" { 13 | fn time(time: *mut time_t) -> time_t; 14 | fn localtime(time: *const time_t) -> *mut tm; 15 | fn strftime(s: *mut c_char, 16 | maxsize: size_t, 17 | format: *const c_char, 18 | timeptr: *const tm) 19 | -> size_t; 20 | } 21 | 22 | fn main() { 23 | unsafe { 24 | let mut buf: [c_char; 256] = uninitialized(); 25 | let mut t: time_t = uninitialized(); 26 | time(&mut t); 27 | strftime(buf.as_mut_ptr(), 28 | 256, 29 | cstr!("%a %b %e %H:%M:%S %Z %Y"), 30 | localtime(&t)); 31 | printf(cstr!("%s\n"), buf.as_ptr()); 32 | } 33 | } 34 | 35 | // ## Results 36 | // 37 | // $ export TZ='Europe/Zurich' 38 | // $ macos/target/debug/e05-strftime 39 | // Sun Oct 2 14:59:33 CEST 2016 40 | // 41 | // $ export TZ='America/Toronto' 42 | // $ macos/target/debug/e05-strftime 43 | // Sun Oct 2 09:00:50 EDT 2016 44 | -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/e17-unlink-fd1.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 4.17: 2 | /// 3 | /// In Section 3.16, we described the /dev/fd feature. For any user to be able to access these 4 | /// files, their permissions must be rw-rw-rw-. 5 | /// Some programs that create an output file delete the file first, in case it already exists, 6 | /// ignoring the return code: 7 | /// unlink(path); 8 | /// if ((fd = creat(path, FILE_MODE)) < 0) 9 | /// err_sys(...); 10 | /// 11 | /// What happens if path is /dev/fd/1? 12 | /// 13 | /// mac only: 14 | /// $ e17-unlink-fd1 15 | /// Permission denied (os error 13) 16 | /// new fd: 3 17 | /// 18 | /// linux only: 19 | /// $ e17-unlink-fd1 20 | /// Operation not permitted (os error 1) 21 | /// new fd: 3 22 | 23 | extern crate libc; 24 | #[macro_use(cstr)] 25 | extern crate apue; 26 | 27 | use apue::LibcResult; 28 | use libc::{S_IRUSR, S_IWUSR, S_IRGRP, S_IWGRP, S_IROTH, S_IWOTH, mode_t, creat, unlink}; 29 | 30 | const FILE_MODE: mode_t = S_IRUSR + S_IWUSR + S_IRGRP + S_IWGRP + S_IROTH + S_IWOTH; 31 | 32 | fn main() { 33 | let fd = unsafe { 34 | unlink(cstr!("/dev/fd/1")) 35 | .check_not_negative() 36 | .or_else(|e| { 37 | println!("{}", e); 38 | Err(e) 39 | }) 40 | .ok(); 41 | creat(cstr!("/dev/fd/1"), FILE_MODE).check_not_negative().expect("creat error") 42 | }; 43 | println!("new fd: {}", fd); 44 | } 45 | -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/f08-access.rs: -------------------------------------------------------------------------------- 1 | /// Figure 4.8: Example of access function 2 | /// 3 | /// Nothing special here.. only routine work 4 | /// 5 | /// $ f08-access /etc/passwd 6 | /// read access OK 7 | /// open for reading OK 8 | /// 9 | /// mac only: 10 | /// $ f08-access /etc/master.passwd 11 | /// access error for "/etc/master.passwd" 12 | /// ERROR: return code 1 13 | /// 14 | /// linux only: 15 | /// $ f08-access /etc/shadow 16 | /// access error for "/etc/shadow" 17 | /// ERROR: return code 1 18 | 19 | 20 | extern crate clap; 21 | extern crate libc; 22 | extern crate apue; 23 | 24 | use clap::App; 25 | use libc::{R_OK, O_RDONLY, access, open, exit}; 26 | use std::ffi::CString; 27 | use apue::LibcResult; 28 | 29 | fn main() { 30 | let matches = App::new("check access rights").args_from_usage("").get_matches(); 31 | let filename = CString::new(matches.value_of("filename").unwrap()).unwrap(); 32 | unsafe { 33 | if access(filename.as_ptr(), R_OK).check_not_negative().is_err() { 34 | println!("access error for {:?}", filename); 35 | exit(1); 36 | } else { 37 | println!("read access OK"); 38 | } 39 | if open(filename.as_ptr(), O_RDONLY).check_not_negative().is_err() { 40 | println!("open error for {:?}", filename); 41 | exit(1); 42 | } else { 43 | println!("open for reading OK"); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/e02-vfork-stack.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 8.2: Recall the typical arrangement of memory in Figure 7.6. 2 | /// Because the stack frames corresponding to each function call are usually 3 | /// stored in the stack, and because after a vfork the child runs in the 4 | /// address space of the parent, what happens if the call to vfork is from a 5 | /// function other than main and the child does a return from this function 6 | /// after the vfork? Write a test program to verify this, and draw a picture 7 | /// of what’s happening. 8 | /// 9 | /// Takeaway: this is a good showcase why vfork is very very VERY 10 | /// dangerous. What happens is that the process which exits the function 11 | /// tears down the stack frame, pulling the rog out under the other processes 12 | /// feet. If he then tries to access a stack var, this is bye bye birdie. 13 | 14 | extern crate libc; 15 | extern crate apue; 16 | 17 | use apue::my_libc::vfork; 18 | use apue::LibcResult; 19 | use libc::sleep; 20 | 21 | fn callme() { 22 | let pid = unsafe { vfork() }.check_not_negative().expect("vfork failed"); 23 | let b = 2; 24 | match pid { 25 | 0 => { 26 | // child immediately returns 27 | } 28 | _ => { 29 | // parent waits a second and tries to access stack var b 30 | unsafe { sleep(1) }; 31 | println!("b={}", b); 32 | } 33 | } 34 | } 35 | 36 | fn main() { 37 | callme(); 38 | } 39 | -------------------------------------------------------------------------------- /src/bin/11-threads/f03-thread-exit.rs: -------------------------------------------------------------------------------- 1 | /// Figure 11.3 Fetching the thread exit status 2 | /// 3 | /// $ f03-thread-exit 4 | /// thread 2 returning 5 | /// thread 1 returning 6 | /// thread 1 exit code: 1 7 | /// thread 2 exit code: 2 8 | 9 | extern crate libc; 10 | extern crate apue; 11 | use libc::{c_void, c_int}; 12 | use libc::{pthread_create, pthread_join, usleep}; 13 | use std::ptr::null_mut; 14 | use apue::LibcResult; 15 | use apue::my_libc::pthread_exit; 16 | 17 | extern "C" fn thr_fn1(_: *mut c_void) -> *mut c_void { 18 | unsafe { usleep(100) }; 19 | println!("thread 1 returning"); 20 | 1 as _ 21 | } 22 | 23 | extern "C" fn thr_fn2(_: *mut c_void) -> *mut c_void { 24 | println!("thread 2 returning"); 25 | unsafe { pthread_exit(2 as _) }; 26 | 99 as _ 27 | } 28 | 29 | fn main() { 30 | unsafe { 31 | let (mut tid1, mut tid2, mut tret) = std::mem::uninitialized(); 32 | pthread_create(&mut tid1, null_mut(), thr_fn1, null_mut()).check_zero().expect("can't create thread 1"); 33 | pthread_create(&mut tid2, null_mut(), thr_fn2, null_mut()).check_zero().expect("can't create thread 2"); 34 | 35 | pthread_join(tid1, &mut tret).check_zero().expect("can't join with thread 1"); 36 | println!("thread 1 exit code: {}", tret as c_int); 37 | pthread_join(tid2, &mut tret).check_zero().expect("can't join with thread 2"); 38 | println!("thread 2 exit code: {}", tret as c_int); 39 | } 40 | } -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f08-avoid-zombie.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.8: Avoid zombie processes by calling fork twice 2 | /// 3 | /// $ f08-avoid-zombie 4 | /// $ f08-avoid-zombie && sleep 0.1 5 | /// second child, parent pid = 1 6 | 7 | 8 | extern crate libc; 9 | extern crate apue; 10 | 11 | use libc::{fork, usleep, waitpid, getppid, exit}; 12 | use apue::{LibcResult, err_sys}; 13 | 14 | // separate function to avoid one indent because of the unsafe block 15 | unsafe fn doit() { 16 | let pid = fork().check_not_negative().expect("fork error"); 17 | match pid { 18 | 0 => { 19 | let pid = fork().check_not_negative().expect("fork error"); 20 | if pid > 0 { 21 | // parent from second fork == first child 22 | exit(0); 23 | } 24 | // we're the second child; our parent becomes init 25 | // as soon as our real parent calls exit() (3 lines above) 26 | // After 2 seconds sleep our parent will be dead and 27 | // init will reap our status. 28 | usleep(1000); 29 | println!("second child, parent pid = {}", getppid()); 30 | exit(0); 31 | } 32 | _ => { 33 | // wait for first child 34 | if waitpid(pid, std::ptr::null_mut(), 0) != pid { 35 | err_sys("waitpid error"); 36 | } 37 | } 38 | } 39 | } 40 | 41 | fn main() { 42 | unsafe { doit() }; 43 | } 44 | -------------------------------------------------------------------------------- /src/bin/10-signals/f10-read-timeout.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.10: Calling read with a timeout 2 | /// 3 | /// Takeaway: First I tried with the regular `signal` function of libc 4 | /// only to find out that the alarm signal does not interrupt the read 5 | /// call. Digging into the C code it got obvious that the signal function 6 | /// gets overriden by `lib/signal.c` which is a "reliable version of signal(), 7 | /// using POSIX sigaction()". But this function gets only introduced in 8 | /// Figure 10.18. This was quite misleading IMO. 9 | /// 10 | /// $ f10-read-timeout 2>&1 11 | /// read error! 12 | /// ERROR: return code 1 13 | 14 | extern crate libc; 15 | #[macro_use(as_void)] 16 | extern crate apue; 17 | 18 | use libc::{STDOUT_FILENO, STDIN_FILENO, SIGALRM, SIG_ERR, c_int}; 19 | use libc::{alarm, write, read, exit}; 20 | use apue::signal; 21 | 22 | const MAXLINE: usize = 4096; 23 | 24 | fn sig_alrm(_: c_int) { 25 | // nothing to do, just return to interrupt the read 26 | } 27 | 28 | fn main() { 29 | unsafe { 30 | let line: [u8; MAXLINE] = std::mem::uninitialized(); 31 | if signal(SIGALRM, sig_alrm) == SIG_ERR { 32 | panic!("signal(SIGALRM) error"); 33 | } 34 | alarm(1); 35 | let n = read(STDIN_FILENO, as_void!(line), MAXLINE); 36 | if n < 0 { 37 | println!("read error!"); 38 | exit(1); 39 | } 40 | alarm(0); 41 | write(STDOUT_FILENO, as_void!(line), n as _); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/f25-st_dev.rs: -------------------------------------------------------------------------------- 1 | /// Figure 4.25: Print st_dev and st_rdev values 2 | /// 3 | /// takeway: `minor()` and `major()` are static inline functions in C and cannot be called 4 | /// from rust -> needed to reimplement them in `lib.rs` 5 | /// 6 | /// $ f25-st_dev > /dev/null # device numbers are different on ev. machine -> only test ret code 7 | 8 | extern crate libc; 9 | #[macro_use(cstr)] 10 | extern crate apue; 11 | 12 | use std::env::args; 13 | use libc::{S_IFMT, S_IFCHR, S_IFBLK, stat}; 14 | use apue::{err_sys, major, minor, LibcResult}; 15 | 16 | fn main() { 17 | let mut ar = args(); 18 | ar.next(); 19 | let mut buf: stat = unsafe { std::mem::uninitialized() }; 20 | while let Some(a) = ar.next() { 21 | print!("{}: ", a); 22 | if unsafe { stat(cstr!(a), &mut buf) }.check_not_negative().is_err() { 23 | err_sys("stat error"); 24 | continue; 25 | } 26 | print!("dev = {}/{}", major(buf.st_dev), minor(buf.st_dev)); 27 | match buf.st_mode & S_IFMT { 28 | S_IFCHR | S_IFBLK => { 29 | let s = if buf.st_mode & S_IFMT == S_IFCHR { 30 | "character" 31 | } else { 32 | "block" 33 | }; 34 | print!(" ({}) rdev = {}/{}", 35 | s, 36 | major(buf.st_rdev), 37 | minor(buf.st_rdev)); 38 | } 39 | _ => {} 40 | } 41 | print!("\n"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/bin/10-signals/e10-sleep60.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 10.10: Write a program that calls sleep(60) in an infinite loop. 2 | /// Every five times through the loop (every 5 minutes), fetch the current 3 | /// time of day and print the tm_sec field. Run the program overnight and 4 | /// explain the results. How would a program such as the cron daemon, 5 | /// which runs every minute on the minute, handle this situation? 6 | /// 7 | /// Answer: no effect, even after about 8 hours on a 4 core server. 8 | /// The effect is probably quite improbable for multicore systems as the chance are high 9 | /// that one core is idle. 10 | /// 11 | /// To be really sure the process could 12 | /// 13 | /// - sleep for 59 seconds and run sleep for the remaining time 14 | /// - if woken up too late (check with time()) sleep for less then 60 seconds 15 | 16 | extern crate libc; 17 | #[macro_use(cstr)] 18 | extern crate apue; 19 | 20 | use libc::{sleep, time, localtime, c_char}; 21 | use std::mem::uninitialized; 22 | use std::ffi::CStr; 23 | use apue::my_libc::strftime; 24 | 25 | fn main() { 26 | unsafe { 27 | let mut buf: [c_char; 256] = uninitialized(); 28 | let mut t = uninitialized(); 29 | loop { 30 | sleep(60); 31 | time(&mut t); 32 | let tm = localtime(&t); 33 | strftime(buf.as_mut_ptr(), 256, cstr!("%a %b %e %H:%M:%S %Z %Y"), tm); 34 | println!("{:?}, tm_sec={}", 35 | CStr::from_ptr(buf.as_ptr()), 36 | (*tm).tm_sec); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/bin/06-system-data-files/f11-strftime.rs: -------------------------------------------------------------------------------- 1 | /// Figure 6.11 shows how to use several of the time functions discussed in this chapter. 2 | /// In particular, it shows how strftime can be used to print a string containing 3 | /// the current date and time. 4 | 5 | extern crate libc; 6 | #[macro_use(cstr, as_char)] 7 | extern crate apue; 8 | 9 | use libc::{tm, time_t, c_char, size_t, printf, exit}; 10 | use std::mem::uninitialized; 11 | 12 | extern "C" { 13 | fn time(time: *mut time_t) -> time_t; 14 | fn localtime(time: *const time_t) -> *mut tm; 15 | fn strftime(s: *mut c_char, 16 | maxsize: size_t, 17 | format: *const c_char, 18 | timeptr: *const tm) 19 | -> size_t; 20 | } 21 | 22 | 23 | fn main() { 24 | unsafe { 25 | let buf1: [c_char; 16] = uninitialized(); 26 | let buf2: [c_char; 64] = uninitialized(); 27 | let mut t: time_t = uninitialized(); 28 | time(&mut t); 29 | let tmp = localtime(&mut t); 30 | let fmt = "time and date: %r, %a %b %d, %Y"; 31 | if strftime(as_char!(buf1), 16, cstr!(fmt), tmp) == 0 { 32 | printf(cstr!("buffer length 16 is too small\n")); 33 | } else { 34 | printf(cstr!("%s\n"), buf1.as_ptr()); 35 | } 36 | if strftime(as_char!(buf2), 64, cstr!(fmt), tmp) == 0 { 37 | printf(cstr!("buffer length 64 is too small\n")); 38 | } else { 39 | printf(cstr!("%s\n"), buf2.as_ptr()); 40 | } 41 | exit(0); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/bin/02-unix-standards/f17-open-max.rs: -------------------------------------------------------------------------------- 1 | /// Figure 2.17: Determine the number of file descriptors 2 | 3 | /// Takeaway: On OSX this outputs 256 which is way too low. 4 | /// `sysctl kern.maxfiles` returns 12288 and `kern.maxfilesperproc` returns 10240. 5 | /// OPEN_MAX is defined in /usr/include/sys/syslimits.h as 10240 but I didn't find a way 6 | /// how to reference that (AFAIK extern blocks can only reference functions, not constants) 7 | /// On Linux it returns 1024 (which matches `ulimit -n`) 8 | /// 9 | /// $ f17-open-max | grep succeeded 10 | /// sysconf succeeded.. 11 | 12 | extern crate libc; 13 | extern crate errno; 14 | extern crate apue; 15 | 16 | use libc::{_SC_OPEN_MAX, sysconf}; 17 | use errno::errno; 18 | use apue::LibcResult; 19 | 20 | const OPEN_MAX_GUESS: i64 = 256; 21 | 22 | unsafe fn open_max(openmax: &mut i64) -> i64 { 23 | if *openmax == 0 { 24 | *openmax = match sysconf(_SC_OPEN_MAX).check_not_negative() { 25 | Ok(val) => { 26 | println!("sysconf succeeded.."); 27 | val 28 | } 29 | Err(_) => { 30 | println!("sysconf failed.."); 31 | match errno().0 { 32 | 0 => OPEN_MAX_GUESS, // indeterminate so just a guess 33 | _ => panic!("pathconf error for _PC_PATH_MAX"), 34 | } 35 | } 36 | } 37 | } 38 | *openmax 39 | } 40 | 41 | fn main() { 42 | let mut openmax = 0; 43 | unsafe { 44 | println!("{:?}", open_max(&mut openmax)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/bin/10-signals/f26-system-ed.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.26: Using system to invoke the ed editor 2 | /// 3 | /// Note that we are using `system` from Figure 8.22 4 | /// (was not clear to me first, so SIGINT and SIGQUIT 5 | /// did not cause sig_int and sig_chld to be called) 6 | /// Also tested with apue::system2 which behaves 7 | /// the same way as libc::system (blocking SIGINT 8 | /// and SIGQUIT) 9 | 10 | /// Proof that it works: 11 | // $ f26-system-ed 12 | // a 13 | // Here is one line of text 14 | // . 15 | // 1,$p 16 | // Here is one line of text 17 | // w temp.foo 18 | // 25 19 | // q 20 | // caught SIGCHLD 21 | // 22 | // ~/oss/apue on  master! ⌚ 21:56:22 23 | // $ f26-system-ed 24 | // a 25 | // hello, world 26 | // . 27 | // 1,$p 28 | // hello, world 29 | // w temp.foo 30 | // 13 31 | // ^C 32 | // ? 33 | // caught SIGINT 34 | // q 35 | // caught SIGCHLD 36 | #[macro_use(cstr)] 37 | extern crate apue; 38 | extern crate libc; 39 | 40 | use apue::{LibcResult, system}; 41 | use libc::{SIGINT, SIGCHLD, c_int}; 42 | use libc::{signal, printf}; 43 | 44 | fn sig_int(_: c_int) { 45 | unsafe { 46 | printf(cstr!("caught SIGINT\n")); 47 | } 48 | } 49 | 50 | fn sig_chld(_: c_int) { 51 | unsafe { 52 | printf(cstr!("caught SIGCHLD\n")); 53 | } 54 | } 55 | 56 | fn main() { 57 | unsafe { 58 | signal(SIGINT, sig_int as usize).check_not_sigerr().expect("signal(SIGINT) error"); 59 | signal(SIGCHLD, sig_chld as usize).check_not_sigerr().expect("signal(SIGCHLD) error"); 60 | system("/bin/ed").expect("system() error"); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/bin/10-signals/f06-sigcld-systemv.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.6 System V SIGCLD handler that doesn’t work 2 | /// 3 | /// linux only: 4 | /// $ f06-sigcld-systemv | sed 's/[0-9]*//g' 5 | /// SIGCLD received 6 | /// pid = 7 | 8 | extern crate libc; 9 | extern crate apue; 10 | 11 | // only Linux defines SIGCLD, it is equal to SIGCHLD 12 | #[cfg(target_os = "linux")] 13 | mod sigcld { 14 | use libc::{c_int, SIGCHLD, SIG_ERR}; 15 | use libc::{signal, wait, fork, usleep, _exit, pause}; 16 | use apue::LibcResult; 17 | 18 | const SIGCLD: c_int = SIGCHLD; 19 | 20 | unsafe fn sig_cld(_: c_int) { 21 | println!("SIGCLD received"); 22 | if signal(SIGCLD, sig_cld as usize) == SIG_ERR { 23 | panic!("signal error"); 24 | } 25 | let mut status: c_int = 0; 26 | let pid = wait(&mut status).check_not_negative().expect("wait error"); 27 | println!("pid = {}", pid); 28 | } 29 | 30 | pub fn doit() { 31 | unsafe { 32 | if signal(SIGCLD, sig_cld as usize) == SIG_ERR { 33 | panic!("signal error"); 34 | } 35 | let pid = fork().check_not_negative().expect("fork error"); 36 | if pid == 0 { 37 | // child 38 | usleep(100); 39 | _exit(0); 40 | } else { 41 | // parent 42 | pause(); 43 | } 44 | } 45 | } 46 | } 47 | 48 | #[cfg(target_os = "linux")] 49 | fn main() { 50 | sigcld::doit(); 51 | } 52 | 53 | #[cfg(not(target_os = "linux"))] 54 | fn main() { 55 | unimplemented!(); 56 | } 57 | -------------------------------------------------------------------------------- /src/bin/10-signals/f05-nonreentrant.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.5 Call a nonreentrant function from a signal handler 2 | /// 3 | /// Findings: 4 | /// 5 | /// - on macos most of the time the program just freezes after 2-3 prints 6 | /// of "in signal handler" 7 | /// - only in one run (out of about 30) there was a malloc error shown 8 | /// - on linux it mostly freezes after only one cycle 9 | /// - in about 10% of all cases it shows "return value corrupted! pw_name = root" 10 | /// - when running the c code from the book it shows the same behaviour 11 | /// 12 | /// The lock is most likely due to a malloc who is threadsafe through 13 | /// locking which results in a deadlock. Explanation here: 14 | /// http://stackoverflow.com/a/3941563/119861 15 | #[macro_use(cstr)] 16 | extern crate apue; 17 | extern crate libc; 18 | 19 | use libc::{c_int, SIGALRM}; 20 | use libc::{getpwnam, alarm, signal, printf}; 21 | use apue::LibcPtrResult; 22 | use std::ffi::CStr; 23 | 24 | extern "C" fn my_alarm(_: c_int) { 25 | unsafe { 26 | printf(cstr!("in signal handler\n")); 27 | getpwnam(cstr!("root")).check_not_null().expect("getpwnam(root) error"); 28 | alarm(1); 29 | } 30 | } 31 | 32 | fn main() { 33 | unsafe { 34 | signal(SIGALRM, my_alarm as usize); 35 | alarm(1); 36 | loop { 37 | let pwd = getpwnam(cstr!("nobody")).check_not_null().expect("getpwnam error"); 38 | let pw_name = CStr::from_ptr((*pwd).pw_name).to_str().unwrap(); 39 | if pw_name != "nobody" { 40 | println!("return value corrupted! pw_name = {}", pw_name); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/bin/10-signals/f25-abort.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.25 Implementation of POSIX.1 abort 2 | /// 3 | /// Status: compiles, not tested yet (assuming this function will be 4 | /// used in the future in the book) 5 | 6 | extern crate libc; 7 | extern crate apue; 8 | 9 | use libc::{SIG_DFL, SIGABRT, SIG_IGN, SIG_SETMASK}; 10 | use libc::{sigaction, fflush, sigdelset, sigfillset, getpid, kill, exit}; 11 | use std::ptr::{null, null_mut}; 12 | use apue::my_libc::sigprocmask; 13 | 14 | 15 | unsafe fn abort() { 16 | let mut action: sigaction = std::mem::uninitialized(); 17 | // load current signal handler and flags into action 18 | sigaction(SIGABRT, null(), &mut action); 19 | if action.sa_sigaction == SIG_IGN { 20 | // Caller can’t ignore SIGABRT, if so reset to default 21 | action.sa_sigaction = SIG_DFL; 22 | sigaction(SIGABRT, &action, null_mut()); 23 | } 24 | if action.sa_sigaction == SIG_DFL { 25 | fflush(null_mut()); 26 | } 27 | let mut mask = std::mem::uninitialized(); 28 | sigfillset(&mut mask); // block all signals 29 | sigdelset(&mut mask, SIGABRT); // leave only SIGABRT through 30 | sigprocmask(SIG_SETMASK, &mask, null_mut()); 31 | kill(getpid(), SIGABRT); // say bye bye to mummy 32 | 33 | // if we're here, process caught SIGABRT and returned 34 | fflush(null_mut()); 35 | action.sa_sigaction = SIG_DFL; 36 | sigaction(SIGABRT, &action, null_mut()); 37 | sigprocmask(SIG_SETMASK, &mask, null_mut()); // what, again? Really? 38 | kill(getpid(), SIGABRT); 39 | exit(1); // now THIS should now never be executed 40 | } 41 | 42 | fn main() { 43 | let _ = abort; 44 | } 45 | -------------------------------------------------------------------------------- /src/bin/12-thread-control/f04-detached-thread.rs: -------------------------------------------------------------------------------- 1 | /// Figure 12.4 Creating a thread in the detached state 2 | /// 3 | /// Findings: 4 | /// - finally needed to clean up error handling, totally rewrote what was former only a 5 | /// to_option() method in lib.rs into several check_* methods, see also this discussion: 6 | /// http://stackoverflow.com/questions/42772307 7 | /// - detached threads on linux can't write to stdout, got 8 | /// "failed printing to stdout: Broken pipe (os error 32)" 9 | /// 10 | /// mac only: 11 | /// $ f04-detached-thread 12 | /// called! 13 | 14 | extern crate libc; 15 | extern crate apue; 16 | 17 | use libc::{c_void, PTHREAD_CREATE_DETACHED}; 18 | use libc::{pthread_create, pthread_attr_destroy, pthread_attr_init, pthread_attr_setdetachstate, 19 | usleep}; 20 | use apue::LibcResult; 21 | 22 | extern "C" fn my_thread(_: *mut c_void) -> *mut c_void { 23 | println!("called!"); 24 | 0 as _ 25 | } 26 | 27 | unsafe fn makethread(func: extern "C" fn(*mut c_void) -> *mut c_void, 28 | arg: *mut c_void) 29 | -> std::io::Result { 30 | let (mut attr, mut tid) = std::mem::uninitialized(); 31 | pthread_attr_init(&mut attr).check_zero()?; 32 | let mut err = 0; 33 | if pthread_attr_setdetachstate(&mut attr, PTHREAD_CREATE_DETACHED).check_zero().is_ok() { 34 | err = pthread_create(&mut tid, &attr, func, arg); 35 | } 36 | pthread_attr_destroy(&mut attr).check_zero()?; 37 | Ok(err) 38 | } 39 | 40 | fn main() { 41 | unsafe { 42 | makethread(my_thread, std::ptr::null_mut()).expect("couldn't do a thread"); 43 | usleep(100); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/bin/03-fileio/f11-fcntl.rs: -------------------------------------------------------------------------------- 1 | /// Figure 3.11: Print file flags for specified descriptor 2 | /// 3 | /// $ f11-fcntl 0 < /dev/tty 4 | /// read only 5 | /// $ f11-fcntl 0 6 | /// read write 7 | /// $ f11-fcntl 0 < /dev/tty 8 | /// read only 9 | /// $ f11-fcntl 1 > /tmp/temp.foo 10 | /// $ cat /tmp/temp.foo 11 | /// write only 12 | /// $ f11-fcntl 2 2>>/tmp/temp.foo 13 | /// write only, append 14 | /// $ f11-fcntl 5 5<>/tmp/temp.foo 15 | /// read write 16 | 17 | extern crate libc; 18 | extern crate apue; 19 | #[macro_use(value_t)] 20 | extern crate clap; 21 | 22 | use libc::{F_GETFL, O_ACCMODE, O_APPEND, O_NONBLOCK, O_SYNC, O_RDONLY, O_WRONLY, O_RDWR, fcntl}; 23 | use clap::App; 24 | use apue::LibcResult; 25 | 26 | fn main() { 27 | unsafe { 28 | let matches = 29 | App::new("fcntl").args_from_usage(" id of the descriptor").get_matches(); 30 | let desc = value_t!(matches.value_of("descr"), u8).unwrap_or_else(|e| e.exit()); 31 | let val = fcntl(desc as _, F_GETFL, 0) 32 | .check_not_negative() 33 | .expect(&format!("fcntl error for fd {}", desc)); 34 | let mode = match val & O_ACCMODE { 35 | O_RDONLY => "read only", 36 | O_WRONLY => "write only", 37 | O_RDWR => "read write", 38 | _ => "unknown access mode", 39 | }; 40 | print!("{}", mode); 41 | if val & O_APPEND > 0 { 42 | print!(", append"); 43 | } 44 | if val & O_NONBLOCK > 0 { 45 | print!(", nonblocking"); 46 | } 47 | if val & O_SYNC > 0 { 48 | print!(", synchronous writes"); 49 | } 50 | println!(""); 51 | } 52 | } -------------------------------------------------------------------------------- /src/bin/06-system-data-files/f02-getpwnam.rs: -------------------------------------------------------------------------------- 1 | /// Figure 6.2: The getpwname function 2 | /// 3 | /// Takeaways: 4 | /// - from the book (p. 186): "the get functions return a pointer to a static structure, 5 | /// so we always have to copy the structure if we want to save it.", although in the 6 | /// C example code they just return the struct so I'd say that's broken. We need 7 | /// to copy the struct. Unfortunately, there's no cheap way to own the struct returned 8 | /// by getpwent(). We need to build PasswdOwned to own the name string. 9 | /// - originally used CString::from_raw on pw.pw_name, this worked well on OSX 10 | /// but segfaulted on Linux. CStr::from_ptr needs to be called on Strings that originate 11 | /// in C 12 | 13 | 14 | extern crate libc; 15 | 16 | use libc::{getpwent, endpwent, setpwent}; 17 | use std::ffi::CStr; 18 | 19 | #[derive(Debug)] 20 | struct PasswdOwned { 21 | name: String, 22 | uid: libc::gid_t, 23 | gid: libc::uid_t, 24 | } 25 | 26 | unsafe fn getpwnam(name: &str) -> Option { 27 | setpwent(); 28 | while let Some(pw) = getpwent().as_ref() { 29 | let pw_name = CStr::from_ptr(pw.pw_name).to_string_lossy().into_owned(); 30 | if pw_name == name { 31 | endpwent(); 32 | let pw = PasswdOwned { 33 | name: pw_name, 34 | uid: pw.pw_uid, 35 | gid: pw.pw_gid, 36 | }; 37 | return Some(pw); 38 | } 39 | } 40 | endpwent(); 41 | None 42 | } 43 | 44 | fn main() { 45 | unsafe { 46 | println!("{:?}", 47 | getpwnam("philipp").expect("no user found with that name!")); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/bin/11-threads/thread-barrier.c: -------------------------------------------------------------------------------- 1 | /* copied from the internet */ 2 | 3 | #ifdef __APPLE__ 4 | 5 | #ifndef PTHREAD_BARRIER_H_ 6 | #define PTHREAD_BARRIER_H_ 7 | 8 | #include 9 | #include 10 | 11 | typedef int pthread_barrierattr_t; 12 | typedef struct { 13 | pthread_mutex_t mutex; 14 | pthread_cond_t cond; 15 | int count; 16 | int tripCount; 17 | } pthread_barrier_t; 18 | 19 | int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) { 20 | if(count == 0) { 21 | errno = EINVAL; 22 | return -1; 23 | } 24 | if(pthread_mutex_init(&barrier->mutex, 0) < 0) { 25 | return -1; 26 | } 27 | if(pthread_cond_init(&barrier->cond, 0) < 0) { 28 | pthread_mutex_destroy(&barrier->mutex); 29 | return -1; 30 | } 31 | barrier->tripCount = count; 32 | barrier->count = 0; 33 | return 0; 34 | } 35 | 36 | int pthread_barrier_destroy(pthread_barrier_t *barrier) { 37 | pthread_cond_destroy(&barrier->cond); 38 | pthread_mutex_destroy(&barrier->mutex); 39 | return 0; 40 | } 41 | 42 | int pthread_barrier_wait(pthread_barrier_t *barrier) { 43 | pthread_mutex_lock(&barrier->mutex); 44 | ++(barrier->count); 45 | if(barrier->count >= barrier->tripCount) { 46 | barrier->count = 0; 47 | pthread_cond_broadcast(&barrier->cond); 48 | pthread_mutex_unlock(&barrier->mutex); 49 | return 1; 50 | } else { 51 | pthread_cond_wait(&barrier->cond, &(barrier->mutex)); 52 | pthread_mutex_unlock(&barrier->mutex); 53 | return 0; 54 | } 55 | } 56 | 57 | #endif // PTHREAD_BARRIER_H_ 58 | #endif // __APPLE__ -------------------------------------------------------------------------------- /src/bin/11-threads/e01-pass-struct.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 11.1: Modify the example code shown in Figure 11.4 to pass the structure between the 2 | /// threads properly. 3 | /// 4 | /// The cleanest solution is - I think - if the main thread is responsible 5 | /// for both mallocing and freeing the memory, thus we don't need 6 | /// the return value (or pthread_exit), but store the changes in to the arg 7 | /// variable directly. 8 | /// 9 | /// The solution in the book works as well of course, but leaks memory. 10 | /// 11 | /// $ e01-pass-struct 12 | /// Foo { a: 55, b: 66, c: 3, d: 4 } 13 | 14 | extern crate libc; 15 | extern crate apue; 16 | use libc::c_void; 17 | use libc::usleep; 18 | use apue::my_libc::pthread_create; 19 | use apue::LibcResult; 20 | use std::ptr::null_mut; 21 | 22 | #[derive(Debug)] 23 | struct Foo { 24 | a: i32, 25 | b: i32, 26 | c: i32, 27 | d: i32, 28 | } 29 | 30 | unsafe extern "C" fn thr_fn1(foo_ptr: *mut c_void) -> *mut c_void { 31 | let foo = foo_ptr as *mut Foo; 32 | (*foo).a = 55; 33 | (*foo).b = 66; 34 | 0 as _ 35 | } 36 | 37 | fn main() { 38 | unsafe { 39 | let foo = Box::new(Foo { 40 | a: 1, 41 | b: 2, 42 | c: 3, 43 | d: 4, 44 | }); 45 | let mut tid1 = std::mem::uninitialized(); 46 | let foo_ptr = Box::into_raw(foo); 47 | pthread_create(&mut tid1, null_mut(), thr_fn1, foo_ptr as *mut c_void).check_zero().expect("can't create thread 1"); 48 | libc::pthread_join(tid1, null_mut()).check_zero().expect("join error"); 49 | let foo: Box = Box::from_raw(foo_ptr); 50 | usleep(100); 51 | println!("{:?}", foo); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/f12-chmod.rs: -------------------------------------------------------------------------------- 1 | /// Figure 4.12: Example of chmod function 2 | /// 3 | /// Findings: 4 | /// 5 | /// - setgid in /tmp/ does not work under OSX for whatever reason. With sudo it works 6 | /// and in another directory and on Linux it all works. Cost me like 2h to track 7 | /// it down (of course first tried to find the bug in the code) 8 | /// full post: http://stackoverflow.com/questions/42811165 9 | /// 10 | /// $ rm -f /tmp/{foo,bar} 11 | /// $ touch /tmp/{foo,bar} 12 | /// $ chmod g+x /tmp/foo 13 | /// 14 | /// linux only: 15 | /// $ f12-chmod 16 | /// $ stat -c %A /tmp/{foo,bar} 17 | /// -rwSrwxr-- 18 | /// -rw-r--r-- 19 | /// 20 | /// mac only: 21 | /// $ f12-chmod 22 | /// $ stat -f "%Sp" /tmp/{foo,bar} 23 | /// -rwSr-xr-- 24 | /// -rw-r--r-- 25 | 26 | extern crate libc; 27 | #[macro_use(cstr)] 28 | extern crate apue; 29 | 30 | use libc::{mode_t, S_IXUSR, S_ISUID, S_IROTH, S_IRGRP, S_IWUSR, S_IRUSR, stat, chmod}; 31 | use apue::LibcResult; 32 | 33 | fn main() { 34 | unsafe { 35 | let mut statbuf: stat = std::mem::uninitialized(); 36 | stat(cstr!("/tmp/foo"), &mut statbuf) 37 | .check_not_negative() 38 | .expect("stat error for /tmp/foo"); 39 | // turn on set-group-ID and turn off group-execute 40 | chmod(cstr!("/tmp/foo"), 41 | (statbuf.st_mode & !S_IXUSR) | S_ISUID as mode_t) 42 | .check_not_negative() 43 | .expect("chmod error for foo"); 44 | 45 | // set absolute mode to "rw-r--r--" 46 | chmod(cstr!("/tmp/bar"), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) 47 | .check_not_negative() 48 | .expect("chmod error for bar"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/bin/10-signals/f22-signal-protection.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.22 Protecting a critical region from a signal 2 | 3 | extern crate libc; 4 | extern crate apue; 5 | extern crate errno; 6 | 7 | use libc::{c_int, SIGINT, SIG_ERR, SIGUSR1, SIG_BLOCK, SIG_SETMASK}; 8 | use libc::{signal, sigemptyset, sigaddset}; 9 | use apue::my_libc::{sigprocmask, sigsuspend}; 10 | use apue::{pr_mask, LibcResult}; 11 | use std::mem::uninitialized as uninit; 12 | 13 | fn sig_int(_: c_int) { 14 | pr_mask("\nin sig_int: "); 15 | } 16 | 17 | fn main() { 18 | unsafe { 19 | let (mut waitmask, mut newmask, mut oldmask) = (uninit(), uninit(), uninit()); 20 | pr_mask("program start: "); 21 | if signal(SIGINT, sig_int as usize) == SIG_ERR { 22 | println!("signal(SIGINT) error"); 23 | } 24 | sigemptyset(&mut waitmask); 25 | sigaddset(&mut waitmask, SIGUSR1); 26 | sigemptyset(&mut newmask); 27 | sigaddset(&mut newmask, SIGINT); 28 | 29 | // Block SIGINT and save current signal mask 30 | sigprocmask(SIG_BLOCK, &newmask, &mut oldmask) 31 | .check_not_negative() 32 | .expect("SIG_BLOCK error"); 33 | // Critical region of code 34 | pr_mask("in critical region: "); 35 | // Pause, allowing all signals except SIGUSR1 36 | sigsuspend(&waitmask).check_minus_one().expect("sigsuspend error"); 37 | pr_mask("after return from sigsuspend: "); 38 | // reset signal mask which unblocks SIGINT 39 | sigprocmask(SIG_SETMASK, &oldmask, std::ptr::null_mut()) 40 | .check_not_negative() 41 | .expect("SIG_SETMASK error"); 42 | pr_mask("program exit: "); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/bin/03-fileio/f12-setfl.rs: -------------------------------------------------------------------------------- 1 | /// Figure 3.12: Turn on one or more of the file status flags for a descriptor 2 | /// this example turns on the O_NONBLOCK flag 3 | /// 4 | /// Takeaways: 5 | /// 6 | /// - Mac lets you set almost every flag (O_READ and O_WRITE), on Linux that's not possible 7 | /// - Mac and Linux have *very* different values for these flags 8 | /// 9 | /// List of all flags: 10 | /// 11 | /// - Linux: /usr/include/bits/fcntl-linux.h 12 | /// - Mac: /usr/include/sys/fcntl.h 13 | /// 14 | /// linux only: 15 | /// $ f12-setfl 5 5<>/tmp/temp.foo 16 | /// current flags: 1000000000000010 17 | /// new flags: 1000100000000010 18 | /// 19 | /// mac only: 20 | /// $ f12-setfl 5 5<>/tmp/temp.foo 21 | /// current flags: 10 22 | /// new flags: 110 23 | 24 | extern crate libc; 25 | extern crate apue; 26 | #[macro_use(value_t)] 27 | extern crate clap; 28 | 29 | use libc::{F_GETFL, F_SETFL, O_NONBLOCK, fcntl}; 30 | use clap::App; 31 | use apue::LibcResult; 32 | 33 | unsafe fn get_fl(fd: i32) -> std::io::Result { 34 | return fcntl(fd, F_GETFL, 0).check_not_negative(); 35 | } 36 | 37 | unsafe fn set_fl(fd: i32, flags: i32) { 38 | let val = get_fl(fd).expect("fcntl F_GETFL error"); 39 | println!("current flags: {:b}", val); 40 | let val = val | flags; 41 | fcntl(fd, F_SETFL, val).check_not_negative().expect("fcntl F_SETFL error"); 42 | } 43 | 44 | fn main() { 45 | let matches = App::new("setfl") 46 | .args_from_usage(" 'id of the descriptor'") 47 | .get_matches(); 48 | let fd = value_t!(matches.value_of("descr"), i32).unwrap_or_else(|e| e.exit()); 49 | unsafe { 50 | set_fl(fd, O_NONBLOCK); 51 | println!("new flags: {:b}", get_fl(fd).unwrap()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/bin/10-signals/f31-sigtstp-handler.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.31 How to handle SIGTSTP 2 | /// 3 | /// Added a println to sig_tstp to show that the signal 4 | /// is really caught 5 | #[macro_use(as_void)] 6 | extern crate apue; 7 | extern crate libc; 8 | 9 | use std::mem::uninitialized; 10 | use std::ptr::null_mut; 11 | use libc::{c_int, SIGTSTP, SIG_UNBLOCK, SIG_DFL, SIG_IGN, STDIN_FILENO, STDOUT_FILENO}; 12 | use libc::{sigemptyset, sigaddset, signal, kill, getpid, read, write}; 13 | use apue::my_libc::sigprocmask; 14 | use apue::{LibcResult, err_sys}; 15 | 16 | const BUFFSIZE: usize = 1024; 17 | 18 | unsafe fn sig_tstp(_: c_int) { 19 | // move cursor to lower left corner, reset tty mode 20 | // unblock SIGTSTP, since it's blocked while we're reading it 21 | println!("last cleanup before SIGTSTP"); 22 | let mut mask = uninitialized(); 23 | sigemptyset(&mut mask); 24 | sigaddset(&mut mask, SIGTSTP); 25 | sigprocmask(SIG_UNBLOCK, &mask, null_mut()); 26 | 27 | signal(SIGTSTP, SIG_DFL); // reset disposition to default 28 | kill(getpid(), SIGTSTP); // and send the signal to ourself 29 | // we won't return from the kill until we're continued 30 | signal(SIGTSTP, sig_tstp as usize); // reestablish signal handler 31 | // ... reset tty mode, redraw screen ... 32 | } 33 | 34 | fn main() { 35 | unsafe { 36 | if signal(SIGTSTP, SIG_IGN) == SIG_DFL { 37 | signal(SIGTSTP, sig_tstp as usize); 38 | } 39 | let buf = vec![0; BUFFSIZE]; 40 | while let Ok(n) = read(STDIN_FILENO, as_void!(buf), BUFFSIZE).check_positive() { 41 | if write(STDOUT_FILENO, as_void!(buf), n as _) != n { 42 | err_sys("write error"); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f03-vfork.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.3: Example of vfork function 2 | /// 3 | /// Findings: 4 | /// - when calling exit() this error is triggered: 5 | /// f03-vfork(17135,0x7fffabbdc3c0) malloc: 6 | /// *** error for object 0x7fc0b4c02830: pointer being freed was not allocated 7 | /// I *think* it's because the rust cleanup tries to free a memory twice, once for every 8 | /// process 9 | /// - when using a normal stack var (no Box), and compiling this in release 10 | /// mode, the parent does not see the var += 1 (but it DOES see the GLOBVAR +=1). 11 | /// The child *does* do the += 1 (printf shows it), so it is not compiled away, 12 | /// there's some other strange effect at hand which I don't understand. 13 | /// Anyway, changing into a Box solves the issue 14 | /// 15 | /// $ f03-vfork | sed 's/pid = [0-9]*, //' 16 | /// before vfork 17 | /// glob = 7, var = 89 18 | 19 | extern crate libc; 20 | #[macro_use(cstr)] 21 | extern crate apue; 22 | 23 | use libc::{printf, getpid, c_int, _exit}; 24 | use apue::{LibcResult}; 25 | use apue::my_libc::vfork; 26 | 27 | static mut GLOBVAR: i64 = 6; 28 | 29 | #[allow(unused_assignments)] 30 | fn main() { 31 | unsafe { 32 | let mut var = Box::new(88); 33 | printf(cstr!("before vfork\n")); 34 | match vfork().check_not_negative().expect("vfork error") { 35 | 0 => { 36 | // child 37 | GLOBVAR += 1; 38 | *var += 1; 39 | _exit(0); 40 | } 41 | _ => { 42 | printf(cstr!("pid = %ld, glob = %d, var = %d\n"), 43 | getpid(), 44 | GLOBVAR, 45 | *var as c_int); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/bin/03-fileio/f02-file-with-hole.rs: -------------------------------------------------------------------------------- 1 | /// Figure 3.2: Create a file with a hole in it 2 | /// 3 | /// Takeaway: On OSX the sparse file seems to use the full disk space: 4 | /// 5 | /// Apperently the sparse files are not using disk space but ls -s is not able to spot this, 6 | /// here's some explanation: http://superuser.com/questions/199109 7 | /// Even in Virtual Box on OSX this is applying, so this seems to be a "feature" of HFS. 8 | /// On a Linux host running on ext4 `ls -s` shows 8 for file.hole and 40 for file.nohole 9 | /// 10 | /// $ f02-file-with-hole 11 | /// $ rm file.*hole 12 | 13 | // this _should_ be the case, but is not on OSX, that's why it's only commented with // 14 | // so it's not tested by test.py: 15 | // 16 | // $ cat file.hole > file.nohole 17 | // $ ls -s file.hole file.nohole 18 | // 40 file.hole 19 | // 40 file.nohole 20 | 21 | extern crate libc; 22 | #[macro_use(cstr, as_void)] 23 | extern crate apue; 24 | 25 | use libc::{mode_t, SEEK_SET, creat, write, lseek}; 26 | use apue::LibcResult; 27 | use std::ffi::CString; 28 | 29 | const FILE_MODE: mode_t = (libc::S_IRUSR | libc::S_IWUSR | libc::S_IRGRP | libc::S_IROTH); 30 | 31 | fn main() { 32 | unsafe { 33 | let s1 = CString::new("abcdefghij"); 34 | let s2 = CString::new("ABCDEFGHIJ"); 35 | let fd = creat(cstr!("file.hole"), FILE_MODE).check_not_negative().expect("creat error"); 36 | assert!(write(fd, as_void!(s1.unwrap().as_bytes()), 10) == 10, 37 | "buffer write error"); 38 | // offset is now 10 39 | lseek(fd, 16384, SEEK_SET).check_not_negative().expect("lseek error"); 40 | assert!(write(fd, as_void!(s2.unwrap().as_bytes()), 10) == 10, 41 | "buffer write error"); 42 | // offset is now 16384 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/bin/10-signals/e12-fwrite-1gb.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 10.12: Write a program that calls fwrite with a large buffer 2 | /// (about one gigabyte). Before calling fwrite, call alarm to schedule a 3 | /// signal in 1 second. In your signal handler, print that the signal was 4 | /// caught and return. Does the call to fwrite complete? What’s happening? 5 | /// 6 | /// Findings: 7 | /// - both on OSX and Linux the alarm is immediately triggered 8 | /// - alarm doesn't stop the fwrite call, all 1GB data is written on disk still 9 | /// - initially wanted to create the buffer with 10 | /// `let buffer: [i8; SIZE] = [50; SIZE]` which resulted in a segfault. 11 | /// After some debugging with strace I found out that it was failing because 12 | /// the memory is allocated in the stack and `getrlimit(RLIMIT_STACK)` returned 13 | /// a max stack size of 2^23 bytes -> the max size of a buffer in the stack 14 | /// is 2^22 because some bytes are needed for the code and the other vars. 15 | #[macro_use(as_void, cstr)] 16 | extern crate apue; 17 | extern crate libc; 18 | extern crate errno; 19 | 20 | use libc::{c_int, SIGALRM, fwrite, fopen, alarm}; 21 | use apue::{LibcResult, LibcPtrResult, signal}; 22 | 23 | fn sig_alrm(_: c_int) { 24 | println!("alarm has happened.."); 25 | } 26 | 27 | const SIZE: usize = 1 << 32; 28 | fn main() { 29 | unsafe { 30 | signal(SIGALRM, sig_alrm).check_not_negative().expect("couldn't set alarm"); 31 | alarm(1); 32 | let buffer: Vec = Vec::with_capacity(SIZE); 33 | let file = fopen(cstr!("/tmp/10_fwrite_1gb.txt"), cstr!("w")) 34 | .check_not_null() 35 | .expect("can't open file"); 36 | fwrite(as_void!(buffer), 1, SIZE, file).check_not_negative().expect("can't write to file"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/bin/11-threads/f15-conditional-var.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | /// Figure 11.15: using a conditional variable 4 | /// 5 | /// Finding: the text for pthread_cond_wait was somehow hard to understand 6 | /// this code made it so much clearer what the function is for 7 | 8 | extern crate libc; 9 | 10 | use libc::{pthread_cond_t, pthread_mutex_t, PTHREAD_COND_INITIALIZER, PTHREAD_MUTEX_INITIALIZER}; 11 | use libc::{pthread_mutex_lock, pthread_cond_wait, pthread_mutex_unlock, pthread_cond_signal}; 12 | use std::collections::VecDeque; 13 | 14 | struct Msg {} 15 | 16 | struct Messages { 17 | messages: VecDeque, 18 | qready: pthread_cond_t, 19 | qlock: pthread_mutex_t, 20 | } 21 | 22 | impl Messages { 23 | fn new() -> Messages { 24 | Messages { 25 | messages: VecDeque::new(), 26 | qready: PTHREAD_COND_INITIALIZER, 27 | qlock: PTHREAD_MUTEX_INITIALIZER, 28 | } 29 | } 30 | 31 | fn process_msg(&mut self) { 32 | loop { 33 | unsafe { 34 | pthread_mutex_lock(&mut self.qlock); 35 | while self.messages.len() == 0 { 36 | pthread_cond_wait(&mut self.qready, &mut self.qlock); 37 | } 38 | let _ = self.messages.pop_front(); 39 | pthread_mutex_unlock(&mut self.qlock); 40 | // now process the message mp 41 | } 42 | } 43 | } 44 | 45 | fn enqueue_msg(&mut self, mp: Msg) { 46 | unsafe { 47 | pthread_mutex_lock(&mut self.qlock); 48 | self.messages.push_front(mp); 49 | pthread_mutex_unlock(&mut self.qlock); 50 | pthread_cond_signal(&mut self.qready); 51 | } 52 | 53 | } 54 | } 55 | 56 | fn main() {} 57 | -------------------------------------------------------------------------------- /src/bin/05-stdio/f12-tmpnam-tmpfile.rs: -------------------------------------------------------------------------------- 1 | /// Code for Figure 5.12 (Demonstrate tmpnam and tmpfile functions) 2 | /// 3 | /// Sad thing is that the rust code is about double the number of lines (40 lines) 4 | /// of the C code (20 lines), mostly because of the string conversions. 5 | /// It's a bit silly that there's no neat abstraction for that. 6 | /// 7 | /// $ f12-tmpnam-tmpfile 2>/dev/null 8 | /// one line of output 9 | 10 | extern crate libc; 11 | #[macro_use(print_err, cstr)] 12 | extern crate apue; 13 | 14 | use std::ffi::{CStr, CString}; 15 | use apue::my_libc::tmpnam; 16 | use libc::{tmpfile, fgets, fputs, rewind, L_tmpnam}; 17 | 18 | const MAXLINE: usize = 4096; 19 | 20 | fn main() { 21 | // method 1: get pointer to new buffer 22 | let tmp = unsafe { 23 | let tmp_ptr = tmpnam(std::ptr::null_mut()); 24 | CStr::from_ptr(tmp_ptr).to_owned().into_string().unwrap() 25 | }; 26 | print_err!("tmp file={}", tmp); 27 | 28 | // method 2: create buffer ourselves, make tmpnam fill this buffer 29 | let tmp = unsafe { 30 | let name = CString::from_vec_unchecked(Vec::with_capacity(L_tmpnam as usize)).into_raw(); 31 | tmpnam(name); 32 | CStr::from_ptr(name).to_owned().into_string().unwrap() 33 | }; 34 | print_err!("tmp file={}", tmp); 35 | 36 | unsafe { 37 | let fp = tmpfile(); 38 | if fp.is_null() { 39 | panic!("tmpfile error"); 40 | } 41 | fputs(cstr!("one line of output"), fp); 42 | rewind(fp); 43 | let line = CString::from_vec_unchecked(Vec::with_capacity(MAXLINE)).into_raw(); 44 | if fgets(line, MAXLINE as i32, fp).is_null() { 45 | panic!("fgets error"); 46 | } 47 | println!("{}", CStr::from_ptr(line).to_owned().into_string().unwrap()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/bin/06-system-data-files/e03-uname.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 6.3: Write a program that calls uname and prints all the fields 2 | /// in the utsname structure. 3 | /// Compare the output to the output from the uname(1) command. 4 | 5 | extern crate libc; 6 | extern crate apue; 7 | 8 | use libc::{utsname, uname}; 9 | use apue::array_to_string; 10 | 11 | #[derive(Debug)] 12 | struct UtsName { 13 | sysname: String, 14 | nodename: String, 15 | release: String, 16 | version: String, 17 | machine: String, 18 | } 19 | 20 | unsafe fn my_uname() -> Option { 21 | let mut uc: utsname = std::mem::uninitialized(); 22 | if uname(&mut uc) == 0 { 23 | return Some(UtsName { 24 | sysname: array_to_string(&uc.sysname).to_owned(), 25 | nodename: array_to_string(&uc.nodename).to_owned(), 26 | release: array_to_string(&uc.release).to_owned(), 27 | version: array_to_string(&uc.version).to_owned(), 28 | machine: array_to_string(&uc.machine).to_owned(), 29 | }); 30 | } 31 | None 32 | } 33 | 34 | 35 | fn main() { 36 | println!("{:?}", unsafe { my_uname().unwrap() }); 37 | } 38 | 39 | // Result: 40 | // 41 | // > uname -a 42 | // Darwin philippkellr-6.local 15.6.0 Darwin Kernel Version 15.6.0: 43 | // Mon Aug 29 20:21:34 PDT 2016; 44 | // root:xnu-3248.60.11~1/RELEASE_X86_64 x86_64 45 | // 46 | // > target/debug/e06-03-uname 47 | // UtsName { sysname: "Darwin", 48 | // nodename: "philippkellr-6.local", 49 | // release: "15.6.0", 50 | // version: "Darwin Kernel Version 15.6.0: Mon Aug 29 20:21:34 PDT 2016; 51 | // root:xnu-3248.60.11~1/RELEASE_X86_64", 52 | // machine: "x86_64" } 53 | // 54 | // -> it has the same infos, uname -a concatenates the fields with a space inbetween 55 | -------------------------------------------------------------------------------- /src/bin/10-signals/f29-sleep.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.29 Reliable implementation of sleep 2 | /// 3 | /// I must say that this signal thing gets a bit 4 | /// boring... 5 | 6 | extern crate apue; 7 | extern crate libc; 8 | 9 | use std::mem::uninitialized; 10 | use libc::{c_int, sigaction, SIGALRM, SIG_SETMASK, SIG_BLOCK}; 11 | use libc::{sigemptyset, sigaddset, sigdelset, alarm}; 12 | use apue::my_libc::{sigprocmask, sigsuspend}; 13 | 14 | fn sig_alrm(_: c_int) { 15 | // nothing to do, just returning wakes up sigsuspend() 16 | } 17 | 18 | unsafe fn sleep(seconds: u32) -> u32 { 19 | let mut newact: sigaction = uninitialized(); 20 | 21 | // set our handler, save previous information 22 | newact.sa_sigaction = sig_alrm as usize; 23 | sigemptyset(&mut newact.sa_mask); 24 | newact.sa_flags = 0; 25 | let mut oldact = uninitialized(); 26 | sigaction(SIGALRM, &newact, &mut oldact); 27 | 28 | // block SIGALRM and save current signal mask 29 | let (mut newmask, mut oldmask) = uninitialized(); 30 | sigemptyset(&mut newmask); 31 | sigaddset(&mut newmask, SIGALRM); 32 | sigprocmask(SIG_BLOCK, &newmask, &mut oldmask); 33 | 34 | alarm(seconds); 35 | let mut suspmask = oldmask; 36 | 37 | // make sure SIGALRM isn't blocked 38 | sigdelset(&mut suspmask, SIGALRM); 39 | 40 | // wait for any signal to be caught 41 | sigsuspend(&suspmask); 42 | 43 | // some signal has been caught, SIGALRM is now blocked 44 | let unslept = alarm(0); 45 | 46 | // reset previous action 47 | sigaction(SIGALRM, &oldact, std::ptr::null_mut()); 48 | 49 | // reset signal mask, which unblocks SIGALRM 50 | sigprocmask(SIG_SETMASK, &oldmask, std::ptr::null_mut()); 51 | unslept 52 | } 53 | 54 | fn main() { 55 | unsafe { 56 | println!("{}", sleep(10)); 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /src/bin/01-overview/f07-read-execute.rs: -------------------------------------------------------------------------------- 1 | /// Figure 1.7 Read commands from standard input and execute them 2 | /// 3 | /// Takeaways: 4 | /// 5 | /// - this was astonishingly very straightforward to port to rust 6 | /// without any stumbling blocks 7 | /// - IMO the if let around fork() makes the code more readable 8 | /// than the C counterpart. also the parent/child distinction 9 | /// in the original C program should have been an if/else block 10 | /// 11 | /// $ echo tty | f07-read-execute 2>&1 12 | /// not a tty 13 | /// % % 14 | 15 | extern crate libc; 16 | #[macro_use(cstr, as_char)] 17 | extern crate apue; 18 | 19 | use libc::{STDIN_FILENO, c_char, printf, strlen, fgets, fdopen, fork, waitpid}; 20 | use apue::{array_to_string, LibcResult}; 21 | use apue::my_libc::execlp; 22 | 23 | const MAXLINE: usize = 100; 24 | 25 | fn main() { 26 | unsafe { 27 | let mut buf: [c_char; MAXLINE] = std::mem::uninitialized(); 28 | let stdin = fdopen(STDIN_FILENO, &('r' as c_char)); 29 | let mut status = 0; 30 | printf(cstr!("%% ")); // print prompt (printf requires %% to print %) 31 | while !fgets(as_char!(buf), MAXLINE as _, stdin).is_null() { 32 | let len = strlen(as_char!(buf)); 33 | if buf[len - 1] == '\n' as _ { 34 | buf[len - 1] = 0; 35 | } 36 | let pid = fork().check_not_negative().expect("fork error"); 37 | if pid == 0 { 38 | // child 39 | execlp(as_char!(buf), as_char!(buf), 0); 40 | panic!("could not execute {}", array_to_string(&buf)); 41 | } else { 42 | // parent 43 | waitpid(pid, &mut status, 0).check_not_negative().expect("waitpid error"); 44 | printf(cstr!("%% ")); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/e07-exec-opendir.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 8.7: We mentioned in Section 8.10 that POSIX.1 requires open directory streams to be 2 | /// closed across an exec. Verify this as follows: call opendir for the root directory, peek at 3 | /// your system’s implementation of the DIR structure, and print the close-on-exec flag. 4 | /// Then open the same directory for reading, and print the close-on-exec flag. 5 | /// 6 | /// From the doc: POSIX.1 specifically requires that open directory streams (recall the opendir 7 | /// function from Section 4.22) be closed across an exec. This is normally done by the opendir 8 | /// function calling fcntl to set the close-on-exec flag for the descriptor corresponding to 9 | /// the open directory stream. 10 | /// 11 | /// $ e07-exec-opendir 12 | /// close-on-exec is set 13 | /// close-on-exec is not set 14 | 15 | extern crate libc; 16 | #[macro_use(cstr)] 17 | extern crate apue; 18 | 19 | use libc::{opendir, fcntl, open, close, closedir, F_GETFD, FD_CLOEXEC, O_RDONLY}; 20 | use apue::{LibcResult, LibcPtrResult}; 21 | use apue::my_libc::{readdir, dirfd}; 22 | 23 | unsafe fn pr_flags(fd: i32) { 24 | let flags = fcntl(fd, F_GETFD); 25 | if flags & FD_CLOEXEC > 0 { 26 | println!("close-on-exec is set"); 27 | } else { 28 | println!("close-on-exec is not set"); 29 | } 30 | 31 | } 32 | 33 | fn main() { 34 | unsafe { 35 | let dp = opendir(cstr!("/")); 36 | let dfd = dirfd(dp); 37 | assert!(!dp.is_null(), "can't open root dir"); 38 | readdir(dp).check_not_null().ok(); // just read one entry and discard it 39 | pr_flags(dfd); 40 | let fd = 41 | open(cstr!("/"), O_RDONLY).check_not_negative().expect("cannot open root for reading"); 42 | pr_flags(fd); 43 | close(fd); 44 | closedir(dp); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f01-fork.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.1: Example of fork function 2 | /// 3 | /// $ f01-fork | sed 's/pid = [0-9]*, //' # sed is used to hide any pid 4 | /// a write to stdout 5 | /// before fork 6 | /// glob = 6, var = 88 7 | /// before fork 8 | /// glob = 7, var = 89 9 | 10 | extern crate libc; 11 | #[macro_use(cstr)] 12 | extern crate apue; 13 | 14 | use libc::{c_int, STDOUT_FILENO, write, fork, usleep, printf, getpid}; 15 | use apue::{err_sys, LibcResult}; 16 | 17 | 18 | // though shall not use static mutable.. 19 | // used here only to reflect the global var in the C source 20 | // which is an external variable in initialized data 21 | static mut GLOBVAR: i64 = 6; 22 | 23 | fn main() { 24 | unsafe { 25 | let buf = "a write to stdout\n"; 26 | // automatic variable on the stack 27 | let mut var: i8 = 88; 28 | if write(STDOUT_FILENO, buf.as_ptr() as _, buf.len() as _) != buf.len() as _ { 29 | err_sys("write error"); 30 | } 31 | // this is line buffered when running in shell (writing to stdout) 32 | // if stdout is going into a file (e.g. starting from intellij) it is fully buffered 33 | // and hence output is delayed after fork -> printed twice 34 | printf(cstr!("before fork\n")); 35 | if let Ok(pid) = fork().check_not_negative() { 36 | match pid { 37 | 0 => { 38 | GLOBVAR += 1; 39 | var += 1; 40 | } 41 | _ => { 42 | usleep(10); 43 | } 44 | } 45 | printf(cstr!("pid = %ld, glob = %d, var = %d\n"), 46 | getpid(), 47 | GLOBVAR, 48 | var as c_int); 49 | } else { 50 | err_sys("fork error"); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/bin/05-stdio/e02-fgets-4.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 5.2: 2 | /// 3 | /// Type in the program that copies a file using line-at-a-time I/O (fgets and fputs) 4 | /// from Figure 5.5, but use a MAXLINE of 4. What happens if you copy lines that exceed 5 | /// this length? Explain what is happening. 6 | /// 7 | /// $ echo hansaplasti > /tmp/e02.txt 8 | /// $ e02-fgets-4 /tmp/e02.txt /dev/stderr 2>/dev/null 9 | /// buffer = han 10 | /// buffer = sap 11 | /// buffer = las 12 | /// buffer = ti 13 | /// $ rm /tmp/e02.txt 14 | 15 | extern crate libc; 16 | #[macro_use(cstr)] 17 | extern crate apue; 18 | 19 | use libc::{fopen, fgets, fputs, printf}; 20 | 21 | const BUFLEN: usize = 4; 22 | 23 | fn main() { 24 | unsafe { 25 | let mut args = std::env::args(); 26 | if args.len() != 3 { 27 | println!("usage:\n{} /path/to/input /path/to/output", 28 | args.next().unwrap()); 29 | std::process::exit(1); 30 | } 31 | args.next(); // skip filename 32 | let f_in = args.next().unwrap(); 33 | let f_out = args.next().unwrap(); 34 | let fd_in = fopen(cstr!(f_in), cstr!("r")); 35 | let fd_out = fopen(cstr!(f_out), cstr!("w")); 36 | 37 | let buffer: [u8; BUFLEN] = std::mem::uninitialized(); 38 | while !fgets(buffer.as_ptr() as *mut i8, BUFLEN as i32, fd_in).is_null() { 39 | printf(cstr!("buffer = %s\n"), buffer.as_ptr()); 40 | fputs(buffer.as_ptr() as *mut i8, fd_out); 41 | } 42 | } 43 | } 44 | 45 | // # Solution: 46 | 47 | // What happens if you copy lines that exceed this length? 48 | // Explain what is happening. 49 | 50 | // Answer: the line is cut into chunks of 3 (3 bytes + null) 51 | // the last chunk of a line can be smaller, so e.g. just 1 byte + null 52 | // 53 | // e.g. 54 | // hansaplasti 55 | // - han 56 | // - sap 57 | // - las 58 | // - ti 59 | -------------------------------------------------------------------------------- /src/bin/11-threads/f10-mutex.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | /// Figure 11.10 Using a mutex to protect a data structure 3 | /// 4 | /// Of course Rust offers `Arc` and `Mutex` which would offer 5 | /// reference counting and locking out of the box. 6 | /// But the point of this Figure is to figure out how we would 7 | /// do that ourselves. 8 | /// 9 | /// On the other hand: instead of `malloc` and `free` Rust 10 | /// does malloc and free for us, so it would be pointless 11 | /// to do that ourselves. 12 | /// 13 | /// Let's hope that these functions are gonna be used at some 14 | /// point to check if they really work.. 15 | 16 | extern crate libc; 17 | 18 | use libc::pthread_mutex_t; 19 | use libc::{pthread_mutex_init, pthread_mutex_lock, pthread_mutex_unlock, pthread_mutex_destroy}; 20 | use std::mem::zeroed; 21 | use std::ptr::null; 22 | 23 | struct Foo { 24 | f_count: i64, 25 | f_lock: pthread_mutex_t, 26 | f_id: i64, 27 | } 28 | 29 | fn foo_alloc(id: i64) -> Option { 30 | unsafe { 31 | let mut fp = Foo { 32 | f_count: 1, 33 | f_lock: zeroed(), 34 | f_id: id, 35 | }; 36 | if pthread_mutex_init(&mut fp.f_lock, null()) != 0 { 37 | return None; 38 | } 39 | Some(fp); 40 | } 41 | None 42 | } 43 | 44 | fn foo_hold(fp: &mut Foo) { 45 | unsafe { 46 | pthread_mutex_lock(&mut fp.f_lock); 47 | fp.f_count += 1; 48 | pthread_mutex_unlock(&mut fp.f_lock); 49 | } 50 | } 51 | 52 | fn foo_rele(fp: &mut Foo) { 53 | unsafe { 54 | pthread_mutex_lock(&mut fp.f_lock); 55 | fp.f_count -= 1; 56 | if fp.f_count == 0 { 57 | // last reference 58 | pthread_mutex_unlock(&mut fp.f_lock); 59 | pthread_mutex_destroy(&mut fp.f_lock); 60 | } else { 61 | pthread_mutex_unlock(&mut fp.f_lock); 62 | } 63 | } 64 | } 65 | 66 | fn main() {} 67 | -------------------------------------------------------------------------------- /src/bin/05-stdio/f13-mkstemp.rs: -------------------------------------------------------------------------------- 1 | /// Code for Figure 5.13 (Demonstrate mkstemp function) 2 | /// 3 | /// implementation in rust was quite straight forward 4 | /// only caveat was that the pointer to the variable on the stack 5 | /// is not possible (or I just didn't find out how to do that and 6 | /// didn't dare to ask on stackoverflow) 7 | /// 8 | /// $ f13-mkstemp 2>/dev/null 9 | /// trying to create first temp file... 10 | /// file exists 11 | 12 | extern crate libc; 13 | #[macro_use(print_err)] 14 | extern crate apue; 15 | 16 | use std::ffi::{CString, CStr}; 17 | use std::io; 18 | use std::mem; 19 | 20 | fn make_temp(template: *mut libc::c_char) -> Result { 21 | unsafe { 22 | let fd = libc::mkstemp(template); 23 | if fd < 0 { 24 | return Err("can't create tmp dir".to_owned()); 25 | } 26 | print_err!("temp name = {:?}", CStr::from_ptr(template)); 27 | libc::close(fd); 28 | let mut stat: libc::stat = mem::uninitialized(); 29 | if libc::stat(template, &mut stat) < 0 { 30 | if io::Error::last_os_error().raw_os_error().unwrap() == libc::ENOENT { 31 | println!("file doesn’t exist"); 32 | } else { 33 | return Err("stat failed".to_owned()); 34 | } 35 | } else { 36 | println!("file exists"); 37 | libc::unlink(template); 38 | } 39 | Ok(CString::from_raw(template).into_string().unwrap()) 40 | } 41 | } 42 | 43 | fn main() { 44 | let good_template = CString::new("/tmp/dirXXXXXX").unwrap(); 45 | println!("trying to create first temp file..."); 46 | let res = make_temp(good_template.into_raw()); 47 | print_err!("{:?}", res); 48 | 49 | // the second part with the bad template I was just 50 | // unable to do in rust, seems that the type safety 51 | // was good enough that even after 30 minutes I couldn't 52 | // succeed 53 | } 54 | -------------------------------------------------------------------------------- /src/bin/10-signals/f12-sigset-impl.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.12 An implementation of sigaddset, sigdelset, and sigismember 2 | /// 3 | /// Works only in MacOS, as in other architectures sigset_t is a struct and the bit vector 4 | /// is an internal variable. 5 | /// 6 | /// This was a bit pointless really (why is a code fragment about bit 7 | /// masking in a book which teaches Unix?), only converted for completeness. 8 | /// 9 | /// mac only: 10 | /// $ f12-sigset-impl 11 | /// 0b10000 12 | /// 0b10000 13 | /// bit 5 is set 14 | /// 0b0 15 | 16 | extern crate apue; 17 | extern crate libc; 18 | extern crate errno; 19 | 20 | #[cfg(target_os = "macos")] 21 | mod sigset_impl { 22 | use libc::sigset_t; 23 | const NSIG: i8 = 32; // from /usr/include/signal.h 24 | 25 | pub fn sigemptyset() -> sigset_t { 26 | 0 as sigset_t 27 | } 28 | 29 | pub fn sigbad(signo: i8) -> bool { 30 | signo <= 0 || signo >= NSIG 31 | } 32 | 33 | pub fn sigaddset(set: &mut sigset_t, signo: i8) { 34 | assert!(!sigbad(signo)); 35 | *set |= 1 << (signo - 1); // turn bit on 36 | } 37 | 38 | pub fn sigdelset(set: &mut sigset_t, signo: i8) { 39 | assert!(!sigbad(signo)); 40 | *set &= !(1 << signo - 1); // turn bit off 41 | } 42 | 43 | pub fn sigismember(set: &sigset_t, signo: i8) -> bool { 44 | assert!(!sigbad(signo)); 45 | set & (1 << (signo - 1)) != 0 46 | } 47 | } 48 | 49 | #[cfg(target_os = "macos")] 50 | use sigset_impl::*; 51 | 52 | #[cfg(target_os = "macos")] 53 | fn main() { 54 | let mut sig = sigemptyset(); 55 | sigaddset(&mut sig, 5); 56 | println!("{:#b}", sig); 57 | sigdelset(&mut sig, 4); 58 | println!("{:#b}", sig); 59 | if sigismember(&sig, 5) { 60 | println!("bit 5 is set"); 61 | } 62 | sigdelset(&mut sig, 5); 63 | println!("{:#b}", sig); 64 | } 65 | 66 | #[cfg(not(target_os = "macos"))] 67 | fn main() { 68 | unimplemented!(); 69 | } 70 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f20-exec-interpreter.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.20 A program that execs an interpreter file 2 | /// 3 | /// Takeaway: the OpenOption module is quite nice! Took me a while to get the right arguments 4 | /// though 5 | /// 6 | /// $ f20-exec-interpreter | head -5 | sed -E 's/[^ ]+(f17|testinterp)//g' 7 | /// argv[0] = -echo-all 8 | /// argv[1] = foo 9 | /// argv[2] = 10 | /// argv[3] = myarg1 11 | /// argv[4] = MY ARG2 12 | 13 | extern crate libc; 14 | #[macro_use(cstr)] 15 | extern crate apue; 16 | extern crate errno; 17 | 18 | use std::io::prelude::*; 19 | use std::fs::OpenOptions; 20 | use std::os::unix::fs::OpenOptionsExt; 21 | 22 | use libc::{fork, waitpid, c_char}; 23 | use apue::LibcResult; 24 | use apue::my_libc::execl; 25 | 26 | fn main() { 27 | let curexe = std::env::current_exe().unwrap(); 28 | let testinterp = curexe.with_file_name("testinterp"); 29 | { 30 | let mut f = OpenOptions::new() 31 | .mode(0o777) 32 | .write(true) 33 | .create(true) 34 | .truncate(true) 35 | .open(&testinterp) 36 | .expect("cannot create testinterp"); 37 | f.write_all(format!("#!{} foo\n", 38 | curexe.with_file_name("f17-echo-all").to_str().unwrap()) 39 | .as_bytes()) 40 | .unwrap(); 41 | } 42 | 43 | unsafe { 44 | let pid = fork().check_not_negative().expect("cannot fork"); 45 | match pid { 46 | 0 => { 47 | execl(cstr!(testinterp.to_str().unwrap()), 48 | cstr!("testinterp"), 49 | cstr!("myarg1"), 50 | cstr!("MY ARG2"), 51 | 0 as *const c_char) 52 | .check_not_negative() 53 | .expect(&format!("execl error: {}", errno::errno())); 54 | } 55 | _ => { 56 | waitpid(pid, std::ptr::null_mut(), 0).check_not_negative().expect("waitpid error"); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/f03-file-type.rs: -------------------------------------------------------------------------------- 1 | /// Figure 4.3: Print type of file for each command-line argument 2 | /// 3 | /// Takeaways: 4 | /// 5 | /// - S_ISREG et al are C macros. S_ISREG is defined as `(((m) & S_IFMT) == S_IFREG)` 6 | /// since S_IFMT is 1111000000000000 we can just and this mask to the mode field 7 | /// and match the result directly (avoiding else ifs) 8 | /// - while let is awesome 9 | /// 10 | /// $ ln -s /var/tmp /tmp/aaa 11 | /// $ f03-file-type /etc/passwd /tmp/ /dev/null /tmp/aaa 12 | /// mode of "/etc/passwd": regular 13 | /// mode of "/tmp/": directory 14 | /// mode of "/dev/null": character special 15 | /// mode of "/tmp/aaa": symbolic link 16 | /// $ rm /tmp/aaa 17 | 18 | extern crate libc; 19 | extern crate apue; 20 | extern crate errno; 21 | 22 | use libc::{stat, lstat, mode_t, S_IFMT, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFREG, S_IFLNK, 23 | S_IFSOCK}; 24 | use apue::LibcResult; 25 | use std::ffi::CString; 26 | 27 | fn main() { 28 | let mut args = std::env::args(); 29 | if args.len() == 1 { 30 | println!("usage:\n{} filenames\n\ne.g. /etc/passwd /tmp", 31 | args.next().unwrap()); 32 | std::process::exit(1); 33 | } 34 | args.next(); // skip filename 35 | while let Some(filename) = args.next() { 36 | unsafe { 37 | let mut buf: stat = std::mem::uninitialized(); 38 | let s = CString::new(filename).unwrap(); 39 | lstat(s.as_ptr(), &mut buf).check_not_negative().expect("lstat error"); 40 | let a: mode_t = buf.st_mode & S_IFMT; 41 | let t = match a { 42 | S_IFREG => "regular", 43 | S_IFBLK => "block special", 44 | S_IFCHR => "character special", 45 | S_IFDIR => "directory", 46 | S_IFIFO => "fifo", 47 | S_IFLNK => "symbolic link", 48 | S_IFSOCK => "socket", 49 | _ => "** unnown mode **", 50 | }; 51 | println!("mode of {:?}: {}", s, t); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f16-exec.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.16 Example of exec functions 2 | /// 3 | /// This probably went a bit overboard with cstr! and check_not_negative :) still, 4 | /// worked pretty out of the box (except: first I accidentally called f16-exec and then wondered 5 | /// why fork suddenly dies with "Resource temporarily unavailable"..) 6 | /// 7 | /// $ f16-exec | cat | head -5 8 | /// argv[0] = echoall 9 | /// argv[1] = myarg1 10 | /// argv[2] = MY ARG2 11 | /// USER=unkown 12 | /// PATH=/tmp 13 | 14 | extern crate libc; 15 | #[macro_use(cstr)] 16 | extern crate apue; 17 | extern crate errno; 18 | 19 | use libc::{fork, waitpid, c_char}; 20 | use apue::LibcResult; 21 | use apue::my_libc::{execle, execlp}; 22 | 23 | fn main() { 24 | unsafe { 25 | let mut curpath = std::env::current_exe().unwrap(); 26 | curpath.pop(); 27 | curpath.push("f17-echo-all"); 28 | 29 | let pid = fork().check_not_negative().expect("fork error"); 30 | match pid { 31 | 0 => { 32 | execle(cstr!(curpath.to_str().unwrap()), 33 | cstr!("echoall"), 34 | cstr!("myarg1"), 35 | cstr!("MY ARG2"), 36 | 0 as *const c_char, 37 | [cstr!("USER=unkown"), cstr!("PATH=/tmp"), 0 as *const c_char].as_ptr()) 38 | .check_not_negative() 39 | .expect(&format!("execle error: {}", errno::errno())); 40 | } 41 | _ => { 42 | waitpid(pid, std::ptr::null_mut(), 0).check_not_negative().expect("wait error"); 43 | } 44 | } 45 | 46 | let pid = fork().check_not_negative().expect("fork error"); 47 | if pid == 0 { 48 | execlp(cstr!("f17-echo-all"), 49 | cstr!("echoall"), 50 | cstr!("only 1 arg"), 51 | 0 as *const c_char) 52 | .check_not_negative() 53 | .expect(&format!("execlp error: {}", errno::errno())); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/bin/01-overview/f10-read-execute2.rs: -------------------------------------------------------------------------------- 1 | /// Figure 1.10 Read commands from standard input and execute them 2 | /// source from Figure 1.7 and added the signal handling 3 | /// 4 | /// $ echo tty | f10-read-execute2 2>&1 5 | /// not a tty 6 | /// % % 7 | 8 | 9 | extern crate libc; 10 | #[macro_use(cstr, as_char)] 11 | extern crate apue; 12 | 13 | use libc::{STDIN_FILENO, SIGINT, SIG_ERR, c_char, c_int, printf, strlen, fgets, fdopen, fork, 14 | waitpid, signal}; 15 | use apue::{array_to_string, LibcResult}; 16 | use apue::my_libc::execlp; 17 | 18 | extern "C" fn sig_int(_: c_int) { 19 | unsafe { 20 | printf(cstr!("interrupted..\n")); 21 | printf(cstr!("%% ")); 22 | } 23 | } 24 | 25 | const MAXLINE: usize = 100; 26 | 27 | fn main() { 28 | unsafe { 29 | let mut buf: [c_char; MAXLINE] = std::mem::uninitialized(); 30 | let stdin = fdopen(STDIN_FILENO, &('r' as c_char)); 31 | let mut status = 0; 32 | let s = sig_int; 33 | if signal(SIGINT, s as usize) == SIG_ERR { 34 | panic!("signal error"); 35 | } 36 | printf(cstr!("%% ")); // print prompt (printf requires %% to print %) 37 | while !fgets(as_char!(buf), MAXLINE as _, stdin).is_null() { 38 | let len = strlen(as_char!(buf)); 39 | if buf[len - 1] == '\n' as _ { 40 | buf[len - 1] = 0; 41 | } 42 | if let Ok(pid) = fork().check_not_negative() { 43 | if pid == 0 { 44 | // child 45 | execlp(as_char!(buf), as_char!(buf), 0); 46 | panic!("could not execute {}", array_to_string(&buf)); 47 | } else { 48 | // parent 49 | if waitpid(pid, &mut status, 0).check_not_negative().is_ok() { 50 | printf(cstr!("%% ")); 51 | } else { 52 | panic!("waitpid error"); 53 | } 54 | } 55 | } else { 56 | panic!("fork error"); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/bin/10-signals/f15-sigpending.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.15 Example of signal sets and sigprocmask 2 | /// 3 | /// Finding: on OSX signals seem to be not queued as well. Running f15-sigpending and then 4 | /// immediately firing ^\ results in only one echo of "caught SIGQUIT" 5 | /// 6 | /// test.py doesn't handle the following lines correctly (yet), -> only in // and not in ///: 7 | // $ f15-sigpending & 8 | // $ pkill -SIGQUIT f15-sigpending && sleep 2 && pkill -SIGQUIT f15-sigpending 9 | // SIGQUIT pending 10 | // caught SIGQUIT 11 | // SIGQUIT unblocked 12 | 13 | extern crate libc; 14 | extern crate apue; 15 | 16 | use libc::{c_int, SIG_SETMASK, SIG_BLOCK, SIGQUIT, SIG_ERR, SIG_DFL}; 17 | use libc::{sigemptyset, sigaddset, sigismember, sleep}; 18 | use apue::my_libc::{sigprocmask, sigpending}; 19 | use apue::{LibcResult, signal}; 20 | 21 | fn sig_quit(_: c_int) { 22 | println!("caught SIGQUIT"); 23 | if unsafe { libc::signal(SIGQUIT, SIG_DFL) } == SIG_ERR { 24 | panic!("can't reset SIGQUIT"); 25 | } 26 | } 27 | 28 | fn main() { 29 | unsafe { 30 | if signal(SIGQUIT, sig_quit) == SIG_ERR { 31 | panic!("can't catch SIGQUIT"); 32 | } 33 | // Block SIGQUIT and save current signal mask. 34 | let (mut newmask, mut oldmask, mut pendmask) = std::mem::uninitialized(); 35 | sigemptyset(&mut newmask); 36 | sigaddset(&mut newmask, SIGQUIT); 37 | sigprocmask(SIG_BLOCK, &newmask, &mut oldmask) 38 | .check_not_negative() 39 | .expect("SIG_BLOCK error"); 40 | sleep(2); // time to send SIGQUIT -> will remain pending 41 | sigpending(&mut pendmask).check_not_negative().expect("sigpending error"); 42 | if sigismember(&pendmask, SIGQUIT) == 1 { 43 | println!("SIGQUIT pending"); 44 | } 45 | 46 | // restore signal mask which unblocks SIGQUIT 47 | sigprocmask(SIG_SETMASK, &oldmask, std::ptr::null_mut()) 48 | .check_not_negative() 49 | .expect("SIG_SETMASK error"); 50 | println!("SIGQUIT unblocked"); 51 | sleep(2); // <-- SIGQUIT will hit here with core dump 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/f16-unlink.rs: -------------------------------------------------------------------------------- 1 | /// Figure 4.16 Open a file and then unlink it 2 | /// 3 | /// In the example in the book the program waits for 15 seconds 4 | /// omitted that part as it slows down the test.py tests 5 | /// 6 | /// $ touch /tmp/f16-unlink.tmp 7 | /// $ f16-unlink 8 | /// 9 | /// mac only: 10 | /// $ ls /tmp/f16-unlink.tmp 2>&1 11 | /// ls: /tmp/f16-unlink.tmp: No such file or directory 12 | /// ERROR: return code 1 13 | /// 14 | /// linux only: 15 | /// $ ls /tmp/f16-unlink.tmp 2>&1 16 | /// ls: cannot access '/tmp/f16-unlink.tmp': No such file or directory 17 | /// ERROR: return code 2 18 | 19 | // proof that this works (only in // so it is not executed by test.py) 20 | // 21 | // $ dd if=/dev/zero of=/tmp/f16-unlink.tmp bs=999999 count=9999 2>/dev/null 22 | // $ df -h /tmp/ 23 | // Filesystem Size Used Avail Capacity iused ifree %iused Mounted on 24 | // /dev/disk1 931Gi 398Gi 533Gi 43% 3467205 4291500074 0% / 25 | // 26 | // $ f16-unlink & 27 | // [1] 92883 28 | // 29 | // $ ls /tmp/f16-unlink.tmp 30 | // ls: /tmp/f16-unlink.tmp: No such file or directory 31 | // 32 | // $ df -kh /tmp/ 33 | // Filesystem Size Used Avail Capacity iused ifree %iused Mounted on 34 | // /dev/disk1 931Gi 398Gi 533Gi 43% 3467369 4291499910 0% / 35 | // 36 | // # after 15 seconds.. 37 | // $ file unlinked 38 | // [1]+ Done f16-unlink 39 | // 40 | // $ df -h /tmp/ 41 | // Filesystem Size Used Avail Capacity iused ifree %iused Mounted on 42 | // /dev/disk1 931Gi 388Gi 542Gi 42% 3467369 4291499910 0% / 43 | 44 | extern crate libc; 45 | #[macro_use(cstr)] 46 | extern crate apue; 47 | 48 | use libc::{open, sleep, unlink, c_int}; 49 | use apue::LibcResult; 50 | 51 | const O_RDWR: c_int = 2; // taken from /usr/include/sys/fcntl.h 52 | 53 | fn main() { 54 | unsafe { 55 | open(cstr!("/tmp/f16-unlink.tmp"), O_RDWR).check_not_negative().expect("open error"); 56 | unlink(cstr!("/tmp/f16-unlink.tmp")).check_not_negative().expect("unlink error"); 57 | sleep(0);// change this to 15 to make the test as explained in the book 58 | println!("file unlinked"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f31-times.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.31 Time and execute all command-line arguments 2 | /// 3 | /// Takeaway: times() returns clock_t which is unsigned for macos. 4 | /// -> cannot check for -1 5 | /// 6 | /// $ f31-times "echo hans" 2>&1 | sed 's/0.01/0/g' 7 | /// command: echo hans 8 | /// hans 9 | /// real: 0 10 | /// user: 0 11 | /// sys: 0 12 | /// child user: 0 13 | /// child sys: 0 14 | /// normal termination, exit status = 0 15 | 16 | extern crate libc; 17 | #[macro_use(cstr)] 18 | extern crate apue; 19 | 20 | use libc::{setbuf, sysconf, system, clock_t, _SC_CLK_TCK}; 21 | use apue::{pr_exit, LibcResult}; 22 | use apue::my_libc::{stdout, times, tms}; 23 | use std::mem::uninitialized; 24 | 25 | unsafe fn do_cmd(cmd: &str) { 26 | println!("command: {}", cmd); 27 | let mut tmsstart: tms = uninitialized(); 28 | let mut tmsend: tms = uninitialized(); 29 | // clock_t (return type of times) is unsigned on macos (and probably 30 | // bsd in general) -> don't check for -1 31 | let start = times(&mut tmsstart); 32 | let status = system(cstr!(cmd)).check_not_negative().expect("system() error"); 33 | let end = times(&mut tmsend); 34 | pr_times(end - start, &tmsstart, &tmsend); 35 | pr_exit(status); 36 | } 37 | 38 | unsafe fn pr_times(real: clock_t, tmsstart: &tms, tmsend: &tms) { 39 | let clktick: f64 = sysconf(_SC_CLK_TCK).check_not_negative().expect("sysconf error") as f64; 40 | println!(" real: {}", real as f64 / clktick); 41 | println!(" user: {}", 42 | (tmsend.tms_utime - tmsstart.tms_utime) as f64 / clktick); 43 | println!(" sys: {}", 44 | (tmsend.tms_stime - tmsstart.tms_stime) as f64 / clktick); 45 | println!(" child user: {}", 46 | (tmsend.tms_cutime - tmsstart.tms_cutime) as f64 / clktick); 47 | println!(" child sys: {}", 48 | (tmsend.tms_cstime - tmsstart.tms_cstime) as f64 / clktick); 49 | } 50 | 51 | fn main() { 52 | unsafe { setbuf(stdout, std::ptr::null_mut()) }; 53 | let mut args = std::env::args(); 54 | args.next(); // skip exe name 55 | while let Some(arg) = args.next() { 56 | unsafe { do_cmd(&arg) }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/bin/11-threads/f13-timedlock.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | /// Figure 11.13 Using pthread_mutex_timedlock 4 | /// 5 | /// After finding out that OSX does not support pthread_mutex_timedlock 6 | /// (it would have helped if this info was printed in the book *before* the code example..) 7 | /// the code was quite straightforward, *except* the code for `strerror` which needed to 8 | /// avoid Rusts array bound checking. 9 | 10 | extern crate libc; 11 | #[macro_use(as_char, cstr)] 12 | extern crate apue; 13 | 14 | #[cfg(target_os = "linux")] 15 | mod timedlock { 16 | 17 | use std::mem::uninitialized; 18 | use libc::{timespec, c_char, PTHREAD_MUTEX_INITIALIZER, CLOCK_REALTIME}; 19 | use libc::{localtime, pthread_mutex_lock, pthread_mutex_timedlock, clock_gettime}; 20 | 21 | use apue::{strerror, array_to_string}; 22 | use apue::my_libc::strftime; 23 | 24 | const BUFLEN: usize = 64; 25 | 26 | unsafe fn print_time(s: &str, tout: ×pec) { 27 | let tmp = localtime(&tout.tv_sec); 28 | let buf: [c_char; BUFLEN] = [0; BUFLEN]; 29 | strftime(as_char!(buf), BUFLEN, cstr!("%r"), tmp); 30 | println!("{} {}", s, array_to_string(&buf)); 31 | } 32 | 33 | pub unsafe fn main() { 34 | let mut lock = PTHREAD_MUTEX_INITIALIZER; 35 | pthread_mutex_lock(&mut lock); 36 | println!("mutex is locked"); 37 | let mut tout: timespec = uninitialized(); 38 | clock_gettime(CLOCK_REALTIME, &mut tout); 39 | print_time("current time is", &tout); 40 | tout.tv_sec += 1; // 10 seconds from now on 41 | // caution: this could lead to deadlock 42 | let err = pthread_mutex_timedlock(&mut lock, &tout); 43 | clock_gettime(CLOCK_REALTIME, &mut tout); 44 | print_time("the time is now", &tout); 45 | if err == 0 { 46 | println!("mutex locked again!"); 47 | } else { 48 | println!("can't lock mutex again: {}", strerror(err)); 49 | } 50 | } 51 | } 52 | 53 | #[cfg(target_os = "linux")] 54 | fn main() { 55 | unsafe { 56 | timedlock::main(); 57 | } 58 | } 59 | 60 | #[cfg(target_os = "macos")] 61 | fn main() { 62 | unimplemented!(); 63 | } 64 | -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/e16-dir-tree-depth.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 4.16: Dir tree depth 2 | /// 3 | /// mac only: 4 | /// $ e16-dir-tree-depth 2>/dev/null 5 | /// PATH_MAX=1024 6 | /// path length: 1022, max path: 1024 7 | /// ERROR: return code 101 8 | /// 9 | /// linux only: 10 | /// $ e16-dir-tree-depth 2>/dev/null 11 | /// PATH_MAX=4096 12 | /// path length: 4094, max path: 4096 13 | /// ERROR: return code 101 14 | 15 | extern crate libc; 16 | #[macro_use(cstr)] 17 | extern crate apue; 18 | 19 | use std::ffi::{CStr, CString}; 20 | use std::io; 21 | use std::str; 22 | use libc::{_PC_PATH_MAX, chdir, mkdir}; 23 | 24 | static BUF_BYTES: usize = 4096; 25 | #[cfg(target_os = "macos")] 26 | const GUESS_PATH_LENGH: usize = 1024; 27 | #[cfg(target_os = "linux")] 28 | const GUESS_PATH_LENGH: usize = 4096; 29 | 30 | fn main() { 31 | unsafe { 32 | let path_max = libc::pathconf(cstr!("."), _PC_PATH_MAX); 33 | let initial = 34 | CString::new("/tmp/someinitialpathwhichisquitelongalreadysowedontneedtoloopforsolong") 35 | .unwrap(); 36 | mkdir(initial.as_ptr(), libc::S_IRWXU); 37 | chdir(initial.as_ptr()); 38 | println!("PATH_MAX={}", path_max); 39 | loop { 40 | libc::mkdir(cstr!("a"), libc::S_IRWXU); 41 | libc::chdir(cstr!("a")); 42 | let buf = { 43 | let mut buf = Vec::with_capacity(BUF_BYTES); 44 | let ptr = buf.as_mut_ptr() as *mut libc::c_char; 45 | if libc::getcwd(ptr, buf.capacity()).is_null() { 46 | panic!(io::Error::last_os_error()); 47 | } 48 | let len = CStr::from_ptr(ptr).to_bytes().len(); 49 | buf.set_len(len); 50 | CString::new(buf) 51 | }; 52 | let s = buf.expect("Not a C string").into_string().expect("Not UTF-8"); 53 | if s.len() > GUESS_PATH_LENGH - 4 { 54 | println!("path length: {}, max path: {}", s.len(), path_max); 55 | } 56 | } 57 | } 58 | } 59 | 60 | // # Solutions: 61 | 62 | // - With the getcwd in place the max directory size is 1024 (MAX_PATH) 63 | // even when provided with a 4096 byte buffer. 64 | // - Without getcwd there seems to be no limit 65 | -------------------------------------------------------------------------------- /src/bin/10-signals/e06-sync-parent-child.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 10.6: Write the following program to test the parent–child 2 | /// synchronization functions in Figure 10.24. The process creates a 3 | /// file and writes the integer 0 to the file. The process then calls fork, 4 | /// and the parent and child alternate incrementing the counter in the file. 5 | /// Each time the counter is incremented, print which process (parent or 6 | /// child) is doing the increment. 7 | /// 8 | /// Learnings: I had some difficulties to get the while loop with AtomicBool 9 | /// right. I initially tried with fetch_xor until I learned that it *always* 10 | /// returns the previous value. 11 | /// 12 | /// $ rm -f /tmp/e06-sync.txt && e06-sync-parent-child && cat /tmp/e06-sync.txt 13 | /// 200 14 | 15 | extern crate apue; 16 | extern crate libc; 17 | 18 | use std::io::prelude::*; 19 | use libc::{fork, getppid}; 20 | use std::fs::{File, OpenOptions}; 21 | use std::io::{Error, SeekFrom}; 22 | use apue::LibcResult; 23 | use apue::sync_parent_child::*; 24 | 25 | fn increase_file_counter() -> Result<(), Error> { 26 | let mut f = OpenOptions::new().read(true) 27 | .write(true) 28 | .open("/tmp/e06-sync.txt")?; 29 | let mut s = String::new(); 30 | f.read_to_string(&mut s)?; 31 | f.seek(SeekFrom::Start(0))?; 32 | let counter = s.parse::().unwrap(); 33 | write!(f, "{}", counter + 1)?; 34 | Ok(()) 35 | } 36 | 37 | fn main() { 38 | unsafe { 39 | { 40 | let mut f = File::create("/tmp/e06-sync.txt").unwrap(); 41 | f.write_all(b"0").unwrap(); 42 | } 43 | let oldmask = tell_wait(); 44 | let pid = fork().check_not_negative().expect("fork error"); 45 | if pid == 0 { 46 | // child 47 | let ppid = getppid(); 48 | for _ in 1..101 { 49 | // child goes first 50 | increase_file_counter().expect("file read/write error"); 51 | tell_parent(ppid); 52 | wait_parent(oldmask); 53 | } 54 | } else { 55 | // parent 56 | for _ in 1..101 { 57 | wait_child(oldmask); 58 | increase_file_counter().expect("file read/write error"); 59 | tell_child(pid); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/bin/12-thread-control/f12-getenv-reentrant.rs: -------------------------------------------------------------------------------- 1 | /// Figure 12.12: A reentrant (thread-safe) version of getenv 2 | /// 3 | /// Findings: 4 | /// - why would the caller need to do his own buffer? Even in C you could 5 | /// just malloc a buffer for the caller, no? Of course the caller would 6 | /// need to free it up, but isn't that normal in C that you need take 7 | /// care of buffers that "others" did malloc for you? 8 | /// 9 | /// $ f12-getenv-reentrant | sed 's/=.*//g' 10 | /// PATH 11 | 12 | extern crate libc; 13 | extern crate apue; 14 | 15 | use std::ffi::CStr; 16 | 17 | use libc::{pthread_mutex_t, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_RECURSIVE, c_char}; 18 | use libc::{pthread_mutexattr_init, pthread_mutexattr_settype, pthread_mutex_init, pthread_mutexattr_destroy, pthread_mutex_lock, pthread_mutex_unlock}; 19 | 20 | use apue::my_libc::{pthread_once, pthread_once_t, PTHREAD_ONCE_INIT}; 21 | 22 | static mut ENV_MUTEX:pthread_mutex_t = PTHREAD_MUTEX_INITIALIZER; 23 | static mut INIT_DONE:pthread_once_t = PTHREAD_ONCE_INIT; 24 | 25 | extern "C" { 26 | pub static environ: *const *const c_char; 27 | } 28 | 29 | extern "C" fn thread_init() { 30 | unsafe { 31 | let mut attr = std::mem::uninitialized(); 32 | pthread_mutexattr_init(&mut attr); 33 | pthread_mutexattr_settype(&mut attr, PTHREAD_MUTEX_RECURSIVE); 34 | pthread_mutex_init(&mut ENV_MUTEX, &attr); 35 | pthread_mutexattr_destroy(&mut attr); 36 | } 37 | } 38 | 39 | fn getenv_r(name:&str) -> Option<&str> { 40 | unsafe { 41 | pthread_once(&mut INIT_DONE, Some(thread_init)); 42 | pthread_mutex_lock(&mut ENV_MUTEX); 43 | let mut cmp = name.to_owned(); 44 | cmp.push_str("="); 45 | let mut i = 0isize; 46 | loop { 47 | if *environ.offset(i) == std::ptr::null() { 48 | break 49 | } 50 | let s = CStr::from_ptr(*(environ.offset(i as _))).to_str().expect("no valid string"); 51 | if s.starts_with(&cmp) { 52 | pthread_mutex_unlock(&mut ENV_MUTEX); 53 | return Some(s); 54 | } 55 | i += 1; 56 | } 57 | pthread_mutex_unlock(&mut ENV_MUTEX); 58 | None 59 | } 60 | } 61 | 62 | fn main() { 63 | println!("{}", getenv_r("PATH").unwrap()); 64 | } -------------------------------------------------------------------------------- /src/bin/11-threads/f04-bogus-pthread-exit.rs: -------------------------------------------------------------------------------- 1 | /// Figure 11.4 Incorrect use of pthread_exit argument 2 | /// 3 | /// Findings: 4 | /// - behaves the same way as described in the book: on OSX it's 5 | /// segfaulting and on Linux the memory is already overwritten and thus 6 | /// printing bogus numbers 7 | /// - The pointer handling in main was quite tricky to do in Rust, 8 | /// I opened http://stackoverflow.com/questions/42235980 but 9 | /// in the end didn't end up with Box::into_raw/from_raw as it behaved 10 | /// differently than the solution below 11 | /// - When working with a static mut FOO_GLOBAL; when saving Foo to this 12 | /// static mut in thr_fn1, then it works of course, since this variable 13 | /// is not dropped at the end of thr_fn1 14 | 15 | extern crate libc; 16 | extern crate apue; 17 | use libc::c_void; 18 | use libc::{pthread_self, pthread_join, usleep}; 19 | use apue::my_libc::{pthread_exit, pthread_create}; 20 | use apue::LibcResult; 21 | use std::ptr::null_mut; 22 | 23 | #[derive(Debug)] 24 | struct Foo { 25 | a: i32, 26 | b: i32, 27 | c: i32, 28 | d: i32, 29 | } 30 | 31 | extern "C" fn thr_fn1(_: *mut c_void) -> *mut c_void { 32 | let foo = Foo { 33 | a: 1, 34 | b: 2, 35 | c: 3, 36 | d: 4, 37 | }; 38 | printfoo("thread 1:", &foo); 39 | unsafe { pthread_exit(&foo as *const Foo as _) }; 40 | 0 as _ 41 | } 42 | 43 | extern "C" fn thr_fn2(_: *mut c_void) -> *mut c_void { 44 | unsafe { 45 | println!("thread 2: ID is {}", pthread_self()); 46 | pthread_exit(0 as _); 47 | } 48 | 0 as _ 49 | } 50 | 51 | 52 | fn printfoo(s: &str, fp: *const Foo) { 53 | unsafe { 54 | let raw = fp as *const i32; 55 | println!("{} structure at {:?}: {:?}", s, raw, &*fp); 56 | } 57 | 58 | } 59 | 60 | fn main() { 61 | unsafe { 62 | let mut fp: Box = std::mem::uninitialized(); 63 | let (mut tid1, mut tid2) = std::mem::zeroed(); 64 | pthread_create(&mut tid1, null_mut(), thr_fn1, null_mut()).check_zero().expect("can't create thread 1"); 65 | pthread_join(tid1, &mut fp as *mut _ as *mut _).check_zero().expect("can't join thread 1"); 66 | usleep(100); 67 | println!("parent starting second thread"); 68 | pthread_create(&mut tid2, null_mut(), thr_fn2, null_mut()).check_zero().expect("can't create thread 2"); 69 | usleep(100); 70 | printfoo("parent:", fp.as_ref()); 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/bin/06-system-data-files/e01-shadow-pw.rs: -------------------------------------------------------------------------------- 1 | /// Excercise 6.1: If the system uses a shadow file and we need to 2 | /// obtain the encrypted password, how do we do so? 3 | /// 4 | /// Takeaways 5 | /// 6 | /// - bindgen is great, spend time to get it working instead of trying to come 7 | /// up with the bindings yourself 8 | /// - user needs to be root, checking for root with getuid 9 | /// - first tried with iterating via getspent, then saw getspnam which is a lot easier of course 10 | extern crate libc; 11 | extern crate apue; 12 | 13 | #[cfg(target_os = "linux")] 14 | mod shadow { 15 | 16 | use std::ffi::{CStr, CString}; 17 | use apue::my_libc::{setspent, getspnam, endspent, getspent}; 18 | 19 | #[derive(Debug)] 20 | pub struct PasswdOwned { 21 | name: String, 22 | pw: String, 23 | } 24 | 25 | pub unsafe fn getpwnam_iter(name: &str) -> Option { 26 | setspent(); 27 | while let Some(pw) = getspent().as_ref() { 28 | let pw_name = CStr::from_ptr(pw.sp_namp).to_string_lossy().into_owned(); 29 | if pw_name == name { 30 | endspent(); 31 | let pw = PasswdOwned { 32 | name: pw_name, 33 | pw: CStr::from_ptr(pw.sp_pwdp).to_string_lossy().into_owned(), 34 | }; 35 | return Some(pw); 36 | } 37 | } 38 | endspent(); 39 | None 40 | } 41 | 42 | pub unsafe fn getpwnam(name: &str) -> Option { 43 | match getspnam(CString::new(name).unwrap().as_ptr()).as_ref() { 44 | Some(pw) => { 45 | Some(PasswdOwned { 46 | name: CStr::from_ptr(pw.sp_namp).to_string_lossy().into_owned(), 47 | pw: CStr::from_ptr(pw.sp_pwdp).to_string_lossy().into_owned(), 48 | }) 49 | } 50 | None => None, 51 | } 52 | } 53 | } 54 | 55 | #[cfg(target_os = "linux")] 56 | fn main() { 57 | unsafe { 58 | if libc::getuid() != 0 { 59 | panic!("you need to start as root, e.g. via sudo"); 60 | } 61 | println!("{:?}", 62 | shadow::getpwnam_iter("philipp").expect("no user found with that name!")); 63 | println!("{:?}", 64 | shadow::getpwnam("philipp").expect("no user found with that name!")); 65 | } 66 | } 67 | 68 | #[cfg(target_os = "macos")] 69 | fn main() { 70 | unimplemented!(); 71 | } 72 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f06-exit-status.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.5: Print a description of the exit status, and 2 | /// Figure 8.6: Demonstrate various exit statuses 3 | /// 4 | /// Takeaway: Rust intercepts the division by zero with its panic handler. 5 | /// To make the exception "fall through" we need to replace rusts panic handler 6 | /// by our own (at least that's what the guys on IRC suggested). Sadly in order to 7 | /// go sure that it was *really* a division by zero we need to do a string comparison.. 8 | /// 9 | /// mac only: 10 | /// $ f06-exit-status 11 | /// normal termination, exit status = 7 12 | /// abnormal termination, signal number = 6 13 | /// abnormal termination, signal number = 8 14 | /// 15 | /// linux only: 16 | /// $ f06-exit-status 17 | /// normal termination, exit status = 7 18 | /// abnormal termination, signal number = 6 (core file generated) 19 | /// abnormal termination, signal number = 8 (core file generated) 20 | 21 | extern crate libc; 22 | extern crate apue; 23 | 24 | use libc::{c_int, SIGFPE}; 25 | use libc::{exit, wait, fork, abort, raise}; 26 | use apue::{LibcResult, err_sys, pr_exit}; 27 | use std::panic; 28 | 29 | 30 | fn handle_panic(e: &panic::PanicInfo) { 31 | match e.payload().downcast_ref::() { 32 | Some(as_string) if as_string == "attempt to divide by zero" => { 33 | unsafe { raise(SIGFPE) }; 34 | } 35 | _ => { 36 | panic!("unknown error occurred"); 37 | } 38 | } 39 | } 40 | 41 | fn main() { 42 | panic::set_hook(Box::new(handle_panic)); 43 | unsafe { 44 | let mut status: c_int = 0; 45 | let mut pid = fork().check_not_negative().expect("fork error"); 46 | if pid == 0 { 47 | // child 48 | exit(7); 49 | } 50 | if wait(&mut status) != pid { 51 | // wait for child 52 | err_sys("wait error"); 53 | } 54 | pr_exit(status); 55 | 56 | pid = fork().check_not_negative().expect("fork error"); 57 | if pid == 0 { 58 | // child 59 | abort(); // generate SIGABRT 60 | } 61 | if wait(&mut status) != pid { 62 | // wait for child 63 | err_sys("wait error"); 64 | } 65 | pr_exit(status); 66 | 67 | pid = fork().check_not_negative().expect("fork error"); 68 | if pid == 0 { 69 | // child 70 | let mut z = 1; 71 | z -= 1; 72 | status /= z; // divide by 0 generates SIGFPE 73 | } 74 | 75 | if wait(&mut status) != pid { 76 | err_sys("wait error"); 77 | } 78 | pr_exit(status); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/bin/09-process-relations/f12-orphan-process-grp.rs: -------------------------------------------------------------------------------- 1 | /// Figure 9.12: Creating an orphaned process group 2 | /// 3 | /// This is quite a fun program actually: The child commits suicide, 4 | /// the parent dies as well in a corratoral damage way, but the child, 5 | /// wearing a bullet proof vest survives. 6 | /// 7 | /// Takeaway: First I left away fflush (I thought println does that) with 8 | /// the effect that it worked on Linux, but on OSX only the first 9 | /// two lines were printed, the remaining three were only printed when this 10 | /// program was run in parallel or when output is redirected to a file 11 | /// (because it is not buffered). 12 | /// 13 | /// The wiring of test.py does not set up a proper tty (yet), 14 | /// therefore only // as commenter of the following example: 15 | /// 16 | // $ f12-orphan-process-grp > /tmp/f12-orph.txt 17 | // $ cat /tmp/f12-orph.txt | sed -E 's/[0-9]{2,}//g' 18 | // parent: pid = , ppid = , pgrp = , tpgrp = 19 | // child: pid = , ppid = , pgrp = , tpgrp = 20 | // child: pid = , ppid = 1, pgrp = , tpgrp = 21 | // read error Input/output error on controlling TTY 22 | // SIGHUP received, pid= 23 | // $ rm /tmp/f12-orph.txt 24 | 25 | extern crate libc; 26 | #[macro_use(cstr)] 27 | extern crate apue; 28 | extern crate errno; 29 | 30 | use libc::{STDIN_FILENO, SIG_ERR, SIGHUP, SIGTSTP, c_int, c_void}; 31 | use libc::{printf, getpid, getppid, getpgrp, tcgetpgrp, fflush, fork, sleep, signal, kill, read}; 32 | use apue::LibcResult; 33 | use apue::my_libc::stdout; 34 | 35 | extern "C" fn sig_hup(_: c_int) { 36 | unsafe { 37 | printf(cstr!("SIGHUP received, pid=%ld\n"), getpid()); 38 | fflush(stdout); 39 | } 40 | } 41 | 42 | unsafe fn pr_ids(name: &str) { 43 | println!("{}: pid = {}, ppid = {}, pgrp = {}, tpgrp = {}", 44 | name, 45 | getpid(), 46 | getppid(), 47 | getpgrp(), 48 | tcgetpgrp(STDIN_FILENO)); 49 | } 50 | 51 | fn main() { 52 | unsafe { 53 | pr_ids("parent"); 54 | let pid = fork().check_not_negative().expect("fork error"); 55 | if pid > 0 { 56 | // parent: sleep to let child stop itself 57 | sleep(1); 58 | } else { 59 | pr_ids("child"); 60 | if signal(SIGHUP, sig_hup as usize) == SIG_ERR { 61 | panic!("signal error"); 62 | } 63 | kill(getpid(), SIGTSTP); 64 | pr_ids("child"); 65 | let s = "0"; 66 | if read(STDIN_FILENO, s.as_ptr() as *mut c_void, 1) != 1 { 67 | println!("read error {} on controlling TTY", errno::errno()); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/bin/10-signals/e11-stdin-stdout-100.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 10.11: Modify Figure 3.5 as follows: 2 | 3 | /// (a) change BUFFSIZE to 100; 4 | /// (b) catch the SIGXFSZ signal using the signal_intr function, printing a message 5 | /// when it’s caught, and returning from the signal handler; and 6 | /// (c) print the return value from write if the requested number of bytes wasn’t written. 7 | /// 8 | /// Modify the soft RLIMIT_FSIZE resource limit (Section 7.11) to 1,024 bytes and run your 9 | /// new program, copying a file that is larger than 1,024 bytes. 10 | /// (Try to set the soft resource limit from your shell. If you can’t do this from your shell, 11 | /// call setrlimit directly from the program.) Run this program on the different systems that 12 | /// you have access to. What happens and why? 13 | /// 14 | /// Findings: 15 | /// - setting RLIMIT_FSIZE on the shell via export did not trigger 16 | /// the exception, only with the explicit `setrlimit` call within the 17 | /// script is the error triggered. Also writing to a file as the solution 18 | /// in https://github.com/x746e/apue/blob/master/ch10/ex11-xfsz/mycat.c does 19 | /// did not solve the issue 20 | /// - somehow rust catches the error itself, so the setting of the signal 21 | /// has no effect. The error: 22 | /// thread 'main' panicked at 'failed printing to stdout: File too large 23 | /// (os error 27)', .../src/libstd/io/stdio.rs:693 note: Run with `RUST_BACKTRACE=1` for 24 | /// a backtrace. fatal runtime error: failed to initiate panic, error 5 25 | 26 | extern crate libc; 27 | #[macro_use(as_void)] 28 | extern crate apue; 29 | extern crate errno; 30 | 31 | use libc::{SIGXFSZ, STDIN_FILENO, STDOUT_FILENO, c_int, rlimit, RLIMIT_FSIZE}; 32 | use libc::{read, write, setrlimit}; 33 | use apue::{LibcResult, signal_intr}; 34 | use errno::errno; 35 | use std::io::Write; 36 | 37 | fn exceed_filesize_limit(_: c_int) { 38 | println!("exceed filesize limit caught"); 39 | } 40 | 41 | fn main() { 42 | let buffsize = 100; 43 | signal_intr(SIGXFSZ, exceed_filesize_limit).expect("can't set SIGXFSZ"); 44 | let mut num_loops = 0; 45 | let limit = rlimit { 46 | rlim_cur: 1000, 47 | rlim_max: 1000, 48 | }; 49 | unsafe { 50 | setrlimit(RLIMIT_FSIZE, &limit); 51 | let buf = vec![0; buffsize]; 52 | while let Ok(n) = read(STDIN_FILENO, as_void!(buf), buffsize).check_positive() { 53 | if write(STDOUT_FILENO, as_void!(buf), n as _) != n { 54 | println!("write error: {}", errno()); 55 | break; 56 | } 57 | num_loops += 1; 58 | } 59 | } 60 | writeln!(&mut std::io::stderr(), "total loops: {}", num_loops).unwrap(); 61 | } 62 | -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/f21-futimens.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | /// Figure 4.21 Example of futimens function 4 | /// 5 | /// Takeaways: 6 | /// 7 | /// - futimens only exists for linux, not for bsd based systems as osx 8 | /// - in the rust struct `stat` the field `st_atim` is missing os we need 9 | /// to fall back on `st_atime` and `st_atime_nsec` 10 | /// - from the rust function definition of `futimens` it was not clear that the 11 | /// function takes an array of size two 12 | /// - there is no way of changing the ctime with commandline tools to the past 13 | /// so we need to touch and then wait one second 14 | /// 15 | /// linux only: 16 | /// $ rm -f /tmp/f21.txt 17 | /// $ touch /tmp/f21.txt 18 | /// $ sleep 1 19 | /// $ f21-futimes /tmp/f21.txt 20 | /// $ a=$(date "+%s"); b=$(stat -c %Z /tmp/f21.txt); echo $(($a-$b)) 21 | /// 0 22 | 23 | extern crate libc; 24 | #[macro_use(cstr, print_err)] 25 | extern crate apue; 26 | 27 | use apue::LibcResult; 28 | 29 | #[cfg(target_os = "linux")] 30 | use libc::{O_RDWR, O_TRUNC, stat, timespec, open, futimens, close}; 31 | use std::ffi::CString; 32 | 33 | #[cfg(target_os = "linux")] 34 | fn main() { 35 | unsafe { 36 | let mut statbuf: stat = std::mem::uninitialized(); 37 | let mut args = std::env::args(); 38 | args.next(); // skip filename 39 | while let Some(filename) = args.next() { 40 | let filename = CString::new(filename).unwrap(); 41 | if stat(filename.as_ptr(), &mut statbuf).check_not_negative().is_err() { 42 | print_err!("{:?}: stat error", filename); 43 | } else if let Ok(fd) = open(filename.as_ptr(), O_RDWR | O_TRUNC).check_not_negative() { 44 | let times: [timespec; 2] = [timespec { 45 | tv_sec: statbuf.st_atime, 46 | tv_nsec: statbuf.st_atime_nsec, 47 | }, 48 | timespec { 49 | tv_sec: statbuf.st_mtime, 50 | tv_nsec: statbuf.st_mtime_nsec, 51 | }]; 52 | // reset times 53 | if futimens(fd, times.as_ptr() as *const _).check_not_negative().is_err() { 54 | print_err!("{:?}: futimens error", filename); 55 | } 56 | close(fd); 57 | } else { 58 | print_err!("{:?}: open error", filename); 59 | } 60 | } 61 | } 62 | } 63 | 64 | #[cfg(not(target_os = "linux"))] 65 | fn main() { 66 | unimplemented!(); 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | I'm reading through "[Advanced Programming in the UNIX Environment](https://www.amazon.com/dp/0321637739)". My dislike of C caused me to learn Rust and port some of the examples and exercises from C to Rust. 2 | 3 | Beware: this is my first Rust project. This code is far from beautiful and might have mistakes (e.g. dangling pointers). PR are *very* welcome. All code is tested on OS X and most on it also on Linux. 4 | 5 | ## Progress 6 | 7 | - [x] Chapter 1: Overview 8 | - [x] Chapter 2: UNIX Standardization and Implementations 9 | - [x] Chapter 3: File I/O 10 | - [x] Chapter 4: Files and Directories 11 | - [x] Chapter 5: Standard I/O Library 12 | - [x] Chapter 6: System Data Files and Information 13 | - [x] Chapter 7: Process Environment 14 | - [x] Chapter 8: Process Control 15 | - [x] Chapter 9: Process Relationships 16 | - [x] Chapter 10: Signals 17 | - [x] Chapter 11: Threads 18 | - [ ] Chapter 12: Thread control 19 | 20 | ## Using this code 21 | 22 | TODO: 23 | 24 | - can you copy-paste this code for your project? Mostly, yes, but some examples are probably optimal because 25 | they copy data (e.g. buffers) even when unneeded. 26 | - explain some basic usage `cstr!`, `to_option()` and working with buffers allocated via vectors. 27 | 28 | ## Building 29 | 30 | - Building should work out of the box for Macos and Linux on rust nightly. 31 | - Some of the binaries only do something on Macos, others only for Linux (see #cfg switches in the main methods) 32 | - If you regularly switch between building MacOs and Linux you can tell cargo to put those files in different directories 33 | using `export CARGO_TARGET_DIR=target/linux` 34 | 35 | ## Code not ported to Rust: 36 | 37 | - Figure 7.9, 7.11, 7.13: setjmp, longjmp: of course Rust solves this with exception handling (i.e. with explicit 38 | error handling or using `panic::catch_unwind`). These sections (as well as the one about malloc, etc.) are 39 | actually very good reasons why to not use C directly but instead turn to something safer like Rust. 40 | In addition to that: Rust doesn't offer a way to safe unwind the stack after a longjump: 41 | https://users.rust-lang.org/t/force-cleanup-before-longjmp/3376 42 | - Figure 7.14: That's exactly why you take Rust over C because Rust will complain at compile time that you cannot 43 | return a stack variable from a function. 44 | - Figure 8.13: avoid race condition: the synchronization features in Rust don't allow syncing of forks (only available 45 | for threads, see e.g. https://github.com/BurntSushi/chan-signal/issues/13), so I skipped this Figure, maybe 46 | coming back to this later (when signals are discussed in later chapters) 47 | - Figure 10.8, 10.9, 10.11, 10.20: rust does not have setjmp/longjmp 48 | -------------------------------------------------------------------------------- /src/bin/11-threads/f05-thread-cleanup.rs: -------------------------------------------------------------------------------- 1 | /// Figure 11.5 Thread cleanup handler 2 | /// 3 | /// Findings: 4 | /// - libc function definition was wrong, opened http://stackoverflow.com/questions/42284562 5 | /// and PR for libc: https://github.com/rust-lang/libc/pull/527 6 | /// - as pthread_cleanup_push and pthread_cleanup_pop are implemented as macros and rust doesn't 7 | /// allow the same style of macros this part needed to stay in C. I tried integrating Rust 8 | /// and C (calls going into both directions), using `my_build.rs` and the gcc module. 9 | /// But: With this addition, any change in any rust file caused a recompilation of the whole 10 | /// project, opened https://github.com/rust-lang/cargo/issues/3724 and added 11 | /// `cargo:rerun-if-changed=` into my_build.rs which now causes only a project rebuild if 12 | /// my_build.rs is changed or `thread_cleanup.c` 13 | /// - when leaving `if (arg) return((void *)1)` in the C code the program would run into a 14 | /// "bus error", I guess because the macros are unbalanced..? 15 | /// 16 | /// The program behaves as described in the book: 17 | /// 18 | /// $ f05-thread-cleanup 2>&1 19 | /// thread 1 start 20 | /// thread 1 push complete 21 | /// thread 2 start 22 | /// thread 2 push complete 23 | /// cleanup: "thread 2 second handler" 24 | /// cleanup: "thread 2 first handler" 25 | /// thread 1 exit code: 0x1 26 | /// thread 2 exit code: 0x2 27 | 28 | extern crate libc; 29 | extern crate apue; 30 | 31 | use std::ffi::CStr; 32 | use libc::c_void; 33 | use libc::{pthread_join, usleep}; 34 | use std::ptr::null_mut; 35 | use apue::my_libc::pthread_create; 36 | use apue::LibcResult; 37 | 38 | #[no_mangle] 39 | pub extern "C" fn cleanup(arg: *mut c_void) { 40 | unsafe { 41 | let s = CStr::from_ptr(arg as _); 42 | println!("cleanup: {:?}", s); 43 | } 44 | } 45 | 46 | 47 | #[link(name = "thread-cleanup")] 48 | extern "C" { 49 | fn thr_fn1(arg: *mut c_void) -> *mut c_void; 50 | fn thr_fn2(arg: *mut c_void) -> *mut c_void; 51 | } 52 | 53 | 54 | fn main() { 55 | unsafe { 56 | let (mut tid1, mut tid2) = std::mem::zeroed(); 57 | let mut tret = std::mem::uninitialized(); 58 | pthread_create(&mut tid1, null_mut(), thr_fn1, 1 as _).check_zero().expect("can't create thread 1"); 59 | usleep(1000); 60 | pthread_create(&mut tid2, null_mut(), thr_fn2, 1 as _).check_zero().expect("can't create thread 2"); 61 | usleep(1000); 62 | pthread_join(tid1, &mut tret).check_zero().expect("can’t join with thread 1"); 63 | println!("thread 1 exit code: {:?}", tret); 64 | usleep(1000); 65 | pthread_join(tid2, &mut tret).check_zero().expect("can’t join with thread 2"); 66 | println!("thread 2 exit code: {:?}", tret); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/bin/10-signals/e05-timers.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 10.5: Using only a single timer (either alarm or the higher-precision setitimer), 2 | /// provide a set of functions that allows a process to set any number of timers. 3 | /// 4 | /// Basic idea: have a mutable static list (yeah.. but there's NO WAY to pass arguments to 5 | /// signal handlers) of function pointers and "due alarm time" 6 | /// 7 | /// On new alarm: 8 | /// - add alarm to list 9 | /// - sort list by "due alarm time" 10 | /// 11 | /// When alarm goes off: 12 | /// - deduct last alarm time from all alarms 13 | /// - remove first element 14 | /// 15 | /// What works: Start all alarms at the start ot the program 16 | /// What doesn't: Start a few alarms, then wait and start a few others 17 | 18 | extern crate libc; 19 | use libc::{c_int, SIGALRM}; 20 | use libc::{signal, alarm}; 21 | use std::time::Duration; 22 | use std::thread; 23 | 24 | #[derive(Clone, Copy)] 25 | struct Timeout { 26 | function: fn(), 27 | timeout_s: u32, 28 | } 29 | 30 | static mut SIGNALS: [Timeout; 16] = [Timeout { 31 | function: dummy, 32 | timeout_s: 999999, 33 | }; 16]; 34 | static mut LAST_TIMEOUT_S: u32 = 0; 35 | 36 | // empty function for filling signals 37 | fn dummy() {} 38 | 39 | fn sig_alrm(_: c_int) { 40 | unsafe { 41 | for (i, signal) in SIGNALS.iter().enumerate() { 42 | // cannot just do signal -= last_timeout_s as signal is not mutable 43 | // (couldn't find out how to make the static initialization with mutable objects, 44 | // sounds super hacky anyway so I gave up..) 45 | SIGNALS[i] = Timeout { 46 | function: signal.function, 47 | timeout_s: signal.timeout_s - LAST_TIMEOUT_S, 48 | }; 49 | } 50 | let f = SIGNALS[0].function; 51 | // "remove" first element and put a dummy element at the end. 52 | // There are probably nicer ways to do that.. 53 | SIGNALS[0] = Timeout { 54 | function: dummy, 55 | timeout_s: 999999, 56 | }; 57 | SIGNALS.sort_by(|a, b| a.timeout_s.cmp(&b.timeout_s)); 58 | LAST_TIMEOUT_S = SIGNALS[0].timeout_s; 59 | alarm(LAST_TIMEOUT_S); 60 | f(); 61 | } 62 | } 63 | 64 | // timeout 1 65 | // timeout 2 66 | // -> 67 | fn set_timeout(callback: fn(), timeout_s: u32) { 68 | unsafe { 69 | SIGNALS[15] = Timeout { 70 | function: callback, 71 | timeout_s: timeout_s, 72 | }; 73 | SIGNALS.sort_by(|a, b| a.timeout_s.cmp(&b.timeout_s)); 74 | signal(SIGALRM, sig_alrm as usize); 75 | LAST_TIMEOUT_S = SIGNALS[0].timeout_s; 76 | alarm(LAST_TIMEOUT_S); 77 | } 78 | } 79 | 80 | fn testme1() { 81 | println!("testme1 called"); 82 | } 83 | 84 | fn testme2() { 85 | println!("testme2 called"); 86 | } 87 | 88 | fn main() { 89 | set_timeout(testme2, 2); 90 | set_timeout(testme1, 1); 91 | thread::sleep(Duration::from_millis(4000)); 92 | } 93 | -------------------------------------------------------------------------------- /src/bin/02-unix-standards/f16-pathname-alloc-space.rs: -------------------------------------------------------------------------------- 1 | /// Figure 2.16 Dynamically allocate space for a pathname 2 | 3 | /// Takeaway: in the C example in the book the return value of pathconf is increased 4 | /// by one "since it’s relative to root" but both on OSX and Linux I don't need to do that. 5 | /// A short online research also showed that _PC_PATH_MAX is not relative to the given path 6 | /// but the absolute pathmax value for the filesystem where the path points to. 7 | /// 8 | /// mac only: 9 | /// $ f16-pathname-alloc-space 10 | /// from libc constant: PATH_MAX=1024 11 | /// from pathconf: pathmax = 1024 12 | /// length of pointer = 1024 13 | /// 14 | /// linux only: 15 | /// $ f16-pathname-alloc-space 16 | /// from libc constant: PATH_MAX=4096 17 | /// from pathconf: pathmax = 4096 18 | /// length of pointer = 4096 19 | 20 | extern crate libc; 21 | #[macro_use(cstr)] 22 | extern crate apue; 23 | extern crate errno; 24 | 25 | use libc::{_SC_VERSION, _SC_XOPEN_VERSION, PATH_MAX, _PC_PATH_MAX, sysconf, pathconf, malloc}; 26 | use apue::{LibcResult, LibcPtrResult}; 27 | use errno::errno; 28 | 29 | const PATH_MAX_GUESS: i64 = 1024; 30 | 31 | unsafe fn path_alloc(pathmax: &mut i64, 32 | posix_version: &mut i64, 33 | xsi_version: &mut i64) 34 | -> (*mut libc::c_void, i64) { 35 | if *posix_version == 0 { 36 | if let Ok(val) = sysconf(_SC_VERSION).check_not_negative() { 37 | *posix_version = val; 38 | } 39 | } 40 | if *xsi_version == 0 { 41 | if let Ok(val) = sysconf(_SC_XOPEN_VERSION).check_not_negative() { 42 | *xsi_version = val; 43 | } 44 | } 45 | println!("from libc constant: PATH_MAX={:?}", PATH_MAX); 46 | // would be too easy to just take the constant so we go on.. 47 | if *pathmax == 0 { 48 | *pathmax = if let Ok(val) = pathconf(cstr!("/"), _PC_PATH_MAX).check_not_negative() { 49 | val 50 | } else { 51 | let e = errno(); 52 | match e.0 { 53 | 0 => PATH_MAX_GUESS, // indeterminate so just a guess 54 | _ => panic!("pathconf error for _PC_PATH_MAX"), 55 | } 56 | } 57 | } 58 | println!("from pathconf: pathmax = {:?}", *pathmax); 59 | 60 | // Before POSIX.1-2001, we aren’t guaranteed that PATH_MAX includes 61 | // the terminating null byte. Same goes for XPG3. 62 | let size = if *posix_version < 200112 && *xsi_version < 4 { 63 | *pathmax + 1 64 | } else { 65 | *pathmax 66 | }; 67 | if let Ok(ptr) = malloc(size as _).check_not_null() { 68 | (ptr, size) 69 | } else { 70 | panic!("malloc error for pathname"); 71 | } 72 | } 73 | 74 | fn main() { 75 | let mut posix_version = 0; 76 | let mut xsi_version = 0; 77 | let mut pathmax = 0; 78 | unsafe { 79 | let (_, size) = path_alloc(&mut pathmax, &mut posix_version, &mut xsi_version); 80 | println!("length of pointer = {:?}", size); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f30-nice.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.30 Evaluate the effect of changing the nice value 2 | /// 3 | /// Takeaway: running this "as is" on a modern laptop doesn't yield 4 | /// any results as the forks run on different cpu cores and unless 5 | /// all cores are not idle the parent and child get a "full core" 6 | /// and hence there's no real difference in the count on child/parent. 7 | /// 8 | /// The relevant value here is the number of "logical cores" which 9 | /// is the number of cores times the numbers of "threads per core", 10 | /// (hyperthreading) 11 | /// see http://unix.stackexchange.com/a/88290/168663 12 | /// OSX: `sysctl -n hw.ncpu` 13 | /// linux: `lscpu` - logical cores = CPU(s) x Core(s) per socket x Thread(s) per core 14 | /// 15 | /// To let the script run in parallel do e.g. this: 16 | /// for i in {1..4}; do f30-nice 20 &; done 17 | /// On OSX I needed to go about 2x the number of logical cores 18 | /// to really see a difference. In Linux on virtualbox the effect 19 | /// was already big (factor 10) when running at 1 x the number of logical cores 20 | 21 | extern crate libc; 22 | extern crate apue; 23 | extern crate errno; 24 | 25 | use libc::{gettimeofday, timeval, exit, setbuf, fork, nice}; 26 | use apue::{LibcResult, err_sys}; 27 | use apue::my_libc::stdout; 28 | use std::ptr::null_mut as null; 29 | use errno::{errno, Errno, set_errno}; 30 | 31 | unsafe fn checktime(end: timeval, str: &str, count: u64) { 32 | let mut tv: timeval = std::mem::uninitialized(); 33 | gettimeofday(&mut tv, null()); 34 | if tv.tv_sec >= end.tv_sec && tv.tv_usec >= end.tv_usec { 35 | println!("{} count = {}", str, count); 36 | exit(0); 37 | } 38 | } 39 | 40 | fn main() { 41 | unsafe { 42 | setbuf(stdout, null()); 43 | // cannot access DEFINEs in rust and _SC_NZERO is not defined in Macos. 44 | // it's 20 anyway.. 45 | let nzero = 20; 46 | let adj = if std::env::args().len() == 2 { 47 | std::env::args().next_back().unwrap().parse::().expect("not a number") 48 | } else { 49 | 0 50 | }; 51 | let mut end: timeval = std::mem::uninitialized(); 52 | gettimeofday(&mut end, null()); 53 | end.tv_sec += 10; 54 | let pid = fork().check_not_negative().expect("fork error"); 55 | let str = if pid == 0 { 56 | println!("current nice value in child is {}, adjusting by {}", 57 | nice(0) + nzero, 58 | adj); 59 | set_errno(Errno(0)); 60 | if nice(adj) == -1 && errno().0 != 0 { 61 | err_sys("child set scheduling priority"); 62 | } 63 | println!("now child nice value is {}", nice(0) + nzero); 64 | "child" 65 | } else { 66 | println!("current nice value in parent is {}", nice(0) + nzero); 67 | "parent" 68 | }; 69 | let mut count: u64 = 0; 70 | loop { 71 | count += 1; // panics when overflowing in debug mode 72 | checktime(end, str, count); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/bin/04-files-and-directories/e06-cp-sparse.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 4.6: Write a utility like cp(1) that copies a file containing holes, without writing 2 | /// the bytes of 0 to the output file. 3 | /// 4 | /// Takeaways: 5 | /// 6 | /// - first I mixed up the system calls of fileio and stdio, note to self: 7 | /// + open, read, write, lseek are all fileio (using file descriptors) 8 | /// + fopen, fgetc, fputc, fseek are all stdio (using the *FILE pointer) 9 | /// - lseek to SEEK_HOLE would have been the perfect solution, only this is non-bsd only 10 | /// - a buffer length of 1 is of course silly, best would be a buffer length of the block size: 11 | /// + it would be a lot faster (especially if the data to hole ratio is high) 12 | /// + even if only the first byte of the buffer is non-null it would occupy one block on the disk 13 | /// + the problem would be to check that the whole buffer is consisting of only zeroes 14 | /// http://stackoverflow.com/questions/1493936 15 | /// 16 | /// $ echo hans > /tmp/sparse.txt 17 | /// $ truncate -s 1K /tmp/sparse.txt 18 | /// $ echo kurt >> /tmp/sparse.txt 19 | /// $ e06-cp-sparse /tmp/sparse.txt /tmp/sparse2.txt 20 | /// hans 21 | /// seeking to 1025 22 | /// kurt 23 | /// $ rm /tmp/sparse*.txt 24 | 25 | extern crate clap; 26 | #[macro_use(cstr)] 27 | extern crate apue; 28 | extern crate libc; 29 | extern crate errno; 30 | 31 | use clap::App; 32 | use std::vec::Vec; 33 | use std::io::{self, Write}; 34 | use apue::LibcResult; 35 | use libc::{O_RDWR, O_RDONLY, O_CREAT, SEEK_SET, SEEK_CUR, c_void, open, read, write, lseek}; 36 | 37 | const BUFLEN: usize = 1; 38 | 39 | fn main() { 40 | unsafe { 41 | let matches = App::new("cp") 42 | .args_from_usage(" source/file\n destination/file") 43 | .get_matches(); 44 | let from = matches.value_of("from").unwrap(); 45 | let to = matches.value_of("to").unwrap(); 46 | let fd1 = open(cstr!(from), O_RDONLY) 47 | .check_not_negative() 48 | .expect(&format!("can't open file {}", from)); 49 | let fd2 = open(cstr!(to), O_RDWR | O_CREAT, 0o600) 50 | .check_not_negative() 51 | .expect(&format!("can't open file {}", to)); 52 | let mut buf: Vec = vec![0; BUFLEN]; 53 | let mut sparse = false; 54 | while read(fd1, buf.as_mut_ptr() as *mut c_void, BUFLEN) == BUFLEN as _ { 55 | if buf[0] == 0 { 56 | // better would be lseek with whence=SEEK_HOLE 57 | // but this is non-bsd only, so we implement that ourselves 58 | // of course this is not fast at all 59 | sparse = true; 60 | continue; 61 | } 62 | // end of a block of zeroes, seek the write file to the right pos 63 | if sparse { 64 | let pos = lseek(fd1, 0, SEEK_CUR); 65 | println!("seeking to {}", pos); 66 | lseek(fd2, pos, SEEK_SET); 67 | sparse = false; 68 | } 69 | write(fd2, buf.as_ptr() as *mut c_void, BUFLEN); 70 | io::stdout().write(&buf).unwrap(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/bin/07-process-env/f16-rlimits.rs: -------------------------------------------------------------------------------- 1 | /// Figure 7.16 Print the current resource limits 2 | /// 3 | /// Takeaways: 4 | /// 5 | /// - the nice trick of using the name of the constant plus the constant value 6 | /// in one go (`#define doit(name) pr_limits(#name, name)`) works also in Rust using macros and 7 | /// `stringify!` 8 | /// - coredumps on OSX are not activated by default, you need to activate them with 9 | /// `ulimit -c unlimited` first: http://stackoverflow.com/questions/9412156 10 | /// 11 | /// mac only: 12 | /// $ f16-rlimits | grep -v NOFILE 13 | /// RLIMIT_AS (infinite) (infinite) 14 | /// RLIMIT_CORE 0 (infinite) 15 | /// RLIMIT_CPU (infinite) (infinite) 16 | /// RLIMIT_DATA (infinite) (infinite) 17 | /// RLIMIT_FSIZE (infinite) (infinite) 18 | /// RLIMIT_MEMLOCK (infinite) (infinite) 19 | /// RLIMIT_NPROC 709 1064 20 | /// RLIMIT_RSS (infinite) (infinite) 21 | /// $ ulimit -c unlimited && f16-rlimits | grep CORE 22 | /// RLIMIT_CORE (infinite) (infinite) 23 | /// 24 | /// linux only: 25 | /// $ f16-rlimits 26 | /// RLIMIT_AS (infinite) (infinite) 27 | /// RLIMIT_CORE 0 (infinite) 28 | /// RLIMIT_CPU (infinite) (infinite) 29 | /// RLIMIT_DATA (infinite) (infinite) 30 | /// RLIMIT_FSIZE (infinite) (infinite) 31 | /// RLIMIT_MEMLOCK 65536 65536 32 | /// RLIMIT_MSGQUEUE 819200 819200 33 | /// RLIMIT_NICE 0 0 34 | /// RLIMIT_NOFILE 1024 65536 35 | /// RLIMIT_NPROC 47643 47643 36 | /// RLIMIT_RSS (infinite) (infinite) 37 | 38 | extern crate libc; 39 | extern crate apue; 40 | 41 | use libc::{RLIM_INFINITY, RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE, 42 | RLIMIT_MEMLOCK, RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_RSS}; 43 | 44 | #[cfg(target_os = "linux")] 45 | use libc::{RLIMIT_MSGQUEUE, RLIMIT_NICE}; 46 | 47 | use libc::{rlimit, getrlimit}; 48 | use apue::LibcResult; 49 | 50 | macro_rules! doit { 51 | ($s:expr) => {{ 52 | pr_limits(stringify!($s), $s) 53 | }} 54 | } 55 | 56 | unsafe fn pr_limits(name: &str, resource: i32) { 57 | let mut limit: rlimit = std::mem::uninitialized(); 58 | getrlimit(resource, &mut limit) 59 | .check_not_negative() 60 | .expect(&format!("getrlimit error for {}", name)); 61 | print!("{:16}", name); 62 | match limit.rlim_cur { 63 | RLIM_INFINITY => print!("(infinite) "), 64 | _ => print!("{:10} ", limit.rlim_cur), 65 | }; 66 | match limit.rlim_max { 67 | RLIM_INFINITY => print!("(infinite)"), 68 | _ => print!("{:10}", limit.rlim_max), 69 | }; 70 | println!(""); 71 | } 72 | 73 | fn main() { 74 | unsafe { 75 | doit!(RLIMIT_AS); 76 | doit!(RLIMIT_CORE); 77 | doit!(RLIMIT_CPU); 78 | doit!(RLIMIT_DATA); 79 | doit!(RLIMIT_FSIZE); 80 | doit!(RLIMIT_MEMLOCK); 81 | #[cfg(target_os = "linux")] 82 | doit!(RLIMIT_MSGQUEUE); 83 | #[cfg(target_os = "linux")] 84 | doit!(RLIMIT_NICE); 85 | doit!(RLIMIT_NOFILE); 86 | doit!(RLIMIT_NPROC); 87 | doit!(RLIMIT_RSS); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/bin/12-thread-control/f16-signal-handling.rs: -------------------------------------------------------------------------------- 1 | /// Figure 12.16: Synchronous signal handling 2 | /// 3 | /// Findings: 4 | /// 5 | /// - instead of pthread_mutex_lock and pthread_cond_signal I found that 6 | /// Rusts Mutex/Condvar serves the same purpose 7 | /// - it took me a while to understand how the mutex works together with 8 | /// pthread_cond_wait (namely, taht pthread_cond_wait unlocks the mutex), 9 | /// although this has been the subject before.. Interesting to see 10 | /// that rusts Mutex/Condvar behave exactly the same: 11 | /// https://doc.rust-lang.org/std/sync/struct.Condvar.html#examples 12 | // 13 | // Does not yet work with test.py, that's why I put it only behind two // 14 | // see http://stackoverflow.com/questions/43730557/ 15 | 16 | // $ f16-signal-handling & pid=$! && sleep 0.1 && kill -s INT $pid && sleep 0.1 && kill -s QUIT $pid 17 | // interrupt 18 | // quit 19 | 20 | extern crate libc; 21 | extern crate apue; 22 | 23 | use apue::LibcResult; 24 | 25 | use std::sync::{Arc, Mutex, Condvar}; 26 | use std::thread; 27 | 28 | use libc::{sigset_t, SIGINT, SIGQUIT, SIG_BLOCK, SIG_SETMASK}; 29 | use libc::{sigwait, exit, sigemptyset, sigaddset, pthread_sigmask}; 30 | use apue::my_libc::sigprocmask; 31 | 32 | unsafe fn thr_fn(mask:&sigset_t, pair:Arc<(Mutex, Condvar)>) { 33 | let mut signo = std::mem::uninitialized(); 34 | loop { 35 | sigwait(mask, &mut signo).check_zero().expect("sigwait failed"); 36 | match signo { 37 | SIGINT => { 38 | println!("\ninterrupt"); 39 | }, 40 | SIGQUIT => { 41 | println!("\nquit"); 42 | let &(ref lock, ref cvar) = &*pair; 43 | let mut quitflag = lock.lock().unwrap(); 44 | *quitflag = true; 45 | cvar.notify_one(); 46 | }, 47 | _ => { 48 | println!("unexpectd signal {}", signo); 49 | exit(1); 50 | } 51 | } 52 | } 53 | } 54 | 55 | fn main() { 56 | unsafe { 57 | let pair = Arc::new((Mutex::new(false), Condvar::new())); 58 | let pair2 = pair.clone(); 59 | let (mut mask, mut oldmask) = std::mem::uninitialized(); 60 | sigemptyset(&mut mask); 61 | sigaddset(&mut mask, SIGINT); 62 | sigaddset(&mut mask, SIGQUIT); 63 | pthread_sigmask(SIG_BLOCK, &mask, &mut oldmask).check_zero().expect("SIG_BLOCK error"); 64 | thread::spawn(move || { 65 | thr_fn(&mask, pair2); 66 | }); 67 | let &(ref lock, ref cvar) = &*pair; 68 | 69 | // wait for the quitflag 70 | // trick is that the lock is unlocked on cvar.wait(quitflag) 71 | { 72 | let mut quitflag = lock.lock().unwrap(); 73 | while !*quitflag { 74 | quitflag = cvar.wait(quitflag).unwrap(); 75 | } 76 | *quitflag = false; 77 | } 78 | 79 | // SIGQUIT has been caught. Lock is unlocked upon dropping of the quitflag var 80 | // reset signal mask which unblocks SIGQUIT 81 | sigprocmask(SIG_SETMASK, &oldmask, std::ptr::null_mut()).check_not_negative().expect("SIG_SETMASK error"); 82 | } 83 | } -------------------------------------------------------------------------------- /src/bin/05-stdio/f15-fmemopen.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports, dead_code)] 2 | 3 | /// Figure 5.15: Investigate memory stream write behavior 4 | /// 5 | /// This works for Linux only (MacOS/BSD don't have fmemopen implemented) 6 | /// Translation C->Rust was very straight forward, only caveat 7 | /// is the glibc bug that causes fflush to reset the file pointer position 8 | /// 9 | /// linux only: 10 | /// $ f15-fmemopen 11 | /// initial buffer contents: 12 | /// before flush: 13 | /// after fflush: 14 | /// len of string in buf = 0 15 | /// after fseek: bbbbbbbbbbbbhello, world 16 | /// len of string in buf = 24 17 | /// after fclose: hello, worldcccccccccccccccccccccccccccccccccc 18 | /// len of string in buf = 46 19 | 20 | extern crate libc; 21 | #[macro_use(cstr)] 22 | extern crate apue; 23 | 24 | use libc::{c_void, size_t, c_char, c_uchar, c_int, FILE, SEEK_SET, memset, fprintf, fseek, fflush, 25 | strlen, printf, fclose}; 26 | 27 | const BSZ: usize = 48; 28 | extern "C" { 29 | pub fn fmemopen(buf: *mut c_void, size: size_t, mode: *const c_char) -> *mut FILE; 30 | } 31 | 32 | trait CArray { 33 | fn as_muti8(&self) -> *mut i8; 34 | fn as_void(&self) -> *mut c_void; 35 | } 36 | 37 | impl CArray for [u8] { 38 | fn as_muti8(&self) -> *mut i8 { 39 | self.as_ptr() as *mut i8 40 | } 41 | fn as_void(&self) -> *mut c_void { 42 | self.as_ptr() as *mut c_void 43 | } 44 | } 45 | 46 | #[cfg(target_os = "linux")] 47 | fn main() { 48 | unsafe { 49 | let mut buf: [c_uchar; BSZ] = std::mem::uninitialized(); 50 | memset(buf.as_void(), 'a' as c_int, BSZ - 2); 51 | buf[BSZ - 2] = '\0' as u8; 52 | buf[BSZ - 1] = 'X' as u8; 53 | let fp = fmemopen(buf.as_ptr() as *mut c_void, BSZ, cstr!("w+")); 54 | if fp.is_null() { 55 | panic!("fmemopen failed"); 56 | } 57 | printf(cstr!("initial buffer contents: %s\n"), buf.as_muti8()); 58 | printf(cstr!("before flush: %s\n"), buf.as_muti8()); 59 | fflush(fp); 60 | // fflush resets the position of the fp, that's a bug: 61 | // https://sourceware.org/bugzilla/show_bug.cgi?id=20005 62 | fseek(fp, ("hello world".len() + 1) as i64, SEEK_SET); 63 | printf(cstr!("after fflush: %s\n"), buf.as_muti8()); 64 | printf(cstr!("len of string in buf = %ld\n"), 65 | strlen(buf.as_muti8())); 66 | 67 | memset(buf.as_void(), 'b' as c_int, BSZ - 2); 68 | buf[BSZ - 2] = '\0' as u8; 69 | buf[BSZ - 1] = 'X' as u8; 70 | fprintf(fp, cstr!("hello, world")); 71 | fseek(fp, 0, SEEK_SET); 72 | 73 | printf(cstr!("after fseek: %s\n"), buf.as_muti8()); 74 | printf(cstr!("len of string in buf = %ld\n"), 75 | strlen(buf.as_muti8())); 76 | 77 | memset(buf.as_void(), 'c' as c_int, BSZ - 2); 78 | buf[BSZ - 2] = '\0' as u8; 79 | buf[BSZ - 1] = 'X' as u8; 80 | fprintf(fp, cstr!("hello, world")); 81 | fclose(fp); 82 | printf(cstr!("after fclose: %s\n"), buf.as_muti8()); 83 | printf(cstr!("len of string in buf = %ld\n"), 84 | strlen(buf.as_muti8())); 85 | } 86 | } 87 | 88 | #[cfg(target_os = "macos")] 89 | fn main() { 90 | unimplemented!(); 91 | } 92 | -------------------------------------------------------------------------------- /src/bin/11-threads/f14-reader-writer-lock.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | /// Figure 11.14 Using reader–writer locks 4 | /// 5 | /// - VecDeque resembles the doubled linked list best which also needs 6 | /// a removal in the middle 7 | /// 8 | /// Status: main() hangs on OSX, works on Linux. Couldn't find out why 9 | /// there's something wrong with the _init function, upon first lock attempt (a1) 10 | /// it always blocks as if the init funciton would already lock the resource. 11 | 12 | extern crate libc; 13 | extern crate apue; 14 | extern crate errno; 15 | 16 | use libc::{pthread_rwlock_t, pthread_t, PTHREAD_RWLOCK_INITIALIZER}; 17 | use apue::my_libc::pthread_rwlock_init; 18 | use libc::{pthread_rwlock_wrlock, pthread_rwlock_unlock, pthread_rwlock_rdlock}; 19 | 20 | use std::collections::VecDeque; 21 | use std::ptr::null; 22 | 23 | struct Queue { 24 | q: VecDeque, 25 | lock: pthread_rwlock_t, 26 | } 27 | 28 | struct Job { 29 | thread_id: pthread_t, 30 | } 31 | 32 | // only equals if pointers are equal 33 | impl PartialEq for Job { 34 | fn eq(&self, other: &Job) -> bool { 35 | &self == &other 36 | } 37 | } 38 | 39 | impl Queue { 40 | // queue_init 41 | fn new() -> Queue { 42 | let mut q = Queue { 43 | q: VecDeque::new(), 44 | lock: PTHREAD_RWLOCK_INITIALIZER, 45 | }; 46 | println!("n1"); 47 | unsafe { 48 | if pthread_rwlock_init(&mut q.lock, null()) != 0 { 49 | println!("fail!"); 50 | } 51 | }; 52 | println!("n2"); 53 | q 54 | } 55 | 56 | fn job_insert(&mut self, job: Job) { 57 | unsafe { pthread_rwlock_wrlock(&mut self.lock) }; 58 | self.q.push_front(job); 59 | unsafe { pthread_rwlock_unlock(&mut self.lock) }; 60 | } 61 | 62 | fn job_append(&mut self, job: Job) { 63 | println!("a1"); 64 | unsafe { pthread_rwlock_wrlock(&mut self.lock) }; 65 | println!("a2"); 66 | self.q.push_back(job); 67 | unsafe { pthread_rwlock_unlock(&mut self.lock) }; 68 | println!("a3"); 69 | } 70 | 71 | fn job_remove(&mut self, job: &Job) { 72 | unsafe { pthread_rwlock_wrlock(&mut self.lock) }; 73 | if Some(job) == self.q.front() { 74 | self.q.pop_front(); 75 | } else if Some(job) == self.q.back() { 76 | self.q.pop_back(); 77 | } else { 78 | self.q.retain(|ref x| *x != job); 79 | } 80 | unsafe { pthread_rwlock_unlock(&mut self.lock) }; 81 | } 82 | 83 | fn job_find(&mut self, id: pthread_t) -> Option<&Job> { 84 | println!("f1"); 85 | if unsafe { pthread_rwlock_rdlock(&mut self.lock) } != 0 { 86 | return None; 87 | } 88 | println!("f2"); 89 | let res = if let Some(pos) = self.q.iter().position(|ref x| x.thread_id == id) { 90 | self.q.get(pos) 91 | } else { 92 | None 93 | }; 94 | unsafe { pthread_rwlock_unlock(&mut self.lock) }; 95 | res 96 | } 97 | } 98 | 99 | fn main() { 100 | let mut q = Queue::new(); 101 | q.job_append(Job { thread_id: 1 }); 102 | assert!(q.job_find(1).expect("expected to find 1").thread_id == 1); 103 | } 104 | -------------------------------------------------------------------------------- /src/bin/10-signals/f23-sigsuspend-global.rs: -------------------------------------------------------------------------------- 1 | /// Figure 10.23 Using sigsuspend to wait for a global variable to be set 2 | /// 3 | /// The thing that needed the most work is setting the quitflag in the signal 4 | /// so that the main loop is seeing the change, plus telling the compiler not to 5 | /// "compile away" the while loop as apparently there is no code setting the flag to false. 6 | /// 7 | /// The code I ended up with (static AtomicBool) instance was what I came up very early 8 | /// (before asking on Stackoverflow), but I mistakenly set QUITFLAG to true in the signal 9 | /// which made the while loop to never exit. This sent me to a journey in failed attempts: 10 | /// 11 | /// - after posting http://stackoverflow.com/questions/41655118 12 | /// I was sure that my solution needs an Arc<>, but then I cannot initiate a Arc<> static 13 | /// variable as it doesn't let me do function calls 14 | /// - turn the signalling funciton into a closure and put the quitflag variable inside main. 15 | /// This won't ever work because closures are inherently different than functions as they 16 | /// capture their environment, see http://stackoverflow.com/questions/32270030 17 | /// 18 | // $ f23-sigsuspend-global 19 | // $ pkill -SIGINT f23-sugsuspend-global 20 | // interrupt 21 | // $ pkill -SIGQUIT f23-sugsuspend-global 22 | // quit 23 | // [1] + 92089 done f23-sugsuspend-global 24 | 25 | 26 | extern crate libc; 27 | extern crate apue; 28 | 29 | use libc::{c_int, SIGINT, SIG_ERR, SIGQUIT, SIG_BLOCK, SIG_SETMASK}; 30 | use libc::{signal, sigemptyset, sigaddset}; 31 | use apue::my_libc::{sigprocmask, sigsuspend}; 32 | use apue::LibcResult; 33 | use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT}; 34 | 35 | // closest I could find for volatile sig_atomic_t in Rust 36 | static mut QUITFLAG: AtomicBool = ATOMIC_BOOL_INIT; 37 | 38 | fn sig_int(signo: c_int) { 39 | unsafe { 40 | match signo { 41 | SIGINT => println!("interrupt"), 42 | SIGQUIT => { 43 | println!("quit"); 44 | QUITFLAG.store(false, Ordering::SeqCst); 45 | } 46 | _ => panic!("unexpected signal"), 47 | } 48 | } 49 | } 50 | 51 | 52 | fn main() { 53 | unsafe { 54 | let (mut newmask, mut oldmask, mut zeromask) = std::mem::uninitialized(); 55 | 56 | if signal(SIGINT, sig_int as usize) == SIG_ERR { 57 | panic!("signal(SIGINT) error"); 58 | } 59 | if signal(SIGQUIT, sig_int as usize) == SIG_ERR { 60 | panic!("signal(SIGQUIT) error"); 61 | } 62 | sigemptyset(&mut zeromask); 63 | sigemptyset(&mut newmask); 64 | sigaddset(&mut newmask, SIGQUIT); 65 | // Block SIGQUIT and save current signal mask 66 | sigprocmask(SIG_BLOCK, &newmask, &mut oldmask).check_not_negative().expect("SIG_BLOCK error"); 67 | 68 | // here comes the AtomicBool into play which goes sure that whenever 69 | // QUITFLAG becomes false it is immediately set to true again 70 | // so no other Thread could "catch" it 71 | QUITFLAG.store(true, Ordering::SeqCst); 72 | while QUITFLAG.fetch_or(true, Ordering::Relaxed) { 73 | sigsuspend(&zeromask); 74 | } 75 | sigprocmask(SIG_SETMASK, &oldmask, std::ptr::null_mut()) 76 | .check_not_negative() 77 | .expect("SIG_SETMASK error"); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/bin/12-thread-control/f13-getenv-setspecific.rs: -------------------------------------------------------------------------------- 1 | /// Figure 12.13: A thread-safe, compatible version of getenv 2 | /// 3 | /// Findings: 4 | /// - I was surprised that the copying around with *(envbuf.offset()) 5 | /// worked at first try and that calling `free` via destructor 6 | /// does not interfer with Rusts thread logic 7 | /// - I first thought PTHREAD_ONCE_INIT is same in Linux as it is on OSX 8 | /// but I was wrong, in Linux it's 0, whereas on OSX it's 0x30B1BCBA 9 | /// Cost me about 1.5h to find out why on Linux this program always threw 10 | /// segfaults because pthread_getspecific was returning a pointer which 11 | /// sometimes, when writing to pointer + offset just crashed in a segfault.. 12 | /// 13 | /// $ f13-getenv-setspecific | sed 's/=.*//g' 14 | /// PATH 15 | /// PATH 16 | /// PATH 17 | /// PATH 18 | /// PATH 19 | 20 | extern crate libc; 21 | extern crate apue; 22 | 23 | use std::ffi::CStr; 24 | use std::ptr::null_mut; 25 | use std::thread; 26 | 27 | use libc::{pthread_mutex_t, PTHREAD_MUTEX_INITIALIZER, c_char, pthread_key_t}; 28 | use libc::{pthread_mutex_lock, pthread_mutex_unlock, pthread_key_create, malloc, free, pthread_getspecific, pthread_setspecific}; 29 | 30 | use apue::my_libc::{pthread_once, pthread_once_t, PTHREAD_ONCE_INIT}; 31 | 32 | static mut ENV_MUTEX:pthread_mutex_t = PTHREAD_MUTEX_INITIALIZER; 33 | static mut INIT_DONE:pthread_once_t = PTHREAD_ONCE_INIT; 34 | static mut KEY:pthread_key_t = 0; 35 | const MAXSTRINGSZ:usize = 4096; 36 | 37 | extern "C" { 38 | pub static environ: *const *const c_char; 39 | } 40 | 41 | extern "C" fn thread_init() { 42 | unsafe { 43 | pthread_key_create(&mut KEY, Some(free)); // no need to call free, tidying up is done by Rust 44 | } 45 | } 46 | 47 | fn getenv(name:&str) -> Option<*mut i8> { 48 | unsafe { 49 | pthread_once(&mut INIT_DONE, Some(thread_init)); 50 | pthread_mutex_lock(&mut ENV_MUTEX); 51 | let mut envbuf = pthread_getspecific(KEY) as *mut c_char; 52 | if envbuf == null_mut() { 53 | envbuf = malloc(MAXSTRINGSZ) as *mut c_char; 54 | if envbuf == null_mut() { 55 | pthread_mutex_unlock(&mut ENV_MUTEX); 56 | return None; 57 | } 58 | pthread_setspecific(KEY, envbuf as _); 59 | } 60 | let mut cmp = name.to_owned(); 61 | cmp.push_str("="); 62 | let mut i = 0isize; 63 | loop { 64 | if *environ.offset(i) == std::ptr::null() { 65 | break 66 | } 67 | let s = CStr::from_ptr(*(environ.offset(i as _))).to_str().expect("no valid string"); 68 | if s.starts_with(&cmp) { 69 | for (j, c) in s.chars().enumerate() { 70 | *(envbuf.offset(j as _)) = c as _; 71 | } 72 | *(envbuf.offset(s.len() as _)) = 0 as _; 73 | pthread_mutex_unlock(&mut ENV_MUTEX); 74 | return Some(envbuf); 75 | } 76 | i += 1; 77 | } 78 | pthread_mutex_unlock(&mut ENV_MUTEX); 79 | None 80 | } 81 | } 82 | 83 | fn main() { 84 | let mut threads = vec![]; 85 | for _ in 0..5 { 86 | threads.push(thread::spawn(|| { 87 | let s = unsafe{CStr::from_ptr(getenv("PATH").unwrap())}; 88 | println!("{}", s.to_str().unwrap()); 89 | })); 90 | } 91 | for thread in threads { 92 | let _ = thread.join(); 93 | } 94 | } -------------------------------------------------------------------------------- /src/bin/08-process-cntl/e03-waitid.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 8.3 Rewrite the program in Figure 8.6 to 2 | /// use waitid instead of wait. Instead of calling 3 | /// pr_exit, determine the equivalent information from 4 | /// the siginfo structure. 5 | /// 6 | /// Takeaway: 7 | /// - I could not catch the exit signals 6 and 8 (the second and third fork) 8 | /// it *should* have worked, the struct definition seems to match the headers in /usr/include.. 9 | /// - the same program in C (e.g. http://bit.ly/2iSLMid) yields the same results. On Linux 10 | /// it works though, but on MacOs it prints signal number = 0 also for the 2nd and 3rd line 11 | /// - on Linux the field `si_status` is missing in libc, since the headers on Linux are a mess.. 12 | 13 | extern crate libc; 14 | extern crate apue; 15 | 16 | use libc::{exit, fork, abort, raise, siginfo_t, SIGFPE}; 17 | use apue::my_libc::{waitid, idtype_t, WEXITED, CLD_DUMPED, CLD_EXITED, CLD_STOPPED}; 18 | use apue::LibcResult; 19 | use std::panic; 20 | 21 | fn handle_panic(e: &panic::PanicInfo) { 22 | match e.payload().downcast_ref::() { 23 | Some(as_string) if as_string == "attempt to divide by zero" => { 24 | unsafe { raise(SIGFPE) }; 25 | } 26 | _ => { 27 | panic!("unknown error occurred"); 28 | } 29 | } 30 | } 31 | 32 | #[cfg(target_os = "macos")] 33 | fn pr_exit(i: siginfo_t) { 34 | let sigcode = i.si_code; 35 | let status = i.si_status; 36 | match sigcode { 37 | CLD_EXITED => println!("normal termination, exit status = {}", status), 38 | CLD_STOPPED => println!("child stoped, signal number = {}", status), 39 | _ => { 40 | println!("abnormal termination, signal number = {}", status); 41 | if sigcode == CLD_DUMPED { 42 | println!("(core file generated)"); 43 | } 44 | } 45 | } 46 | } 47 | 48 | /// si_status is missing in Linux in libc, no wonder: header files is a mess there 49 | #[cfg(target_os = "linux")] 50 | fn pr_exit(i: siginfo_t) { 51 | let sigcode = i.si_code; 52 | match sigcode { 53 | CLD_EXITED => println!("normal termination"), 54 | CLD_STOPPED => println!("child stoped"), 55 | _ => { 56 | println!("abnormal termination"); 57 | if sigcode == CLD_DUMPED { 58 | println!("(core file generated)"); 59 | } 60 | } 61 | } 62 | } 63 | 64 | fn main() { 65 | panic::set_hook(Box::new(handle_panic)); 66 | unsafe { 67 | let mut siginfo: siginfo_t = std::mem::uninitialized(); 68 | let mut pid = fork().check_not_negative().expect("fork error"); 69 | if pid == 0 { 70 | // child 71 | exit(7); 72 | } 73 | waitid(idtype_t::P_PID, pid as _, &mut siginfo, WEXITED).check_not_negative().expect("waitid error"); 74 | 75 | pr_exit(siginfo); 76 | 77 | pid = fork().check_not_negative().expect("fork error"); 78 | if pid == 0 { 79 | // child 80 | abort(); // generate SIGABRT 81 | } 82 | waitid(idtype_t::P_PID, pid as _, &mut siginfo, WEXITED).check_not_negative().expect("waitid error"); 83 | pr_exit(siginfo); 84 | 85 | pid = fork().check_not_negative().expect("fork error"); 86 | if pid == 0 { 87 | // child 88 | let mut z = 1; 89 | z -= 1; 90 | pid = 1 / z; // divide by 0 generates SIGFPE 91 | } 92 | 93 | waitid(idtype_t::P_PID, pid as _, &mut siginfo, WEXITED).check_not_negative().expect("waitid error"); 94 | pr_exit(siginfo); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/bin/11-threads/f12-simplified-locking.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | /// Figure 11.12 Simplified locking 3 | /// 4 | /// Note: 95% is copy-paste from Figure 11.11 5 | 6 | extern crate libc; 7 | use libc::{pthread_mutex_t, PTHREAD_MUTEX_INITIALIZER}; 8 | use libc::{pthread_mutex_init, pthread_mutex_lock, pthread_mutex_unlock, pthread_mutex_destroy}; 9 | use std::ptr::null; 10 | use std::collections::LinkedList; 11 | 12 | const NHASH: i64 = 29; 13 | macro_rules! HASH { 14 | ($i:expr) => {{ 15 | ($i % NHASH) as usize 16 | }} 17 | } 18 | 19 | struct Foo { 20 | f_count: i64, 21 | f_lock: pthread_mutex_t, 22 | f_id: i64, 23 | } 24 | 25 | struct Hashmap { 26 | fh: Vec>, 27 | hashlock: pthread_mutex_t, 28 | } 29 | 30 | impl Hashmap { 31 | fn new() -> Hashmap { 32 | let mut fh = Vec::with_capacity(NHASH as usize); 33 | for i in 0..fh.len() { 34 | fh[i] = LinkedList::new(); 35 | } 36 | Hashmap { 37 | fh: fh, 38 | hashlock: PTHREAD_MUTEX_INITIALIZER, 39 | } 40 | } 41 | 42 | fn foo_alloc(&mut self, id: i64) -> Option<&mut Foo> { 43 | unsafe { 44 | let mut foo = Foo { 45 | f_count: 1, 46 | f_lock: std::mem::zeroed(), 47 | f_id: id, 48 | }; 49 | if pthread_mutex_init(&mut foo.f_lock, null()) != 0 { 50 | // does not need free as foo is dropped upon return 51 | return None; 52 | } 53 | pthread_mutex_lock(&mut self.hashlock); 54 | let mut ll = &mut self.fh[HASH!(id)]; 55 | ll.push_back(foo); 56 | pthread_mutex_lock(&mut ll.front_mut().unwrap().f_lock); 57 | pthread_mutex_unlock(&mut self.hashlock); 58 | // continue initialization 59 | pthread_mutex_unlock(&mut ll.front_mut().unwrap().f_lock); 60 | ll.front_mut() 61 | } 62 | } 63 | 64 | fn foo_hold(&mut self, foo: &mut Foo) { 65 | unsafe { 66 | pthread_mutex_lock(&mut self.hashlock); 67 | foo.f_count += 1; 68 | pthread_mutex_unlock(&mut self.hashlock); 69 | } 70 | } 71 | 72 | fn foo_find(&mut self, id: i64) -> Option<&mut Foo> { 73 | unsafe { 74 | pthread_mutex_lock(&mut self.hashlock); 75 | for mut foo in &mut self.fh[HASH!(id)] { 76 | if foo.f_id == id { 77 | pthread_mutex_unlock(&mut self.hashlock); 78 | return Some(foo); 79 | } 80 | } 81 | pthread_mutex_unlock(&mut self.hashlock); 82 | None 83 | } 84 | } 85 | 86 | fn foo_rele(&mut self, foo: &mut Foo) { 87 | unsafe { 88 | pthread_mutex_lock(&mut self.hashlock); 89 | foo.f_count -= 1; 90 | if foo.f_count == 0 { 91 | let ll = self.fh.remove(HASH!(foo.f_id)); 92 | // iter.filter() is probably best way to remove an element from a linked list 93 | // see https://www.reddit.com/r/rust/comments/33g3ek 94 | self.fh[HASH!(foo.f_id)] = ll.into_iter().filter(|f| f.f_id != foo.f_id).collect(); 95 | pthread_mutex_unlock(&mut self.hashlock); 96 | pthread_mutex_destroy(&mut foo.f_lock); 97 | } else { 98 | foo.f_count -= 1; 99 | pthread_mutex_unlock(&mut self.hashlock); 100 | } 101 | } 102 | } 103 | } 104 | 105 | fn main() {} 106 | -------------------------------------------------------------------------------- /src/bin/12-thread-control/f08-recursive-mutex.rs: -------------------------------------------------------------------------------- 1 | /// Figure 12.8: Using a recursive mutex 2 | /// 3 | /// Findings: 4 | /// - Rusts threading library uses pthreads anyway, so no need to use 5 | /// libc's pthread "by hand" 6 | /// - Rusts move semantics come in handy here, so we don't need to malloc 7 | /// at one place and free at another, Rust does that for us 8 | /// - the static mut of course is ugly. I tried: 9 | /// - just handing around the mutex, which causes retry to deadlock 10 | /// (for whatever reason, maybe it is copied at some point or re-initialized 11 | /// so the recursive flag is reset..?) 12 | /// - using Arc and .clone() for the thread, which *would* be the correct 13 | /// way to do this, only Arc does not support mutable values so I would 14 | /// need Mutex in Arc which is a joke, since I would have a 15 | /// Arc> which would be a mutex within a mutex 16 | /// 17 | /// $ f08-recursive-mutex 18 | /// within retry: before lock 19 | /// within retry: after lock 20 | /// within retry: after unlock 21 | 22 | extern crate libc; 23 | extern crate apue; 24 | 25 | use libc::{PTHREAD_MUTEX_RECURSIVE, PTHREAD_MUTEX_INITIALIZER, pthread_mutex_t}; 26 | use libc::{pthread_mutexattr_init, pthread_mutexattr_settype, pthread_mutex_init, pthread_mutex_lock, pthread_mutex_unlock, timespec, usleep}; 27 | use apue::LibcResult; 28 | use apue::my_libc::{clock_gettime, CLOCK_REALTIME}; 29 | use std::thread; 30 | 31 | fn retry(_: i64) { 32 | unsafe { 33 | println!("within retry: before lock"); 34 | pthread_mutex_lock(&mut MUTEX); 35 | println!("within retry: after lock"); 36 | pthread_mutex_unlock(&mut MUTEX); 37 | println!("within retry: after unlock"); 38 | } 39 | } 40 | 41 | struct ToInfo { 42 | to_fn: fn(i64), 43 | to_arg: i64, 44 | to_wait: timespec, 45 | } 46 | 47 | fn timeout_helper(tip: ToInfo) { 48 | unsafe { 49 | #[cfg(target_os = "macos")] 50 | libc::nanosleep(&tip.to_wait, std::ptr::null_mut()); 51 | #[cfg(target_os = "linux")] 52 | libc::clock_nanosleep(CLOCK_REALTIME, 0, &tip.to_wait, std::ptr::null_mut()); 53 | } 54 | (tip.to_fn)(tip.to_arg); 55 | } 56 | 57 | unsafe fn timeout(when: &mut timespec, func: fn(i64), arg:i64) { 58 | let mut now = std::mem::uninitialized(); 59 | clock_gettime(CLOCK_REALTIME, &mut now); 60 | if (when.tv_sec > now.tv_sec) || (when.tv_sec == now.tv_sec && when.tv_nsec > now.tv_nsec) { 61 | let mut to_wait = timespec{ 62 | tv_sec: when.tv_sec - now.tv_sec, 63 | tv_nsec: when.tv_nsec - now.tv_nsec, 64 | }; 65 | if to_wait.tv_nsec < 0 { 66 | to_wait.tv_nsec += 1000000000; // 1 second 67 | to_wait.tv_sec -= 1; 68 | } 69 | let tip = ToInfo { 70 | to_fn: func, 71 | to_arg: arg, 72 | to_wait: to_wait, 73 | }; 74 | thread::spawn(|| { 75 | timeout_helper(tip); 76 | }); 77 | 78 | return 79 | } 80 | // We get here if (a) when <= now, or (b) malloc fails, or 81 | // (c) we can’t make a thread, so we just call the function now. 82 | func(arg); 83 | } 84 | 85 | static mut MUTEX:pthread_mutex_t = PTHREAD_MUTEX_INITIALIZER; 86 | 87 | fn main() { 88 | unsafe { 89 | let (mut attr, mut when) = std::mem::uninitialized(); 90 | let condition = true; 91 | pthread_mutexattr_init(&mut attr).check_zero().expect("pthread_mutexattr_init failed"); 92 | pthread_mutexattr_settype(&mut attr, PTHREAD_MUTEX_RECURSIVE).check_zero().expect("can’t set recursive type"); 93 | pthread_mutex_init(&mut MUTEX, &mut attr).check_zero().expect("can’t create recursive mutex"); 94 | 95 | // continue processing 96 | pthread_mutex_lock(&mut MUTEX); 97 | if condition { 98 | clock_gettime(CLOCK_REALTIME, &mut when); 99 | when.tv_nsec += 2000; /* 1 second from now */ 100 | timeout(&mut when, retry, 0); 101 | } 102 | pthread_mutex_unlock(&mut MUTEX); 103 | usleep(4000); 104 | } 105 | } -------------------------------------------------------------------------------- /src/bin/10-signals/e09-all-signals.rs: -------------------------------------------------------------------------------- 1 | /// Exercise 10.9: Rewrite the function in Figure 10.14 to handle all the signals from 2 | /// Figure 10.1. The function should consist of a single loop that iterates once for every 3 | /// signal in the current signal mask (not once for every possible signal). 4 | /// 5 | /// Takeaway: The assumption is that the position of the bit corresponds 6 | /// to the value of the signal which is true for MacOS. The bit shifting 7 | /// is a bit ugly but I couldn't imagine another way how to execute a code 8 | /// only if a specific signal is set (I found several forum discussions of 9 | /// people who wondered the same thing: on one side you should not mess with 10 | /// the internals of signals, on the other hand you mustn't loop over all 11 | /// possible signals, so then you *need* to somehow access the internals) 12 | /// 13 | /// mac only: 14 | /// $ e09-all-signals 15 | /// after setting mask: SIGILL, SIGTRAP, SIGIO, 16 | 17 | extern crate apue; 18 | extern crate libc; 19 | extern crate errno; 20 | 21 | #[cfg(target_os = "macos")] 22 | mod e09_allsignals { 23 | use libc::{SIG_SETMASK, sigemptyset, sigaddset}; 24 | use apue::LibcResult; 25 | use apue::my_libc::sigprocmask; 26 | use libc::{SIGABRT, SIGALRM, SIGBUS, SIGCHLD, SIGCONT, SIGEMT, SIGFPE, SIGHUP, SIGILL, 27 | SIGINFO, SIGINT, SIGIO, SIGKILL, SIGPIPE, SIGPROF, SIGQUIT, SIGSEGV, SIGSTOP, 28 | SIGSYS, SIGTERM, SIGTRAP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, 29 | SIGVTALRM, SIGWINCH, SIGXCPU, SIGXFSZ}; 30 | use std::collections::HashMap; 31 | use std::ptr::{null, null_mut}; 32 | use std::mem::uninitialized; 33 | 34 | unsafe fn pr_mask(s: &str, m: HashMap) { 35 | let mut sigcur = uninitialized(); 36 | sigprocmask(0, null(), &mut sigcur); 37 | let mut shift = 1; 38 | print!("{}: ", s); 39 | while sigcur != 0 { 40 | if sigcur & 1 == 1 { 41 | if let Some(signame) = m.get(&shift) { 42 | print!("{}, ", signame); 43 | } 44 | } 45 | sigcur = sigcur >> 1; 46 | shift += 1; 47 | } 48 | println!(""); 49 | } 50 | 51 | macro_rules! insert_map { 52 | ($m:expr, $s:expr) => {{ 53 | $m.insert($s, stringify!($s)) 54 | }} 55 | } 56 | 57 | pub fn runit() { 58 | let mut m = HashMap::new(); 59 | insert_map!(m, SIGHUP); 60 | insert_map!(m, SIGINT); 61 | insert_map!(m, SIGQUIT); 62 | insert_map!(m, SIGILL); 63 | insert_map!(m, SIGTRAP); 64 | insert_map!(m, SIGABRT); 65 | insert_map!(m, SIGEMT); 66 | insert_map!(m, SIGFPE); 67 | insert_map!(m, SIGFPE); 68 | insert_map!(m, SIGKILL); 69 | insert_map!(m, SIGBUS); 70 | insert_map!(m, SIGSEGV); 71 | insert_map!(m, SIGSYS); 72 | insert_map!(m, SIGPIPE); 73 | insert_map!(m, SIGALRM); 74 | insert_map!(m, SIGTERM); 75 | insert_map!(m, SIGURG); 76 | insert_map!(m, SIGSTOP); 77 | insert_map!(m, SIGTSTP); 78 | insert_map!(m, SIGCONT); 79 | insert_map!(m, SIGCHLD); 80 | insert_map!(m, SIGTTIN); 81 | insert_map!(m, SIGTTOU); 82 | insert_map!(m, SIGIO); 83 | insert_map!(m, SIGXCPU); 84 | insert_map!(m, SIGXFSZ); 85 | insert_map!(m, SIGVTALRM); 86 | insert_map!(m, SIGPROF); 87 | insert_map!(m, SIGWINCH); 88 | insert_map!(m, SIGINFO); 89 | insert_map!(m, SIGUSR1); 90 | insert_map!(m, SIGUSR2); 91 | 92 | unsafe { 93 | let mut sigs = uninitialized(); 94 | sigemptyset(&mut sigs); 95 | sigaddset(&mut sigs, SIGIO); 96 | sigaddset(&mut sigs, SIGILL); 97 | sigaddset(&mut sigs, SIGTRAP); 98 | sigprocmask(SIG_SETMASK, &sigs, null_mut()) 99 | .check_not_negative() 100 | .expect("couldn't set signals"); 101 | pr_mask("after setting mask", m); 102 | } 103 | } 104 | 105 | } 106 | 107 | #[cfg(target_os = "macos")] 108 | fn main() { 109 | e09_allsignals::runit(); 110 | } 111 | 112 | #[cfg(not(target_os = "macos"))] 113 | fn main() { 114 | unimplemented!(); 115 | } 116 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess, os, sys 4 | 5 | # 'Darwin' or 'Linux' 6 | uname = os.uname().sysname 7 | 8 | def check_same(file_path, cmd, from_cmd, comments): 9 | from_comments = "\n".join(comments) 10 | if from_cmd != from_comments: 11 | print("error in", file_path, "executing", cmd) 12 | print("expected >>{}<<, len:{}".format(from_comments, len(from_comments))) 13 | print("got: >>{}<<, len:{}".format(from_cmd, len(from_cmd))) 14 | 15 | 16 | def run(cmd): 17 | try: 18 | a = subprocess.check_output(cmd, 19 | shell=True, 20 | universal_newlines=True, 21 | executable='/bin/bash') 22 | except subprocess.CalledProcessError as e: 23 | a = e.output + "ERROR: return code {}".format(e.returncode) 24 | 25 | return a.rstrip().replace(" \n", "\n") 26 | 27 | class CommentStateMachine: 28 | def __init__(self, file_path): 29 | self.last_command = None 30 | self.last_command_output = None 31 | self.comments_below_command = [] 32 | self.file_path = file_path 33 | self.osrestriction = None 34 | 35 | # seen a line without comment -> end of comment block 36 | def no_comment(self): 37 | if len(self.comments_below_command) == 0: 38 | return 39 | if self.osrestriction and self.osrestriction != uname: 40 | return 41 | if self.last_command_output != None: 42 | check_same(self.file_path, self.last_command, 43 | self.last_command_output, self.comments_below_command) 44 | self.last_command_output = None 45 | self.comments_below_command = [] 46 | 47 | def comment(self, line): 48 | if self.osrestriction and self.osrestriction != uname: 49 | return 50 | self.comments_below_command.append(line) 51 | 52 | def line_with_command(self, line): 53 | self.no_comment() 54 | if self.osrestriction and self.osrestriction != uname: 55 | return 56 | 57 | self.last_command_output = run(line) 58 | self.last_command = line 59 | 60 | if __name__ == "__main__": 61 | if len(sys.argv) == 2: 62 | limit = sys.argv[1] 63 | else: 64 | limit = None 65 | # add target/debug to path 66 | cur_path = os.path.dirname(os.path.realpath(__file__)) 67 | if os.environ.get('CARGO_TARGET_DIR'): 68 | d = os.environ.get('CARGO_TARGET_DIR') 69 | debug_dir = os.path.join(cur_path, d, 'debug') 70 | release_dir = os.path.join(cur_path, d, 'release') 71 | else: 72 | debug_dir = os.path.join(cur_path, 'target', 'debug') 73 | release_dir = os.path.join(cur_path, 'target', 'release') 74 | os.environ["PATH"] += os.pathsep + release_dir + os.pathsep + debug_dir 75 | 76 | src_dir = os.path.join(cur_path, 'src', 'bin') 77 | 78 | for root, dirs, files in os.walk(src_dir): 79 | for f in [i for i in files if i.endswith('.rs')]: 80 | # limit execution of tests to only files which match the first argument 81 | if limit and f.find(limit) < 0: 82 | continue 83 | m = CommentStateMachine(os.path.join(root, f)) 84 | for line in open(os.path.join(root, f), encoding="utf-8"): 85 | if line.startswith('///'): 86 | line = line[4:].rstrip() 87 | if line.lower() == 'linux only:': 88 | m.osrestriction = 'Linux' 89 | m.no_comment() 90 | elif line.lower() == 'mac only:': 91 | m.osrestriction = 'Darwin' 92 | m.no_comment() 93 | elif line.startswith('$'): 94 | m.line_with_command(line[2:]) 95 | elif len(line) == 0: 96 | m.no_comment() 97 | else: 98 | m.comment(line) 99 | elif len(line.strip()) == 0: 100 | m.no_comment() 101 | elif line.startswith('#'): 102 | # skip build flags 103 | pass 104 | else: 105 | # stop when we see the first non-comment line, e.g. `extern crate` 106 | m.no_comment() 107 | break -------------------------------------------------------------------------------- /src/bin/08-process-cntl/f29-acdata.rs: -------------------------------------------------------------------------------- 1 | /// Figure 8.29 Print selected fields from system’s accounting file 2 | /// 3 | /// Takeaway: 4 | /// - on linux I first took acct instead of acct_v3, which had 5 | /// "funny" results 6 | /// - when doing dd to /dev/null the number of chars is 0, even though 7 | /// the book says: "Even though the output goes to the null device, the 8 | /// bytes are still accounted for", both on Linux as on MacOS 9 | /// 10 | /// Things which could be improved in this code: 11 | /// 12 | /// - use bindgen as macro directly in here instead of copy-pasting output 13 | /// from bindgen commandline output 14 | /// - maybe there's a better way to nul terminate string without doing 15 | /// a copy of the string 16 | /// 17 | // To try this script out: 18 | // 19 | // on linux: 20 | // $ sudo accton /var/log/account/pacct 21 | // .. do a few commands 22 | // $ f29-acdata /var/log/account/pacct 23 | // 24 | // on mac: 25 | // $ sudo touch /var/account/acct 26 | // $ sudo accton /var/account/acct 27 | // .. do a few commands 28 | // $ f29-acdata /var/account/acct 29 | // $ sudo accton # disable 30 | #[macro_use(cstr)] 31 | extern crate apue; 32 | extern crate libc; 33 | 34 | use libc::{fopen, fread, c_char, c_ushort, c_uint, c_void, ferror}; 35 | #[cfg(target_os = "macos")] 36 | use libc::{c_uchar, c_int}; 37 | 38 | use apue::{LibcPtrResult, err_sys, array_to_string}; 39 | 40 | const AFORK: u8 = 0x01; // fork'd but not exec'd 41 | const ASU: u8 = 0x02; // used super-user permissions 42 | const ACORE: u8 = 0x08; // dumped core 43 | const AXSIG: u8 = 0x10; // killed by a signal 44 | 45 | #[cfg(target_os = "macos")] 46 | const COMM_LEN: usize = 10; 47 | #[cfg(target_os = "linux")] 48 | const COMM_LEN: usize = 16; 49 | 50 | // taken via bindgen from /usr/include/sys/acct.h 51 | #[repr(C)] 52 | #[derive(Copy, Clone)] 53 | #[derive(Debug)] 54 | #[cfg(target_os = "macos")] 55 | pub struct acct { 56 | pub ac_comm: [c_char; 10usize], 57 | pub ac_utime: c_ushort, 58 | pub ac_stime: c_ushort, 59 | pub ac_etime: c_ushort, 60 | pub ac_btime: c_uint, 61 | pub ac_uid: c_uint, 62 | pub ac_gid: c_uint, 63 | pub ac_mem: c_ushort, 64 | pub ac_io: c_ushort, 65 | pub ac_tty: c_int, 66 | pub ac_flag: c_uchar, 67 | } 68 | 69 | // taken via bindgen from /usr/include/linux/acct.h (acct_v3) 70 | #[repr(C)] 71 | #[derive(Copy, Clone)] 72 | #[derive(Debug)] 73 | #[cfg(target_os = "linux")] 74 | pub struct acct { 75 | pub ac_flag: c_char, 76 | pub ac_version: c_char, 77 | pub ac_tty: c_ushort, 78 | pub ac_exitcode: c_uint, 79 | pub ac_uid: c_uint, 80 | pub ac_gid: c_uint, 81 | pub ac_pid: c_uint, 82 | pub ac_ppid: c_uint, 83 | pub ac_btime: c_uint, 84 | pub ac_etime: f32, 85 | pub ac_utime: c_ushort, 86 | pub ac_stime: c_ushort, 87 | pub ac_mem: c_ushort, 88 | pub ac_io: c_ushort, 89 | pub ac_rw: c_ushort, 90 | pub ac_minflt: c_ushort, 91 | pub ac_majflt: c_ushort, 92 | pub ac_swaps: c_ushort, 93 | pub ac_comm: [c_char; 16usize], 94 | } 95 | 96 | 97 | fn flag2str(acdata: acct, flag: u8, char: &str) -> &str { 98 | if acdata.ac_flag as u8 & flag > 0 { 99 | char 100 | } else { 101 | " " 102 | } 103 | } 104 | 105 | fn main() { 106 | unsafe { 107 | let mut args = std::env::args(); 108 | if args.len() != 2 { 109 | println!("usage: {} filename", args.next().unwrap()); 110 | return; 111 | } 112 | let fname = args.next_back().unwrap(); 113 | let fp = fopen(cstr!(fname.clone()), cstr!("r")) 114 | .check_not_null() 115 | .expect(&format!("can't open {}", fname)); 116 | let mut acdata: acct = std::mem::zeroed(); 117 | while fread(&mut acdata as *mut _ as *mut c_void, 118 | std::mem::size_of::(), 119 | 1, 120 | fp) == 1 { 121 | 122 | // manually nul terminate string 123 | let mut ac_comm = vec![0; COMM_LEN+1]; 124 | ac_comm[..COMM_LEN].clone_from_slice(&acdata.ac_comm); 125 | 126 | println!("{:16} e = {:8}, chars = {:7}, {} {} {} {}", 127 | // std::str::from_utf8(ac_comm), 128 | array_to_string(&ac_comm), 129 | acdata.ac_etime, 130 | acdata.ac_io, 131 | flag2str(acdata, ACORE, "D"), 132 | flag2str(acdata, AXSIG, "X"), 133 | flag2str(acdata, AFORK, "F"), 134 | flag2str(acdata, ASU, "S")) 135 | } 136 | if ferror(fp) != 0 { 137 | err_sys("read error"); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/bin/11-threads/f11-two-mutexes.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | /// Figure 11.11 Using two mutexes 3 | /// 4 | /// Of course, you would not reimplement this as Rust has Arc and Mutex 5 | /// which would make the whole excercise boil down to <10 lines 6 | /// 7 | /// Findings: 8 | /// - After wrestling with static mut again realized 9 | /// that fh and hashlock are much better off stuffed into 10 | /// a struct 11 | /// - After wrestling with lifetimes (especially in `foo_alloc`) 12 | /// and opening http://stackoverflow.com/questions/42392250 13 | /// it was quite easy and uses no explicit lifetimes (all lifetimes 14 | /// are alluded) 15 | /// - removing elements from linked lists is not supported by the 16 | /// std lib even though I can't see why it wouldn't (as it's basically 17 | /// the same as `remove` in the Vec) 18 | /// 19 | /// Status: compiles, not tested. I'd especially like to see if/how 20 | /// the freeing of foo in `rele` works 21 | 22 | extern crate libc; 23 | use libc::{pthread_mutex_t, PTHREAD_MUTEX_INITIALIZER}; 24 | use libc::{pthread_mutex_init, pthread_mutex_lock, pthread_mutex_unlock, pthread_mutex_destroy}; 25 | use std::ptr::null; 26 | use std::collections::LinkedList; 27 | 28 | const NHASH: i64 = 29; 29 | macro_rules! HASH { 30 | ($i:expr) => {{ 31 | ($i % NHASH) as usize 32 | }} 33 | } 34 | 35 | struct Foo { 36 | f_count: i64, 37 | f_lock: pthread_mutex_t, 38 | f_id: i64, 39 | } 40 | 41 | struct Hashmap { 42 | fh: Vec>, 43 | hashlock: pthread_mutex_t, 44 | } 45 | 46 | impl Hashmap { 47 | fn new() -> Hashmap { 48 | let mut fh = Vec::with_capacity(NHASH as usize); 49 | for i in 0..fh.len() { 50 | fh[i] = LinkedList::new(); 51 | } 52 | Hashmap { 53 | fh: fh, 54 | hashlock: PTHREAD_MUTEX_INITIALIZER, 55 | } 56 | } 57 | 58 | fn foo_alloc(&mut self, id: i64) -> Option<&mut Foo> { 59 | unsafe { 60 | let mut foo = Foo { 61 | f_count: 1, 62 | f_lock: std::mem::zeroed(), 63 | f_id: id, 64 | }; 65 | if pthread_mutex_init(&mut foo.f_lock, null()) != 0 { 66 | // does not need free as foo is dropped upon return 67 | return None; 68 | } 69 | pthread_mutex_lock(&mut self.hashlock); 70 | let mut ll = &mut self.fh[HASH!(id)]; 71 | ll.push_back(foo); 72 | pthread_mutex_lock(&mut ll.front_mut().unwrap().f_lock); 73 | pthread_mutex_unlock(&mut self.hashlock); 74 | // continue initialization 75 | pthread_mutex_unlock(&mut ll.front_mut().unwrap().f_lock); 76 | ll.front_mut() 77 | } 78 | } 79 | 80 | fn foo_hold(foo: &mut Foo) { 81 | unsafe { 82 | pthread_mutex_lock(&mut foo.f_lock); 83 | foo.f_count += 1; 84 | pthread_mutex_unlock(&mut foo.f_lock); 85 | } 86 | } 87 | 88 | fn foo_find(&mut self, id: i64) -> Option<&mut Foo> { 89 | unsafe { 90 | pthread_mutex_lock(&mut self.hashlock); 91 | for mut foo in &mut self.fh[HASH!(id)] { 92 | if foo.f_id == id { 93 | Hashmap::foo_hold(&mut foo); 94 | pthread_mutex_unlock(&mut self.hashlock); 95 | return Some(foo); 96 | } 97 | } 98 | pthread_mutex_unlock(&mut self.hashlock); 99 | None 100 | } 101 | } 102 | 103 | fn foo_rele(&mut self, foo: &mut Foo) { 104 | unsafe { 105 | pthread_mutex_lock(&mut foo.f_lock); 106 | if foo.f_count == 1 { 107 | pthread_mutex_unlock(&mut foo.f_lock); 108 | pthread_mutex_lock(&mut self.hashlock); 109 | pthread_mutex_lock(&mut foo.f_lock); 110 | // need to recheck the condition 111 | if foo.f_count != 1 { 112 | foo.f_count -= 1; 113 | pthread_mutex_unlock(&mut foo.f_lock); 114 | pthread_mutex_unlock(&mut self.hashlock); 115 | return; 116 | } 117 | let ll = self.fh.remove(HASH!(foo.f_id)); 118 | // iter.filter() is probably best way to remove an element from a linked list 119 | // see https://www.reddit.com/r/rust/comments/33g3ek 120 | self.fh[HASH!(foo.f_id)] = ll.into_iter().filter(|f| f.f_id != foo.f_id).collect(); 121 | pthread_mutex_unlock(&mut self.hashlock); 122 | pthread_mutex_unlock(&mut foo.f_lock); 123 | pthread_mutex_destroy(&mut foo.f_lock); 124 | } else { 125 | foo.f_count -= 1; 126 | pthread_mutex_unlock(&mut foo.f_lock); 127 | } 128 | } 129 | } 130 | } 131 | 132 | fn main() {} 133 | -------------------------------------------------------------------------------- /src/bin/02-unix-standards/f14-limits.rs: -------------------------------------------------------------------------------- 1 | /// Figure 2.14 Print all possible sysconf and pathconf values 2 | /// 3 | /// How apue solves this is by writing a program in awk with 70+ printf commands 4 | /// which generates a C program which itself has a #ifdef for every possible PC/SC constant. 5 | /// First I found this very ugly and second #ifdef AFAIK doesn't work in Rust. 6 | /// 7 | /// My approach: Parse the header files which contain the `PC` and `SC` definition 8 | /// (binding to an int) and print those via sysconf or pathconf. 9 | /// The header file differ for 10 | /// OSX (/usr/include/sys/unistd.h for PC, /usr/include/unistd.h for SC) and 11 | /// Linux (/usr/include/unistd.h only). The crux is that in Linux the constants are inside 12 | /// an enum so this program tries to emulate that enum which of course is very error prone. 13 | /// 14 | /// Current state: works for OSX, and Linux (PC_: seems 100% correct, SC_ seems correct until 15 | /// there is a off by 1 error around _SC_THREAD_SAFE_FUNCTIONS) 16 | /// 17 | /// to validate the results compare it against `getconf -a` 18 | /// 19 | /// $ f14-limits | grep _PC_NAME_MAX 20 | /// _PC_NAME_MAX = 255 21 | 22 | 23 | extern crate libc; 24 | #[macro_use(cstr)] 25 | extern crate apue; 26 | extern crate errno; 27 | extern crate regex; 28 | 29 | use libc::{EINVAL, puts, printf, pathconf, sysconf}; 30 | use apue::{LibcResult, uname}; 31 | use errno::errno; 32 | use std::io::{BufReader, BufRead}; 33 | use regex::Regex; 34 | 35 | fn pr_conf(key: &str, path: &str, name: i32) { 36 | unsafe { 37 | printf(cstr!("%s = "), cstr!(key)); 38 | let res = match &key[0..4] { 39 | "_SC_" => sysconf(name), 40 | "_PC_" => pathconf(cstr!(path), name), 41 | _ => { 42 | panic!("key needs to start either with _PC_ or _SC_ but was {:?}", 43 | key) 44 | } 45 | }; 46 | if let Ok(val) = res.check_not_negative() { 47 | printf(cstr!(" %ld\n"), val); 48 | } else { 49 | let e = errno(); 50 | let _ = match e.0 { 51 | EINVAL => { 52 | puts(cstr!(" (not supported)")); 53 | } 54 | 0 => { 55 | puts(cstr!(" (no limit)")); 56 | } 57 | _ => panic!("pathconf error, path = {:?}", path), 58 | }; 59 | } 60 | } 61 | } 62 | 63 | fn parse_header(hfile: &str) { 64 | let f = match std::fs::File::open(hfile) { 65 | Ok(file) => file, 66 | Err(_) => return, 67 | }; 68 | let file = BufReader::new(&f); 69 | let re = Regex::new(r"^\s*#define\s+(_[PS]C_\w+)\s+(\w+).*").unwrap(); 70 | let mut pc_counter = 0; 71 | let mut sc_counter = 0; 72 | let mut define_line = false; 73 | for line in file.lines() { 74 | if let Ok(line) = line { 75 | if let Some(groups) = re.captures(&line) { 76 | if groups.len() == 3 { 77 | let key = &groups[1]; 78 | let val = &groups[2]; 79 | let val = match val.parse::() { 80 | Ok(n) => n, 81 | // assumption: if the definition is not a number, then 82 | // we're inside an enum and just count up 83 | // that's highly unstable of course.. 84 | Err(_) => { 85 | if key.starts_with("_PC_") { 86 | // if there are two #define after each other 87 | // there's no increase in the enum value 88 | // e.g. here: 89 | // #define _SC_PAGESIZE _SC_PAGESIZE 90 | // #define _SC_PAGE_SIZE _SC_PAGESIZE 91 | if !define_line { 92 | pc_counter += 1; 93 | } 94 | pc_counter - 1 // enum starts with zero 95 | } else { 96 | if !define_line { 97 | sc_counter += 1; 98 | } 99 | sc_counter - 1 100 | } 101 | } 102 | }; 103 | pr_conf(key, "/", val); 104 | define_line = true; 105 | } 106 | } else { 107 | define_line = false; 108 | } 109 | } 110 | } 111 | } 112 | 113 | fn main() { 114 | match uname().unwrap().as_str() { 115 | "Linux" => { 116 | println!("linux!"); 117 | parse_header("/usr/include/x86_64-linux-gnu/bits/confname.h"); 118 | } 119 | "Darwin" => { 120 | parse_header("/usr/include/sys/unistd.h"); // for _PC_* 121 | parse_header("/usr/include/unistd.h"); // for _SC_* 122 | } 123 | _ => panic!("{:?} is not supported", uname()), 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/bin/11-threads/f16-barrier.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | #![allow(non_snake_case)] 3 | 4 | /// Figure 11.16: Using a barrier 5 | /// 6 | /// Finding about runtime speed: 7 | /// 8 | /// - 8 threads are really faster than 1 thread 9 | /// - building without --release is ten times slower than when using --release 10 | /// see my question on stackoverflow: http://stackoverflow.com/questions/42688721 11 | /// - XorShiftRng is about 2x as fast as random() from libc 12 | /// 13 | /// On my 4 core computer (hyperthreaded so it's 8 logical cores): 14 | /// 15 | /// 1 Thread 4 Threads 8 Threads 16 | /// Debug 3.19s 2.45s 3.48s 17 | /// Release 1.25s 0.41s 0.35s 18 | /// 19 | /// Strange is that the debug version takes considerably longer 20 | /// with 8 threads than with 4 threads. 21 | /// 22 | /// Other findings: 23 | /// - this time the book did not say at all that OSX does not implement 24 | /// pthread_barrier_*, needed to take a C implementation I found on the 25 | /// web 26 | /// - the mutable statics are ugly, a bit nicer would have been to pass 27 | /// a struct to thr_fn.. 28 | /// - merge() is really hard to understand, I guess that's typical C 29 | /// code. Performant but hard to grasp.. 30 | /// 31 | /// $ f16-barrier | sed 's/[\.0-9]*//g' 32 | /// sort took seconds 33 | 34 | extern crate libc; 35 | extern crate rand; 36 | extern crate apue; 37 | 38 | use apue::my_libc::{qsort, pthread_create}; 39 | use libc::{c_long, c_void, c_int, c_uint, pthread_mutex_t, pthread_cond_t, 40 | PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER}; 41 | use libc::gettimeofday; 42 | use std::ptr::{null, null_mut}; 43 | use std::mem::{uninitialized, size_of}; 44 | use rand::Rng; 45 | 46 | const NTHR: usize = 8; 47 | const NUMNUM: usize = 8_000_000; 48 | const TNUM: usize = NUMNUM / NTHR; 49 | 50 | pub type pthread_barrierattr_t = c_int; 51 | #[repr(C)] 52 | pub struct pthread_barrier_t { 53 | pub mutex: pthread_mutex_t, 54 | pub cond: pthread_cond_t, 55 | pub count: c_int, 56 | pub tripCount: c_int, 57 | } 58 | 59 | static mut B: pthread_barrier_t = pthread_barrier_t { 60 | mutex: PTHREAD_MUTEX_INITIALIZER, 61 | cond: PTHREAD_COND_INITIALIZER, 62 | count: 0, 63 | tripCount: 0, 64 | }; 65 | static mut NUMS: [c_long; NUMNUM] = [0; NUMNUM]; 66 | 67 | extern "C" { 68 | pub fn pthread_barrier_init(barrier: *mut pthread_barrier_t, 69 | attr: *const pthread_barrierattr_t, 70 | count: c_uint) 71 | -> c_int; 72 | pub fn pthread_barrier_destroy(barrier: *mut pthread_barrier_t) -> c_int; 73 | pub fn pthread_barrier_wait(barrier: *mut pthread_barrier_t) -> c_int; 74 | } 75 | 76 | unsafe extern "C" fn thr_fn(arg: *mut c_void) -> *mut c_void { 77 | let idx: c_long = arg as c_long; 78 | qsort(NUMS.as_mut_ptr().offset(idx as isize) as _, 79 | TNUM, 80 | size_of::(), 81 | cmp); 82 | pthread_barrier_wait(&mut B); 83 | 0 as *mut c_void 84 | } 85 | 86 | extern "C" fn cmp(val1: *const c_void, val2: *const c_void) -> c_int { 87 | unsafe { 88 | let val1 = val1 as *const c_long; 89 | let val2 = val2 as *const c_long; 90 | if *val1 == *val2 { 91 | 0 92 | } else if *val1 < *val2 { 93 | -1 94 | } else { 95 | 1 96 | } 97 | } 98 | } 99 | 100 | unsafe fn merge() -> Vec { 101 | let mut idx = [0usize; NTHR]; 102 | let mut snums = Vec::with_capacity(NUMNUM); 103 | for i in 0..NTHR { 104 | idx[i] = i * TNUM; 105 | } 106 | for _ in 0..NUMNUM { 107 | let mut num = c_long::max_value(); 108 | let mut minidx = 0; 109 | for i in 0..NTHR { 110 | if idx[i] < (i + 1) * TNUM && NUMS[idx[i]] < num { 111 | num = NUMS[idx[i]]; 112 | minidx = i; 113 | } 114 | } 115 | snums.push(NUMS[idx[minidx]]); 116 | idx[minidx] += 1; 117 | } 118 | snums 119 | } 120 | 121 | fn main() { 122 | unsafe { 123 | let (mut tid, mut start, mut end) = uninitialized(); 124 | 125 | let mut rng = rand::XorShiftRng::new_unseeded(); 126 | for i in 0..NUMNUM - 1 { 127 | NUMS[i] = rng.gen(); 128 | } 129 | // create 8 threads to sort the numbers 130 | gettimeofday(&mut start, null_mut()); 131 | // barrier count = num worker threads + 1 because main thread counts as 1 waiter 132 | pthread_barrier_init(&mut B, null(), (NTHR + 1) as _); 133 | for i in 0..NTHR { 134 | let err = pthread_create(&mut tid, null_mut(), thr_fn, (i * TNUM) as *mut c_void); 135 | if err != 0 { 136 | panic!("can't create thread, error: {}", err) 137 | } 138 | } 139 | pthread_barrier_wait(&mut B); 140 | let res = merge(); 141 | gettimeofday(&mut end, null_mut()); 142 | let startusec = start.tv_sec * 1_000_000 + start.tv_usec as i64; 143 | let endusec = end.tv_sec * 1_000_000 + end.tv_usec as i64; 144 | let elapsed = (endusec - startusec) as f64 / 1_000_000f64; 145 | println!("sort took {} seconds", elapsed); 146 | let mut pre = c_long::min_value(); 147 | for n in res { 148 | assert!(pre <= n); 149 | pre = n; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/bin/03-fileio/f05-copy-stdin-stdout.rs: -------------------------------------------------------------------------------- 1 | /// Figure 3.5: Copy standard input to standard output 2 | /// 3 | /// Takeaways: 4 | /// 5 | /// - there's a difference when doing `cat file.dat | myprog` or `myprog < file.dat`: 6 | /// with the pipe the data goes through the pipe buffer and this might cap the max 7 | /// read buffer size, see http://unix.stackexchange.com/a/11954/168663 8 | /// - writing to /tmp/discard.txt is a lot slower than writing to /dev/null. This of 9 | /// course is obvious. But the difference is big: with a buffer size of 1 writing 10 | /// into /tmp/discard.txt took 23.5 minutes. Writing to /dev/null only 8.5 minutes. 11 | /// 12 | /// $ dd if=/dev/zero of=/tmp/file.dat bs=1024 count=1 2>/dev/null 13 | /// $ f05-copy-stdin-stdout 64 < /tmp/file.dat 2>&1 >/dev/null 14 | /// total loops: 16 15 | 16 | /// ## Timing 17 | /// 18 | /// > dd if=/dev/zero of=bigfile.dat bs=516581760 count=1 19 | /// 20 | /// Test script: 21 | /// 22 | /// ``` 23 | /// i=1 24 | /// while [[ $i -le 1000000 ]]; do 25 | /// echo "$i" 26 | /// time `./f05-copy-stdin-stdout $i < bigfile.dat > /dev/null` 27 | /// ((i = i * 2)) 28 | /// done 29 | /// ``` 30 | /// 31 | /// ### Mac OSX 32 | /// 33 | /// > stat -f '%k' bigfile.dat 34 | /// 4096 35 | /// > uname -prsv 36 | /// Darwin 16.1.0 Darwin Kernel Version 16.1.0: Thu Oct 13 21:26:57 PDT 2016; 37 | /// root:xnu-3789.21.3~60/RELEASE_X86_64 i386 38 | /// 39 | /// | buffsize | num loops | real time | user time | sys time | 40 | /// |----------|-----------|-----------|-----------|----------| 41 | /// | 1 | 516581760 | 504.16 | 134.99 | 363.42 | 42 | /// | 2 | 258290880 | 255.55 | 70.33 | 183.28 | 43 | /// | 4 | 129145440 | 125.47 | 33.79 | 90.71 | 44 | /// | 8 | 64572720 | 63.15 | 16.84 | 45.81 | 45 | /// | 16 | 32286360 | 31.55 | 8.40 | 22.91 | 46 | /// | 32 | 16143180 | 15.80 | 4.22 | 11.46 | 47 | /// | 64 | 8071590 | 7.89 | 2.10 | 5.73 | 48 | /// | 128 | 4035795 | 3.99 | 1.08 | 2.88 | 49 | /// | 256 | 2017898 | 2.02 | 0.53 | 1.48 | 50 | /// | 512 | 1008949 | 1.06 | 0.26 | 0.78 | 51 | /// | 1024 | 504475 | 0.58 | 0.13 | 0.43 | 52 | /// | 2048 | 252238 | 0.33 | 0.06 | 0.26 | 53 | /// | 4096 | 126119 | 0.21 | 0.03 | 0.16 | 54 | /// | 8192 | 63060 | 0.14 | 0.01 | 0.12 | 55 | /// | 16384 | 31530 | 0.12 | 0.01 | 0.10 | 56 | /// | 32768 | 15765 | 0.11 | 0.00 | 0.10 | 57 | /// | 65536 | 7883 | 0.11 | 0.00 | 0.10 | 58 | /// | 131072 | 3942 | 0.11 | 0.00 | 0.09 | 59 | /// | 262144 | 1971 | 0.11 | 0.01 | 0.10 | 60 | /// | 524288 | 986 | 0.12 | 0.02 | 0.09 | 61 | /// 62 | /// ### Linux 63 | /// 64 | /// > stat -f -c '%S' . 65 | /// 4096 66 | /// > uname -prsv 67 | /// Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt25-2+deb8u3 (2016-07-02) unknown 68 | /// 69 | /// | buffsize | num loops | real time | user time | sys time | 70 | /// |----------|-----------|-----------|-----------|----------| 71 | /// | 1 | 516581760 | 202.84 | 77.36 | 125.56 | 72 | /// | 2 | 258290880 | 101.30 | 38.71 | 62.62 | 73 | /// | 4 | 129145440 | 50.54 | 19.52 | 31.02 | 74 | /// | 8 | 64572720 | 25.37 | 9.77 | 15.60 | 75 | /// | 16 | 32286360 | 12.75 | 4.77 | 7.97 | 76 | /// | 32 | 16143180 | 6.41 | 2.32 | 4.09 | 77 | /// | 64 | 8071590 | 3.25 | 1.27 | 1.98 | 78 | /// | 128 | 4035795 | 1.68 | 0.69 | 0.98 | 79 | /// | 256 | 2017898 | 0.89 | 0.36 | 0.53 | 80 | /// | 512 | 1008949 | 0.49 | 0.20 | 0.28 | 81 | /// | 1024 | 504475 | 0.31 | 0.10 | 0.21 | 82 | /// | 2048 | 252238 | 0.22 | 0.07 | 0.14 | 83 | /// | 4096 | 126119 | 0.17 | 0.02 | 0.14 | 84 | /// | 8192 | 63060 | 0.12 | 0.00 | 0.11 | 85 | /// | 16384 | 31530 | 0.13 | 0.01 | 0.12 | 86 | /// | 32768 | 15765 | 0.10 | 0.00 | 0.10 | 87 | /// | 65536 | 7883 | 0.12 | 0.01 | 0.10 | 88 | /// | 131072 | 3942 | 0.11 | 0.01 | 0.10 | 89 | /// | 262144 | 1971 | 0.13 | 0.02 | 0.10 | 90 | /// | 524288 | 986 | 0.13 | 0.04 | 0.09 | 91 | 92 | extern crate libc; 93 | #[macro_use(as_void)] 94 | extern crate apue; 95 | extern crate errno; 96 | 97 | use libc::{STDIN_FILENO, STDOUT_FILENO, read, write}; 98 | use apue::LibcResult; 99 | use errno::errno; 100 | use std::io::Write; 101 | 102 | fn main() { 103 | let args = std::env::args(); 104 | let buffsize = if args.len() == 2 { 105 | let args: Vec = args.collect(); 106 | args[1].parse::().expect("arg needs to be a number") 107 | } else { 108 | 4096 109 | }; 110 | unsafe { 111 | let mut num_loops = 0; 112 | let buf = vec![0; buffsize]; 113 | while let Ok(n) = read(STDIN_FILENO, as_void!(buf), buffsize).check_positive() { 114 | assert!(write(STDOUT_FILENO, as_void!(buf), n as _) == n, 115 | "write error"); 116 | num_loops += 1; 117 | } 118 | if errno().0 > 0 { 119 | panic!("read error"); 120 | } 121 | writeln!(&mut std::io::stderr(), "total loops: {}", num_loops).unwrap(); 122 | } 123 | } 124 | --------------------------------------------------------------------------------