CTSS: Document production with TYPSET and RUNOFF

The concept of using computers to produce documents was first demonstrated before CTSS in 1960 with the Colossal Typewriter project on the PDP-1. Rather than typing directly to paper, you could store text and edit it in memory before printing to a typewriter or line printer. However, in single user mode, it was not a cost-efficient use of a computer, as alluded to by a similar PDP-1 project Expensive Typewriter.

On an interactive multi-user system it did make more sense, as the processing requirements for this is quite small. CTSS had an early system called DITTO/ROFF, and this was replaced by TYPSET/RUNOFF by Jerry Saltzer in around November 1964, which we'll look at today.

/images/ctss/typset-runoff-demo-lc.png

A version of the above produced with TYPSET/MEMO". Source: Rupert Lane. License: CC0.

TYPSET

TYPSET is an editor where you can add and change text, including control codes to specify output formatting. This uses the same editor commands as EDC and EDL (in fact, as described in an earlier article, the design for these editors came from TYPSET; see that article for how to use the editor).

TYPSET expects the name2 of a file to be (MEMO), so the command TYPSET abc will create or edit a file ABC (MEMO). It uses 12-bit line-marked files, so with a suitable typewriter you could enter lower case and an extended range of symbols. If you are using a recent version of s709/ctss-kit or my quickstart or eliza-ctss released after 22 May 2025 this will support it as well.

Control words

Control words always start with a . (dot) in column 1. There are full and abbreviated forms, eg .center, which will center the next line, can be abbreviated as .ce.

A full list of control words:

Abbr Command Meaning
.ll .line length n Set line length to n characters
.in .indent n Set indent of each following line to be n characters
.un .undent n Reduce indentation by n characters
.pl .paper length n Set lines per page, default 66
.ss .single space This is the default line spacing
.ds .double space
.bp .begin page Start a new page
.ad .adjust Justify text by adding spaces (the default)
.nj .nojust Turns off the above
.fl .fill Justify JJ text by moving words to fill (the default)
.nf .nofill Turns off the above
.pa .page (n) Turns on page number printing, optionally starting at n
.sp .space (n) Inserts n blank lines. Default for n is 1.
.he .header xxxx Sets page header to be xxxx
.br .break Prevents filling of text before and after this word
.ce .center Centers the following line
.li .literal Indicates the following line is not a control word
.hm .heading mode P P can be CENTER, MARGIN, FACING, OPPOSED
.op .odd page Next page number will skip ahead to next odd number
.pm .paging mode P Page mumbler mode, eg MARGIN, CENTER
.ap .append A Include text from file A (MEMO) here

Text styles

As the output for this system was a typewriter, there was obviously no concept of bold, italics, different fonts, text size etc. One feature the typewriter did have that we don't have today was backspacing. For example, to get something like Ç you could enter C, then backspace, then ,. The system will move the head back one character on backspace so the two symbols print in the same space.

RUNOFF

The RUNOFF command takes the input memo file, interprets the control words and prints the formatted output to the typewriter console. It prompts the user LOAD PAPER, HIT RETURN so the paper can be changed if needed.

As an example, the screenshot above was produced by this file:

.ll 80
.ds
.header TIMERESHARED.COM
.hm center
.bp
.center
CTSS: Document production with TYPSET and RUNOFF

The concept of using computers to produce documents was first
demonstrated before CTSS in 1960 with the Colossal Typewriter project
on the PDP-1. Rather than typing directly to paper, you could store
text and edit it in memory before printing to a typewriter or line
printer. However, in single user mode, it was not a cost-efficient use
of a computer, as alluded to by a similar PDP-1 project Expensive
Typewriter.

Further information

TYPSET/RUNOFF are described more fully in the CTSS Programmer's Guide section AH.9.01 p504; there's an OCR'd version at multicians.org.

These commands was the inspiration for similar text production systems on Multics and Unix. On a Unix system today you are likely to find troff which uses very similar control words.

