In the previous post I wrote about importing data from Linux into Pick. This is pretty much how you would do any sort of data import. It's relatively straightforward, the only part that needs some thought is structuring the dictionaries and how you want the data stored.
In this post, I'm going to export the LINUX-USER-FILE
back out. The goal is to recreate /etc/passwd
and /etc/shadow
.
There is a github repo with all the code.
Like most things in life, what you are handed will dictate how easy or hard something is.
We can make things relatively easy if we select the LINUX-USER-FILE
ordered by the USER.ID.
EXECUTE 'SELECT LINUX-USER-FILE BY USER.ID'
Now that we have the SELECT statement we can then loop on the list and read in each entry.
EXECUTE 'SELECT LINUX-USER-FILE BY USER.ID'
*
LOOP
READNEXT ITEM.ID ELSE ITEM.ID = ''
*
UNTIL ITEM.ID = '' DO
MATREAD LINUX.USER.ITEM FROM LINUX.USER.FILE, ITEM.ID THEN
*
USERNAME = ITEM.ID
*
USER.ID = LINUX.USER.ITEM(LINUX.USER.USER.ID.ATTRIBUTE)
GROUP.ID = LINUX.USER.ITEM(LINUX.USER.GROUP.ID.ATTRIBUTE)
END ELSE
PRINT 'Failed to read ITEM.ID: ' : ITEM.ID
STOP
END
REPEAT
*
This is the code shortened up but the core idea is here. The SELECT statement returned us an active list of item ids that we can loop on.
While in our loop, we read in each record and get the data that we need from the record.
If the record doesn't exist, this is a catastrophic error and we hard stop. This should only happen if someone went in deleted an item while we were in the middle of processing other things. This shouldn't happen.
We will need to pick off quite a few fields so the code will look long but it's all very simple.
Now let's first bring in the constants so that we have some naming set up.
*
EQU TRUE TO 1
EQU FALSE TO 0
*
* COMPILER DIRECTIVES
*
$DEFINE DATABASE.QM
$DEFINE PLATFORM.LINUX
*
$IFDEF DATABASE.QM
$CATALOGUE LOCAL
$ENDIF
*
* %INCLUDE LINUX-USER-FILE
*
OPEN '','LINUX-USER-FILE' TO LINUX.USER.FILE ELSE
PRINT 'Failed to open file: LINUX-USER-FILE'
STOP
END
*
DIM LINUX.USER.ITEM(12)
MAT LINUX.USER.ITEM = ''
*
EQU LINUX.USER.PASSWORD.ATTRIBUTE TO 1
EQU LINUX.USER.USER.ID.ATTRIBUTE TO 2
EQU LINUX.USER.GROUP.ID.ATTRIBUTE TO 3
EQU LINUX.USER.GECOS.ATTRIBUTE TO 4
EQU LINUX.USER.DIRECTORY.ATTRIBUTE TO 5
EQU LINUX.USER.SHELL.ATTRIBUTE TO 6
EQU LINUX.USER.LAST.CHANGED.ATTRIBUTE TO 7
EQU LINUX.USER.MIN.AGE.ATTRIBUTE TO 8
EQU LINUX.USER.MAX.AGE.ATTRIBUTE TO 9
EQU LINUX.USER.WARNING.PERIOD.ATTRIBUTE TO 10
EQU LINUX.USER.INACTIVITY.PERIOD.ATTRIBUTE TO 11
EQU LINUX.USER.EXPIRY.DATE.ATTRIBUTE TO 12
*
@USER1 = 'EXPORT.LINUX.FILE'
@USER2 = 'EXPORT.LINUX.FILE'
*
* %END
*
OPENPATH '/tmp' TO TMP.FILE ELSE
PRINT 'Failed to open: /tmp'
STOP
END
*
EXECUTE 'SELECT LINUX-USER-FILE BY USER.ID'
*
Here we have our constants which should look familiar. We also open the /tmp
directory as we will be exporting the passwd
and shadow
here.
Now for the juicy part:
PASSWORD.BUFFER = ''
SHADOW.BUFFER = ''
*
LOOP
READNEXT ITEM.ID ELSE ITEM.ID = ''
*
UNTIL ITEM.ID = '' DO
MATREAD LINUX.USER.ITEM FROM LINUX.USER.FILE, ITEM.ID THEN
*
USERNAME = ITEM.ID
*
USER.ID = LINUX.USER.ITEM(LINUX.USER.USER.ID.ATTRIBUTE)
GROUP.ID = LINUX.USER.ITEM(LINUX.USER.GROUP.ID.ATTRIBUTE)
*
GECOS = LINUX.USER.ITEM(LINUX.USER.GECOS.ATTRIBUTE)
CONVERT @VM TO ',' IN GECOS
*
DIRECTORY = LINUX.USER.ITEM(LINUX.USER.DIRECTORY.ATTRIBUTE)
SHELL = LINUX.USER.ITEM(LINUX.USER.SHELL.ATTRIBUTE)
*
PASSWORD = LINUX.USER.ITEM(LINUX.USER.PASSWORD.ATTRIBUTE)
LAST.CHANGED = LINUX.USER.ITEM(LINUX.USER.LAST.CHANGED.ATTRIBUTE)
MIN.AGE = LINUX.USER.ITEM(LINUX.USER.MIN.AGE.ATTRIBUTE)
MAX.AGE = LINUX.USER.ITEM(LINUX.USER.MAX.AGE.ATTRIBUTE)
WARNING.PERIOD = LINUX.USER.ITEM(LINUX.USER.WARNING.PERIOD.ATTRIBUTE)
INACTIVITY.PERIOD = LINUX.USER.ITEM(LINUX.USER.INACTIVITY.PERIOD.ATTRIBUTE)
EXPIRY.DATE = LINUX.USER.ITEM(LINUX.USER.EXPIRY.DATE.ATTRIBUTE)
*
PASSWD = ''
PASSWD<1> = USERNAME
PASSWD<2> = 'x'
PASSWD<3> = USER.ID
PASSWD<4> = GROUP.ID
PASSWD<5> = GECOS
PASSWD<6> = DIRECTORY
PASSWD<7> = SHELL
*
SHADOW = ''
SHADOW<1> = USERNAME
SHADOW<2> = PASSWORD
SHADOW<3> = LAST.CHANGED
SHADOW<4> = MIN.AGE
SHADOW<5> = MAX.AGE
SHADOW<6> = WARNING.PERIOD
SHADOW<7> = INACTIVITY.PERIOD
SHADOW<8> = EXPIRY.DATE
*
CONVERT @AM TO ':' IN PASSWD
CONVERT @AM TO ':' IN SHADOW
*
PRINT PASSWD
PRINT SHADOW
PRINT
*
PASSWORD.BUFFER<-1> = PASSWD
*
IF EXPIRY.DATE = '' THEN
SHADOW.BUFFER<-1> = SHADOW : ':'
END ELSE
SHADOW.BUFFER<-1> = SHADOW
END
*
END ELSE
PRINT 'Failed to read ITEM.ID: ' : ITEM.ID
STOP
END
REPEAT
*
Here we have the core loop. We loop and read the record in and set up variables for each part of the record. In my case every field is going to be used so every field gets a variable.
We then build the PASSWD
and SHADOW
record and then we can do a simple convert to get them into the right format.
This will then get printed out to the screen and added to a BUFFER variable.
The one thing to note is that the GECOS field gets it's value marks converted to commas as we had done that conversion on the import side.
The most noteworthy part of this code is:
IF EXPIRY.DATE = '' THEN
SHADOW.BUFFER<-1> = SHADOW : ':'
END ELSE
SHADOW.BUFFER<-1> = SHADOW
END
We append :
to each line if the EXPIRY.DATE is blank as blanks at the end will get collapsed. This means that a single attribute mark at the end is not enough information to tell if there was a single blank at the end or if ther was any number of blanks at the end.
This has to do with the way multivalue works and is a gotcha that will come to bite you. There are flags that will force the attribute marks to populate even when there are blanks but I can't seem to find it right now.
For example the following example of a record, where @
is the attribute mark:
1@1@@@
will show up as:
1@1@
However if there is even a single non-null, then all the multivalue marks will appear:
1@1@@@1
Now the final step is to write out the BUFFERs to the /tmp
directory.
WRITE PASSWORD.BUFFER ON TMP.FILE, 'export.passwd'
PRINT 'Exported /tmp/export.passwd'
*
WRITE SHADOW.BUFFER ON TMP.FILE, 'export.shadow'
PRINT 'Exported /tmp/export.shadow'
*
* END OF PROGRAM.
*
END
*
With that we are done! We should be able to run our routine and have it actually print out each line to the screen and also have the two export files read to be migrated.