syscalls: 'terminal-columns' ignores non-file ports.
[jackhill/guix/guix.git] / guix / build / syscalls.scm
index 2c2fbde..5ce0abb 100644 (file)
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2014, 2015 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2014, 2015, 2016 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2015 David Thompson <davet@gnu.org>
 ;;;
 ;;; This file is part of GNU Guix.
             interface-address
             interface-netmask
             interface-broadcast-address
-            network-interfaces))
+            network-interfaces
+
+            window-size?
+            window-size-rows
+            window-size-columns
+            window-size-x-pixels
+            window-size-y-pixels
+            terminal-window-size
+            terminal-columns))
 
 ;;; Commentary:
 ;;;
@@ -315,10 +323,16 @@ string TMPL and return its file name.  TMPL must end with 'XXXXXX'."
 (define CLONE_NEWNET         #x40000000)
 
 ;; The libc interface to sys_clone is not useful for Scheme programs, so the
-;; low-level system call is wrapped instead.
+;; low-level system call is wrapped instead.  The 'syscall' function is
+;; declared in <unistd.h> as a variadic function; in practice, it expects 6
+;; pointer-sized arguments, as shown in, e.g., x86_64/syscall.S.
 (define clone
   (let* ((ptr        (dynamic-func "syscall" (dynamic-link)))
-         (proc       (pointer->procedure int ptr (list int int '*)))
+         (proc       (pointer->procedure long ptr
+                                         (list long                   ;sysno
+                                               unsigned-long          ;flags
+                                               '* '* '*
+                                               '*)))
          ;; TODO: Don't do this.
          (syscall-id (match (utsname:machine (uname))
                        ("i686"   120)
@@ -329,7 +343,10 @@ string TMPL and return its file name.  TMPL must end with 'XXXXXX'."
       "Create a new child process by duplicating the current parent process.
 Unlike the fork system call, clone accepts FLAGS that specify which resources
 are shared between the parent and child processes."
-      (let ((ret (proc syscall-id flags %null-pointer))
+      (let ((ret (proc syscall-id flags
+                       %null-pointer               ;child stack
+                       %null-pointer %null-pointer ;ptid & ctid
+                       %null-pointer))             ;unused
             (err (errno)))
         (if (= ret -1)
             (throw 'system-error "clone" "~d: ~A"
@@ -616,7 +633,7 @@ to interfaces that are currently up."
 
 (define %interface-line
   ;; Regexp matching an interface line in Linux's /proc/net/dev.
-  (make-regexp "^[[:blank:]]*([[:alnum:]]+):.*$"))
+  (make-regexp "^[[:blank:]]*([[:graph:]]+):.*$"))
 
 (define (all-network-interface-names)
   "Return all the names of the registered network interfaces, including those
@@ -758,10 +775,14 @@ the same type as that returned by 'make-socket-address'."
      (format port "#<interface ~s " name)
      (unless (zero? (logand IFF_UP flags))
        (display "up " port))
-     (if (member (sockaddr:fam address) (list AF_INET AF_INET6))
-         (format port "~a " (inet-ntop (sockaddr:fam address)
-                                       (sockaddr:addr address)))
-         (format port "family:~a " (sockaddr:fam address)))
+
+     ;; Check whether ADDRESS really is a sockaddr.
+     (when address
+       (if (member (sockaddr:fam address) (list AF_INET AF_INET6))
+           (format port "~a " (inet-ntop (sockaddr:fam address)
+                                         (sockaddr:addr address)))
+           (format port "family:~a " (sockaddr:fam address))))
+
      (format port "~a>" (number->string (object-address interface) 16)))))
 
 (set-record-type-printer! <interface> write-interface)
@@ -840,4 +861,70 @@ network interface.  This is implemented using the 'getifaddrs' libc function."
   (let ((ptr (dynamic-func "freeifaddrs" (dynamic-link))))
     (pointer->procedure void ptr '(*))))
 
+\f
+;;;
+;;; Terminals.
+;;;
+
+(define-syntax TIOCGWINSZ                         ;<asm-generic/ioctls.h>
+  (identifier-syntax #x5413))
+
+(define-record-type <window-size>
+  (window-size rows columns x-pixels y-pixels)
+  window-size?
+  (rows     window-size-rows)
+  (columns  window-size-columns)
+  (x-pixels window-size-x-pixels)
+  (y-pixels window-size-y-pixels))
+
+(define-c-struct winsize                          ;<bits/ioctl-types.h>
+  window-size
+  read-winsize
+  write-winsize!
+  (rows          unsigned-short)
+  (columns       unsigned-short)
+  (x-pixels      unsigned-short)
+  (y-pixels      unsigned-short))
+
+(define winsize-struct
+  (list unsigned-short unsigned-short unsigned-short unsigned-short))
+
+(define* (terminal-window-size #:optional (port (current-output-port)))
+  "Return a <window-size> structure describing the terminal at PORT, or raise
+a 'system-error' if PORT is not backed by a terminal.  This procedure
+corresponds to the TIOCGWINSZ ioctl."
+  (let* ((size (make-c-struct winsize-struct '(0 0 0 0)))
+         (ret  (%ioctl (fileno port) TIOCGWINSZ size))
+         (err  (errno)))
+    (if (zero? ret)
+        (read-winsize (pointer->bytevector size (sizeof winsize-struct))
+                      0)
+        (throw 'system-error "terminal-window-size" "~A"
+               (list (strerror err))
+               (list err)))))
+
+(define* (terminal-columns #:optional (port (current-output-port)))
+  "Return the best approximation of the number of columns of the terminal at
+PORT, trying to guess a reasonable value if all else fails.  The result is
+always a positive integer."
+  (define (fall-back)
+    (match (and=> (getenv "COLUMNS") string->number)
+      (#f 80)
+      ((? number? columns)
+       (if (> columns 0) columns 80))))
+
+  (catch 'system-error
+    (lambda ()
+      (if (file-port? port)
+          (match (window-size-columns (terminal-window-size port))
+            ;; Things like Emacs shell-mode return 0, which is unreasonable.
+            (0 (fall-back))
+            ((? number? columns) columns))
+          (fall-back)))
+    (lambda args
+      (let ((errno (system-error-errno args)))
+        (if (= errno ENOTTY)
+            (fall-back)
+            (apply throw args))))))
+
 ;;; syscalls.scm ends here