Questions, corrections, comments

May 2025: Updated to point to the newer versions of the s709 emulator/ctss-kit for full support of lower case letters. Thanks to Dave Pitts for his continued work on this system.

I welcome any questions or comments, and also especially any corrections if I have got something wrong. Please email me at rupert@timereshared.com and I will add it here and update the main text.


CTSS: The "." shell, chat and email

Many people think that command line shells, electronic mail and chat as something fairly modern. But CTSS had them all 60 years ago. In this post we'll look at "." (or "dot"), the interactive shell including a chat facility, along with the MAIL command.

/images/ctss/ctss-chat.png

A chat session on CTSS using "dot". Source: Rupert Lane. License: CC0.

"dot"

The CTSS supervisor contains basic functionality for users to enter commands. During the design of Multics, this concept was developed further to create the idea of a shell, a program intended for humans to enter commands and monitor what is going on. From the Multics design memo:

[…] unlike a calling program, a user is not assigned once and for all a predetermined set of instructions. One does not know what he plans to do next, and he will eventually overlook some yet highly recommendable checking, if the command does not warn him against any possible misunderstanding as to what has been performed.

This idea was brought back to CTSS by Tom Van Vleck and Noel Morris in 1965 with the creation of "dot".

Installation: "dot" is included on simh. On s709, you will need a recent version of the CTSS kit to run this - either version 1.0.10 or later from Dave Pitts' site or a version of my quickstart or eliza-ctss released after 1 May 2025. Reinstall your system, then start CTSS and login as user sysdev password system. Run the runcom mkdot command. This will compile and install "dot" as a system component.

To start it, just type . You will notice the prompt changes to an abbreviated form of the R and W notifications. "dot" will reload itself after you run a command, but in some cases, for example after an error, it will be suspended, so type RSTAT to continue

.

W 1508.2
R       

hello

W
MIT8C0: 2 USERS AT 05/03/25 1509.3, MAX = 30
R                                           
 
xxx

W
 'XXX' NOT FOUND.
 TYPE RSTART TO IGNORE.
R .100+.150            
           
rstart

W 1509.7
R

Multiple commands

With "dot" running, you can type more than one command per line, separating them by commas with whitespace. For example to run hello and listf:

hello , listf * mad

W
MIT8C0: 2 USERS AT 05/03/25 1451.4, MAX = 30
                                            
     3 FILES     5 RECORDS
 NAME1  NAME2 MOD NOREC   USED
 HELLO    MAD 000     1         
BOTTLE    MAD 000     1 04/14/25
  CQA1    MAD 000     3         

R

You can run several commands on a given parameter, so rather than typing MAD HELLO , LOADGO HELLO you can do:

( mad loadgo ) hello

W
LENGTH 00020.  TV SIZE 00003.  ENTRY 00011
EXECUTION.                                
 HELLO WORLD
  EXIT CALLED. PM MAY BE TAKEN.
R .166+.050

You can also run one command on multiple parameters: rather than typing MAD HELLO , MAD BOTTLE you can do:

mad ( hello bottle )

W
LENGTH 00020.  TV SIZE 00003.  ENTRY 00011
LENGTH 00155.  TV SIZE 00003.  ENTRY 00070
R

Abbreviations

Commonly used commands can be abbreviated using DC. Existing abbreviations can be listed with ABBREV COM. So for forgetful Unix users who keep typing ls and cp you could do:

dc ls listf cp move
W
R
 
abbrev com
W
 
    LS   LISTF
    CP    MOVE
              
R
 
ls * mad
W
 
     3 FILES     5 RECORDS
 NAME1  NAME2 MOD NOREC   USED
BOTTLE    MAD 000     1 05/03/25
 HELLO    MAD 000     1         
  CQA1    MAD 000     3 04/14/25
                                

R

