Translate

2021-12-07

Watch changes in a directory

Updated 20230123

There are at least two cases when watching for file changes, the private computer and the server.

The difference is the frequency of the changes. In a server you might have to decide what files you want to watch to not overload the server unnecessarily with thousands of changes every second. On your own machine changes may be a single number/second per directory. This latter case is what the following script can handle.

#!/bin/bash
#
#########
#   Name: 
#       pfdirwatch.sh
#
#   Version
#       20220402
#           Changes to make the display in the terminal window more readable when starting
#           w/o "n" or "y". The terminal will roll on, never be cleared, the printout limited
#           only by the number of lines available in the terminal window
#       20220207
#           added a log in /tmp. It will be deleted each time the machine reboots.
#           Each line in the log has a time assigned. 
#           NOTE: using parentheses in comments sometimes causes error in bash scripts!! It shouldn't but it does.
#           Removed parentheses!
#       20220130
#           bugfix. (The script only checked for changes in it's own working dir)
#       20220107
#           bugfix avoiding unnecessary message
#       20220101
#           Original version
#
#   Description:
#       This script takes two snapshots of the file system and then compares
#       them with respect to file time (changes) and existence (deleted or created).
#       Since it's rather primitive, changes can happen and vanish between the points
#       in time when the snapshots are taken. I use it to check for repetitive tasks
#       that changes log files. If changes occur, the task is running.
#       
#       The time between snapshots are lengthened by the $TIME_TO_READ bcs you need
#       to get a glimpse of what happened. 
#       
#   Usage:
#       I normally use it like:
#       pfdirwatch.sh  /home/xhosa/tools/scripts 10 n
#       NOTE:It will only work if you are in the same dir as the script when starting it!!
#           this will start the script looking like
#+++++++++++++++++++++
#We are in dir /home/xhosa/tools/scripts
#Command: /home/xhosa/tools/scripts/pfdirwatch.sh /home/xhosa/tools/scripts 5 n
#Pid: 598858
#+++++++++++++++++++++ 
#
#15:46:45
#
#       Setting the "n" to a "y" will run it once with only the time displayed and the next
#       loop the "y" will have been set to "n" and it will continue like above
#
#       Leaving out the "y"/"n" as the third argument makes the program run without the 
#        header. The terminal display will roll on, the TIME_TO_READ will not limit the time
#        you can view changes (they will be readable only limited by the number of lines available
#        in the terminal).
#
#########
# --TODO--: another variant of this script, logging to a file and starting tail on it. This way I'd have both live data
# available plus a log!!
#
# change this to what suits you. If you choose /tmp these files will be cleared when restarting
# otherwise you'll have to do that yourself.
TMP="/tmp/pfdirwatch"
FILE="/tmp/pfdirwatch.sh.log"
TIME_TO_READ=6

## creating log
if [ ! -f "$FILE" ]; then
    date +%Y-%m-%d_%H:%M:%S >> $FILE
    echo -e "Command:" $0 $1 $2 $3 >> $FILE
    echo "Logfile: " $FILE
fi

   echo -n "."  >> $FILE

   ## if pfdirwatch is a file, remove it, created the dir pfdirwatch
if [[ ! -d $TMP ]]
then                        # it's a file
    if [[ -f $TMP ]]
    then
        rm $TMP > /dev/null  2&>1
    fi
    mkdir $TMP
    chmod 0750 $TMP
