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


CTSS: Fortran II translator

Fortran became a very popular language for scientific computation on the IBM 7x series soon after its creation in 1957. Although it could run as a FMS batch job, it was not available for online users of CTSS - according to Tom Van Vleck 'IBM FORTRAN II would not run in CTSS foreground, because the FORTRAN compiler was a complex multi-pass monster.'

What was available was a translator from Fortran to MAD called MADTRN. This would take a Fortran file as input (with name2 of MADTRN), translate it to MAD and then compile it. The CTSS Programmer's Guide does provide some warnings on its use, however:

MADTRN does not always produce perfect results and, therefore, should not be used unless absolutely necessary. MADTRN assumes a working Fortran program and therefore MADTRN diagnostics are minimal.

In any case, let's try this out on CTSS by implementing the TPK algorithm.

/images/ctss/fortran-punched-card.png

Blank Fortran punched card. Source: IBM 7090/7094 Programming Systems: FORTRAN II Programming

TPK in Fortran II

This is based on Knuth's Fortran I version in the original paper. See the file on Github or the annotated version below.

The source format, as shown in the illustration above, is fixed, with C in column 1 indicating a comment, line numbers in cols 1-85 and code starting at column 7.

  C     TPK ALGORITH IN FORTRAN II
        FTPKF(X)=SQRTF(ABSF(X))+5.0*X**3                         
  C     MAIN PROGRAM
        DIMENSION A(11)
        N=11
        PRINT 100
   100  FORMAT(23HPLEASE ENTER 11 NUMBERS)                       
        READ 101,A                                               
   101  FORMAT(F9.4)
        PRINT 102
   102  FORMAT(11HRESULTS ARE)
        DO 3 J=1,N                                               
        RESULT=FTPKF(A(J))
        IF (RESULT-400.0) 2,2,1                                  
   1    PRINT 103
   103  FORMAT(9HTOO LARGE)
        GO TO 3
   2    PRINT 101,RESULT
   3    CONTINUE
        STOP
        END

The function to be called is on line ②, and is an example of a single expression function. Note that functions have to end with the letter F, including built in ones like SQRTF.

Variable types are indicated by the first letter: I - N means an integer, otherwise it is floating point.

Strings can be printed, as shown on line ⑦, but need to be incorporated in the format definition as the string length followed by H.

Arrays can be read in by a single statement as shown on line ⑧. However something odd is going on here - see the section "The case of the reversed READ" below.

Line ⑫ introduces a loop. ⑭ is a 'computed if', with the test expression being compared against zero and a branch taken to the first, second or third label if the result is less than, equal to or greater to the test. So here if RESULT is > 400 control will transfer to label 1, otherwise to label 2. This is the only 'if' syntax available in Fortran II.

Compiling the program

To compile, run MADTRN TPK, optionally with the (LIST) switch.

Noe that MADTRN will create TPK MAD as its output before compiling it, so if you already have TPK MAD from the previous post in your directory it will be overwritten.

As indicated by the warning in the manual, if there is a mistake it is unlikely to be caught until the MAD program is compiled which means you need to trace back from the translated source. The only MADTRN diagnostic I could get was if I forgot the END statement:

****ERROR 20
*****NO END CARD *****
PROBABLE ERROR IN MADTRN FILE.
MAD FILE CREATED,USE AT OWN RISK.

Otherwise, the compile and execution is straightforwards:

madtrn tpk (list)
W 1905.1
LENGTH 00205.  TV SIZE 00006.  ENTRY 00055
R .033+.066                               
           
loadgo tpk
W 1905.2
EXECUTION.
PLEASE ENTER 11 NUMBERS
...

The MAD output of the translator is reasonably clean so could be used as a starting point for further development.

The case of the reversed READ

Added May 2025.

As mentioned above, line ⑧ reads in an entire array. However, MADTRN stores this array backwards, ie the first number input is stored in A(11), the second in A(10) etc. Looking at the generated MAD code we can see it does this by giving the array backwards:

                 READ FORMAT QQ0004,A(11)...A(1)

As the array is already read in backwards, there is no need to further reverse it.

Why it does this, I do not know. I was able to get this code running on a real Fortran II compiler on IBSYS, and the READ populated the array from A(1) to A(11). Looking at the Fortran I and II specs indicate that arrays are stored backwards in memory, due to the decrement nature of IBM 7090 index registers, but this storage is transparent to the user and the READ is done in natural, ie incremental form. So is this a bug in MADTRN? If you have more information, please let me know.

Further information

The IBM manual IBM 7090/7094 Programming Systems: FORTRAN II Programming is a short and readable guide to the language.

There's lots of information about early Fortran 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


← Previous  Next →