MacLochlainns Weblog

Michael McLaughlin's Technical Blog

Site Admin

Archive for the ‘Linux Development’ tag

Ruby+MySQL on Ubuntu

without comments

This post goes through installing and configuring Ruby and Ruby on Rails for MySQL. The first step requires updating the Ubuntu OS:

sudo apt-get update

Interestingly, I found that the man-db service had inadvertently stopped. It raised the following error:

E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem.

You run this command to find the problem with the dpkg utility:

sudo dpkg --configure -a

It returned:

Setting up man-db (2.10.2-1) ...
Updating database of manual pages ...
man-db.service is a disabled or a static unit not running, not starting it.

The following command started the man-db service:

sudo systemctl start man-db.service

Next, you install the prerequisite packages with this command:

sudo apt-get install -y git-core zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev

Use the cd command to change to the student home directory. Clone the asdf as the multiple runtime version manager with this command:

git clone https://github.com/excid3/asdf.git ~/.asdf

The following is the output of the git clone command:

Cloning into '/home/student/.asdf'...
remote: Enumerating objects: 8756, done.
remote: Counting objects: 100% (829/829), done.
remote: Compressing objects: 100% (476/476), done.
remote: Total 8756 (delta 428), reused 657 (delta 334), pack-reused 7927
Receiving objects: 100% (8756/8756), 3.10 MiB | 4.29 MiB/s, done.
Resolving deltas: 100% (5148/5148), done.

Next, you fix your .bashrc file by adding the following components:

echo '. "$HOME/.asdf/asdf.sh"' >> ~/.bashrc
echo '. "$HOME/.asdf/completions/asdf.bash"' >> ~/.bashrc
echo 'legacy_version_file = yes' >> ~/.asdfrc
echo 'export EDITOR="code --wait"' >> ~/.bashrc

Source the modifies shell, which you can do like this:

exec $SHELL

or, like:

. ${HOME}/.bashrc

Add the following asdf plug-ins:

asdf plugin add ruby
asdf plugin add nodejs

Install Ruby with the following command:

asdf install ruby 3.3.0

Install Ruby Global with this syntax:

asdf global ruby 3.3.0

Update the Ruby Gems with this command:

gem update --system

You can confirm your Ruby install with two commands. First, use the which utility to check the Ruby install:

which -a ruby

It should return:

/home/student/.asdf/shims/ruby

Then, check the Ruby version:

ruby -v

It should return:

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-linux]

Assuming you’ve installed and configured MySQL 8 on Ubuntu, you need this additional library to support the necessary Ruby Gem:

sudo apt-get install -y libmysqlclient-dev

Now, you can install the current MySQL Ruby Gem:

gem install mysql2

You can now write a mysql_connection.rb program to verify a connection to the MySQL 8 database, like:

# Include Ruby Gem libraries.
require 'rubygems'
require 'mysql2'
 
begin
  # Create new database connection.
  db = Mysql2::Client.new( :host     => 'localhost' \
                         , :username => 'student'   \
                         , :password => 'student'   \
                         , :database => 'studentdb')
 
  # Create a result set.
  stmt = db.query('SELECT version() AS version')
 
  # Read through the result set hash.
  stmt.each do | row |
    puts "#{row['version']}"
  end
  # Release the result set resources.
  stmt.free
 
rescue Mysql2::Error => e
  # Print the error.
  puts "ERROR #{e.errno} (#{e.sqlstate}): #{e.error}"
  puts "Can't connect to the MySQL database specified."
  # Signal an error.
  exit 1
 
ensure
  # Close the connection when it is open.
  db.close if db
end

Call the program with this syntax:

ruby mysql_connection.rb

It should return:

Connected to the MySQL database server.

You can verify the version with this mysql_version.rb program:

# Include Ruby Gem libraries.
require 'rubygems'
require 'mysql2'
 
begin
  # Create new database connection.
  db = Mysql2::Client.new( :host     => 'localhost' \
                         , :username => 'student'   \
                         , :password => 'student'   \
                         , :database => 'studentdb')
 
  # Create a result set.
  rs = db.query('SELECT version() AS version')
 
  # Read through the result set hash.
  rs.each do | row |
    puts "#{row['version']}"
  end
 
  # Release the result set resources.
  rs.free
 
rescue Mysql2::Error => e
  # Print the error.
  puts "ERROR #{e.errno} (#{e.sqlstate}): #{e.error}"
  puts "Can't connect to the MySQL database specified."
  # Signal an error.
  exit 1
 
ensure
  # Close the connection when it is open.
  db.close if db
end

On Ubuntu, it should return:

8.0.35-0ubuntu0.22.04.1

If you don’t know anything about the mysql2 Ruby Gem, you should read the documentation. It’s very concise and requires a basic understanding of Ruby programming. The two specific pages who may want to check for the next examples are:

The mysql_version.rb version uses the known string literal for columns or column aliases returned by the SQL statement, which becomes the stmt (or statement) in the program. The next program eliminates the need to enumerate with the text-based columns from the query by using the Statement#fields array values by use of a numeric index. The numeric index returns the field names from the Statement#fields class to use in as the name for values in the Result#fields value found in the row variable of the for loop.

# Include Ruby Gem libraries.
require 'rubygems'
require 'mysql2'
 
# Begin block.
begin
  # Create a new connection resource.
  db = Mysql2::Client.new( :host     => 'localhost' \
                         , :username => 'student'   \
                         , :password => 'student'   \
                         , :database => 'studentdb')
 
  # Create a result set.
  stmt = db.query("SELECT   DISTINCT i.item_title, ra.rating " +       \
                  "FROM     item i INNER JOIN rating_agency ra " +     \
                  "ON       i.item_rating_id = ra.rating_agency_id " + \
                  "WHERE    ra.rating_agency = 'MPAA'" +               \
                  "ORDER BY 1")
 
  # Read through the result set hash.
  stmt.each do | row |
    out = ""
    i = 0
    while i < stmt.fields.count()
      # Check when not last column and use the:
      #   - Hash returned by the result set for the value, and
      #   - String array value returned by the statement object
      #     as the name value of the hash by leveraging its 
      #     numeric index.
      if i < stmt.fields.count() - 1
        out += "#{row[stmt.fields[i]]}"
        out += ", "
      else
        out += "#{row[stmt.fields[i]]}"
      end
      i += 1
    end
    puts "#{out}"
  end
 
  # Release the result set resources.
  stmt.free
 
rescue Mysql2::Error => e
  # Print the error.
  puts "ERROR #{e.errno} (#{e.sqlstate}): #{e.error}"
  puts "Can't connect to MySQL database specified."
  # Signal an error.
  exit 1
 
ensure
  # Close the connection when it is open.
  db.close if db
end

It returns the select two columns from the query:

A Man for All Seasons, G
Around the World in 80 Days, G
Beau Geste, PG
Brave Heart, R
Camelot, G
Casino Royale, PG-13
...
Tomorrow Never Dies, PG-13
Tora! Tora! Tora!, G
Tron, PG

The following mysql_query_params.rb Ruby example accepts a single argument to leverage a wild card query in MySQL:

require 'rubygems'
require 'mysql2'
 
# Input external arguments.
arguments = ARGV
 
# Check for one input parameter and substitute an empty string
# when one isn't found.
if arguments.length == 1
  argument = arguments[0]