else
    ## pfdirwatch IS a dir, remove all files in it
    for N in `ls -i -A /tmp/pfdirwatch/`; do ((loop++));done
    if(( $loop>0 ))
    then
        rm /tmp/pfdirwatch/* > /dev/null 2&>1
    fi
    
fi

## report on terminal will be readable for this no of seconds.
TIME_TO_READ=4

# asking for the number of arguments to the script, the script itself NOT counted!
if [ $# -lt 2 ]
then echo -e \\n"Usage '$0 folder waitseconds "\\n
exit 1
fi

## checking that argument 1 is a folder
if [ ! -d $1 ]
then
echo "Folder name is false!!"
exit 1
fi

## the "n" as argument 3 will make this report printout on the screen.
if [[ $3 == 'n' ]]
then
clear
echo -e \\n+++++++++++++++++++++
echo "This script's working directory: "`pwd`
echo "Command:" $0 $1 $2 $3
pid=$BASHPID
echo "Pid: "$pid
echo -e +++++++++++++++++++++ \\n
fi
echo `date +%H:%M:%S`
arg3="n"

# find only in this dir (no deeper) only regular files, stat lists %y(modification time) %s(ize) %n(ame) 
##find  $1 -maxdepth 1 -type f -not \( -name "$pid.lista.txt"  -o -name "$pid.listab.txt" \) -exec stat -c '%y %s %n ' {}  \;
find  $1 -maxdepth 1 -type f -not \( -name "$pid.lista.txt"  -o -name "$pid.listab.txt" \) -exec stat -c '%y %s %n ' {}  \; > $TMP/$pid.lista.txt

# list looks like:
# < 2011-08-10 12:47:43.000000000 +0200 196 ./tjosan 
# > 2011-08-10 12:49:42.000000000 +0200 203 ./tjosan 
# > 2011-08-10 12:49:42.000000000 +0200 0 ./124942 

# $2 (below) is the measure of time between now and then #the snapshots#, where eventual differences among the files will be
# discovered. This value is a gas pedal, sort of. Comment it or set it to 0 and you'll have live seconds working 
# in the printout.
sleep $2

##find  $1 -maxdepth 1 -type f -not \( -name "$pid.lista.txt" -o -name "$pid.listab.txt" \) -exec stat -c '%y %s %n ' {}  \;
find  $1 -maxdepth 1 -type f -not \( -name "$pid.lista.txt" -o -name "$pid.listab.txt" \) -exec stat -c '%y %s %n ' {}  \; > $TMP/$pid.listab.txt


# "sed -n '/^[><]/p'>" 
# From sed manual: 'By default sed prints all processed input (except input that has been modified/deleted
# by commands such as d). Use -n to suppress output, and the p command to print specific lines.' 
# These are decided by "'/^"=from the beginning of the line, "[> or <]"=lines beginning with either, "p"= pls 
# print these lines.
# array for diff-data:
diff $TMP/$pid.lista.txt $TMP/$pid.listab.txt|sed -n '/^[><]/p'
diff $TMP/$pid.lista.txt $TMP/$pid.listab.txt|sed -n '/^[><]/p'> $TMP/$pid.diffdata


# checking the block size of the $TMP/$pid.diffdata file
l=`ls -s  $TMP/$pid.diffdata|awk '{print $1}'`

# the ls command sys "no blocks are reserved for this file"
#if (( l == 0 ))
#then
    #this script| temp dir| time between snapshots
# exec  $0 $1 $2 $3
#fi
cat $TMP/$pid.diffdata|awk '{print $6 }'|uniq -d > $TMP/$pid.dupdata
cat $TMP/$pid.diffdata|awk '{print $6 }'|uniq -u $TMP/$pid.dupdata $TMP/$pid.uniqdata|sort 

# create indexed array. "-A" means "associative". Here are the snapshots compared.
declare -a fs
declare -a ft
for udata in `cat $TMP/$pid.dupdata|awk '{print '$6'}'`
do
loop=0
while read ddata 
do
#echo "udata: "$udata
#echo "ddata: "`echo $ddata|awk '{print $6}'`

if [ "$udata" == "`echo $ddata|awk '{print $6}'`" ]
then
fs[$loop]=`echo $ddata|awk '{print $5}'`
# this is the file time:
ft[$loop]=`echo $ddata|awk '{print $3}'`
((loop++))
fi
done < $TMP/$pid.diffdata

if ! [ ${fs[0]} == ${fs[1]} ]
then
echo "Size of $udata has changed: ${fs[0]}  => ${fs[1]}";
arg3="y"
echo -e "Size of $udata has changed: ${fs[0]}  => ${fs[1]}"; >> $FILE
fi
if ! [ ${ft[0]} == ${ft[1]} ]
then
echo "Filetime of $udata has changed: "  `echo ${ft[0]}|cut -c -8`  " => "  `echo ${ft[1]}|cut -c -8` 
arg3="y"
        echo -e "Filetime of $udata has changed: "  `echo ${ft[0]}|cut -c -8`  " => "  `echo ${ft[1]}|cut -c -8` >>  $FILE

fi


done
for uqdata in `cat $TMP/$pid.uniqdata|awk '{print '$6'}'`
do
loop=0
while read ddata 
do
#echo "uqdata: "$uqdata
#echo "ddata: "`echo $ddata|awk '{print $6}'`
        
if [ "$uqdata" == "`echo $ddata|awk '{print $6}'`" ]
then
            DT=`date +%H:%M:%S`

if [ `echo $ddata|awk '{print $1}'` == "<" ]
then
echo ${DT}" Deleted file: "`echo $ddata|awk '{print $6}'` 
arg3="y"

                echo ${DT}" Deleted file: "`echo $ddata|awk '{print $6}'` >> $FILE

else
echo ${DT}" Added  file: "`echo $ddata|awk '{print $6}'`
arg3="y"

echo ${DT}" Added file: "`echo $ddata|awk '{print $6}'` >> $FILE
fi
fi
done < $TMP/$pid.diffdata
done
echo
# if changes are detected you'll have $TIME_TO_READ seconds to read about them
# the ls command sys "no blocks are reserved for this file"
if (( l == 0 ))
then
    #this script| temp dir| time between snapshots|"n"
exec  $0 $1 $2 $3
fi

    sleep $TIME_TO_READ

clear
# and here we go again, looping
exec  $0 $1 $2 $3

# - Example code #
# declare g as array and filling it directly from a command. Since there are
# five space-delimited fields in each stat-line and (in the current dir) 150 files
# we'll get 5 * 150 = 750 entries
# echo "1"
# declare -a g=(`find  . -maxdepth 1 -type f -not \(-name "$BASHPID.lista.txt"\) -exec stat -c '%y %s %n ' {}  \; |sort`)
# - end#

## - Example code #
## - Filling array from a file
# declare -a f
# cnt=0
# while read line
# do
#   f[$cnt]=$line
#   echo  ${f[$cnt]}
#   ((cnt++))
# done < lista.txt
## note: this means "${}" = execute, "#" = number of, f[] = the 'f' array, "@" all members of
## so this means: "what is the number of all items in the f-array?"
# echo "Number of f-items: "${#f[@]}
## end #


This is how it looks when running










and in fact, I've used it to keep an eye on the "screen_setup.sh_last_run.log" from the blogpost
https://a-zproj.blogspot.com/2021/12/xubuntux11-cant-remember-dual-monitor.html
It seems like that problem has been worked around nicely. That's me, enough irritation always causes me to use quick and dirty things instead of fixing the problem at the root.

2021-12-05

Xubuntu/X11 can't remember a dual monitor configuration

(Updates are at the end of the post!) 

As soon as I leave my monitor for a cup of coffee or other everyday activities taking some ten minutes, when I come back my main screen is mirrored onto the secondary one. In the long run it's really irritating. But; this happens only when I'm not actively working. Sometimes I lean back to think and if I takes too long the configuration goes to mirroring.

I found a way to lessen the problem. I used two scripts (which perhaps ought to be merged but for this blog post they are what they are).  I thought I could put the setup script in .profile but I ran into problems, the login prompt didn't come up. I didn't explore that in any depth, I was in no mood to solve anything other that the screen problem.
I'll add one more thing: I run Xubuntu 20.04.02

First I need to know what my monitors are called in the X11 context, using xrandr:

snkdb@bender2:~$xrandr --listmonitors

Monitors: 2
0: +*DisplayPort-0 2560/597x1440/336+0+0  DisplayPort-0
1: +DisplayPort-1 1920/519x1200/324+2560+0  DisplayPort-1

Script "screen_setup.sh":
#!/bin/bash
### setting the correct configuration ###

# I had problems using "-" in the display port names. "DP-0" wasn't accepted.
# there's some confusion here...

DP0=DisplayPort-0
DP1=DisplayPort-1
# main screen: 2560x1440

function setup {
    xrandr --output $DP0 --auto --pos 0x0 --output $DP1 --auto --pos 2560x0
}

setup
# mostly for checking it worked as meant to 
date '+%Y-%m-%d %H:%M:%S' > <path>/screen_setup.sh_last_run.log
# END

Script "screen_setup_run_locally.sh":
#!/bin/bash ### running the configuration script (screen_setup.sh) repeatedly ###

# directions, how to use [in this case setting the wait time to 10s. You may have to experiment here]
if [ "$1" == "" ]; 
 then 
     echo "Pls enter the number of wait time seconds as an argument!"; 
     echo " like: 'screen_setup_run_locally.sh  10'<ENTER>"; 
     exit; 
fi 

# wait 10s
sleep $1 

# start this script in another process with the same wait time
exec $0 $1
#END

Then I created a launcher on the desktop. The command : <path>/screen_setup_run_locally.sh 10. After I have logged in I click it once and then screen_setup_run_locally.sh will stay put and do it's job. You can check it by starting ksysguard, choosing the Process Table tab, entering "screen_setup.sh" in the search line to the right of the "End Process"-button. In my environment it's a line looking:
sh  <your user name> <PID> <IO Write [~4k each 10s]> <Memory [~200k]> <shared Mem. value>


The result is: when I'm gone I won't find the configuration deteriorated as before when I come back after a pause.
The launcher icon on the desktop is possible to drag and drop onto the panel where I prefer to have it.


Update 20211206:
Very strange X11 behavior; setting up the scripts, xrandr --listmonitors resulted in DisplayPort-0 and 
DisplayPort-1. After the nightly backup, my machine shuts down. When I started it, the scripts didn't work, warning that the display ports didn't exist. xrandr --listmonitors  now resulted in ports names like DP-1 and DP-2! The format is changed and they now start at 1 instead of 0. What the f?! Why?
As of now I don't know why. Since the construction of these scripts isn't really my main goal in life I just added a second line to the screen_setup.sh:

xrandr --output $DP0 --auto --pos 0x0 --output $DP1 --auto --pos 2560x0
xrandr --output "DP-1" --auto --pos 0x0 --output "DP-2" --auto --pos 2560x0

One of them will always end up in a warning about not existing ports so the launcher will get an addition: <path>/screen_setup_run_locally.sh 10 &> /dev/null
This will take care about the annoying warning messages.

I'm pleased to present really good example of a kludge!!

















2021-12-04

Photography, birds in flight part 2. Change of camera Lumix FZ82 => Sony RX10 IV


Updated 20220828

The Lumix FZ82 has many good features but also severe limitations. Like any camera :)
Among the good things are 1200mm optical zoom, 4k, low weight. Among the bad things a totally worthless viewfinder. It's almost useless.

Since it's a "superzoom" it has a 1/2.3 sensor chip. The main consequence is that you have to shoot only in sunlight if the object is moving fast. Birds in flight normally do just that.

If the light is weak your images will be grainy.

I thought I'd try with a 1" chip camera so I bought a Lumix FZ1000 with 400mm optical zoom, thinking the increase in quality could balance out the longer zoom of the Lumix FZ82. I was prepared to be somewhat disappointed but "somewhat" was too much. The higher quality of about everything else wasn't enough.

For a long time I'd been interested in the Sony RX10 but the price was ~6 times higher compared to any camera I've owned up to that point. It was much money down the drain if it didn't worked out. I was *really* undecided for a long, long time but in the end I just ordered it and logged out. A big step for me! Phew!

It arrived and the feeling of quality in every detail was staggering! Smooth and heavy. In fact staggeringly heavy. I mount a red dot sight on my cameras for bird photography and the FZ82 weighs about 775g and the Sony 1318g [1lb, 11oz and 2lb, 14oz respectively] with these attached..

After using the lighter camera for years, the greater weight is still bothering me. Every time I take it up I think "Whew! Heavy!"

So far so good. In the autumn 2021 I went to a place where migratory birds pass every year. This was the ultimate test for the camera. The light was not good, cloudy and some rain. My old camera was *not* going to create great results with these preconditions, I knew that for sure.

After a couple of days I started to feel comfortable with the Sony, results were OK. Since it caught more light due to better lens and bigger chip, some margin for cropping emerged. What was disconcerting was the bokeh. Since the light was weak, the camera chose big apertures (I normally prefer short exposure times to get sharp birds :)  and the bokeh was extreme compared the the FZ82. 

To make a long story short: Yes, it worked, changing camera from Lumix FZ82 to Sony RX10 IV. The final proof is, I haven't used the Lumix since the Sony arrived. I'd say the "big step" was worth the money!
There are irritating things in this camera and there are equally irritating things in the Lumix, one just has to accommodate these by working around them or get used to them. I think that goes for any camera independent of prize.

Below images in cloudy conditions. White Wagtail (Motacilla alba), two images. Rather muscular guy! They are crops of about 25% of the original image.

White-tailed eagle (Haliaeetus albicilla) 
one image, taken when my gut feeling was "this is too far out" but it turned out to be an acceptable picture in my book. It's not a crop, this is as it came out of the camera in spite of the low light (very cloudy). NOTE: You have to look at in in full size on a computer, looking at it on your mobile is no use.

And lastly, the camera with the red dot sight. I have no problem mounting it, takes about 30 min. I never use the flash, I use my mobile for the very few times I need it. So, I glue with Bondic. There will forever be a mark but no functions of the camera are damaged. Other people may want to invest more but the problem is, fastening the sight in just the flash shoe is not enough. I *must* be fixated to the camera so it does not lose it's settings every time you transport it or use it. Horrible, I know :o)). New camera and all. But, a tool is a tool and it must do it's job.









I bought a strip of aluminium from Bauhaus, bent it, used a metal saw and a fine file to make it fit in the flashlight mount. Then I glued it to the lid of the flash. 
The last step is to adjust the red dot sight to hit the middle of the image in the view finder. When these two correspond exactly, you are done!

Using it, you look through the red dot sight ( NOTE: it MUST have green light as an option!! ) and as soon as you have the object in sight, you press the trigger button. That's all. There are caveats but you'll find them out yourself.

The sight cost about $30 from Alibaba.

A tip: I set my camera to 1/1000s or faster, always. That's the "S" setting that makes the camera always use this high speed setting.






2021-05-29

Creating a signed kernel module [pre installing Virtual Box]

 


 Update 20210723: Virtual box didn't recognize my network hardware. Since I still have my old laptop I'll use that one for Windows (ugh!). This was the end of My Virtual Box adventure. I leave the text below, it's still valid.


NOTES

Virtual Box [Recommended: 18.04 LTS but installed on 20.04.2 (find your with: lsb_release -a)]
It's said some of the modules in Virtual box needs signing if run on an UEFI system:

  • vboxdrv

  • vboxnetadp

  • vboxnetflt

  • vboxpci

So let's create a key!


* apt update

* apt-get install linux-headers-$(uname -r)

* apt install build-essential

* openssl version <Enter>
OpenSSL 1.1.1f  31 Mar 2020

* vim /usr/lib/shim/mok/openssl.cnf <Enter>

/usr/lib/shim/mok/openssl.cnf, after editing, looks like:

--- begin openssl.cnf:

    HOME                    = /var/lib/shim-signed/mok
    RANDFILE                = /var/lib/shim-signed/mok/.rnd

    [ req ]
    distinguished_name      = req_distinguished_name
    x509_extensions         = v3_ca
    string_mask             = utf8only
    prompt                  = no

    [ req_distinguished_name ]
    countryName             = (like US, GB, NO etc)
    stateOrProvinceName     = (something suitable)
    localityName            = (your home town?)
    0.organizationName      = (hostname of my machine)
    commonName              = Secure Boot Signing
    emailAddress            = xxx.yyy@some.com

    [ v3_ca ]
    subjectKeyIdentifier    = hash
    authorityKeyIdentifier  = keyid:always,issuer
    basicConstraints        = critical,CA:FALSE


    # We use extended key usage information to limit what this auto-generated

    # key can be used for.
    #
    # codeSigning:  specifies that this key is used to sign code.
    #
    # 1.3.6.1.4.1.2312.16.1.2:  defines this key as used for module signing
    #                                       only. See https://lkml.org/lkml/2015/8/26/741.
    #
    extendedKeyUsage        = codeSigning,1.3.6.1.4.1.2312.16.1.2
    nsComment               = "OpenSSL Generated Certificate"

--- :end openssl.cnf


* openssl req -config ./openssl.cnf -new -x509 -newkey rsa:2048 -nodes -days 36500 -outform DER -keyout "MOK.priv" -out "MOK.der"  <Enter>

Oooops!

    Can't load /var/lib/shim-signed/mok/.rnd into RNG 

    140194088219968:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:98:Filename=/var/lib/shim-signed/mok/.rnd

Explanation:

    NOTE: current version of openssl on this system: v1.1.1f !!

Quote:
        "https://github.com/wazuh/wazuh/issues/3395

        Error cause
        The error message is due the version of OpenSSL, concretly v1.1.1.
        This error is fixed in v1.1.1a. Error fixed. 

        Consequences
        Despite the error message, OpenSSL works well. It generates different keys and certificates values, so it hasn't got important consequences.

        Posible solutions
        Comment RANDFILE variable at the beginning of /etc/ssl/openssl.conf file.
        Ignore error message.

        Conclusion
        Considering that it doesn't affect to our system's security, we could ignore the error message"

 END of quote       

    DUH!

    

checking for /var/lib/shim-signed/mok/.rnd  [OK!]


* locate MOK.priv <Enter>
/usr/lib/shim/mok/MOK.priv                  [OK!]

* locate MOK.der <Enter>
/usr/lib/shim/mok/MOK.der                   [OK!]

* mokutil --import MOK.der <Enter>
input password:                     [secret]
input password again:               [guess what!]

root@myMachine:/usr/lib/shim/mok# 


From https://ubuntu.com/blog/how-to-sign-things-for-secure-boot:

    "Once this is done, reboot. Just before loading GRUB, shim will show a blue screen (which is actually another piece of the shim project called “MokManager”). use that screen to select “Enroll MOK” and follow the menus to finish the enrolling process. You can also look at some of the properties of the key you’re trying to add, just to make sure it’s indeed the right one using “View key”. MokManager will ask you for the password we typed in earlier when running mokutil; and will save the key, and we’ll reboot again."

When that blue screen shows itself there are three alternatives, two of then have to do with things on the disk. Skip these, choose the first one (I just can't remember what it said but it was very short, one word, related to the subject at hand.)


So let's check if the new key is in the system...

* cat /proc/keys <Enter>

11b98d3e I------     1 perm 1f010000     0     0 asymmetri (hostname of my machine): Secure Boot Signing: 91888ab4e46ee6eaeb96d3ae588a2e75df735c1b: X509.rsa df735c1b []

120c60ec I--Q---     1 perm 3f030000     0     0 keyring   _ses: 2

124cadb8 I------     2 perm 1f010000     0     0 blacklist bin:3b0287533e0cc3d0ec1aa823cbf0a941aad8721579d1c499802dd1c3a636b8a9

12804b37 I------     2 perm 1f010000     0     0 blacklist bin:fecfb232d12e994b6d485d2c7167728aa5525984ad5ca61e7516221f079a1436

12cad596 I--Q---     2 perm 3f030000     0     0 keyring   _ses: 1

138da1cb I------     2 perm 1f010000     0     0 blacklist bin:aeebae3151271273ed95aa2e671139ed31a98567303a332298f83709a9d55aa1

1390873b I--Q---     1 perm 0b0b0000     0     0 user      invocation_id: 16

13f385bb I--Q---     1 perm 0b0b0000     0     0 user      invocation_id: 16

1407a2b3 I------     2 perm 1f010000     0     0 blacklist bin:ad6826e1946d26d3eaf3685c88d97d85de3b4dcb3d0ee2ae81c70560d13c5720

143480ec I------     2 perm 1f010000     0     0 blacklist bin:29c6eb52b43c3aa18b2cd8ed6ea8607cef3cfae1bafe1165755cf2e614844a44


Looking for the names, check the "asymmetri" entries! It's quite confusing at first with a LOT of lines and strange number sequences.

Done!

These modules must eventually be signed if the installation didn't manage to leave the installable.

/usr/lib/modules/5.8.0-53-generic/misc/vboxdrv.ko
usr/lib/modules/5.8.0-53-generic/misc/vboxnetadp.ko
/usr/lib/modules/5.8.0-53-generic/misc/vboxnetflt.ko
vboxpci.ko was not found to be installed.

* lsmod | grep vbox <Enter>

vboxnetadp             28672  0
vboxnetflt             28672  0
vboxdrv               516096  2 vboxnetadp,vboxnetflt

This looks good. I do not have to sign them, the kernel has accepted to load them already.
Waiting for a dvd burner so I can install XP from one of my CD copies...

To be continued... NOPE it ends here!


2021-05-20

Reprogram an old Visual Basic application in the Linux environment

 An old Visual Basic program running on a Intel Core 2 Duo Processor SL7100 LV (1.2GHz, 4MB L2 cache, 800MHz FSB). Is there a way to reprogram this app i the Linux environment? Has anybody made something like this in Linux?

Update: I forgot to explain "why the reprogramming?". The source is lost, that's why and I would like to explore the programs possibilities, but alas!

It's a function displaying the resulting values as dots in color when the program is running. The colors depend on the magnitude of the value.

Any idea sent to per dot funke at gmail dot com would really be appreciated!


Update: solved by installing KVM and visual Basic 6.

:0)


2021-05-15

Finally, Rust and what I need it for. The end.




It seems that the most time efficient alternative for me is to write a small rust routine that makes system calls to the kernel and if necessary some other well-defined support function if required and then use it/them in PHP to get somewhere. The ultimate goal is to be able to write to the framebuffer in Linux to draw what I want. If I'm going to do it in Rust, I need a course in how to talk to the graphics card and even the simplest operations mean that you have to write hundreds of rust lines AND you have to know how the hardware resonates in order to know what it wants to know. It's incredibly time consuming.

There are other options, Open-GL, sneak applications that use GL (Glutin & Co) and a lot of other things and to get acquainted with to know how to use them ... before I get anywhere I am already dead.

There is no doubt that Time is the thing I have absolutely the least of (I'm old!). The more time it takes, the more aware I become that it is the result that counts.

Rust is extremely effective but plentiful, linewise. But, it may be the missing link in my case so I do not consider the time I spent on it as lost. On the contrary, missing links in a chain make it too short. I can unreservedly say that Rust has impressed me. It is a high quality project. PHP seems to me a toy, compared to Rust.


2021-04-20

Learning Rust. Part 14. Training project. Editor last blogpost.





 The "completed" editor.
This took 19 days from the day i downloaded Rust (excepting breakfast, dinner, garden work, walks and other everyday chores. One thing happened that I didn't expect at all: a mail came from Rust's org telling me they found my intense interest in the documentation so encouraging that they choose to push me up to "trusted" level. I won't deny it had some effect :0)

This is the last part of seven iterations. The first is in https://a-zproj.blogspot.com/2021/03/learning-rust-part-4-training-project.html
The editor now has the following commands implemented:

Commands:
[[=header, [=item, /*=start comment, */=end comment, a=add line, i=insert comment, l=list, d=delete line(s)

