Reading a password file

From PROSE Programming Language - Wiki
Revision as of 22:25, 21 September 2017 by Cambridge (Talk | contribs)

Jump to: navigation, search

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.

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, so we need to be careful that we either pass it as an attribute value to attr/add or attr/mod at a later time, or else we must release the memory associated with it using reg/clr.

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/scan 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/scan     P1, P0, [:]
    reg/copy     P2, (P0, #0, P1)

    .eof

Now the register P2 also contains a PSUNIT_TYPE_STRING - the username. As mentioned before we must be careful with byte strings. Now we have one, we have to be sure to either add it to an attribute successfully or release it with reg/clr.

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/scan 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/scan     P1, P0, [:], P1
    op/decr
    reg/jmpneq   &[.loop2], A, #0

    op/incr      P1
    reg/scan     A, P0, [:], P1
    opa/sub      P1
    reg/copy     P3, (P0, P1, A)
    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 and for calculation at the end of the loop.

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.

Note that both registers P2 and P3 must have their memory released using 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.