Linux Distros That Suck at Multiple Hard Drives

Some Linux distros really suck at dealing with multiple hard drives. Too many “maintainers” only have a laptop.

Background

You need a wee bit of background before we jump in. Hopefully you can see the featured image. Recently picked up this Lenovo M93p ThinkCentre from eBay. I specifically bought an M93p instead of M83 because I wanted two hard drives. I had a 480 GB SSD I wanted to transfer the Windows 10 over to and I had a 6TB Western Digital Black I wanted to use for the other operating systems.

Why did I buy this particular M93p?

Lenovo M93p Ports

I actually added the PS/2 ports today. The little cable showed up to do that. It already had both serial ports, wifi, and the NVIDIA add-on video card. If your eyes are real good you will notice that on the other side of that Wifi antenna is a parallel port.

Software engineers need a lot of ports. If book sales start picking up I may even have to break down and buy another dot matrix printer to print shipping labels with. Yes, parallel port dot matrix printers are still made. You can buy them from newegg.com today. There are lots of legal requirements to print with impact printers on multi-part forms in various shipping and transport industries. They also do a more economic and reliable job on mailing labels . . . if you buy the right one . . . and you have the proper printer stand.

Printer stand back

The best ones from days of old have both a center feed slot and a rear feed slot to accommodate either type of printer. Long time readers of this blog will remember I started work on a Qt and USB series and then life got in the way. That was all USB serial ports talking to real serial ports. My Raspberry Qt series also involved quite a bit of serial port work. My How Far We’ve Come series also involved quite a bit of serial port stuff as well.

Putting it mildly, I still do a fair bit of serial port work from time to time. If I get done with RedDiamond and RedBug without life getting in the way I’m going to start a new post series using CopperSpice and serial ports. The makers of Qt have honked off their installed base with the new “subscription licensing” for Qt 6.x and beyond. Even more honkable, if that is possible, is the chatter that they are trying to license the OpenSource QtCreator as well. Yeah, people are making a hasty exit from the Qt world and many are headed to CopperSpice.

Sadly Needed Windows

Unlike every other machine in this office, I needed to have Windows on this machine. There is some stuff coming up that will require it. There is no way in Hell I was going to try writing my serial port code using Linux in a VM. I may edit it there, but testing is a completely different story.

You’ve never spent days trying to track down why some characters don’t get through. Worse yet, the serial port just “stops working.” After you do a bunch of digging you find that someone baked in some super secret control strings to do special things in the interface driver of the VM. Nothing nefarious. Usually to support “remoting in” via cable connection.

Boot Managers

In the days of DOS and GUI DOS that Microsoft insisted on calling Windows, this was no big deal. BootMagic and about a dozen other competitors existed to help Noobies and seasoned pros alike install multiple operating systems onto the same computer. Honestly, I can’t even remember all of the different products that had a brief life helping with this very task.

OS/2 had Boot Manager backed in. Those of us needing to develop for multiple operating systems usually ran OS/2 as our primary. It just made life so much easier.

Early floppy based Linux distributions came with Lilo. It was generally pretty good at realizing Linux wasn’t going to be on the primary disk. SCSI controllers could support six drives and distributions were different enough you had to boot and build on each.

Grub

Later many distros went with Grub. To this day Grub has issues. The biggest issue is that each Linux distro adopts new versions of Grub at their own pace and Grub has a bit of history when it comes to releasing incompatible versions.

Adding insult to injury is the fact many Linux distros like to hide files Grub needs in different places. When you run your distros version of “update-grub” (as it is called in Ubuntu) it has to be a real good guesser when it wants to add a Grub menu line for a different distro.

Your second fatal injury happens during updates. Say you have an RPM based distro but have Ubuntu as the primary Grub OS. When your RPM based distro updates and changes the boot options for its own Grub menu entry in its own little world it has no way of informing the Grub that is actually going to attempt booting. Sometimes an “update-grub” will fix it and sometimes it won’t. A bit heavier on won’t that will.

Drives got too big

That’s the real problem. During the SCSI days when 80MEG was a whopper we put each OS on its own disk and just changed the boot device. That was our “boot manager.” Every OS existed in its own little universe.

As drives got bigger various “boot manager” type products could play games with MBR based partitions. Only one partition could be “active” so a tiny little boot manager got stuff into the MBR and it changed the active partition to match the requested OS.