"Completed" means just that, the functions work but no more. There are (some) safeguards for entering wrong things from the keyboard and possibly a number of not existing/bad handling of errors, generally. One more thing one could wish is a way to go back to older versions of the edited file. There is none. But:

It's working, after a fashion. Functions have been added; delete-lines() and add_a_line(). Delete_lines accepts more than one line number to be deleted, they do not have to be sequential. add_a_line() accepts only one. Also check_num() is added as to stop entering text when numbers are expected.

I've learned many things and that was the goal.

Most of the code below has been commented earlier. Have you been following this subject up to now, I'm certain you'll handle the few new things.

The first two lines:

    let args: Vec<String> = env::args().collect();
    println!("{:?}", args);

gets the arguments, which makes it possible to enter the .ini-file name I'd like to process. The first argument (args[0]) is the program's own name, the next is the first argument. 
From that follows that the next line will contain the file name:

    let fname_ini=args[1].to_string();

and during development the program will be started by "cargo run ed.ini".

#![allow(warnings)]
use std::fs;
use std::fs::{File,OpenOptions};
use std::io::{self, Read, Write, ErrorKind};
use std::process::exit;
use std::str;
use std::char;
use termios::{Termios, TCSANOW, ECHO, ICANON, tcsetattr};
use std::string::String;
use std::io::BufWriter;
use std::env;


fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:?}", args);
    let fname_ini=args[1].to_string();
    let msg = "\nCommands:\n[[=header, [=item, /*=start comment, */=end comment, a=add line, i=insert comment, l=list, d=delete line(s) ".to_string();
    println!("{}", msg);
    let input=rdl("\nEnter command:\n".to_string());
    

// trim_end_matches(' ') returns a string slice (&str) By default there is a
/
/ newline character  at the end of the string produced by rdl() so you need to use trim!

    let input=input.trim_end_matches('\n'); // returns a string slice (&str)

    match input {
        "[[" => header(input.to_string(),fname_ini.to_string()),
        "["  => item(input.to_string(),fname_ini.to_string()),
        "/*" => comment_start(fname_ini.to_string()),
        "*/" => comment_end(input.to_string()),
        "A" | "a" => add_a_line(fname_ini.to_string()),
        "D" | "d" => delete_lines(fname_ini.to_string()),
        "H" | "h" => help(input.to_string()),        // theoretically. No help yet
        "I" | "i" => insert_comment(fname_ini.to_string()),
        "L" | "l" => list(fname_ini.to_string()),
        &_ => ret(input)    // must be str because 
        }
    main();
}


fn ret(input:&str){
    println!("Input is not acceptable {}",&input);
    main();
}