else
  argument = ""
end
 
# Begin block.
begin
  # Create a new connection resource.
  db = Mysql2::Client.new( :host     => 'localhost' \
                         , :username => 'student'   \
                         , :password => 'student'   \
                         , :database => 'studentdb')
 
  # Create a result set.
  stmt = db.prepare("SELECT   DISTINCT i.item_title, ra.rating " +       \
                    "FROM     item i INNER JOIN rating_agency ra " +     \
                    "ON       i.item_rating_id = ra.rating_agency_id " + \
                    "WHERE    ra.rating_agency = 'MPAA'" +               \
                    "AND      i.item_title LIKE CONCAT(?,'%')" +         \
                    "ORDER BY 1")
 
  # Bind the variable into the query.
  rs = stmt.execute(argument)
 
  # Read through the result set hash.
  rs.each do | row |
    out = ""
    i = 0
    while i < rs.fields.count()
      # Check when not last column and use the:
      #   - Hash returned by the result set for the value, and
      #   - String array value returned by the statement object
      #     as the name value of the hash by leveraging its 
      #     numeric index.
      if i < rs.fields.count() - 1
        out += "#{row[rs.fields[i]]}"
        out += ", "
      else
        out += "#{row[rs.fields[i]]}"
      end
      i += 1
    end
    puts "#{out}"
  end
 
  # Release the result set resources.
  rs.free
 
rescue Mysql2::Error => e
  # Print the error.
  puts "ERROR #{e.errno} (#{e.sqlstate}): #{e.error}"
  puts "Can't connect to MySQL database specified."
  # Signal an error.
  exit 1
 
ensure
  # Close the connection when it is open.
  db.close if db
end

If you call the mysql_query_params.rb program with this syntax:

ruby mysql_aquery_params.rb Harry

It’ll return the following from the studentdb database:

Harry Potter and the Chamber of Secrets, PG
Harry Potter and the Deathly Hallows, Part 1, PG-13
Harry Potter and the Deathly Hallows, Part 2, PG-13
Harry Potter and the Goblet of Fire, PG-13
Harry Potter and the Half Blood Prince, PG
Harry Potter and the Order of the Phoenix, PG-13
Harry Potter and the Prisoner of Azkaban, PG
Harry Potter and the Sorcerer's Stone, PG

After that, you should install Rails (check for current version beyond 1/2024). Install Ruby Global with this syntax:

gem install rails -v 7.1.3

Check the version installed:

rails -v

It should return:

Rails 7.1.3

Run this command to enable Rails for MySQL 8:

rails new myapp -d mysql

If you want to configure a username and password for MySQL, edit the config/database.yml file.

As always, I hope this helps somebody looking for step-by-step guide.

Written by maclochlainn

February 3rd, 2024 at 4:57 pm

Ubuntu Next.js Install

without comments

You begin by setting up Node with its version manager. You can do this in a Terminal shell with the following command:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

After running that command, you should reboot your system. Then, open a new Terminal session and start NVM with this command in your home directory:

nvm install --lts

After installing Node, create a new Next.js application to test if everything is working. Create a blog-app application with the following command in the Ubuntu bash shell session:

npx create-next-app@latest blog-app

It produces a small console log and asks you complete interactive responses as shown:


Need to install the following packages:
create-next-app@14.0.4
Ok to proceed? (y) y
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
Creating a new Next.js app in /home/student/blog-app.

Now, you can launch the application from the command-line interface (CLI):

npm run dev &

You can view the running application by using the following URL in a local browser:

http://localhost:3000

It should render the following web application:


As always, I hope this helps those curious about new things and who need a set of instructions.

Written by maclochlainn

January 11th, 2024 at 12:26 am

Posted in Next.js,Ubuntu

Tagged with

Oracle 23c Free SQL*Plus

without comments

It’s always frustrated me when using the sqlplus command-line interface (CLI) that you can’t just “up arrow” to through the history. At least, that’s the default case unless you wrap the sqlplus executable.

I like to do my development work as close to the database as possible. The delay from SQL Developer to the database or VSCode to the database is just too long. Therefore, I like the native sqlplus to be as efficient as possible. This post shows you how to install the rlwarp utility to wrap sqlplus and create a sandboxed student user for a local development account inside the Oracle 23c Free container. You should note that the Docker or Podman Container is using Oracle Unbreakable Linux 8 as it’s native OS.

You can connect to your Docker version of Oracle Database 23c Free with the following command:

docker exec -it -u root oracle23c bash

You can’t just use dnf to install rlwrap and get it to magically install all the dependencies. That would be too easy, eh?

Attempting to do so will lock your base OS and eventually force you to kill with prejudice the hung dnf process (at least it forced me to do so). You need to determine the rlwrap dependencies and then install them first. In that process, I noticed that the which utility program wasn’t installed in the container.

Naturally, I installed the which utility first with this command:

dnf install -y which

The rlwrap dependencies are: glibc, ncurses, perl, readline, python, and git. Only the perl, python, and git are missing from the list of formal dependencies but there’s another dependency the epel-release package.

If you want to verify whether a package is installed, you can use the rpm command like this:

rpm -qa | grep package_name

I installed the perl programming environment (a big install) with this command:

dnf install -y perl

I installed the python3 with this command:

dnf install -y python3

I installed the git module with this command:

dnf install -y git

I installed the epel-release container with this command:

dnf install -y epel-release

After installing all of these, you’re now ready to install the core rlwrap utility program. Like the other installations, you use:

dnf install -y rlwrap

At this point, you need to create a sandboxed user account for the Docker instance because as a developer using the root user for simple tasks is a bad idea. While you could do this with a Docker command, the Oracle 23c Free edition raised a lock on the /etc/group file when I tried it. Naturally, that’s not a problem because you can connect as the root user with this syntax:

docker exec -it -u root oracle23c bash

As the root user, create a student account as a developer account in the Oracle 23c Free container:

useradd -u 501 -g dba -G users -d /home/student -s /bin/bash/ -c "Student" -n student

You’ll be unable to leverage the tnsnames.ora file unless you alter the prior command to replace dba with oinstall or add the following command:

usermod -a -G oinstall student

Exit the Oracle 23c Free container as the root user and reconnect as the student user with this syntax:

docker exec -it --user student oracle23c bash

While you’re connected as the root user, you should create an upload directory as a subdirectory of the $ORACLE_BASE directory. The $ORACLE_BASE directory in the Oracle Database 23c Free Docker image is the /opt/oracle directory.

You should use the following syntax to create the upload directory and change its permission to that of the Oracle Database 23c Free installation (for a future blog post on developing external table deployment on the Docker image):

mkdir /opt/oracle
chown -R oracle:install /opt/oracle/upload

You also can add the following student function to the Ubuntu student user’s .bashrc file. It means all you need to type to connect to the Oracle Database 23c Free Docker instance is “student“. I like shortcuts like this one, which let you leverage one-line Python commands.

