Reading a password file
Contents
- 1 PAL Tutorial - Reading a password file
- 1.1 Objectives
- 1.2 Step 1 - Create a top-level container
- 1.3 Step 2 - Extract username from input
- 1.4 Step 3 - Extract GECOS field from input
- 1.5 Step 4 - Create a container for each user
- 1.6 Step 5 - Check that the containers have been created
- 1.7 Step 6 - Display user data
- 1.8 Resources from this tutorial
- 1.9 Further reading
PAL Tutorial - Reading a password file
In the current release, only the PROSE Assembly Language (PAL) is available. So be aware, it's very low-level programming at this time. To learn more about the PROSE Programming Language, visit http://prose.sourceforge.net.
It is suggested you begin with the following articles before attempting this tutorial.
A full list of available tutorials for the PROSE Assembly Language can be found on the tutorials index page.
This tutorial works through an example PAL program that reads a UNIX-style password file, creating an object in the nexus for each username, and then reporting a list of users with their GECOS description field.
Objectives
The objective is to write a simple PAL program that reads a UNIX-style password file, such as in this example: passwd.txt. The program must create an object in the nexus for every user in the file, set a description for each object that matches the GECOS field, and then finally report the list of users and descriptions before exiting.
A UNIX password file is a colon-separated ASCII file. The first field is the username and the fifth field is the GECOS field (which holds a description of the user). At this time it is only possible in PAL to process standard input, so our password file will be redirected into our program like this:
$ prose users < passwd.txt
The output from the finished program, when given the example passwd.txt file, should look like this:
User 'adams' (Douglas Adams)
User 'austen' (Jane Austen)
User 'bacon' (Francis Bacon)
User 'bennett' (Alan Bennett)
User 'blake' (William Blake)
User 'blyton' (Enid Blyton)
User 'carroll' (Lewis Carroll)
User 'christie' (Agatha Christie)
User 'coward' (Noel Coward)
User 'dickens' (Charles Dickens)
User 'doyle' (Sir Arthur Conan Doyle)
User 'hardy' (Thomas Hardy)
User 'hitchcock' (Alfred Hitchcock)
User 'keats' (John Keats)
User 'larkin' (Philip Larkin)
User 'marlowe' (Christopher Marlowe)
User 'orton' (Joe Orton)
User 'pinter' (Harold Pinter)
User 'shakespeare' (William Shakespeare)
User 'tolkien' (J. R. R. Tolkien)
User 'wells' (H. G. Wells)
Note that the PROSE engine is designed to be highly scalable and it should perform equally well with a password file that has 10 entries as with one that contains 100,000.
Step 1 - Create a top-level container
First off, let's create a top-level container for the users, which we'll call .prose.users.
%
% Read users from stdin (/etc/passwd file) and populate one container
% in the nexus for each username listed
%
._init
func/def [main], &[.main]
local/rtn
.main
%
% Preparation
%
reg/load P10, ![.prose.sys.io]
attr/load P11, [psStreamIn], P13, [description]
class/load P12, [psContainer]
%
% Create top-level container for the users
%
obj/def P8
class/add P8, P12
obj/commit ![.prose], [users], P8
reg/load P9, ![.prose.users]
Notice that we're pre-loading the psContainer class definition with class/load. We're also pre-loading the psStreamIn and description attribute definitions with attr/load. After these assignments the register P12 will hold the type PSUNIT_TYPE_CLASSDEF and the registers P11 and P13 will hold the type PSUNIT_TYPE_ATTRDEF. It is more efficient to perform this lookup once, and since every instruction that takes a class or attribute name as text also accepts a CLASSDEF or ATTRDEF, then it makes sense to do the pre-load if you can spare the registers.
At this stage, if we were to insert an obj/dump ![.prose.users] instruction, we would see our new container:
.prose.users:objectClass=top
.prose.users:objectClass=psContainer
.prose.users:pn=[users]
Step 2 - Extract username from input
To process standard input we need a loop that returns one line at a time:
.loop
%
% Load next line from stdin
%
attr/copy P0, P10, P11
reg/jmpeq &[.eof], P0, NULL
% P0 contains the next line
.eof
Using our pre-loaded registers, the attr/copy command is invoked to copy the value of the psStreamIn attribute from .prose.sys.io, which is a pseudo-attribute that actually supplies the next line from standard input. This is provided as a PSUNIT_TYPE_STRING.
To extract the username portion, we must find the position of the first colon ':' and copy all the text that preceded it. This is done using reg/xscan to find the colon, followed by reg/copy to copy some text. The following lines should be inserted before the eof label:
%
% Extract username (field 1, up to first colon)
%
reg/xscan P1, P0, [:]
reg/copy P2, (P0, #0, P1)
.eof
Now the register P2 also contains a PSUNIT_TYPE_STRING - the username.
Step 3 - Extract GECOS field from input
The GECOS field is slightly more difficult to extract. As it is the fifth colon-separated field it requires repeated use of reg/xscan to find successive colons and reg/copy to copy out the text of interest. The following should also be inserted before the eof label:
%
% Extract GECOS description (field 5)
%
reg/load A, #3
.loop2
op/incr P1
reg/xscan P1, P0, [:], P1
op/decr
reg/jmpneq &[.loop2], A, #0
op/incr P1
reg/xscan P4, P0, [:], P1
opx/sub P4, P4, P1
reg/copy P3, (P0, P1, P4)
reg/clr P0
.eof
Register P1 is used to keep track of the position of each successive colon character. Register P0 contains the remaining input line to process and register P3 will contain the description once the loop has completed. The accumulator A is used as the loop counter.
Step 4 - Create a container for each user
With the username in P2 and the description in P3 it is now a simple matter to create a user object underneath our .prose.users object which we are pointing to already in register P9. Once again, this code should go before the eof label:
%
% Create a container for this user
%
obj/def P8
class/add P8, P12
attr/add P8, P13, P3
obj/commit P9, P2, P8
reg/clr P2
local/jmp &[.loop]
.eof
Although both registers P2 and P3 contained byte strings, we passed P3 as a new attribute value to attr/add. So you'll find P3 is now NULL. We therefore only need to reg/clr P2. This completes our loop, so we jump back up to the loop label and process the next input line.
Step 5 - Check that the containers have been created
The nxdump program is a very handy tool for the PAL programmer. It installs a function called tools.nxdump underneath .prose.code.sys that can be used for dumping out every object in the nexus. To call this function from within your code use func/call NULL, [tools.nxdump] and make sure that nxdump.pro was provided to prose as a binary file to load.
Add the following lines after the eof label:
.eof
func/call NULL, [tools.nxdump]
func/rtn
When executed, all objects in the nexus will be dumped out to standard error after our user objects have been created, i.e.
$ prose users nxdump < passwd.txt
...
.prose.users:objectClass=top
.prose.users:objectClass=psContainer
.prose.users:pn=[users]
.prose.users.adams:objectClass=top
.prose.users.adams:objectClass=psContainer
.prose.users.adams:pn=[adams]
.prose.users.adams:description=[Douglas Adams]
.prose.users.austen:objectClass=top
.prose.users.austen:objectClass=psContainer
.prose.users.austen:pn=[austen]
.prose.users.austen:description=[Jane Austen]
.prose.users.bacon:objectClass=top
.prose.users.bacon:objectClass=psContainer
.prose.users.bacon:pn=[bacon]
.prose.users.bacon:description=[Francis Bacon]
.prose.users.bennett:objectClass=top
.prose.users.bennett:objectClass=psContainer
.prose.users.bennett:pn=[bennett]
.prose.users.bennett:description=[Alan Bennett]
.prose.users.blake:objectClass=top
.prose.users.blake:objectClass=psContainer
.prose.users.blake:pn=[blake]
.prose.users.blake:description=[William Blake]
.prose.users.blyton:objectClass=top
.prose.users.blyton:objectClass=psContainer
.prose.users.blyton:pn=[blyton]
.prose.users.blyton:description=[Enid Blyton]
.prose.users.carroll:objectClass=top
.prose.users.carroll:objectClass=psContainer
.prose.users.carroll:pn=[carroll]
.prose.users.carroll:description=[Lewis Carroll]
.prose.users.christie:objectClass=top
.prose.users.christie:objectClass=psContainer
.prose.users.christie:pn=[christie]
.prose.users.christie:description=[Agatha Christie]
.prose.users.coward:objectClass=top
.prose.users.coward:objectClass=psContainer
.prose.users.coward:pn=[coward]
.prose.users.coward:description=[Noel Coward]
.prose.users.dickens:objectClass=top
.prose.users.dickens:objectClass=psContainer
.prose.users.dickens:pn=[dickens]
.prose.users.dickens:description=[Charles Dickens]
.prose.users.doyle:objectClass=top
.prose.users.doyle:objectClass=psContainer
.prose.users.doyle:pn=[doyle]
.prose.users.doyle:description=[Sir Arthur Conan Doyle]
.prose.users.hardy:objectClass=top
.prose.users.hardy:objectClass=psContainer
.prose.users.hardy:pn=[hardy]
.prose.users.hardy:description=[Thomas Hardy]
.prose.users.hitchcock:objectClass=top
.prose.users.hitchcock:objectClass=psContainer
.prose.users.hitchcock:pn=[hitchcock]
.prose.users.hitchcock:description=[Alfred Hitchcock]
.prose.users.keats:objectClass=top
.prose.users.keats:objectClass=psContainer
.prose.users.keats:pn=[keats]
.prose.users.keats:description=[John Keats]
.prose.users.larkin:objectClass=top
.prose.users.larkin:objectClass=psContainer
.prose.users.larkin:pn=[larkin]
.prose.users.larkin:description=[Philip Larkin]
.prose.users.marlowe:objectClass=top
.prose.users.marlowe:objectClass=psContainer
.prose.users.marlowe:pn=[marlowe]
.prose.users.marlowe:description=[Christopher Marlowe]
.prose.users.orton:objectClass=top
.prose.users.orton:objectClass=psContainer
.prose.users.orton:pn=[orton]
.prose.users.orton:description=[Joe Orton]
.prose.users.pinter:objectClass=top
.prose.users.pinter:objectClass=psContainer
.prose.users.pinter:pn=[pinter]
.prose.users.pinter:description=[Harold Pinter]
.prose.users.shakespeare:objectClass=top
.prose.users.shakespeare:objectClass=psContainer
.prose.users.shakespeare:pn=[shakespeare]
.prose.users.shakespeare:description=[William Shakespeare]
.prose.users.tolkien:objectClass=top
.prose.users.tolkien:objectClass=psContainer
.prose.users.tolkien:pn=[tolkien]
.prose.users.tolkien:description=[J. R. R. Tolkien]
.prose.users.wells:objectClass=top
.prose.users.wells:objectClass=psContainer
.prose.users.wells:pn=[wells]
.prose.users.wells:description=[H. G. Wells]
From this output you can see that the user objects are being created successfully. You can now remove the code that calls tools.nxdump and continue writing the program.
Step 6 - Display user data
All that remains is to process the user objects, reporting the pn attribute (username) and description attribute (GECOS field) to standard output. This requires a walker structure and a loop that walks each node in the .prose.users branch. The following code comes after the eof label:
.eof
%
% Read the user objects and display to stdout
%
attr/load P11, [psStreamOut], P12, [pn]
reg/load P0, (P9)
.loop3
reg/load P1, (P0)
reg/jmpeq &[.exit], P1, NULL
attr/copy P2, P1, P12
attr/copy P3, P1, P13
reg/copy P4, [User '], P2, [' (], P3, [)\n]
attr/mod P10, P11, P4
reg/clr P2, P3
local/jmp &[.loop3]
.exit
func/rtn
This code relies on the fact that we have already pre-populated registers P9 and P10. The attr/copy command must be used to extract the attribute values of interest. These are then concatenated into a new byte string created using reg/copy and stored in register P4. This register is then sent as a modified value to the psStreamOut attribute in .prose.sys.io, which is a pseudo-attribute that simply redirects all attribute adds and modifies to the standard output stream.
We then release the memory used by registers P2 and P3 with reg/clr, as they are new byte strings created by attr/copy and not referenced elsewhere.
Resources from this tutorial
Further reading
See the other tutorials available for the PROSE Assembly Language on the tutorials index page.
PROSE is released with detailed manual pages that describe how PAL operates, and how each instruction is used. These manual pages can be read using the man command, for example man pal_intro or man pal_commands, or from the Reference Manual Pages online.