fn header(section_header:String,fname_ini:String) {

    println!("Header was: {}",section_header);
    let f = open_file (fname_ini.to_string());
    let prompt:String = "Section header chosen. Please enter the header text: >> ".to_string();
    let inp:String =rdl(prompt).trim().to_string();
     // .<=to_string()
    let mut header = String::new();                                      // as in the first example. 
    header.push_str("\n[");                   // let's add the first piece, "["
    header.push_str(inp.as_str());               // let's add the second part, 'inp'
    header.push_str("]\n");                    // let's add the third part. "]"

    println!("Complete header is: {} ",header);
    let _res = write_file(header, f);
    main();
}

fn item(section_item:String,fname_ini:String){

    println!("Item was: {}",section_item);
    let f = open_file (fname_ini.to_string());
             // getting the section item & value value
    let prompt:String = "Section item chosen. Please enter the item name: ".to_string();
    let inp_name:String =rdl(prompt).trim().to_string();
    let prompt:String = "Section item chosen. Please enter the item value: ".to_string();
    let inp_value:String =rdl(prompt).trim().to_string();
    // putting together the complete item line

    let mut inp_line = String::new();
    inp_line.push_str(&inp_name.as_str());

    let delim:String=String::from(": ");
    inp_line.push_str(&delim);
    inp_line.push_str(&inp_value);
    let n:String = String::from("\n");
            // let's add a NewLine (needing 2 statements!)
    inp_line = inp_line + &n;
    println!("Complete item line is: {} ",inp_line);
   
    let res = write_file(inp_line.to_string(), f);            // adding the item line to the file
    main();
}