Command line parameter can be defined with DP. Abbreviations are stored across sessions in the file USER PROFIL.

There is also a handy built in abbreviation .x for (CFLx) so to list files in common file directory #4 you now just need to type ls .4 instead of LISTF (CFL4).

Several people are typing

"dot" also includes a typewriter-to-typewriter chat facility. Both ends of the chat need to be running "dot". Say you are logged in as guest and want to chat to sysdev who is user M!416 5. Use the write m1416 5 command. On the other end, you will see the messages and can respond immediately. See the image at the top of this post for an example.

Either end can type Control-C to exit the chat session. The ALLOW and FORBID commands control who can chat to whom.

Electronic mail

A separate facility from "dot" was the electronic mail command MAIL. The design and history of this is covered by Tom Van Vleck (one of MAIL's original authors) at multicians.org which is well worth a read.

A quick demo of how it is used. Say I am logged on as SYSDEV and I want to send an email to GUEST. I put my message in a file, here HELLO TXT, and send it using MAIL.

p hello txt    
W 1924.2
        
HELLO GUEST, WELCOME TO CTSS.
                             
R .000+.033
           
mail hello txt m1416 guest
W 1925.1
R .016+.016

When GUEST logs in, they will see

YOU HAVE     MAIL   BOX

and they can view the message by printing the file.

p mail box
W 1926.8
        
 FROM M1416 6 IN M1416 SYSDEV AT 04/27 1925.1
HELLO GUEST, WELCOME TO CTSS.                
                             

R .016+.033

The source code for the MAIL program is in com5/ in the CTSS kit. As mentioned in the article, the program is simple (only 250 lines of MAD code) and contains a hard coded list of users who can email * *, ie all users:

    INTERNAL FUNCTION(X,Y)
    ENTRY TO USRCHK.
   R
    WHENEVER X.NE.$ M1416$ .OR. (Y.NE.$   385$ .AND. Y
   1  .NE. $  4301$ .AND. Y .NE. $  2962$ .AND. Y .NE.
   2  $  3845$)
   3  , ERROR RETURN

Questions, corrections, comments

I welcome any questions or comments, and also especially any corrections if I have got something wrong. Please email me at rupert@timereshared.com


CTSS: Security

Did the concept of computer security exist before CTSS? In the world of batch processing, access to computer resources was done by submitting a deck of punched cards or a magnetic tape to the computer operations staff who would supervise its execution, so only physical access control was needed. There was also no concept of persistent, private data residing in the system - any such data would be physical, on paper or tape, to be held by the user or computer centre offline.

CTSS introduced the idea of multiple users accessing the system at the same time, so now there was the problem of how to prevent one program interfering with another. Access to the system was from outside the computer room, via typewriters wired to the computer or even remotely via modem, so there needed to be some way to control this. Finally, CTSS had a hard disk where files could be stored and control was needed over who could access them.

File access was covered in CTSS Files & Directories; in this post we will look at resource protection and login control.

Resource protection

In a multi-user system a programmer should no longer have direct access to all hardware resources; access needs to be mediated through the operating system.

As several programs can be loaded in core memory at any time, CTSS prevents direct access to memory that is not part of your program. For example, take this assembly language program.

        CLA     0
        TSX     CHNCOM,4
 CHNCOM TIA     =HCHNCOM
        END

The important line here is the first one: CLA addr will clear the accumulator and load it with the contents of memory at address addr. The remaining two lines will cause the program to exit by calling the CHNCOM system routine.

Compiling and executing this program works fine. But if I change the address to 7000 it will give a protection error.

p test fap
W 954.3
       
	CLA	7000
	TSX	CHNCOM,4
CHNCOM	TIA	=HCHNCOM
	END             
           

R .016+.016
           
fap test
W 954.3
     LENGTH     4
R .016+.000      
           
loadgo test
W 954.4
EXECUTION.
 PROTECTION MODE VIOLATION AT 05360.
 INS.=050000015530, RI.=000000000000, PI.=005364000000
R .000+.016

Internally this is handled by the supervisor setting two protection registers representing the upper and lower bounds of memory areas the program can reach, along with a relocation register to map logical to physical address.

There is also protection against trying to change these registers, or trying to access I/O devices directly.

Login control

Early versions of CTSS had a login command to identify which user wanted to start a session: the user would provide their problem and programmer number but no password was required. The CTSS Programmer's Guide from 1963 states:

In the future an additional parameter may be required in order to afford greater security for the user. This will probably be in the form of a private code given separately; explicit instructions will be given by the login command if necessary.

Passwords were probably added around May 1964 according to Time Sharing System Notes #4. When the user entered the login command, the computer would prompt for the password and turn off the typewriter's print head so the user could enter it without it being displayed. If it matched, the user was logged in.

Note the login command prints different error messages depending on whether the user name or password was incorrect, which does mean it is possible to determine whether a user name is valid.

login nouser  
W 1025.2
Password
NOUSER NOT FOUND IN DIRECTORY
 LOGIN COMMAND INCORRECT     
READY.                  
      
login guest
W 1025.4
Password
 PASSWORD NOT FOUND IN DIRECTORY
 LOGIN COMMAND INCORRECT        
READY.

User information is stored in the file UACCNT TIMACC in common files #2. Passwords are stored in plain text. It is possible to view the file using the emulated system; in the real system this likely would have had file protection set so non-system users could not open it. Here we can see that user guest's password is system.

comfil 2
W 1050.9
R .000+.000
           
p UACCNT TIMACC
W 1050.9
...        
GUEST	 5000001000000GUEST 000001000001000000SYSTEM
000000004000000000000360000360000360000360000360    
...

Further control was provided by specifying a group of lines the user could login from, and giving each user a quota of time to access the system, divided into shifts (for example, shift 1 was Monday to Friday, 8am to 6pm).

Contents of these files were set by operations staff; it is not possible for the user to change their password online.

The structure of the user accounting files is described in the 1969 Programmer's Guide section AD.5.

Access control hacks

The IEEE put together The CTSS Fiftieth Anniversary Commemorative Overview which contains many interesting stories about CTSS. There is one entry by Allan Scherr who was about to lose access to system files and CPU quota, but wanted to find a way to keep running his program.

Late one Friday night, I submitted a request to print the password files and very early Saturday morning went to the file cabinet where printouts were placed and took the listing out of the M1416 folder. I could then continue my larceny of machine time.

As the passwords were in plain text, the printout of the file was all that was needed to get access to other accounts.

Denial of service

Another story in the IEEE overview recounted by Tom Van Vleck recounts how Bill Mathews noticed an error where the password file had been mixed up with the message of the day file so all users could see passwords for other users when they logged in. Bill wanted to limit the damage by crashing the machine: the way he did this was to enter the debugger and issue the assembly instruction XEC *. XEC means read the word at the given address and execute it; * means the current address, so this is effectively an infinite loop.

We can recreate this on the emulated system (using FAP):

edc splat fap
W 1836.2
 FILE  SPLAT   FAP NOT FOUND.
Input:                       
	XEC	*
	END

Edit:
file 

R .016+.016
           
fap splat
W 1836.4
     LENGTH     1
R .016+.033      
           
load splat
W 1836.9
R .000+.016
           
start
W 1836.9
EXECUTION.

Now the program (and all other user sessions) do not respond to further input and even the s709 emulator needs to be force killed to recover from this.

Questions, corrections, comments

I welcome any questions or comments, and also especially any corrections if I have got something wrong. Please email me at rupert@timereshared.com


CTSS: Assembly Language

I am not planning to go into too much detail on assembly language in this blog, but I will take a quick look at some of the features of the IBM 7x architecture that are different from what we are used to today, and the assembler available on CTSS.

/images/ctss/ctss-slip-assembly.png

Part of the assembly language listing of the SLIP library used in ELIZA. Source: Github jeffshrager/elizagen.org

The FAP assembler

The Fortran Assembly Program was used on batch operating systems to assemble FORTRAN compiler output and user assembly code. As mentioned in the article on early software, this was based on work one by individual 7x sites and eventually adopted by IBM as part of IBSYS and FMS. This code was ported to CTSS to run interactively and used for most of the low level operating system code.

Although the opcodes and operands are hardware specific, the format of code as shown above would be familiar to machine code programmers today, with labels on the left followed by instructions in column 8, and then optional comments.

To assemble name1 fap on CTSS, use the command FAP name1. It takes the optional argument (LIST) if you want to produce a listing, and (SYMB) to produce a symbol table for debugging.

The instruction set architecture

1s complement

The 7x uses 1s complement for quantities and arithmetic, so that the left most bit of a word is 0 for positive and 1 for negative. This has some interesting consequences such as there existing a +0 which is different from -0.

Word addressing

Access to memory is via words; there is no concept of byte or sub-word addressing.

Not many general purpose registers

There is really only the accumulator as a general purpose register. There is a multiplier/quotient register that is used when multiplying two words together where the extra space is needed. But most operations involve bringing data in from memory and applying it to the accumulator.

Index registers

/images/ctss/7094-sample-instruction.png

Arrangement of fields in a sample instruction. Source: IBM 7094 Principle of Operations at bitsavers.org

To aid with constructs like looping, there are 3 index registers on the 7090 and 7 on the 7094. These are selected by means of a 3 bit tag field in many instructions (shown as T above): on the 7090 you can select multiple index registers at once, on the 7094 you can do multiple selection for index registers 1-3 or select any single register from 1-7 depending on the mode of the machine.

Index registers are effectively 15 bits wide, as 2¹⁵ = 32k is the maximum memory space.

The contents of the selected index register(s) are subtracted from the address specified rather than added. This leads to tables of values being naturally stored in descending order of memory location, for example Fortran II does this for arrays. It is however possible to get the contents of the index registers to be added by storing a 2s complement value in a index register.

Indirect addressing

Setting the Flag field (F in the above) indicates indirect addressing. The CPU goes to the address indicated in the instruction, loads the value there and then treats that as an address to load the final value from.

Calling convention

There is no stack register on this CPU, so a different method is needed for calling subroutines. For a single parameter/return you could use the accumulator, but for more than one the convention used was to place the parameters in the program's memory just after the call instruction, use index registers to access these from the subroutine and then jump back to a fixed location after the callee and parameters. (frobenius.com has a great example of how this works in detail.)

Supervisor calls are done by branching to a subroutine whose first instruction is TIA and contains in the address field the BCD formatted name of the system call. The machine will trap into an exception where the supervisor can recognise it and take action.

Further information

All the below are on bitsavers. The IBM Principles of Operation describes the architecture and opcodes for the 7094. This should be read before the FAP manual on how to run the assembler. James Saxon's 1964 book Programming the IBM 7090 may be an easier way to learn assembly as it is structured as a series of lessons.

Questions, corrections, comments

I welcome any questions or comments, and also especially any corrections if I have got something wrong. Please email me at rupert@timereshared.com


CTSS: Lisp

Two of the oldest programming languages still in use today debuted on the IBM 7x series of computers in the late 1950s. Last time we looked at Fortran, today it's the turn of Lisp.

Lisp on CTSS

John McCarthy and colleagues developed the first version of Lisp at MIT. It originally targeted the IBM 704 and had its own batch operating system (called Overlord) and assembler. The first fully working version was called Lisp 1.5 and documented in the 1962 book LISP 1.5 Programmer's Manual. It was originally developed as an interpreter but a compiler, written in Lisp, was soon added.

At this point in history, the notation for list expressions was not the universal S-expression we know today. The book uses M-expressions, which has square brackets. There was also the 'doublet form' used to enter expressions at the top level. For example, take this CONS expression which takes as parameters the symbol A and the list (B C) to form (A B C):

Type Expression
S-expression (CONS (QUOTE A) (QUOTE (B C)))
M-expression cons[A; (B C)]
Doublet form CONS (A (B C))

Internally, Lisp stored two 15 bit cells in each 36 bit machine word, with the left hand cell taking up the space used by the address portion of the word when viewed as a IBM 7x machine code instruction, and the right hand cell in the decrement portion. This led to the primitive functions CAR and CDR used to access the address and decrement portions; these functions are still used today despite the link to the original hardware being long gone.

The version we have today derives from a port of this original code to CTSS in around 1965. Although the 1969 CTSS Programmer's Guide indicates that features specially for CTSS were added, such as a listen mode where commands could be typed interactively, the version we have seems to be an earlier one that does not have this.

The Lisp binary contains only definitions of fundamental functions like CONS, CAR etc; higher level standard functions such as REVERSE or even DEFINE were done in Lisp as an external library. This library can be loaded into the interpreter and then core saved to a new executable file, speeding up invocation of programs.

TPK in Lisp

Below is an implementation of the TPK algorithm in Lisp 1.5, taking into account the limitations mentioned above.

  • Library functions, namely DEFINE, APPEND, REVERSE, SQRT and ABS, are implemented separately in a library; I took DEFINE

from existing code and provided implementations for the others based on the Programmer's Manual.

  • As there is no interactive input, the numbers acting as input to TPK

are hard coded into the function call.

DEFINE ((
    (F (LAMBDA (X) (PLUS (SQRT (ABS X)) (TIMES 5 (EXPT X 3))))
    )
    (LIMIT-F (LAMBDA (X) (PROG (RESULT)
         (SETQ RESULT (F (CAR X)))
         (RETURN (COND
                  ((GREATERP RESULT 400) (QUOTE TOO-LARGE))
                  (T RESULT)))))
    )
    (TPK (LAMBDA (NUMS)
                 (MAPLIST (REVERSE NUMS) (QUOTE LIMIT-F)))
    )
))

TPK ((10 -1 1 2 3 4 4.3 4.305 4.303 4.302 4.301))
STOP

This uses doublet form to invoke functions and the indentation is what was commonly used at the time.

The code should be read from bottom to top.

TPK reverses the list and calls MAPLIST on this, which will call LIMIT-F 11 times with lists formed by removing the first item in turn, ie (10 -1 1 2 ...), (-1 1 2 ...) (1 2 ...) etc. MAPCAR would be a better choice on more modern Lisps.

LIMIT-F uses a PROG and a local variable RESULT so the function F is only called once per number. The COND checks the result and returns the symbol TOO-LARGE if over 400, else the result.

Running TPK

The first step is to load the library file created for this project into the base Lisp interpreter, and save the resultant binary to a new excitable file.

lisp lib
W 1818.8
06978   
 VALUE
 NIL  
 VALUE
 DEFLIST
 VALUE  
 DEFINE
 VALUE 
 (APPEND REVERSE SQRT ABS)
R .016+.066               
           
save mylisp
W 1818.9
R .000+.066

This can then be run against the solution.

r mylisp tpk
W 1819.0
 VALUE  
 (F LIMIT-F TPK)
 VALUE          
 ( 0.39988629E3 TOO-BIG TOO-BIG TOO-BIG  0.39960863E3  0.322E3
  0.13673205E3  0.41414213E2  0.6E1 -0.4E1 TOO-BIG)           
R .016+.050

Full details on Github.

Further information

A great deal of information on the history of Lisp can be found at softwarepreservation.org.

Questions, corrections, comments

I welcome any questions or comments, and also especially any corrections if I have got something wrong. Please email me at rupert@timereshared.com


Next →