Cheap but effective trick as long as you didn’t need more than four partitions. Only a primary partition could be flagged for active booting. Lilo and the other Linux boot managers started allowing Linux distros to boot from Extended partitions.

Today we have GPT and UEFI

I’m not intimate with how these work. The Unified Extensible Firmware Interface (UEFI) created the spec for GUID Partition Table (GPT). {A GUID is a Globally Unique Identifier for those who don’t know. That’s really more than you need to know.}

Theoretically we can have an unlimited number of partitions but Microsoft and Windows have capped support at 128. The UEFI should be replacing Grub, Lilo, and all of these other “boot manager” type techniques.

We shouldn’t have all of these problems

As you install each OS it should obtain its partition GUID then find the boot device and locate the UEFI partition on it. Then it should look for a matching GUID to update and if not found, create an entry. There is a spec so every entry should be following the same rules.

(If you read up on the OS/2 boot manager you will see that from the 10,000 foot level UEFI and the OS/2 boot manager conceptually have a lot in common.)

When any computer boots from UEFI and there are multiple operating systems in the UEFI partition, UEFI should show the menu and let the user select. This should all be in hardware and firmware now. We shouldn’t have Microsoft trying to lock us into their buggy insecure OS and Linux distros shouldn’t be trying to ham-fist Grub into UEFI.

The Split

I wanted all Linux distros to boot from the 6TB drive. I wanted Windows and UEFI to stay on the tiny SSD. This isn’t unreasonable. As all of the background should tell you, I’ve been doing things like this for decades. I did not want to try and stuff everything on the 6TB.

Each Linux distro would get 500 GB – 800 GB depending on how much I thought I would be doing in them. This means I should be able to put up to 12 different distros on the drive.

That may sound like a lot, but it’s not. You’ve never written code that worked perfectly on a Ubuntu LTS and failed rather bad on some of the YABUs supposedly using that LTS as their base . . . I have. The only way to know things for certain is to have a bunch of test systems. When you are testing serial port (or other device stuff) you need to be running on hardware, not in a VM.

Manjaro was the first failure

Manjaro kernel 5.9.16-1 was actually a double failure. I have this distro running on a pair of machines, but it is the only OS on them. Rather like what they’ve done with the KDE desktop. I rather hate the fact PostgreSQL cannot access the /tmp directory bulk import to restore a database doesn’t work on that platform. There are a few other odd Manjaro bugs as well.

I wanted to do some pacman packaging and some testing of the future serial port code in CopperSpice on Manjaro so it was first on the list. It booted fast and seemed to install clean. Rebooted the computer and boom, Windows came up. Navigated to the Advanced Settings under Settings in Control panel and tried to switch the boot OS. Boom! Windows is the only entry.