fn comment_end(com_end:String){

   println!("comment_end was: {}",com_end);            // if you start program and immediatly end it
}

fn help(cont_hlp:String){

    println!("Help was: {}",cont_hlp);                            // there ought to be some help text but nooo...
}

// just showing the user what the current file looks like by listing it
fn list(fname_ini:String){

    let  cont_list = std::fs::read_to_string(&fname_ini).expect("Something went wrong reading the file");
    let s_slice: &str = &*cont_list;
    let v: Vec<&str> = s_slice.split('\n').collect();
    let mut l_num:i32 = -1;
    print!("{}","\n");

    for val in v.iter() {
        l_num += 1;
        if l_num > 0 {
            println!("{} {}",l_num,val);
        }
    }
    return;
}

fn rdl(prompt:String) -> std::string::String {

    print!("{}", prompt);
             // print whatever 'prompt is
    io::stdout().flush().unwrap();       // to force printout NOW, otherwise lines will come in wrong order!
    let mut st = String::new();          // a string to stuff my input into
    match  std::io::stdin().read_line(&mut st) {
        Ok(_) => (st),
        Err(e) => (e.to_string()),
    }
}

fn rdl_num(prompt:String,lines:usize) -> usize  {

    print!("{}: ",prompt);
    io::stdout().flush().unwrap();
    let mut lnum_try = String::new();
    std::io::stdin().read_line(&mut lnum_try);
    lnum_try = lnum_try.trim().to_string(); 
    let n = lnum_try.parse::<usize>().unwrap();
    return n;
}


fn open_file (fname:String) -> std::fs::File{


/*
NOTE: Files are automatically closed when they go out of scope.
"The most general way for your function to use files is to take an open file handle as parameter, as this allows it to also use file handles that are not part of the filesystem." (Though, not to forget, file handles can't be copied or de-referenced!) 
*/

    let f = OpenOptions::new().read(true).append(true).open(&fname);
    let mut f = match f {
        Ok(f) => return f,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create(&fname){
            Ok(f) => return f,
            Err(e) => panic!("Failed creating file 'data' {:?}", e)
            },
                                    // one more is coming, a comma
            other_err => {
            panic!("Failed opening file 'data'  {:?}", other_err)
            }
        }
    };                                                                                            //
END of "let f = match f {", a semicolon!
}


fn copy_file(source:String,target:String) -> std::io::Result<()> {

    fs::copy(source, target)?;                     // Copy foo.txt to bar.txt
    Ok(())
}


fn write_file(contents:String,mut f:std::fs::File)  -> std::io::Result<String> {

    f.write_all(contents.as_bytes())?;
    f.sync_data().expect("write_file: Failed syncing data");
    Ok("OK".to_string())
}


fn delete_file(fname:String)  -> std::io::Result<()> {

    fs::remove_file(fname)?;
    Ok(())
 }

fn create_empty(fname:String) -> std::io::Result<()> {

    File::create(fname)?;
    Ok(())
}

 

// Getting char after char, checking for "*/" which will end it
 fn comment_start(fname_ini:String) {

    let fname_slice: &str = &fname_ini;
    let mut s = String::new();
    let a:&str = "1";
    let b:&str = "1";

    while a == "1" && b == "1" {                    // infinite loop
        s = t_get(s,fname_slice.to_string());        // getting the next char
        if s.len()<1 {
            comment_start(fname_slice.to_string());
        }

        let n1 = s.len()-1;
        let ch1 = s.chars().nth(n1).unwrap();
            // checking the last char
        s=t_get(s,fname_slice.to_string());
        if s.len()<1 {
            comment_start(fname_slice.to_string());

        }

        let n2 = s.len()-1;
        let ch2 = s.chars().nth(n2).unwrap();
        if (ch1 == '*') && (ch2 == '/') {
            let s=add_comment_tags(s);
                      // tags are "/*" and "*/"
            let f = open_file(fname_slice.to_string());
            write_file(s,f);
            print!("\n");
            list(fname_slice.to_string());
            io::stdout().flush().unwrap();
            return;
        }        

        if ch2 == '*' {                                   // if the check before misses...
            s=t_get(s,fname_slice.to_string());
            if s.len()<1 {
                comment_start(fname_slice.to_string());
            }
            let n3 = s.len()-1;
            let ch3 = s.chars().nth(n3).unwrap()
            if ch3 == '/' {
                                 // this will catch it ("*/").

            let s=add_comment_tags(s)
            let f = open_file(fname_slice.to_string());
            write_file(s,f);
            list(fname_slice.to_string());
            return
            }
        }
    }
}