student () 
{
    # Discover the fully qualified program name. 
    path=`which docker 2>/dev/null`
    file=''
 
    # Parse the program name from the path.
    if [ -n ${path} ]; then
        file=${path##/*/}
    fi
 
    # Wrap when there is a file and it is rewrap.
    if [ -n ${file} ] && [[ ${file} = "docker" ]]; then
        python -c "import subprocess; subprocess.run(['docker exec -it --user student oracle23c bash'], shell=True)" 
    else
        echo "Docker is unavailable: Install the docker package."
    fi
}

Open a Ubuntu Terminal shell and type a student function name to connect to the Docker Oracle Database 23c Free instance where you can now test things like external tables with the SQL*Plus command line without installing it on the Ubuntu local operating system.

student@student-virtual-machine:~$ student
[student@d28375f0c43f ~]$ sqlplus c##student/student@free
 
SQL*Plus: Release 23.0.0.0.0 - Production on Wed Jan 3 02:14:22 2024
Version 23.3.0.23.09
 
Copyright (c) 1982, 2023, Oracle.  All rights reserved.
 
Last Successful login time: Wed Jan 03 2024 01:56:44 +00:00
 
Connected to:
Oracle Database 23c Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.3.0.23.09
 
SQL>

Then, I added this sqlplus function to the /home/student/.bashrc file, which is owned by the student user. However, I also added the instruction to change to the student user’s home directory because the Oracle 23c Free container will connect you to the /home/oracle directory by default. I also added the default long list (ll) alias to the .bashrc file.

sqlplus () 
{
    # Discover the fully qualified program name. 
    path=`which rlwrap 2>/dev/null`
    file=''
 
    # Parse the program name from the path.
    if [ -n ${path} ]; then
        file=${path##/*/}
    fi;
 
    # Wrap when there is a file and it is rewrap.
    if [ -n ${file} ] && [[ ${file} = "rlwrap" ]]; then
        rlwrap sqlplus "${@}"
    else
        echo "Command-line history unavailable: Install the rlwrap package."
        $ORACLE_HOME/bin/sqlplus "${@}"
    fi
}
 
# Change to the user's home directory.
cd ${HOME}
 
# Create a long list alias:
alias ll='ls -l --color=auto'

After you’ve configured your student user, you can configure the oracle user account to work like a regular server. Exit the Docker Oracle Database 23c Free as the student user, then connect as the root user with this command:

docker exec -it -u root oracle23c bash

As the root user you can become the oracle user with the following command:

su - oracle

Now, add the following .bashrc shell in the /home/oracle directory:

# The oracle user's .bashrc
 
# Source global definitions
if [ -f /etc/bashrc ]; then
	. /etc/bashrc
fi
 
# User specific environment
if ! [[ "$PATH" =~ "$HOME/.local/bin:$HOME/bin:" ]]
then
    PATH="$HOME/.local/bin:$HOME/bin:$PATH"
fi
export PATH
 
# Uncomment the following line if you don't like systemctl's auto-paging feature:
# export SYSTEMD_PAGER=
 
# User specific aliases and functions
export ORACLE_SID=FREE
export ORACLE_BASE=/opt/oracle
export ORACLE_HOME=/opt/oracle/product/23c/dbhomeFree
export PATH=$PATH:/$ORACLE_HOME/bin
 
# Change to the user's home directory.
cd ${HOME}
 
# Create a long list alias:
alias ll='ls -l --color=auto'
 
sqlplus () 
{
    # Discover the fully qualified program name. 
    path=`which rlwrap 2>/dev/null`
    file=''
 
    # Parse the program name from the path.
    if [ -n ${path} ]; then
        file=${path##/*/}
    fi;
 
    # Wrap when there is a file and it is rewrap.
    if [ -n ${file} ] && [[ ${file} = "rlwrap" ]]; then
        rlwrap sqlplus "${@}"
    else
        echo "Command-line history unavailable: Install the rlwrap package."
        $ORACLE_HOME/bin/sqlplus "${@}"
    fi
}

You need to manually source the .bashrc for the oracle user because it’s not an externally available user. Use this syntax to connect as the internal user:

sqlplus / as sysdba

It’ll display:

SQL*Plus: RELEASE 23.0.0.0.0 - Production ON Wed Jan 3 07:08:11 2024
Version 23.3.0.23.09
 
Copyright (c) 1982, 2023, Oracle.  ALL rights reserved.
 
 
Connected TO:
Oracle DATABASE 23c Free RELEASE 23.0.0.0.0 - Develop, Learn, AND Run FOR Free
Version 23.3.0.23.09
 
SQL>

After all this, I can now click the “up arrow” to edit any of the sqlplus command history. If you like to work inside sqlplus natively, this should help you.

Written by maclochlainn

December 20th, 2023 at 11:11 pm

AlmaLinux Installation

without comments

These are the instructions for installing AlmaLinux 9 on MacOS Intel with VMware. It’s broken into three parts: Configuration, Installation, and Setup.

You should download the current or desired older version of AlmaLinux from the website. It’s a good idea to install it in a source directory or folder.

You need to launch VMware, click the File menu option and then the New menu option before you can configure, install, and setup AlmaLinux. It will show you the following dialog.

Use the Finder to drag the AlmaLinux-9-latest-x86_64-dvd.iso file on to the Install from disc or image to begin the configuration process.

Configuration

Oracle12cInstall01

  1. The first thing you need to do is configure the VMware container. You click on the wrench icon in the menu bar that lets you edit the hardware settings for this virtual machine. This opens the general settings dialog.

Oracle12cInstall02

  1. The General System Settings dialog lets you will configure the isolation properties for “drag and drop” and “copy and paste”, and define the network adapter.

Oracle12cInstall03

  1. Click on the Isolation menu option from the General System Settings dialog, and enable “Drag adn Drop” and “Copy and Paste” checkboxes. Then, return to the General System Settings dialog/li>

Oracle12cInstall03

  1. Click on the Network Adapter menu option from the General System Settings dialog, and click the “Autodetect” checkbox. Then, return to the General System Settings dialog.

Installation

Oracle12cInstall02

  1. The first the install asks you to do is type an I for install or T for test. Generally, you can skip the test if this is a copied file rather than DVD.

Oracle12cInstall01

  1. Click the Tab key to configure the target installation or Enter key to skip the automatic boot delay.

Oracle12cInstall03

  1. Choose the installation language and click the Continue button.

Oracle12cInstall04

  1. The Installation Summary dialog lets you fix anything with a warning message orange triangle. Fix the Root Password first by clicking on the text.

Oracle12cInstall05

  1. Enter the Root Password twice, click the Lock root account checkbox to unselect it, and click the Allow root SSH login with password checkbox to enable it.

Oracle12cInstall06

  1. Click the Done button to complete setting the root password.

Oracle12cInstall07

  1. After fixing the root password, click on the Installation Destination item to change the default partition.

Oracle12cInstall08

  1. Click the Done button to accept the initial size of the VM partition.

Oracle12cInstall08

  1. Click the Software Selection element to add software components to the default installation.

Oracle12cInstall09

  1. Click the checkboxes for the following additional software:
    • Debugging Tools
    • Performance Tools
    • Remote Desktop Clients
    • Remote Management for Linux
    • Legacy UNIX Compatibility
    • Console Internet Tools
    • Development Tools
    • .NET Development
    • Graphical Administration Tools
    • System Tools

    Click the Done button to accept the added software elements.

Oracle12cInstall08

  1. Click the Begin Installation button to begin the installation of AlmaLinux.

Oracle12cInstall11

  1. The Installing Progress dialog will show a progress bar for several minutes.

Oracle12cInstall11

  1. The Installing Progress dialog eventually completes, and you click the Reboot System button to complete the installation.

Setup

Oracle12cInstall02

  1. The Welcome to AlmaLinux page begins the set up of the operating systems.

Oracle12cInstall01

  1. Click the Slide to disable location services.

Oracle12cInstall01

  1. After clicking the Slide location services are disabled.

Oracle12cInstall01

  1. The Online Account dialog lets you connect your email, online calendar, contacts, documents, and photos. You click the Skip button to avoid setting up any of the online accounts.

Oracle12cInstall01

  1. The About You dialog lets you enter user account with sudoer privileges.

Oracle12cInstall01

  1. The About You dialog enters a title case user’s name and a lowercase user’s name.

Oracle12cInstall01

  1. The Password dialog lets you enter a case sensitive password.

Oracle12cInstall01

  1. The Password dialog displays the entry of the passwords as dots.

Oracle12cInstall01

  1. The Setup Complete dialog leaves you to click the Start Using AlmaLinux button to complete the setup.

Oracle12cInstall01

  1. The blank image page displays until you click on it.

Oracle12cInstall01

  1. The Welcome to AlmaLinux dialog invites you to take a tour or decline it. Click the No Thanks button to decline the tour.

Oracle12cInstall01

  1. After declining the tour, it displays the core AlmaLinux screen.

Oracle12cInstall01

  1. Rebooting the system is necessary to complete the installation. Click on the circle icon on the right to begin the process to restart or shutdown the operating system. Click on the Power Off / Log Out dropdown to continue.

Oracle12cInstall01

  1. Click the Restart… menu option to restart the operating system.

Oracle12cInstall01

  1. Click the Restart… menu option to confirm the restart of the operating system.

Oracle12cInstall01

  1. Click the Student icon to get prompted for a password.

Oracle12cInstall01

  1. Enter the Student password to connect to the AlmaLinux operating system.

Oracle12cInstall01

  1. This displays the standard working interface for the AlmaLinux operating system.

Written by maclochlainn

October 28th, 2022 at 11:39 pm

Fedora for macOS ARM64

without comments

I’m always updating VMs, and I was gratified to notice that there’s a Fedora arm64 ISO. If you’re interested in it, you can download the Live Workstation from here or the Fedora Server from here.

Unfortunately, I only have macOS running on i7 and i9 Intel Processors. It would be great to hear back how it goes for somebody one of the new Apple M1 chip.

I typically install the workstation version because it meets my needs to run MySQL and other native Linux development tools. However, the server version is also available. Fedora is a wonderful option, as a small footprint for testing things on my MacBookPro.

Written by maclochlainn

May 7th, 2022 at 1:34 pm

JavaScript Streams?

without comments

A quick followup to my post on how to write a server-side shell component in JavaScript. Naturally, it’s based on a question posed to me about the original article. It asked, “Why didn’t I use JavaScript’s streams instead of synchronized files?”

Moreover, the question asks why I wrote logic (lines 69 thru 105) that wrote to local files rather than a stream. While they didn’t provide an example, here’s a rewritten solution that uses a stream.

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
  else {
    // Returns RowDataPacket from query.
    for(let element in result) {
      data += result[element].item_title + ', ' + result[element].release_date = '\n'
    }
 
    // Write file when data string is not empty.
    if (data.length > 0) {
      buffer = Buffer.alloc(data.length, data)
 
      // Check for data from database query and write file.
      if (path.length > 0) { 
        let writeStream = fs.createWriteStream(path)
        writeStream.write(buffer) 
        writeStream.on(buffer) 
      } 
      // Set standard out (stdout) and exit program.
      console.log(data)
      process.exit(0)
    }
    else {
      console.error('Query returned no rows.')
    }
  }

The Node.js stream replacement code has a significant problem when the target file can’t be read and written to by a Node.js application. This could be an ownership- or permission-driven problem coupled with the lazy file opening behavior of a stream in JavaScript. The lazy open is a hidden behavior of the createWriteStream() method, which actually calls the fs.open() method. It may raise the following type of error:

events.js:174 
     throw er; // Unhandled 'error' event 
     ^ 
 
Error: EACCES: permission denied, open 'output.csv' 
Emitted 'error' event at: 
   at lazyFs.open (internal/fs/streams.js:277:12) 
   at FSReqWrap.args [as oncomplete] (fs.js:140:20)

You can prevent this type of unhandled exception by putting this type of block right after you verify the target file name in the script. It ensures that your program raises a handled exception before your code tries to access a target file as a stream.

69
70
71
72
73
74
75
76
// Verify access to the file. 
try { 
  fs.accessSync(path, (fs.constants.R_OK && fs.constants.W_OK)) 
  access = true 
} 
catch { 
  console.error("Error accessing [%s] file.", path) 
}

Naturally, you also need to define the access variable at the top of your script. The preceding block lets you set the access variable to true on line 72 when you have permissions to the file used by the stream. It also lets you replace line 76 (from the prior example code) with the following statement that effectively blocks any attempt to use a stream that will fail because of the lazy file opening process:

76
      if (access && (path.length > 0)) {

Adding the extra block does lengthen the program, and change line numbers. I hope I’ve adjusted in a way that makes sense by referencing the old numbers for the change of the decision making if-statement.

As always, I hope this helps those looking for a related solution.

Written by maclochlainn

August 4th, 2020 at 1:47 am

Node.js Fedora Install

with 2 comments

I want to add the MEAN (MongoDB, Express.js, Angular.js, and Node.js) stack to my backend server development course. This post documents the installation and configuration of components on Fedora 30.

The first step requires installing the Node package. The Node package also contains the Node package manager (npm). You install the Node packages as the root user or as a sudoer user with the following command.

yum install -y npm

It should produce the following installation log:

Last metadata expiration check: 1:10:42 ago on Wed 08 Jul 2020 06:57:52 PM MDT.
Dependencies resolved.
================================================================================================================================
 Package                         Architecture          Version                                     Repository              Size
================================================================================================================================
Installing:
 npm                             x86_64                1:6.13.4-1.10.19.0.1.fc30                   updates                3.8 M
Installing dependencies:
 nodejs                          x86_64                1:10.19.0-1.fc30                            updates                 88 k
 nodejs-libs                     x86_64                1:10.19.0-1.fc30                            updates                9.1 M
Installing weak dependencies:
 nodejs-full-i18n                x86_64                1:10.19.0-1.fc30                            updates                7.3 M
 
Transaction Summary
================================================================================================================================
Install  4 Packages
 
Total download size: 20 M
Installed size: 91 M
Downloading Packages:
(1/4): nodejs-10.19.0-1.fc30.x86_64.rpm                                                         173 kB/s |  88 kB     00:00    
(2/4): nodejs-full-i18n-10.19.0-1.fc30.x86_64.rpm                                               2.8 MB/s | 7.3 MB     00:02    
(3/4): nodejs-libs-10.19.0-1.fc30.x86_64.rpm                                                    2.7 MB/s | 9.1 MB     00:03    
(4/4): npm-6.13.4-1.10.19.0.1.fc30.x86_64.rpm                                                   1.3 MB/s | 3.8 MB     00:02    
--------------------------------------------------------------------------------------------------------------------------------
Total                                                                                           4.9 MB/s |  20 MB     00:04     
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Running scriptlet: npm-1:6.13.4-1.10.19.0.1.fc30.x86_64                                                                   1/1 
  Preparing        :                                                                                                        1/1 
  Installing       : nodejs-libs-1:10.19.0-1.fc30.x86_64                                                                    1/4 
  Installing       : nodejs-full-i18n-1:10.19.0-1.fc30.x86_64                                                               2/4 
  Installing       : npm-1:6.13.4-1.10.19.0.1.fc30.x86_64                                                                   3/4 
  Installing       : nodejs-1:10.19.0-1.fc30.x86_64                                                                         4/4 
  Running scriptlet: nodejs-1:10.19.0-1.fc30.x86_64                                                                         4/4 
  Verifying        : nodejs-1:10.19.0-1.fc30.x86_64                                                                         1/4 
  Verifying        : nodejs-full-i18n-1:10.19.0-1.fc30.x86_64                                                               2/4 
  Verifying        : nodejs-libs-1:10.19.0-1.fc30.x86_64                                                                    3/4 
  Verifying        : npm-1:6.13.4-1.10.19.0.1.fc30.x86_64                                                                   4/4 
 
Installed:
  nodejs-1:10.19.0-1.fc30.x86_64           nodejs-full-i18n-1:10.19.0-1.fc30.x86_64     nodejs-libs-1:10.19.0-1.fc30.x86_64    
  npm-1:6.13.4-1.10.19.0.1.fc30.x86_64    
 
Complete!

After installing the Node package, you should use the Node package manager (npm) to install the Node Monitor nodemon. nodemon is a popular utility that automatically lets you restart Node programs when you make changes to the source code.

While npm is installed as part of the Node package, you must use npm to install the Node Monitor. The following command installs the nodemon globally on your Fedora system. The -g flag lets you install it globally, which is important when you manage package.json files.

npm install -g nodemon

You install nodemon globally but most of your web app or project files will be installed locally. Node is a different paradigm than building an Apache or IIS web application because Node provides a framework for you to build a web server.

Here’s a quick Hello World! example that I borrowed a JavaScript helloworld.js file from an excellent Web Development with Node & Express: Leveraging the JavaScript Stack by Ethan Brown. For those who haven’t worked with JavaScript in years, semicolons are optional now.

/* Construct a web server. */
const http = require('http')
const port = process.env.PORT || 3000
 
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' })
  res.end('Hello world!')
})
 
server.listen(port, () => console.log(`server started on port ${port}); ` +
                                      'press Ctrl-C to terminate...'))

I put this in /var/www/html/node directory, which is owned by the superuser, root. You need to start the server before accessing it from a browser. You can start the program with the following syntax as a privileged user:

node /var/www/html/node/helloworld.js

Then, you can use the localhost to access it with the following URL:

http://localhost:3000

It will display the following:

Next, you need to use the Node Package Manager (npm) to install the Express.js packages. You do that with the following syntax:

npm install -g express express-generator

It should produce a console out put like the following:

npm WARN deprecated mkdirp@0.5.1: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
/usr/local/bin/express -> /usr/local/lib/node_modules/express-generator/bin/express-cli.js
+ express@4.17.1
+ express-generator@4.16.1
added 60 packages from 42 contributors in 4.798s

After you install all the packages, you can inspect them with the following command. The packages are found in the /usr/local/lib/node_modules/express directory. The listing is generated from the package.json file on Fedora and Ubuntu Linux.

npm list -g

It should display something like this:

/usr/local/lib
├─┬ express@4.17.1
│ ├─┬ accepts@1.3.7
│ │ ├─┬ mime-types@2.1.27
│ │ │ └── mime-db@1.44.0
│ │ └── negotiator@0.6.2
│ ├── array-flatten@1.1.1
│ ├─┬ body-parser@1.19.0
│ │ ├── bytes@3.1.0
│ │ ├── content-type@1.0.4 deduped
│ │ ├── debug@2.6.9 deduped
│ │ ├── depd@1.1.2 deduped
│ │ ├─┬ http-errors@1.7.2
│ │ │ ├── depd@1.1.2 deduped
│ │ │ ├── inherits@2.0.3
│ │ │ ├── setprototypeof@1.1.1 deduped
│ │ │ ├── statuses@1.5.0 deduped
│ │ │ └── toidentifier@1.0.0
│ │ ├─┬ iconv-lite@0.4.24
│ │ │ └── safer-buffer@2.1.2
│ │ ├── on-finished@2.3.0 deduped
│ │ ├── qs@6.7.0 deduped
│ │ ├─┬ raw-body@2.4.0
│ │ │ ├── bytes@3.1.0 deduped
│ │ │ ├── http-errors@1.7.2 deduped
│ │ │ ├── iconv-lite@0.4.24 deduped
│ │ │ └── unpipe@1.0.0 deduped
│ │ └── type-is@1.6.18 deduped
│ ├─┬ content-disposition@0.5.3
│ │ └── safe-buffer@5.1.2 deduped
│ ├── content-type@1.0.4
│ ├── cookie@0.4.0
│ ├── cookie-signature@1.0.6
│ ├─┬ debug@2.6.9
│ │ └── ms@2.0.0
│ ├── depd@1.1.2
│ ├── encodeurl@1.0.2
│ ├── escape-html@1.0.3
│ ├── etag@1.8.1
│ ├─┬ finalhandler@1.1.2
│ │ ├── debug@2.6.9 deduped
│ │ ├── encodeurl@1.0.2 deduped
│ │ ├── escape-html@1.0.3 deduped
│ │ ├── on-finished@2.3.0 deduped
│ │ ├── parseurl@1.3.3 deduped
│ │ ├── statuses@1.5.0 deduped
│ │ └── unpipe@1.0.0
│ ├── fresh@0.5.2
│ ├── merge-descriptors@1.0.1
│ ├── methods@1.1.2
│ ├─┬ on-finished@2.3.0
│ │ └── ee-first@1.1.1
│ ├── parseurl@1.3.3
│ ├── path-to-regexp@0.1.7
│ ├─┬ proxy-addr@2.0.6
│ │ ├── forwarded@0.1.2
│ │ └── ipaddr.js@1.9.1
│ ├── qs@6.7.0
│ ├── range-parser@1.2.1
│ ├── safe-buffer@5.1.2
│ ├─┬ send@0.17.1
│ │ ├── debug@2.6.9 deduped
│ │ ├── depd@1.1.2 deduped
│ │ ├── destroy@1.0.4
│ │ ├── encodeurl@1.0.2 deduped
│ │ ├── escape-html@1.0.3 deduped
│ │ ├── etag@1.8.1 deduped
│ │ ├── fresh@0.5.2 deduped
│ │ ├── http-errors@1.7.2 deduped
│ │ ├── mime@1.6.0
│ │ ├── ms@2.1.1
│ │ ├── on-finished@2.3.0 deduped
│ │ ├── range-parser@1.2.1 deduped
│ │ └── statuses@1.5.0 deduped
│ ├─┬ serve-static@1.14.1
│ │ ├── encodeurl@1.0.2 deduped
│ │ ├── escape-html@1.0.3 deduped
│ │ ├── parseurl@1.3.3 deduped
│ │ └── send@0.17.1 deduped
│ ├── setprototypeof@1.1.1
│ ├── statuses@1.5.0
│ ├─┬ type-is@1.6.18
│ │ ├── media-typer@0.3.0
│ │ └── mime-types@2.1.27 deduped
│ ├── utils-merge@1.0.1
│ └── vary@1.1.2
└─┬ express-generator@4.16.1
  ├── commander@2.15.1
  ├── ejs@2.6.1
  ├─┬ minimatch@3.0.4
  │ └─┬ brace-expansion@1.1.11
  │   ├── balanced-match@1.0.0
  │   └── concat-map@0.0.1
  ├─┬ mkdirp@0.5.1
  │ └── minimist@0.0.8
  └── sorted-object@2.0.1

You can also create a secure node site (HTTPS) with the following additional steps. They include creating a self-signed secure public and private key. This creates the public key:

openssl genrsa -out key.pem

The openssl command will generate a private key key.pem file. It generates something like the following text message to console:

Generating RSA private key, 2048 bit long modulus (2 primes)
...........+++++
.............................................+++++
e is 65537 (0x010001)

Next, you need to generate a self-signed certificate. You do this in two steps.

  1. Create a Distinguished Name (DN) file. The csr.pem file is the DN file. You need it to create a self-signed certificate:

    openssl req -new -key key.pem -out csr.pem

    It will prompt you for values, like the following:

    You are about to be asked to enter information that will be incorporated
    into your certificate request.
    What you are about to enter is what is called a Distinguished Name or a DN.
    There are quite a few fields but you can leave some blank
    For some fields there will be a default value,
    If you enter '.', the field will be left blank.
    -----
    Country Name (2 letter code) [XX]:
    State or Province Name (full name) []:MiddleEarth
    Locality Name (eg, city) [Default City]:Rivendell
    Organization Name (eg, company) [Default Company Ltd]:Fellowship    
    Organizational Unit Name (eg, section) []:Self
    Common Name (eg, your name or your server's hostname) []:Localhost
    Email Address []:bilbo@loth.org
     
    Please enter the following 'extra' attributes
    to be sent with your certificate request
    A challenge password []:friend
    An optional company name []:Bilbo
  2. Use the DN file to create your secure certificate. The following openssl command creates the certificate file by using your private key key.pem and DN csr.pem files.

    openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem

    It should generate a Secure certificate cert.pem file and return something like the following to the console.

    Signature ok
    subject=C = XX, ST = MiddleEarth, L = Rivendell, O = Fellowship, OU = Self, CN = Localhost, emailAddress = bilbo@loth.org
    Getting Private key

You can put these private key (key.pem) and certificate (cert.pem) files in an ssl subdirectory of the directory where you put the JavaScript program. The following creates a secure server page with the following code.

/* Construct a secure web server. */ 
const https = require('https') 
const fs = require('fs') 
const port = process.env.PORT || 3000 
 
const options = { 
 key: fs.readFileSync('ssl/key.pem'), 
 cert: fs.readFileSync('ssl/cert.pem') 
} 
 
const server = https.createServer((options, res) => { 
 res.writeHead(200, { 'Content-Type': 'text/plain' }) 
 res.end('Hello world!') 
}) 
 
server.listen(port, () => console.log(`server started on port ${port}); ` + 
                                     'press Ctrl-C to terminate...'))

If you try launch your browser using the localhost instead of a DNS or file resolved network name on the designated port, it will raise the following security error:

This site can't be reached
https's server IP address could not be found.
DNS_PROBE_FINISHED_NXDOMAIN

An alternate approach to writing a secure server includes using Express.js library. The syntax changes somewhat and you include two new libraries, as shown below:

/* Construct a secure web server. */ 
const https = require('https') 
const express = require('express') 
const fs = require('fs') 
const app = express() 
const port = process.env.PORT || 3001 
 
const options = { 
 key:  fs.readFileSync('ssl/key.pem'), 
 cert: fs.readFileSync('ssl/cert.pem') 
} 
 
https.createServer(options, app).listen(port, () => { 
 console.log(`Express started in ${app.get('env')} mode ` + 
             `on port + ${port}.`) 
})

This will fail with the following error message if you’re running it with a global installation unless you set the $NODE_PATH environment variable correctly. Without setting the variable you may get the following error message:

internal/modules/cjs/loader.js:638 
   throw err; 
   ^ 
 
Error: Cannot find module 'express' 
   at Function.Module._resolveFilename (internal/modules/cjs/loader.js:636:15) 
   at Function.Module._load (internal/modules/cjs/loader.js:562:25) 
   at Module.require (internal/modules/cjs/loader.js:692:17) 
   at require (internal/modules/cjs/helpers.js:25:18) 
   at Object.<anonymous> (/var/www/html/node/helloworldsecure.js:3:17) 
   at Module._compile (internal/modules/cjs/loader.js:778:30) 
   at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10) 
   at Module.load (internal/modules/cjs/loader.js:653:32) 
   at tryModuleLoad (internal/modules/cjs/loader.js:593:12) 
   at Function.Module._load (internal/modules/cjs/loader.js:585:3)

Sometimes they’ll advise you to do an individual user installation of Express.js to get past this error but that’s not necessary. You just need to set the $NODE_PATH environment variable as follows:

export NODE_PATH=/usr/local/lib/node_modules

This will enable the JavaScript to work without error and without a specific user installation. Assuming you name either of these programs as helloworldsecure.js, you run them with the following command:

node helloworldsecure.js

You can terminate the program with a Ctrl+c or if use the kill -15 pid command if you started it as a background process. You can find the process ID (pid) with the jobs command.

As always, I hope this is helpful to those starting out with this cool technology stack.

Written by maclochlainn

July 8th, 2020 at 9:10 pm

Fix VMware Networking

with 3 comments

Occasionally, my students loose their network connection when copying their virtual machines. This article shows you how to rebuild your Internet connection.

The first step requires you to identify the port number on your host operating system, which is typically Windows OS or Mac OS X. You can find that by running the following search from a Mac OS X Terminal session or Windows OS Command session.

If you’re on the Mac OS X, you launch a Terminal session and then use the sudo command to open a shell as the root super user, like this:

sudo sh

As the root super user on Mac OS X , you run the netstat command like this:

sh-3.2# netstat -a | grep 1.ntp | grep -v grep
udp4       0      0  192.168.147.1.ntp      *.*

VMware uses the same subdomain with one difference for the gateway, it uses node 2:

192.168.147.2

The alternate syntax to find Vmware’s subdomain requires you to use an Administrator account on Windows, like this:

C:\> netstat -a | findstr /C:.ntp

After you determine the subdomain, you need to ensure VMware is configured correctly. You navigate to the menu and choose Virtual Machine and then Settings from the dropdown menu. The software shows you the following:

Linux7Network1

Then, click on the Network Adapter under the Removable Devices, and you see the following screen:

Linux71Network2

You need to make sure that you’re using Internet Sharing, or Share with my Mac. If you’re not using it select it now.

Launch the hosted Linux OS and open a Terminal seesion. Inside the Terminal, you should find the machine’s address as the root address with the ifconfig utility. The technique follows:

[student@localhost ~]$ sudo sh
[sudo] password for student: 
sh-4.2# ifconfig
eno16777736: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        ether 00:0c:29:70:77:64  txqueuelen 1000  (Ethernet)
        RX packets 34  bytes 4190 (4.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
 
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 0  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Next, you need to edit some files, they assume the VMware Network Gateway is 192.168.147.2 and the machine’s address is “00:0c:29:70:77:64“. The first file you need to edit is the /etc/resolv.conf file, and it should look like this:

domain localdomain
search localdomain
nameserver 192.168.147.2

The second file you need to edit is the /etc/sysconfig/network file. It should look like this:

# Created by anaconda
NETWORKING=yes
HOSTNAME=localhost.localdomain
GATEWAY=192.168.147.2

The third file you need to edit is the /etc/sysconfig/network-scripts/ifcfg-eth0 file. It should look like this:

DEVICE=eth0
HWADDR=00:0c:29:70:77:64
ONBOOT=yes
NM_CONTROLLED=yes
BOOTPROTO=dhcp
TYPE=Ethernet
DNS1=192.168.147.2
USERCTL=no
PEERDNS=yes
IPV6INIT=no

The last step requires that you reboot the machine or run the /etc/rc.d/init.d/network to restart the network. I hope this helps those trying to restore their VMware hosted operating systems network connection.

Written by maclochlainn

May 26th, 2016 at 12:58 am

Add Gedit Plugins

with one comment

Fedora comes with vim and gedit installed but the gedit installation is bare bones. You can update gedit to include supplemental Plug-ins with the following yum command as the root user:

yum install -y gedit-plugins

It generates the following log file:

Loaded plugins: langpacks, refresh-packagekit
mysql-connectors-community                                  | 2.5 kB  00:00     
mysql-tools-community                                       | 2.5 kB  00:00     
mysql56-community                                           | 2.5 kB  00:00     
pgdg93                                                      | 3.6 kB  00:00     
updates/20/x86_64/metalink                                  |  14 kB  00:00     
updates                                                     | 4.9 kB  00:00     
(1/2): pgdg93/20/x86_64/primary_db                          |  86 kB  00:00     
(2/2): updates/20/x86_64/primary_db                         |  11 MB  00:03     
(1/2): updates/20/x86_64/pkgtags                            | 1.5 MB  00:00     
(2/2): updates/20/x86_64/updateinfo                         | 2.0 MB  00:01     
Resolving Dependencies
--> Running transaction check
---> Package gedit-plugins.x86_64 0:3.10.1-1.fc20 will be installed
--> Processing Dependency: libgit2-glib for package: gedit-plugins-3.10.1-1.fc20.x86_64
--> Running transaction check
---> Package libgit2-glib.x86_64 0:0.0.6-2.fc20 will be installed
--> Processing Dependency: libgit2.so.0()(64bit) for package: libgit2-glib-0.0.6-2.fc20.x86_64
--> Running transaction check
---> Package libgit2.x86_64 0:0.19.0-2.fc20 will be installed
--> Processing Dependency: libxdiff.so.1()(64bit) for package: libgit2-0.19.0-2.fc20.x86_64
--> Processing Dependency: libhttp_parser.so.2()(64bit) for package: libgit2-0.19.0-2.fc20.x86_64
--> Running transaction check
---> Package http-parser.x86_64 0:2.0-5.20121128gitcd01361.fc20 will be installed
---> Package libxdiff.x86_64 0:1.0-3.fc20 will be installed
--> Finished Dependency Resolution
 
Dependencies Resolved
 
================================================================================
 Package          Arch      Version                            Repository  Size
================================================================================
Installing:
 gedit-plugins    x86_64    3.10.1-1.fc20                      updates    830 k
Installing for dependencies:
 http-parser      x86_64    2.0-5.20121128gitcd01361.fc20      fedora      23 k
 libgit2          x86_64    0.19.0-2.fc20                      fedora     281 k
 libgit2-glib     x86_64    0.0.6-2.fc20                       fedora      82 k
 libxdiff         x86_64    1.0-3.fc20                         fedora      33 k
 
Transaction Summary
================================================================================
Install  1 Package (+4 Dependent packages)
 
Total download size: 1.2 M
Installed size: 5.2 M
Downloading packages:
(1/5): http-parser-2.0-5.20121128gitcd01361.fc20.x86_64.rpm |  23 kB  00:00     
(2/5): libgit2-0.19.0-2.fc20.x86_64.rpm                     | 281 kB  00:00     
(3/5): libgit2-glib-0.0.6-2.fc20.x86_64.rpm                 |  82 kB  00:00     
(4/5): libxdiff-1.0-3.fc20.x86_64.rpm                       |  33 kB  00:00     
(5/5): gedit-plugins-3.10.1-1.fc20.x86_64.rpm               | 830 kB  00:01     
--------------------------------------------------------------------------------
Total                                              899 kB/s | 1.2 MB  00:01     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction (shutdown inhibited)
  Installing : libxdiff-1.0-3.fc20.x86_64                                   1/5 
  Installing : http-parser-2.0-5.20121128gitcd01361.fc20.x86_64             2/5 
  Installing : libgit2-0.19.0-2.fc20.x86_64                                 3/5 
  Installing : libgit2-glib-0.0.6-2.fc20.x86_64                             4/5 
  Installing : gedit-plugins-3.10.1-1.fc20.x86_64                           5/5 
  Verifying  : libgit2-0.19.0-2.fc20.x86_64                                 1/5 
  Verifying  : libgit2-glib-0.0.6-2.fc20.x86_64                             2/5 
  Verifying  : gedit-plugins-3.10.1-1.fc20.x86_64                           3/5 
  Verifying  : http-parser-2.0-5.20121128gitcd01361.fc20.x86_64             4/5 
  Verifying  : libxdiff-1.0-3.fc20.x86_64                                   5/5 
 
Installed:
  gedit-plugins.x86_64 0:3.10.1-1.fc20                                          
 
Dependency Installed:
  http-parser.x86_64 0:2.0-5.20121128gitcd01361.fc20                            
  libgit2.x86_64 0:0.19.0-2.fc20                                                
  libgit2-glib.x86_64 0:0.0.6-2.fc20                                            
  libxdiff.x86_64 0:1.0-3.fc20                                                  
 
Complete!

When you launch the gedit utility, you click on the

Gedit Plug-in Installation

GeditPref_01

  1. After you install the Gedit Plug-ins, you can configure the plug-ins by launching Gedit and then click on the gedit menu option. Then, click on the Preferences menu option to enable the new plugins, like the Embedded Terminal plug-in.

GeditPref_02

  1. You have four tab options when working with the Preferences menu. The first tab is the View tab, as shown to the left.

GeditPref_03

  1. The second tab is the Editor tab, as shown to the left.

GeditPref_04

  1. The third tab is the Font & Colors tab, as shown to the left.

GeditPref_05

  1. The fourth tab is the Plugins tab, as shown to the left. Scroll down the list and check the Embedded Terminal and Python Console plug-ins’ checkbox. The Embedded Terminal lets you edit a file and have command line access to a Terminal session from the gedit menu; and the Python Console session from the gedit menu.

GeditPref_06

  1. Click on the View menu, and then choose the Bottom Panel menu option.

GeditPref_07

  1. After enabling the Bottom Panel in the Gedit menu, you can edit a file and click on the Terminal by simply clicking on the subpanel. You can see the split image on the left. There’s also a set of bottom tabs that lets you switch from a Linux Terminal session to the Python console.

As always, I hope this helps those working with gedit on the Fedora operating system.

Written by maclochlainn

May 15th, 2015 at 1:23 am

C Shared Libraries

without comments

I wrote a shared C library example to demonstrate external procedures in the Oracle Database 11g PL/SQL Programming book. I also reused the same example to demonstrate Oracle’s external procedures in the Oracle Database 12c PL/SQL Advanced Programming Techniques book last year. The example uses a C Shared Library but a PL/SQL wrapper and PL/SQL test case.

One of my students asked me to simplify the unit test case example by writing the complete unit test in the C Progamming Language. The student request seemed like a good idea, and while poking around on the web it appears there’s a strong case for a set of simple shared C library examples. This blog post isn’t meant to replace the C Programming web site and C Programming Tutorial web site, which I recommend as a great reference point.

Like most things, the best place to start is with basics of C programming because some readers may be very new to C programming. I’ll start with basic standalone programs and how to use the gcc compiler before showing you how to use shared C libraries.

The most basic program is a hello.c program that serves as a “Hello World!” program:

1
2
3
4
5
#include <stdio.h>
 
int main() {
  printf("Hello World!\n");
  return(0); }

Assuming you put the C source files in a src subdirectory and the executable files in a bin subdirectory. You compile the program with the gcc program from the parent directory of the src and bin subdirectories, as follows:

gcc -o bin/hello src/hello.c

Then, you execute the hello executable program from the parent directory as follows:

bin/hello

It prints:

Hello World!

You can modify the basic Hello World! program to accept a single input word, like this hello_whom.c program:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
/* The executable main method. */
int main() {
  // Declare a character array to hold an input value.
  char whom[30];
 
  /* Print a question and read a string input. */
  printf("Who are you? ");
  scanf("%s", whom);
  printf("Hello %s!\n", whom);
  return(0); }

You can compile the hello_whom.c program as follows:

gcc -o bin/hello_whom src/hello_whom.c

Then, you execute the hello_whom executable program from the parent directory as follows:

bin/hello_whom
Who are you? Stuart

It prints:

Hello Stuart!

Alternatively, you can modify the hello_whom.c program to accept a stream of text, like the following hello_string.c program:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
/* The executable main method. */
int main() {
  // Declare a character array to hold an input name.
  char phrase[4000];
 
  /* Print a question and read a string input. */
  printf("Hello? ");
  scanf("%[^\n]%*c", phrase);
  printf("Hello %s!\n", phrase);
  return(0); }

The [] is the scan set character. The [^\n] on line 10 defines the input as not a newline with a white space, and the %*c reads the newline character from the input buffer. After you compile the program you can call it like this:

bin/hello_string
Hello? there, it reads like a C++ stream

It would print:

Hello there, it reads like a C++ stream!

These example, like the previous examples, assume the source files are in a src subdirectory and the executable files are in the bin subdirectory. All compilation commands are run from the parent directory of the src and bin subdirectories.

The first example puts everything into a single writingstr.c file. It defines a writestr() function prototype before the main() function and the writestr() function after the main() function.

The code follows below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
 
/* Declare two integer variables. */
char path[255], message[4000];
 
/* Define a prototype for the writestr() function. */
void writestr(char *path, char *message);
 
/* The executable main method. */
int main() {
  printf("Enter file name and message: ");
  scanf("%s %[^\n]%*c", &path, &message);
  printf("File name:    %s\n", path);
  printf("File content: %s\n", message);
  writestr(path, message);
  return(0); }
 
void writestr(char *path, char *message) {
  FILE *file_name;
  file_name = fopen(path,"w");
  fprintf(file_name,"%s\n",message);
  fclose(file_name); }

You can compile the writingstr.c function with the following syntax:

gcc -o bin/writingstr src/writingstr.c

You can run the writingstr executable file with the following syntax:

bin/writingstr
Enter file name and message: /home/student/Code/c/test.txt A string for a file.
File name:    /home/student/Code/c/test.txt
File content: A string for a file.

You’ll find a test.txt file written to the /home/student/Code/C directory. The file contains only the single sentence fragment entered above.

Now, let’s create a writestr.h header file, a writestr.c shared object file, and a main.c testing file. You should note a pattern between the self-contained code and the approach for using shared libraries. The prototype of the writestr() function becomes the definition of the writestr.h file, and the implementation of the writestr() function becomes the writestr.so shared library.

The main.c file contains the only the main() function from the writingstr.c file. The main() function uses the standard scanf() function to read a fully qualified file name (also known as a path) as a string and then a text stream for the content of the file.

You define the writestr.h header file as:

1
2
3
4
5
6
#ifndef writestr_h__
#define writestr_h__
 
extern void writestr(char *path, char *message);
 
#endif

You define the writestr.c shared library, which differs from the example in the book. The difference is the #include statement of the writestr.h header file. The source code follows:

1
2
3
4
5
6
7
8
#include <stdio.h>
#include "writestr.h"
 
void writestr(char *path, char *message) {
  FILE *file_name;
  file_name = fopen(path,"w");
  fprintf(file_name,"%s\n",message);
  fclose(file_name); }

You define the main.c testing program as:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include "writestr.h"
 
/* Declare two integer variables. */
char path[255], message[4000];
 
/* The executable main method. */
int main() {
  printf("Enter file name and message: ");
  scanf("%s %[^\n]%*c", &path, &message);
  writestr(path, message);
  return(0); }

Before you begin the process to compile these, you should create an environment file that sets the $LD_LIBRARY_PATH environment variable or add it to your .bashrc file. You should point the $LD_LIBRARY_PATH variable to the directory where you’ve put your shared libraries.

# Set the LD_LIBRARY_PATH environment variable.
export LD_LIBRARY_PATH=/home/student/Code/c/trylib/libfile

With programs defined, you need to first compile the writestr.c shared library first. You use the following syntax from the parent directory of the src and bin subdirectories.

gcc -shared -fPIC -o bin/writestr.so src/writestr.c

If you haven’t set the $LD_LIBRARY_PATH, you may raise an exception. There’s also an alternative to setting the $LD_LIBRARY_PATH before you call the gcc executable. You can use the -L option set the $LD_LIBRARY_PATH for a given all to the gcc executable, like:

gcc -L /home/student/Code/c/trylib/libfile -shared -fPIC -o bin/writestr.so src/writestr.c

Then, you compile the main.c program. You must put the writestr.so shared library before you designate the main target object and main.c source files, like this:

gcc bin/writestr.so -o bin/main src/main.c

Now, you can perform a C-only unit test case by calling the main executable. However, you must have set the $LD_LIBRARY_PATH environment variable at runtime too. You see the following reply to the “Enter file name and message” question when you run the main program unit:

bin/main
Enter file name and message: /home/student/Code/c/trylib/libfile/test.txt A long native string is the second input to this C program.

You can now see that the a new test.txt file has been written to the target directory, and that it contains the following string:

A long native string is the second input to this C program.

As always, I hope this helps those you want to write shared libraries in the C programming language.

Written by maclochlainn

May 7th, 2015 at 1:46 am