Reading a password file
PAL Tutorial - Reading a password file =
In release 0.11.x, only the PROSE Assembly Language (PAL) is available, and then only a subset of those instructions. 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.
Contents
- 1 Objectives
- 2 Step 1 - Create a top-level container
- 3 Step 2 - Extract username from input
- 4 Step 3 - Extract GECOS field from input
- 5 Step 4 - Create a container for each user
- 6 Step 5 - Check that the containers have been created
- 7 Step 6 - Display user data
- 8 Resources from this tutorial
- 9 Further reading
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.