// shortening comment_start() by not repeating this twice tags are "/*" and "*/"
fn add_comment_tags(mut s:String) -> std::string::String {

    let prefix:String = "\n/* ".to_string();
    let suffix:String = "\n".to_string();
    s = prefix + &s + &suffix;
    s
}

// This is the Termios crate (it's name changed for clarity), as it is downloaded
fn getachar() ->  [u8;1]{       // result is an ARRAY!
  let stdin = 0;            // couldn't get std::os::unix::io::FromRawFd to work on /dev/stdin or /dev/tty                                 // 

  let termios = Termios::from_fd(stdin).unwrap();
    let mut new_termios = termios.clone();
          // make a mutable copy of termios                                                // that we will modify
    new_termios.c_lflag &= !(ICANON | ECHO);   // no echo and canonical mode

    tcsetattr(stdin, TCSANOW, &mut new_termios).unwrap();
    let stdout = io::stdout();
    let mut reader = io::stdin();
    let mut buffer = [0;1];
                         // read exactly one byte [into this ARRAY!]
    stdout.lock().flush().unwrap();
    reader.read_exact(&mut buffer).unwrap();
    let c = buffer;
    tcsetattr(stdin, TCSANOW, & termios).unwrap();
  // reset the stdin to nothing

    return c;
}


fn t_get(mut s:String, fname_ini:String) -> String {

  // This is a wrapper for Termios, (backspace editing is possible, 
    // remember, the expanding line is shown to the user, char after char)
    // It gets one character to be added to the s:String

    let mut raw = getachar();   // get a char

    // print!("{}",raw[0]);  // if activated, shows dec # of u8
    if raw[0] > 127 {              // non-ASCII char is beeping
        raw = [7; 1];
    }

    if raw[0] == 127 {               // backspace!
        if s.len()<1 {
            comment_start(fname_ini);
        }
        s.pop();
                 // here the backspace is effected. Remove last char
        print!(" {}",s);       // you'll get the string back minus the last character
        return s;          // here

    };

    let  mybyte = str::from_utf8(&raw).unwrap();
    s.push_str(&mybyte);
    let n1 = s.len()-1;
    let ch1 = s.chars().nth(n1).unwrap();
    io::stdout().flush().unwrap();

    s                           // the String + the new char at the end, returned (if no backspace)
}


/* conceptually this function works like insert_comment() [previously named "comment_start()"]. The file is read in, placed in a vector which is the written back to the original file name. Here, this is done w/o any intermediate file. */

fn add_a_line(fname_ini:String) {

    let fname = fname_ini.as_str();
    list(fname.to_string());
    let  cont = std::fs::read_to_string(&fname_ini).expect("Something went wrong reading the file");
    cont.trim();

    let s_slice: &str = &*cont;
    let v_cont: Vec<&str> = s_slice.split('\n').collect();
  // to be able to index
    let v_cont_lines = v_cont.len()-1;
    if v_cont_lines == 0 {
        println!("The file is empty. Add at least a [section]!");
        main();
    }    

    let mut prompt="Enter line number after which you wish to add a line:
".to_string();
    let mut inp:String =rdl(prompt).trim().to_string();
    let mut inp_num = check_num(fname_ini.to_string(),inp);
    if inp_num =="Bad number".to_string(){
          // inp_num should be a number:String
        add_a_line(fname.to_string());
    }

    if inp_num =="".to_string(){                  // inp_num is (should be) a  a number:String
        println!("Input is empty");
        add_a_line(fname.to_string());

    }

    let inp_ix = inp_num.parse::<usize>().unwrap();     // convert it to an usize!
    let mut x:usize = 0;
    let mut string_cont = String::new();
    while x <= inp_ix {
                               // adding the lines to the output file up                                                                                                         // to and including the input linenumber

        string_cont.push_str(v_cont[x]);
        string_cont.push_str("\n");
        x += 1;
    }

    let my_prompt="Enter the line you wish to add. End with <Enter>: ".to_string();
    let new_line:String = rdl(my_prompt).trim().to_string();
    if new_line =="".to_string() {
        println!("Input string is empty");
        add_a_line(fname.to_string());
    }

    let nl = "\n";
    string_cont = string_cont + &new_line + &nl;
  // by adding a &String it becomes a &str
    while x > inp_ix && x < v_cont_lines && inp_ix > 1 {
        string_cont.push_str(v_cont[x]);
           // which means this will work! All lines after the 
                                                                                    // input line number are added
        string_cont.push_str("\n");
        x += 1;
    }

    let bu = ".bu";
    let target = fname.to_string() + &bu;
     // create target name (="<fname>.bu")
    copy_file(fname.to_string(),target);    // use it for copying
    delete_file(fname.to_string());         // delete the original
    println!("string_cont:{:?}",string_cont);
    let mut f=open_file(fname.to_string());
    let mut f = BufWriter::new(f);
    f.write_all(string_cont.as_bytes()).expect("Unable to write string_cont1");
    f.flush().unwrap(); 
                   // necessary, otherwise no output

    main();
}