(*&^)(*&)(*

Let’s Install Ubuntu!

I had real dread when I reached for Ubuntu. That installer has had a lot of assumptions baked into it over the years. I was pleasantly surprised and slightly disturbed.

Installation went smooth and when I rebooted I was greeted with a Grub menu. Both Windows and Manjaro were on the Grub menu, but, should we really be seeing Grub on a UEFI system with multiple operating systems? Shouldn’t there be a UEFI menu that just has an entry for Ubuntu and when you select Ubuntu shouldn’t that be when you see a Ubuntu Grub menu?

Let’s See if Manjaro Boots Now!

Once I verified Ubuntu could boot and apply updates I rebooted and selected Manjaro. That’s as far as you get. The Lenovo logo stays on the screen and nothing else happens. HP owners have the same problem according to Reddit.

Fedora 33 Was Next

The Fedora installer was the worst of the lot. If you chose the second drive via one of the manual methods, it looked for a UEFI partition on that drive. It wasn’t smart enough to determine what the boot device was and go look there. You couldn’t get out of the screen either. There was no back or cancel, you had to power down.

Summary

Manjaro at least tried to install. It failed to create anything in the UEFI partition of the boot disk and it failed to show any error with respect to UEFI creation failure. It refuses to boot from the entry Ubuntu created for it in Grub. Double failure. I suspect this is due to a combination of super secret stuff needed on the menu entry, Manjaro using a different version of Grub, and Manjaro potentially hiding the files in a place Ubuntu doesn’t know to look.

Fedora failed to get out of the starting blocks. That graphical installer needs a whole lot of work!

Ubuntu worked despite my expectations of abject failure.

Just because Ubuntu worked doesn’t mean every YABU will. Most tend to write their own installers. If the developer working on the installer only has a laptop, they are going to take unreasonable shortcuts.

Related posts:

Fedora 33 Black Screen Again

How to Install PostgreSQL on Fedora 33

Fedora 32 – Black Screen After Login

Qt and USB – Pt. 1

Recently I’ve had some discussions on the qt-interest mailing list about Qt and USB and why it is not integrated in the package. Many of the kids there aren’t old enough to shave so they don’t remember this _exact_ same journey with serial ports. Couldn’t be done in a cross platform blah blah blah. Then there were various “unofficial” Qt based serial port classes. Then we had “almost” official stuff showing up in the Qt Playground. Eventually it all lead to the QSerialPort class. So much for that “can’t be done in a cross platform blah blah blah.”

Adding insult to injury, there already is a cross platform C library for generic USB access. It is called libusb. It is OpenSource with a list of platforms almost as long as the Qt list of supported platforms. So, instead creating a QUsb wrapper class for this library, each project rolls their own. No, they can never get rolled back in. The process of trying to submit a user contribution and push it all the way through to inclusion is akin to rolling naked twelve miles through broken glass. Takes about as long too. So, rather than getting something included and maintained as part of the library we will once again head down a broken and disjointed path. (Yes, I’m one of those people who was using all of those different, incompatible serial port classes and then had to rewrite everything once again when QSerialPort was officially adopted. They didn’t even keep the class name from the playground!)

I had to pack up my Raspberry Pi to clean off a table for an upcoming project. Will be quite some time before I can test this code on Raspberry Pi. I have been applying all updates, but, this machine is otherwise close to where we left off with the Raspberry Pi experiments.

As to the install of libusb development files, that took me two attempts.

You can visit the site and read through the doc, but, there is a parting of the ways between 0.1-4 and the 1.0 and forward stuff. Everyone wants to get the 1.0 and newer stuff for new development.

One must start out with baby steps. Given that mantra, my sample application doesn’t do much.

It’s just a main window with a text browser. The text browser gave me some place to dump what would otherwise be standard output. I’m even going to be honest here and tell you much of the code to retrieve that information was stolen/ported from examples found at dreamincode.net. One thing I want to do for the next baby step is find a method of translating VendorID and ProductID values into some human usable text. There appears to be at least one OpenSource effort, but I haven’t had time to drill down into that project. Been helping with corn planting season so you all can eat. There are no “amber waves of grain” until someone puts the seed in the ground so keep that in mind the next time you and your can’t-look-up-from-the-idiot-phone friends are bitching about the farm bill.

White 2-195 – My most favorite tractor ever – not me in image though

I know you are going to steal this code so let me say this.

You are not allowed to change the class name!

 

#ifndef LOGIKALUSB_H
#define LOGIKALUSB_H

#include
 #include

class LogikalUSB : public QObject
 {
 Q_OBJECT
 public:
 explicit LogikalUSB(QObject *parent = nullptr);
 ~LogikalUSB();

size_t deviceCount() { return dev_count;}
 void getDeviceReport(QString *txt);
 libusb_device *getDevice(unsigned int sub);
 libusb_device_descriptor *getDeviceDescriptor(int sub);

signals:

public slots:

private:
 libusb_device **devs = nullptr; // pointer to pointer of device used to retrieve list
 libusb_context *ctx = nullptr; // libusb session
 size_t dev_count = 0; // number of entries in list
 };

#endif // LOGIKALUSB_H

Like I said, this is baby steps. I am just creating a wrapper class around libusb.

 

#include "logikalusb.h"

#include 
#include 

LogikalUSB::LogikalUSB(QObject *parent) : QObject(parent)
{
    int r0_status;              // return values


    r0_status = libusb_init(&ctx);
    if (r0_status < 0)
    {
        // TODO:: need an error log util to call
        std::cout << "Init Error " << r0_status << std::endl;
        return;
    }

    libusb_set_debug(ctx, 3);   // verbosity level

    int x = libusb_get_device_list(ctx, &devs);
    if (x < 0)
    {
        std::cout << "Get Device Error " << x << std::endl;
        dev_count = 0;
    }
    else
    {
        dev_count = (unsigned int) x;
    }

}

LogikalUSB::~LogikalUSB()
{
    if (devs)
    {
        libusb_free_device_list(devs, 1);   // free the list, unref the devices
    }

    if (ctx)
    {
        libusb_exit(ctx);
    }
}

libusb_device *LogikalUSB::getDevice(unsigned int sub)
{
    libusb_device *retVal = nullptr;

    if (sub < dev_count)
    {
        retVal = devs[sub];
    }

    return retVal;
}

void LogikalUSB::getDeviceReport(QString *txt)
{
    QTextStream out(txt);

    for (size_t y=0; y < dev_count; y++)
    {
        libusb_device *dev = devs[y];
        libusb_device_descriptor desc;
        int r0_status = libusb_get_device_descriptor(dev, &desc);
        if (r0_status < 0)
        {
            std::cout << "Get Device Descriptor Error " << r0_status << std::endl;
            continue;
        }

        out << "Number of possible configurations: " << (int)desc.bNumConfigurations << "   ";
        out << "Device Class: " << (int)desc.bDeviceClass<<"  ";
        out << "VendorID: " << desc.idVendor << "   ";
        out << "ProductID: " << desc.idProduct << endl;

        libusb_config_descriptor *config;
        libusb_get_config_descriptor(dev, 0, &config);

        out << "Interfaces: " << (int)config->bNumInterfaces << "   ";

        const libusb_interface *inter;
        const libusb_interface_descriptor *interdesc;
        const libusb_endpoint_descriptor *epdesc;

        for(int i=0; i < (int)config->bNumInterfaces; i++)
        {
            inter = &config->interface[i];
            for(int j=0; jnum_altsetting; j++)
            {
                interdesc = &inter->altsetting[j];
                out << "Interface Number: " << (int)interdesc->bInterfaceNumber << " | ";
                out << "Number of endpoints: "<< (int)interdesc->bNumEndpoints << " | ";

                for(int k=0; k<(int)interdesc->bNumEndpoints; k++)
                {
                    epdesc = &interdesc->endpoint[k];
                    out << "Descriptor Type: " << (int)epdesc->bDescriptorType << " | ";
                    out << "EP Address: " << (int)epdesc->bEndpointAddress << " | ";
                }
            }
        }

        out << endl << endl << endl;
    }
}

 

We had to have a class destructor here because of the dynamic allocation. One of my big regrets here is the API is obviously set up for pre-C11 standards, making us keep a count and utilize older style for() loops.

The biggest benefit this USB library brings to the table is “user mode.” I just ran this as myself. I didn’t create any udev rules or have to jump through any other hoops as of yet. This was a major pain in the early iterations for the various serial port classes. You almost always had to be root. Truth be told, I am just reading right now. I haven’t chosen two ports to use for direct communication between two instances of this application. Ultimately  would like to find a place to set up my Raspberry Pi with a long enough cable, cross compiling for target, then running.

That reminds me. I need to email a client of mine. That is the last place I saw my 20 or so foot long USB cable.

I will work on this project as long as rain and time allow. Very soon I will be starting a new contract so we are talking hours to devote to this, not weeks or months. I really do want to get these ID values translating to human consumable strings. That will make the evolution of this experiment much cleaner. If we can simply and easily get real names we can have a pick list.

 

Raspberry Qt – Part 9

Making the serial port actually work

By default your Pi has a getty/mgetty process enabled. There are many out of date instructions about disabling it. Please pay attention to the date of the post when looking for advice. The Pi world is fast changing. While those instructions were good at one time they are invalid now just as these instructions may well be invalidated in the future.

Open a terminal window and type

sudo raspi-config

advanced_options

Arrow down to “Advanced Options” and hit return.

a7-serial

Arrow down to the Serial device and hit return.

would_you_like_2

Use the Tab key to highlight “No” then hit return.

serial_disabled

Hit return.

tab_to_finish

Tab to “Finish” and hit return.

would-you-like-to-reboot

Reboot your Pi. Once booted open a terminal window and type

sudo nano /boot/config.txt

Near the bottom of this file you will see

enable_uart=0

change it to be

enable_uart=1

then type ctrl-x save the file and exit. Reboot again. Some of you are probably familiar with a computer BIOS where you can configure the system. The Pi uses a script called config.txt. There is a side effect caused by disabling serial login. The logic also disables the serial port. We want serial login disabled but we want to leave the serial port enabled.

You will find many discussions on-line about the various voltage levels of serial ports. Perhaps I’ve always been lucky because I have always used HP machines when working with a Pi. That hacking of a serial cable without using a TTL converter has always worked for me without cooking a Pi. I need to take the time now to show you the recommended method of connecting.

The first thing you need to know is that not all TTL adapters will work with your Pi. You need to open a search engine and search for:

RS232 Serial Port To TTL Converter Module MAX3232

It is important you get the 3232 because the older 232 doesn’t support the 3.3v of the Pi. Depending on the age of your computer the voltage of the serial port could be anywhere from 12v down to 5v. Here is the bottom of my TTL converter.

0801160954a

The VCC pin is power followed by GrouND, Receive and Transmit. Thankfully the cable which came with mine had different symbols on the wires so it was easy to trace.

0801160954b

Notice that I put the connector on so the wire with the big red lines was on the power pin. That made the connector line up well for pins on the Pi. You just have to be one pin over from the top front of the pin set on the PI.

0801160956

To complete the cabling I used a DB9-DB25 serial mouse adapter I had laying around.

0801160957a

Now we are connected in the official manner.

Raspberry Qt – Part 7

Finding the serial port

There is one chunk of the code we really do need to discuss and that is the snippet of how the application chooses its serial port beginning around line 48 in serialthread.cpp. I even put a comment in the code to explain why it looks the way it does.

    // One cannot be certain about order if more than one serial port is on the system.
    // My HP development machine had only one physical serial port yet Ubuntu identified
    // ttys4 and ttys0. Naturally ttys4 ended up coming out of the list first. I have no idea what
    // device it thought was a serial port.
    //
    // Yes, it is a horrible waste of resources since foreach() makes a copy of the container/collection before
    // it begins working on it.
    QMap <QString, QSerialPortInfo> portMap;
    foreach( const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    {
        portMap.insert( info.portName(), info);
    }

    foreach( QSerialPortInfo info, portMap)
    {
        QSerialPort serial;
        serial.setPort(info);
        QString txt;
        QTextStream strm( &txt);

        strm << "Testing: " << info.portName() << " manufacturer: " << info.manufacturer()
                 << " description: " << info.description() << " serial number: " << info.serialNumber()
                    << " baud: " << serial.baudRate() << " data: " << serial.dataBits() << " parity: " << serial.parity()
                       << " stop: " << serial.stopBits();

        LogikalLogger::Instance().logString( LogikalLogger::LL_INFO, txt);
        if (serial.open(QIODevice::ReadOnly))
        {
            serial.close();
            if (!portFound)
            {
                m_portInfo = info;
                txt = "port " + info.portName() + " chosen as device";
                LogikalLogger::Instance().logString( LogikalLogger::LL_INFO, txt);
                portFound = true;
                // don't crash out of the foreach, won't take long to spin through it.
            }
        }
        else
        {
            LogikalLogger::Instance().logString( LogikalLogger::LL_ERR, "Test failed: " + serial.errorString());
        }
    }

Believe it or not I had been using the code for finding the serial port over a number of years on different projects both personal and professional. Always just took the QList from availablePorts() and walked down it. Never had a problem. The machine I chose to use for this project gave me my first problem. While there is physically only one serial port, Ubuntu thinks there is both a ttys0 and ttys4 and the little caveat found with availablePorts() came back to bite me. I have no idea what this machine believes is ttys4 but, it came back first in the list and it successfully opened. Most frustrating! Software didn’t work and the mini-tester verified we were not connected.

As a result of that experience (remember: experience is what you get when you don’t get what you want) we now have 2 foreach() loops. The first creates a QMap ordered by port name. We are lucky that most operating systems number serial ports but start with a base name so they can be sorted.

Assignment 1:

Get your Linux environment set up to cleanly build and run the application on your desktop. Connect your cable to your Pi, tail out syslog and achieve the same results I have shown you in Part 6.

Assignment 2:

Modify the code in serialthread.cpp to read and use serial port information from a config/ini/whatever file. By information I mean port number, baud rate, data, parity and stop bits. Fall back to finding the first port if and only if you cannot successfully load the information or open the serial port.