fn delete_lines(fname_ini:String) {

  // deletes lines in a file displayed with list()
    // input line numbers are withheld when writing it back
    //  Since all lines corresponding to the input line numbers, are withheld
    // they do not have to be sequential.
    // a backup file is created and will not be removed afterwards

    let v = fname_ini.as_str();
    let  cont = std::fs::read_to_string(&fname_ini).expect("Something went wrong reading the file");
    cont.trim();
    let s_slice: &str = &*cont;
    let v_cont: Vec<&str> = s_slice.split('\n').collect();
// to be able to index
    let v_cont_lines = v_cont.len()-1;              // file is now read and stored in a vector
    list(fname_ini.to_string());

    let prompt="Delete line: Please enter line number(s); Commas in between!: ".to_string();
    let mut inp:String =rdl(prompt).trim().to_string();
   // line numbers are read

    let mut inp_num = check_num(fname_ini.to_string(),inp); // line numbers are rchecked and stored in a vector
    let inp_num_slice:&str = &*inp_num;
    let v_inp_num:Vec<&str> = inp_num_slice.split(',').collect();
    let v_inp_num_len = v_inp_num.len();            

    let mut result = String::new();
    let mut found:bool = false;
    let mut x:usize = 0;
    while x < v_cont_lines {
                        // for each line in the file...                 for i in (0..v_inp_num_len) {                    // the line number is checked against                                 
                                      // each  vector item
     if &*x.to_string() == v_inp_num[i]{         // dereference bcs left side i usize and right side is &str. This was not the first thing to come to my mind...
             found = true;
            }

        }

        if found == false {
            result.push_str(v_cont[x]);
            result.push_str("\n");
        }else{
            found = false;
        }
        x += 1;
    }

    let bu = ".bu";
    let target = fname_ini.to_string() + &bu;
        // create target name (="<fname>.bu")
    copy_file(fname_ini.to_string(),target).expect("Unable to copy file to backup");
    delete_file(fname_ini.to_string());
    let mut file = open_file(fname_ini.to_string());
    file.write_all(result.as_bytes()).expect("Unable to write string_cont1");
    file.sync_data().expect("write_file: Failed syncing data");
    return
}


fn check_num(fname_ini:String,mut st:String) -> std::string::String{

  // checking all characters are numeric:ish. Commas and whitespace are removed.
    // If all's good the string is returned, otherwise err message

    st.retain(|c| !c.is_whitespace());      //"retain all characters c such that f(c) returns false" =' '. |c|
                                                                        // defines whatever is tested, followed by a the test

    st.trim_matches(',');               // trim ev. last ','
    let s_slice: &str = &*st;
    let v_cont: Vec<&str> = s_slice.split(',').collect();
    let v_cont_len = v_cont.len();
    let mut x:usize = 0;
    let mut s_num = String::new();
    let mut p:String = "".to_string();
    while x < v_cont_len {
        if v_cont[x].to_string().chars().all(char::is_numeric) {
            x += 1;
            continue;
       }else{
        print!("This is not a number: {:?}",v_cont[x]);
        let prompt="   <Enter> to continue".to_string();
        let mut inp:String =rdl(prompt).trim().to_string();
        return ("Bad number".to_string());
      }
      x += 1;
    }
    st
}


/*The function makes it possible to enter a comment after a specific line number.
It contains the following actions:
Read the file to edit, store it in a vector (to be able to index it).
Make a backup copy of the file. Create/overwrite an empty temp file where the comment will be saved.
Creating a comment with function start_comment(), storing it in the tmp file.
Moving the vector's string elements/lines up to the specific line from the vector to a string
Moving the remaining vector elements to another string
Writing the fist string, the comment and the last string to the original file
Done
*/

fn insert_comment(fname_ini:String){
    let  cont = std::fs::read_to_string(&fname_ini).expect("Something went wrong reading the file");
    let s_slice: &str = &*cont;
    let v_cont: Vec<&str> = s_slice.split('\n').collect();
// to be able to index
    let lines = v_cont.len()-1;
    let fname = fname_ini.as_str();
                //String can't be copied, make str for copying
    list(fname.to_string());                      // showing the file (w. line numbers)
    let bu = ".bu";
    let tmp = ".tmp";
    let s_orig = String::new();
    let s_new = String::new();
    let n:String = "\n".to_string();    

    let mut lnum:usize = rdl_num("The line number for the start of your comment: ".to_string(),lines);

    if lnum > 0 {
        lnum -= 1;
    }
    let target = fname.to_string() + &bu;
             // create target name (="<fname>.bu")
    let target_slice = target.as_str();               // for using name several times w/o moving

    let tmpfile = fname.to_string() + tmp;          // create tmp file name
    let tmpfile_slice = tmpfile.as_str();             // for using name several times w/o moving
    let mut f = create_empty(tmpfile_slice.to_string());    // Creating empty tmp file
    copy_file(fname.to_string(),target_slice.to_string());   // copy original to; backup
    println!("Backup copy is named {}",target_slice.to_string());
    println!("Created working copy {}",tmpfile_slice.to_string());
    println!("Enter comment. Enter '*/' to end comment");

    comment_start(tmpfile_slice.to_string());    

    let mut x:usize = 0;
    let mut string_cont1 = String::new();

    while x <= lnum {
        string_cont1.push_str(v_cont[x]);
        string_cont1.push_str("\n");
        x += 1;
    }
    let  comment = std::fs::read_to_string(tmpfile_slice.to_string()).expect("Something went wrong reading the file");
    let mut x = lnum + 1;
    let mut string_cont2 = String::new();

    while x > lnum && x < lines {
        string_cont2.push_str(v_cont[x]);
        string_cont2.push_str("\n");
        x += 1;
    }

    string_cont2.trim();
    delete_file(fname.to_string());
    let mut f=open_file(fname.to_string());
    let mut f = BufWriter::new(f);
    f.write_all(string_cont1.as_bytes()).expect("Unable to write string_cont1");
    f.write_all(comment.as_bytes()).expect("Unable to write comment");
    f.write_all(string_cont2.as_bytes()).expect("Unable to write string_cont2");
    f.flush().unwrap(); // BufWriter needs this otherwise there may be no output

    return

}

And that is the end of the training project. For now. It's one thing to get something working, quite another to do it an elegant fashion :0)