Wednesday, July 8, 2015

Windows 8.1 and BootCamp: I Hate It

I normally use a Mac to get things done. Of course there are times that require I use Windows (unless there's a Mac release of Active Directory management tools...), and when that happens I use Windows 7 running in VirtualBox.

Why?

Because I hate Windows 8.

Every time I've used it, I'm reminded of more reasons why I don't like it. And I've talked to many other people who have expressed the same first impression I had: the user interface was created only for tablets with touch screens. That is by far the most common opinion I run into. "If this were on a tablet, I'd probably like it more," they lament.

My son has a Mac. I said if he wanted a computer, he was either going to get a Mac or he'd have to learn how to support his own technology. For his purposes...which he swore was mostly Minecraft with a heavy does of watching YouTube videos...a Mac was perfectly fine.

Fast forward a bit; somewhere he was exposed to videos of Steam. He wanted to play some kind of video games that were only supported on Windows. Windows! "Please? Please? PLEAASE!?," he begged.

"If you want it, you have to buy it."

So he bought Windows. A boxed 8.1 DVD set. Ugh.

I eventually agreed as a birthday gift to install Windows using BootCamp on his MacBook Pro. If you're not familiar with BootCamp, it is a concession made by Apple for people who for some reason or another require not only Windows on their Mac, but direct access to the hardware. Usually it's because of a shortcoming in virtualization; some driver pukes or a piece of hardware's interface isn't perfectly emulated. Once in awhile it's because someone finds the small performance hit from virtualization to be unacceptable. For these occasions Apple released a suite of drivers and a configuration utility to assist in repartitioning your hard drive and installing a dedicated full installation of Windows on the Mac.

I have never been a fan of BootCamp. Part of the reason the Mac is great is because of OS X being tightly integrated with the hardware. Apple doesn't toss the operating system on as an afterthought. Windows more or less cripples a machine that was designed to work with OS X, and Apple has no reason to go out of their way to make the experience better.

But I agreed to try getting that overpriced Windows DVD to install on his Mac. I installed a 500GB hybrid drive...his existing drive was too small to adequately handle the installation. I used the BootCamp Assistant utility, included with OS X, to divide the drive into two partitions. BootCamp Assistant then took the ISO image I created from the 64 bit disc and applied the installation to an external USB drive, then tried to download support software. I had a slow connection so I let it run while I went to bed.

Woke up in the morning and tried the reboot. It wouldn't work. I mean, Windows 8.1 setup came up, but there was no keyboard or mouse. It was stuck at the setup screen.

Reboot. I held the command-R keys to get to recovery mode, and from there told it to boot to OS X. "Maybe the installation was corrupt, maybe I should try that again."

I re-ran the BootCamp Assistant and re-created the install. It complained because a partition already existed for Windows, but then dutifully reformatted the external drive for the installation process and tried downloading BootCamp drivers. After sitting for several minutes, it puked an error hinting that for some reason known only to robots and silicon gods it couldn't download the utilities.

This happened a few times. I'd reattempt the installation process, only to have it throw an error at me.

Eventually I gave up and told it only to create the install disk. I'd worry about the install of BootCamp utilities later.

Reboot...nothing. It just wasn't able to run the installer properly. What the hell? Bad ISO image? Not supporting the USB drive because it wasn't fast enough or recognized properly at boot?

I put in the Windows DVD and tried the install. Booting from the DVD worked; I had my keyboard and mouse working and it prompted for the 25 character key. Finally...installed the operating system. Only...no BootCamp utility. And no drivers. Including the network driver, so I couldn't get the laptop to a point where it could download drivers and troubleshoot on its own. Worse, it didn't have the utility for booting to OS X from the system tray.

I ended up telling it to reboot and using the recovery boot option to choose OS X as the startup disk. Yes, the Option key is supposed to give you a menu to choose your startup disk...somehow that key is not working on his laptop.

I poked around and discovered a that you can download the BootCamp utilities bundle from Apple. It was nearly a gig in size; I let it trickle in over a couple of hours. Once that had completed I saved it to a USB drive and rebooted to Windows.

Only Windows refused to recognize the drive. Windows could tell the USB drive was plugged in, but wouldn't recognize it as being properly formatted with a filesystem, no matter what filesystem was formatted on the drive.

I rebooted back to OS X and found a spare burnable DVD. I burned a disk of the unzipped drivers and setup program and rebooted back to Windows; huzzah! It sees the utilities!

Then Windows puked an error. The app could not be run.

I did a quick search and discovered the specific version of the download I used may not be able to work on that model Mac. There was a slightly lower version that was supposed to work, though. Another gig download.

At this point I was no longer at home; we were on vacation, and I didn't have access to another disc on which to burn the drivers. I'd have to find another way to get the downloaded files to a spot Windows could see the files.

While the second version of the support files installer completed I started transferring two other files down; one, Parallels, a virtualization application that could run the BootCamped Windows installation in a VM and should also easily see the local OS X filesystem as a shared folder. The other was an NTFS driver from Paragon software.

Another clarification; OS X can see the Windows partition. The problem is that it will mount the drive read-only. By default...without playing with some flags and command-line magic...OS X cannot write to the partition. The Paragon driver was theoretically less involved to try; it has a trial period that would let me transfer the file in question to the Windows partition.

Installed driver, reboot again to OS X, and copied the file over. No errors! YES! Now to reboot to...where's Windows?

Yeah, for reasons I'm not entirely sure of the Windows startup drive disappeared as an option from the startup disk list. sigh

I uninstalled the Paragon software and the Windows drive reappeared as a startup disk option. YAY!

Reboot. Unzipped the new BootCamp utilities. Ran setup. "Do you want to let <setup.exe> make changes to your system?"

YES DAMMIT.

"Sorry, this app cannot run."

DAMMIT!

Wait...what if...

Yeah. I gave a full Fullerton sigh when I realized that at some point in my swapping things around and trying to get Windows to install properly the 64 bit and 32 bit DVD's were swapped. BootCamp was for 64 bit Windows and the 32 bit version was currently installed. No no no no no...

I rebooted again and wrangled the 64 bit DVD to install. Once more...I think at this point I'd entered the 25 digit code three or four times at this point...I entered that damned code and managed to reach the custom install selection. I highlighted the partition and told it to format it to a clean slate.

"I can't install there. The partition type is not GPT."

The fuck?

Fine. Delete the partition, Windows. Recreate and format it.

"I can't install there. It's not GPT!"

Flabbergasted that Windows isn't apparently smart enough to create the proper partition itself, I rebooted to OS X and opened Disk Utility. From there I deleted that partition and recreated it, which confirmed it was created by default as a GPT partition...something else must have altered it during the installation process. I formatted it as FAT for good measure.

Time to iterate through my install checklist again. Rebooted with the DVD. Made sure it was 64 bit. Re-enter that damned key again. Custom install. Highlight the partition. "I can't install there. It's not NTFS!"

"Format it, dumbass."

"Okay!"

This time, no error. It installed.

Booted, finished "configuring hardware." Told me in big cartoony letters how wonderful Windows was going to be. Let me configure a user account. No hardware was working beyond the keyboard, mouse and display.

Okay, so it was the non-64 bitness keeping the setup for BootCamp utilities working...let's stick in the disc I had made. This time the original "not gonna do it" error didn't appear; it gave me a different error, that it wasn't meant for my model Mac! In this case it was a better error, though, because now it was the installer giving me a specific error and not Windows puking a generically unhelpful error.

Reboot to recovery boot. Change startup disk to OS X. Reboot.

Reinstall Paragon driver. Reboot. Copy the older version of BootCamp utilities over to Windows. Uninstall Paragon. Select Windows as the startup disk. Reboot.

Unzip the utilities in Windows. Run setup.

It worked.

Holy shit. It worked. It's been two days of downloading and reconfiguring and troubleshooting...and now it was working. No more warning symbols on the network hardware. Webcam worked. Bluetooth. Updated USB. It was all working!

This wasn't even a case of being irritated with the interface as I normally experienced...I'd managed to remember that I had to scroll the pointer along the right side to actually find a menu item I wanted, or to even find the damned shutdown menu. I was accidentally running into the fact that Windows 8 has a cartoony, dumbed down interface to tell me something that also was available from an actual, more familiar control panel version when I accidentally took a wrong turn in the access methods (example: there's a Windows Update that gives you some kind of text wall status, and another that uses the Windows Update control panel...not that either one gives real-time updates on status.) At this point I didn't even have the energy to complain about the unhelpfully generic error messages (YOU KNEW THE PROBLEM WAS RELATED TO A 64 BIT DRIVER ON A 32 BIT OS, YOU BASTARD.)

Some people would blame Apple. I can't. Not just out of fanboyism, but because I really suspect that as far as they're concerned, if you want to run Windows you should have a PC. There's a reason I said BootCamp was a concession...after following Apple news stories for years, reading about Steve Jobs, and paying attention to the details Apple puts into their products, I honestly think they find the notion of Windows on their hardware a distasteful pile of wretchedness they tolerate only to keep gamers from using it as an excuse to refuse trying Apple products. The drivers seem almost half-hearted; they usually work, but tend to lag behind Windows releases when updates are needed. And they aren't necessarily great; for example, failing to support dual-Thunderbolt displays. Windows will see one, but leave the other blank. Why? Apple doesn't seem to care to address it.

And frankly I can see why. The OS is coupled to the hardware. Apple tossed basic support over the fence and shrugged before walking away.

I can't blame Microsoft for the lack of Apple support. It would be nice if they saw more of that hardware by default, but...meh. Apple comes with OS X. Microsoft shouldn't necessarily have to support a system that isn't really meant to run their product.

Nope. The experiences I had getting BootCamp to run is pretty much the kind of pain I expect in trying to shoehorn an operating system Apple doesn't like onto a hardware configuration Microsoft doesn't want to support. Part of me expects this setup to break in the near future...just one update away from another pain point.

What I can say:

I initially told my son he would have to install BootCamp. He should learn how to use his system properly, and if he really wanted this he should do it. And I made no secret that I was disappointed when he didn't make much effort to do this, opting instead to beg until his birthday for us to do this for him. At the time I didn't expect it to be this much of a hassle, though...there's absolutely no way he could have figured this out on his own without a week of study and probably a lot of back and forth on the apple.stackexchange.com site, learning about partitions, formatting, and how drivers interact on systems. A non-problematic install would have been one thing, but something was definitely broken on his system's ability to properly boot from the USB drive, and something with the Apple download site wasn't cooperating half the time with our slow connection to get the proper utilities in the first place.

Windows 8x sucks at giving proper error messages. C'mon...how hard is it to know the error was running a 64 bit installer on a 32 bit operating system?

I'm pretty sure Apple could have created a read/write NTFS driver. In fact, the ability is in OS X...just shut off by default. You can follow some semi-documented procedure to enable it and mount an NTFS volume if you're willing to risk it...I'm just afraid of what risks they're not talking about.

Microsoft is supposedly making it easier...free, even...to get Windows 10. I pray to $DEITY that means they're getting rid of that goddamned 25-character key. I must have entered it five or six times over the course of this ordeal, and every time it was installed before starting a process that would end in NOPE NOT GONNA DO IT HAHA!

Oh, and after getting the key entered the final time and getting drivers working and getting online...Windows still needed activation, so if I didn't get the network driver working...which in one iteration I had this situation...Windows was warning me that I had to call Microsoft to activate it before certain things would work properly. I was even more pissed at the fact this was a purchased, non-pirated, store-box DVD edition of Windows having me enter...and enter...and enter...that 25 character code and it still had another hoop to jump. OS X? It'll reinstall over the Internet by booting to recovery mode. No questions. Just a few clicks. Screw Windows.

If BootCamp fails and you need to reinstall, you may have to delete the partition from Disk Utility. Recreate it from Disk Utility. I reformatted it as a FAT partition so it would be visible from within Windows' setup...if it's using a GUID partition table, Windows should be willing to see and format it.

If you're installing BootCamp utilities separately, make sure you get the correct version for your Mac hardware. Unlike most of the OS X operating system and drivers, BootCamp is finicky.

I suspect Parallels would have worked to bridge my software-transfer problem. Paragon worked in this case, so far without cruft being visible after the uninstaller was run. Trying to get pain-in-the-ass software like this is great for reminding you that you can get creative to solve problems. The stress probably does shorten your life a little, too, though.

Last thing...don't run BootCamp unless you really, really need to. Parallels is a fantastic product. VirtualBox works well most of the time and for most purposes. BootCamp forces you to get meh driver support and sacrifice a fixed portion of your hard drive. If you're going to use it, have a really good reason to do so.

Now if you'll excuse me, I'm going to see if I have some more Bacardi in the kitchen. This installation of Windows has left me with an extraordinary amount of stress to burn off...

Saturday, July 4, 2015

Revisiting the Haiku Operating System

In 1996 Apple was looking for the successor to Mac OS. Project Copland was deemed a failure, and Apple decided to search for a third-party operating system on which to base a modern operating system. If you have any interest in operating systems or computing history, there's plenty of information to be found surrounding the (broken) promises of Copland and Gershwin.

Choices were narrowed down to two operating systems; Be, Inc's BeOS, spearheaded by an ex-Apple executive name Jean-Louis Gassée, and NeXT with their NeXTSTEP operating system, pushed by another ex-Apple employee named Steve Jobs.

I'm pretty sure you can figure out which operating system won the contest.

Be, Inc never recovered from the loss, and after a swift decline it was swallowed by Palm and eventually digested into nothing. I had played with BeOS R5 and was very impressed; Be had created a fast, single-user, lightning fast, multithreaded, speedy multimedia operating system. They initially created a BeBox machine specifically geared to run their operating system; notable to the case design were large "blinkenlight" stripes that indicated CPU usage. And did I mention it was fast?

BeOS was really a geeky tinkerer's dream; the BeBox even had a "geek port" for hardware hackers to play with programming various electronics with minimal danger to the system (it had 3 fuses backing it on the motherboard.) Be, Inc kept shifting targets...porting BeOS from the BeBox to the Mac PowerPC, then to the Intel platform...it retained a small but dedicated group of hackers determined to keep the OS alive.

When Be finally gave its final death rattle, a group of fans reimplemented BeOS in what is now known as Haiku. The project started in 2001, and in 2008 it became "self hosting." I grew nostalgic for the project and curious at what the current state was, so I ventured to the website and downloaded the R1/Alpha 4.1 version released on November 14, 2012.

I tinkered with some VirtualBox settings (to virtualize the installation...I didn't have a hardware box to spare), configured it with 512MB RAM and a 5GB hard drive (believe it or not, both are more than the minimum requirements...) and a bridged network adapter. Another neat hack I found online; Haiku isn't really "VirtualBox-aware", so there's no seamless mouse integration (you click on the desktop and it captures the pointer until you hit the key combination to release it), but it does understand the USB tablet as a pointer. Tell VirtualBox to use that as the pointer and you get the seamless mouse integration functionality.

The bootup time was fast, despite emulating a "live CD" boot. The installer formatted the drive and ran without hiccups. It removed the virtual CD, rebooted, and voila'...Haiku!

Pretty!
It only took a few moments to verify that the network connection was working (and was indeed bridged, so it looked like another machine on the network rather than a NAT device behind my computer which in turn was NAT'ed again to the Internet; sometimes things don't like double-NAT weirdness.) I decided to try a simple accessibility check by giving the default user a password then enabling SSHD on Haiku. I was able to SSH to a terminal from my Mac without any problem.

The alpha came with a nice selection of some basic applications.

Activity Monitor + the app menu
If you're a C++ fan, you'll love Haiku. It's not only written in C++, but it includes the "BeBook", essentially an API reference to give programmers a reference for writing more Haiku applications. The command line tells me that gcc version 2.95.3-haiku-121101 is included...

♪ Do you wanna build a program? ♪
Obviously there's a web browser called WebPositive included, as you can see that it is being used to view the BeBook. It's not the most functional or up to date browser, but for the most part it's usable.

Haiku also includes a user guide so you can learn how to navigate workspaces (virtual desktops! With their own backgrounds and resolutions!) and find files. Just poking around the interface yielded some nice surprises in terms of what I could do with the operating system.

Ooh! A Quitter with graphs!
There are lots of little toys to explore, from SSH to web browser to a spinning teapot demonstration and an IRC client. There's even a web server! And it appears that it is possible to build Haiku from Haiku; the website has instructions on how to compile it from a self-hosted view or cross-compiled from several other platforms. It's really kind of amazing...if you enjoy C++ programming, maybe you should check out Haiku.

One of the main things to keep in mind about BeOS/Haiku is that it is truly meant to run as a user's operating system, not a multi-user operating system. Windows has the concept of user login. Linux, and the BSD roots in Darwin (OS X), also ingrained the idea of multi-user thinking in computing. Commonplace ideas like a home directory aren't really used in Haiku because it doesn't expect more than one person to use the device. Anything that was done on OS X or Windows to separate user configurations or add separation for security isn't necessarily implemented on Haiku, leading to some confusion when poking around (like, why is my home directory /boot/home? There's no user folder...)

That said, it sounded like there were plans for some limited forms of multi-user support being planned for a future release.

After poking for a bit and being wowed by the speed at which it runs (given that it was running in a virtual machine with minimal processors allocated...Haiku is heavily multithreaded, so it likes more processors to run on) I started looking for an updater. Apparently the alpha release didn't have an update system; given the number of other applications on the system I was a little surprised that hadn't been added along the way.

In addition to the alpha there are "nightly builds"; according to the online documentation, there's a package management system called pkgman that will update your system and let you install applications from a repository. The alpha release works fairly well, let's try the nightly build! Latest features!

That...was a mistake.

Like the alpha release, it installed without much of a hiccup. But the network refused to come up. It just stayed stuck on "configuring." After cycling through all the available network cards VirtualBox can emulate, I changed the network mode from Bridged to NAT, and it started working. It made me sad to lose bridging support.

I wanted to use a SSH session so I could refer to some documentation on my Mac and paste commands from reference sites into Haiku. I added the password to the default user and tried to launch SSHD...and it failed. I couldn't find a reason for it, nor could I find a fix online in their forums.

I wanted to join the forum so I could post a question about it; naturally this means a confirmation email. A confirmation email that never came. I sent a message through the "contact us" form, and still didn't hear anything back. (Of course I checked the spam folders and junk folders. I never found an email from them.)

Basically the nightly builds, running on Virtualbox, were broken. And I couldn't get help from the community site because it seems they are broken too. I even had some odd errors pop up as it booted and when I tried changing settings about an operating system component failing. Seemed rather random, but at the time I was frustrated with the components that were broken. And yes, I'm aware that the nightly builds explicitly warn of regressions and broken parts; I simply hadn't expected what for me would be multiple showstopper regressions. The first things I had tried doing...network access, and enabling SSHD...both failed spectacularly, as did the act of trying to join the forums to ask about possible fixes. It left me questioning if this was really a nightly build or if the project had zombied along the way and no one updated the website to say the project had essentially shut down.

I reverted back to the alpha R4 release to play around with it some more. It's a beautiful operating system...if I won the lottery I'd love to fund more work on it. The R4 alpha is usable enough for basic work, and if you're a C++ developer it's probably worth playing around in. Or if you're a student learning C++ it might be worth using since GCC is installed.

Seeing as I've been playing with Go recently, I thought it would be fun if there were a Go port for Haiku available; of course, that was a very long shot and my expectations were low. The only reason I thought about it was because Go has a number of platforms on which it currently works, including Plan 9.

It turns out, according to the forums, there was work done by someone trying to port an older version of Go to Haiku. It doesn't look like the person completely succeeded, though. That was a shame, since Go with its goroutines seems to heavily support the kind of multithreading that BeOS, and Haiku, were made for.

If you're a fan of operating systems and like playing with a piece of history, I highly recommend giving Haiku (the R1Alpha4 release) a try. It's usable. Its fast. And it's got a lot of nice touches, especially when you consider the history of the operating system.

Side note - I see in the Haiku blogs that a report was recently published, so I'm guessing the project is still alive and updating. Perhaps I am simply running into a number of regressions with their nightly-builds that happen to really converge against the VirtualBox platform? I know that if I really wanted to pursue the issue there appears to be an IRC channel available for questions, or maybe if I dig around I could find another email address to try getting the forum thing sorted out, but then it's a question of how much effort I want to put into pursuing this...

Sunday, June 21, 2015

Can We Stop Pretending Gun Violence is a Mental Health Issue?

It's happened again. Another "mass shooting." I didn't even realize there wasn't a set definition of a mass shooting...it's just a broad term meaning multiple people were shot. You'd think that with all that happening so periodically in the US, we'd at least have a definition for it by now.

I suppose since it's unpleasant, we...or the gun lobby and gun supporters...avoid defining it, like they avoid tracking the weapons or collecting accurate statistics when they reflect poorly on gun enthusiasts.

When something like the church shooting happen, the groups that hold up the framework supporting what enables people to do the things they do, and their opponents, adopt PR spin. Usually pithy and trite sayings that are easily repeatable so each side can echo-chamber to their loyal members that they are in the right and the other side is delusional. Any challenge to that worldview is immediately met with a semi-rational explanation as to why that's the exception, despite not all explanations are of equal merit under the scrutiny of statistics.

For gun enthusiasts it means such brilliant insights as "gun free zones make easy targets,..." You know. Places like Naval yards or military bases. Obviously those were exceptional because of some bullshit regulation that prevents personnel from carrying weapons, so the shooters anticipated taking everyone down before someone from the armory could go all Tony Stark on their asses.

See, responsible gun owners should carry weapons. It's not just a right, it's almost an imperative that people should carry weapons, to be available at a moment's notice to take out a bad guy. Because only a good guy with a gun can stop a bad guy with a gun. Unless that good guy left his gun in the bathroom. Or in a theater.

Bah, they just need more training. Responsible gun owners with proper training. Like the police. I mean, police departments wouldn't get all sorts of body armor and toys from the DOD, turning your local police department into a paramilitary group, without proper training, right? Surely the boys in blue get periodic weapons training so they're not only careful with the deadly weapons but practiced enough to effectively use them as a last resort when de-escalation efforts fail on suspects? That's why they tend to be accurate with their weapons when they have to discharge bullets around civilians.

If you were on social media you probably saw the religious explanation for school shootings. Speaking to invisible beings that watch you shower lends comfort to some folks, and there seems to be an oddly skewed link between religious, conservative folks also being gun-loving folks. When another school shooting takes place, you inevitably see the comics on social media about, "Why didn't God stop the shooter? Because God isn't allowed in the schools anymore."

Yup. If we only had more religion, we'd have less murder. Unless God told that person to do it. Which He seems to do at a disturbing rate. Apparently it's sometimes just His plan for kids to get shot.

Even if facts were brought up to cloud the claim like God isn't banned from public schools (the school cannot promote prayer, but students are free to do so. It's a "separation of church and state" thing that seems rather simple because practically speaking...it is), I'd also note that I'm pretty sure God wasn't banned from the church where Mr. Roof decided to try starting a race war by gunning down parishioners. The same folks that felt the need to hijack whichever school shooting tragedy to promote their religious insecurities stay quiet when this happens.

By far the most popular refrain, despite having some of the above regurgitated and swirled around in sound bites, is that all of the shootings are tied to mental health. These people are crazy!

To be sure, a large number of shootings in the US are self inflicted. Depression can be a bitch. And depression is a mental health issue, one that is making gun enthusiasts look bad with all their killing themselves with a gun enthusiasts favorite toy making them look bad. I'm sure that secretly gun enthusiasts with more people would kill themselves with bleach or electrical outlets so, in their minds, people would naturally try banning bleach or electricity rather than precious hobby murder death toys. The high percentage of suicides, and suicides being linked to depression, fits nicely into the narrative that it's not a gun problem but a crazy problem.

Sometimes I hear this and wonder how far the crazy extends. The pro-weapons movement is supported most vocally by Republicans but tacitly by Democrats, so you hear Republicans and conservatives using this line the most...while the biggest step forward in getting health care extended to all US citizens, the Affordable Care Act, is square in the gun sights of Republicans. Mental health is the real problem, but we'll discuss it after we try again to dismantle "Obamacare," right?

Perhaps it's the most winning refrain because it's so flexible. Once you define any act of using a gun to shoot innocent people, it's...crazy! And it's easy to classify a bad guy shooter as "some crazy bastard" or "a loon on a shooting spree."

It doesn't matter if the person was actually mentally disturbed. I mean, there are the examples where people say something like God made them do it and that earns them a psychological evaluation, but there's a certain kind of cognitive dissonance that makes it sane to say that an invisible all powerful being watches you 24/7. As long as it's in line with your own belief system, that's sane behavior. But that's besides the point. The point is that these people are called crazy simply by defining "crazy" as being willing to shoot innocent people. Suddenly you can't go wrong with framing people getting mowed down in a hail of bullets as an act of a mentally disturbed person expressing their crazy. Then "We need to talk about the REAL issue of MENTAL HEALTH."

Not only does it deflect from the root cause, but it adds a stigma to people with mental disorders. Bonus!

Let's pretend this is a mental health issue. Like, an actual one, not the made up all-encompassing excuse used by gun lobbies. Mental health problems tend to come from a place where the brain has an issue. A chemical imbalance. Or it's just wired different from what is generally seen as "normal" (remembering that many of the people claiming shooters are crazy also believe in invisible beings influencing people's behaviors...if it weren't tied to religion, I really don't think people would hesitate to slap a 'crazy' label on that.)

Sometimes it's treated through talk therapy. Or drugs. Or some coping mechanisms. But you're never really cured like you get rid of a sickness. It's always there.

You know what doesn't have to be there? Guns.

The root cause, as I see it, is gun availability. There's nearly 89 guns per 100 people in the US as of 2014. Despite claims that guns aren't so easy to get, there exist nice loopholes in the laws that bypass special checks into whether the person that wants a gun should really have one (it is a right, after all.) Hell, if you really want a gun with less accountability you could get it on the Internet. That buys nicely into the narrative that if you outlaw guns, only outlaws will have guns. Which is another deflection (since that would apply nicely as an argument against having any laws, since why bother having them? Criminals will just break the law anyway...)

Most of those suicides I mentioned before? They're deaths by opportunity. You hit a dark spot...really dark spot...and that gun is available to you. What would have happened if that person didn't have the gun? Would they go through the hassle of acquiring the gun just to shoot themselves? Maybe. Or maybe they'd be inclined to call for help. Or talk to someone. Maybe they'd do what gun enthusiasts claim and find some way to off themselves. It would be stupid to think you'd stop everyone from killing themselves who are harboring thoughts of harming themselves, and some of them do take time to plan it out. But the people who act on impulse? Probably not so much.

And having a gun available when the impulse hits is definitely a factor in suicides and homicides.

Look, I get it. Guns make you feel powerful. One little twitch of a finger and you can snuff out the life of an animal or send a target flying off a wood block thirty yards distant. Wrapping your fingers around a cold metal pistol grip makes it easy to fantasize about taking down a burglar or some threat to your family breaking into your home in the middle of the night. Blow that rapists' balls off! You're a hero! (Just try to actually shoot a burglar and not your own kid.)

But I don't carry a gun. I enjoy target shooting. I remember shooting in my high school gun club. My father and I would target shoot from the back porch. I grew up in the country...guns, bars and churches were commonplace. I would still go out and target shoot, but it's not the first thing that leaps to mind as something to occupy my time. The home I grew up in had a couple of weapons available, but we weren't gun enthusiasts.

I'm wary of guns...I know that if I fuck up, I can injure myself or someone else with just a momentary lapse in judgement. I like knives and swords...fucking up with those takes a little more effort. Guns? It doesn't seem uncommon for people who consider themselves "responsible" enthusiasts, even buying guns legally with the rather optional background checks and permits, end up having a gun found around the house or dropped somewhere in public, like a bathroom or theater. People have trouble not losing their phones or cameras...but at least if someone finds those, they won't run the risk of accidentally killing someone.

At some level gun advocates know that they're peddling an excuse. I mean, the world they paint when describing how to curb shootings would have everyone taking safety courses and carrying a concealed weapon because hey...criminals are pretty level-headed people, not impulsive and opportunistic. Obviously they'd never apply the "it won't happen to them" or "I'm exceptional" thinking to their situation and open fire knowing that everyone around them was packing heat as well. School shooting? Wouldn't happen if everyone from the janitor to the school principal had Glocks in their pockets. Keep in mind that these same advocates, statistically speaking, think most other people they share the road with are too stupid to drive properly.

There's the excuse that this is a mental health issue, while ignoring that you can't "cure" brain wiring. And while you can drug people and counsel people, dealing with a soft issue that has a significant stigma attached isn't going to prevent impulsive shootings and suicides when guns are available at the local Wal-Mart (or Nazi/Gun shows...for some reason they seem to often get tied together, perhaps because a significant number of gun enthusiasts have a deep love of war trinkets that happen to tie Nazis? Maybe that ties to the mental health issue again?...and the argument seems downright disingenuous when the party advocating this as the real reason behind gun violence dumps so much effort into dismantling a law that aimed to bring health insurance to people in the first place as well as allowing our military veterans, a group of people who are very prone to depression and mental health problems, extremely sub-par care.

They must know much of the PR spin excusing gun violence in America is bullshit when so much evidence is presented...pure numbers showing how America compares to other first-world nations, from the number of bullets our police use against our own citizens to the number of deaths attributed to weapons to the number of accidental shootings are reported to...well, the list goes on...and enthusiasts continue to deny there's a problem that can't be hand-waved away. I look at these numbers and agree there is a mental health problem; they're crazy if they don't see that America has a gun problem. They won't say it, but you can tell they know...the NRA dumps tons of cash into lobbying efforts aimed specifically at preventing studies for accurate weapon statistics. (Some say the lobbying is to prevent the government from rounding gun owners up after collecting big ol' lists of people to target as threats when they're just exercising their rights...paranoia is a mental health issue too, isn't it? Because I'm not sure that a government with murder drones and bunker busting bombs and...you know...an actual military is going to be all that afraid of your cache of AR-15's in the basement...)

Nowadays shootings are rather common in the US. It bugs me sometimes. But my new line is I don't care. I realized nothing would change when Sandy Hook happened. Twenty kids. Elementary school aged. Mowed. Down. And what changed? Squat.

I grew up hearing the constant, "Do it for the kids! THINK OF THE KIDS!" line used as an argument for everything up to and including trying to ban books and movies that would have kids seeing tits. Sandy Hook had little kids murdered and nothing fucking changed.

In fact, you heard people say the discussion about gun control had to be tabled because people were too emotional to think straight. Now's not the proper time! We need to wait until tempers cooled...conveniently, waiting also meant that people's interest in getting things changed would also cool, once something else shiny crossed the FaceBooks and Twitters. Democrats and Republicans alike did jack shit to keep it from happening again. Oh, and Lanza, the gunner, was roundly criticized as crazy. Don't worry, folks...he was crazy. An outlier. And he's dead now! He won't do it again!

When 9/11 happened, it didn't take long for the US to begin military operations into a country that had nothing to do with 9/11. It took even less time for people to start attacking anyone with the Muslim label. Hell, there were some incidents against people who just weren't light-skinned enough or had a name that was a little too...middle eastern-y. Apparently waiting and cooling down before taking action only applies if it means infringing on a hobby with an associated lobby carrying deep pockets.

At least when Hinckley tried shooting the President (did you know that Mr. H in Greatest American Hero became Mr. H because of that shooting? He was originally Mr. Hinckley. They changed it to Hanely, and referred to him as Ralph or Mr. H after the assassination attempt. Don't worry, though. John Hinckley was crazy too) there was a law passed that pretended to make an effort in stopping gun violence. But when you can mow down 20 toddlers and not have a damn thing done about it? That is pretty much a giant neon sign that this will not change in the foreseeable future.

Gun enthusiasts aren't fighting for protection or rights. They like shooting guns. They like blowing things away. They like the image and power guns convey. They aren't interested in anything that can possibly limit their right to shooting people or keeping others safe...when guns are stolen from a gun shop, there's no way to tell if those particular weapons are ever recovered again (was that a gun-free zone? That is why criminals pick certain targets, right? I bet that gun store didn't have any guns to protect the shop in case someone discovered a break-in...)

It's entirely about themselves and their love of guns. It's a precious hobby with a lobby behind it. Not protection, since guns are far more likely to be used to shoot yourself or a homicide than for protection. It's exceptionalism; these reams of statistics, what facts can actually be found, comparisons to other countries, news stories of idiots leaving guns in theaters...that's not them and never could be them. They treat weapons with respect, and never like some kind of toy or irresponsible tool of intimidation, especially since it could make you and your enthusiast movement look like the mentally unstable people that mass shootings are blamed on in the first place.

The twisted part is the number of people for whom mass shootings don't signal a time to reflect, but rather panic that someone will take their guns away. Purchases spike as enthusiasts fear they'll have a witch hunt, with eager anti-freedom liberals knocking on doors to confiscate precious murder tools. There's been a shooting! Get more guns!

To those people I assure you, no one is coming to get your guns. Someone with ready access to a gun...don't worry, he was crazy and the person he got the gun from was irresponsible, unlike the vast majority of not-crazy and very responsible gun owners out there...blew away 20 elementary schoolers and not a damn thing was done about it. Since then there were deaths in naval yards and military bases and college campuses and there's been hardly a squeak. The only reason it's getting some attention at the moment is the shooting at the church...motivated by racism. Still the party line is mental health.

It's reached a point where racism is now a mental health issue, and they feel totally secure in using that ridiculous line to protect their golden One Ring. I'm almost curious to see how deep the absurdity will go.

Friday, May 22, 2015

"What's It Like To Live In New York City?"

I needed to kill some time before meeting up with my son and parents in his classroom for a class pizza party. I decided to pass this time by hanging around my wife's classroom and siphon Internet connectivity while she taught some high schoolers that light is faster than sound (apparently there are high schoolers that do not know this, which is, in my opinion, utterly pathetic and a subject for a separate rant.)

One of my wife's students asked what it was like to live in the city. On the surface that seems like a simple question, probably because it was likely meant as a shallow kind of question people ask in passing, like asking about the weather or inquiring how you are doing. But when I thought about it, the question is deceptively complex.

The town I grew up in is rural. Very rural. Not necessarily outhouse-for-a-bathroom or travel fifteen minutes just to find another inhabited house level rural (although my great-grandmother did have an outhouse and lived in this general area...), but it was rural enough that we tend to have more cows than people and about as many bars as churches. The county I grew up in was nearly 1,200 square miles and had a population density of approximately 55 people per square mile, which sounded impressive to me until I found that New York City alone is 470 square miles and has a population density a little under 28,000 people per square mile. The people are stacked into skyscrapers like food on the plate of our local Chinese Buffet customer.

People are bad with numbers. But the fact that the entire county can't hold a candle to the population density of the city should tell you the magnitude of difference between the home town where she was accustomed to living versus the city in which I spend most of my time now. If not, I suppose the fact that New York City has people running small stores dedicated just to selling "I <heart> NY" kitsch, and managing to stay in business despite the soul-crushing amount charged for rent, while my home town can barely sustain a business that isn't tied to a national chain might be more relatable as a measure.

"Have you ever been to the city?" I ask.

"Once. On a field trip."

"Oh, a field trip. I'm guessing you went to Times Square?"

That was exactly where she went. She alluded to how expensive it was, and how the kids had to pay for everything. "Even taking pictures of people in costumes!" she said.

Oh, Times Square. I was overwhelmed with it at first too; it's so iconic, so recognizable. That's exactly why Times Square, along with Central Park, led the way in increased police patrols when the local government decided to clean up prostitution and crime and make the city more tourist-friendly.

And tourist-friendly it became, which also meant it became a magnet for spectacle. I've had my picture taken with body-painted women in Times Square. There's an indoor ferris wheel at the Times Square Toys R Us. It's been featured in countless television shows, movies and New Years Rockin' Eve specials.

"Technically you didn't have to pay them. But they'd harass you if you didn't," I said.

But that doesn't answer the question; what's it like to live in New York City? In order to answer that question, we would have to have some common reference point. I'm not entirely sure there is one.

Sure, there are a lot of big buildings. Tall things that don't dot the landscape; they are the landscape. This is perpetuated in countless shows. But it's not the entire picture. Or rather, it's not the New York City I know. Skyscrapers are basically three quarters of Manhattan, one of five boroughs that comprise the city. The other boroughs have a few high structures and plenty of population-dense areas, but the phallic representations symbolic of Wall Street are pretty much condensed around Wall Street.

I have seen celebrity; been there, done that. I've seen Carol Burnett. I've seen Tyrese Gibson with Run DMC's Rev Run. I've had a book signed by Stephen King as well as Neil Gaiman. It took a few moments for Norma and I to realize Al Roker pedaled right past us as we walked through midtown. I debated going to a book signing by Hillary Clinton and another by Danielle Fishel. I can't begin to enumerate the list of celebrities I've missed performing on Broadway if I were driven enough to spend hundreds of dollars to see them on stage.

In the hometown I think we once had Bea Arthur visit a local theater to give a talk.

I've been to the Body Worlds museum, where you see actual plasticized human bodies dissected and frozen in different poses for eternity. There's a museum dedicated to the subway system.  There are exhibits about the technology of the Avengers and Hunger Games. I still have a visit to the Museum of Sex on my bucket list.

My home town has a museum dedicated to...well, there's a historic site with information about some french settlers that were in the area for a period of time. And the local history society has a museum with artifacts from the area.

There had to be an intersection of understanding somewhere.

"Imagine living next to a shopping mall," I said, referring to the major shopping hub about half an hour away from the school we were sitting in. Much of the town heads there on the weekend for their outlet shopping needs when the local K-Mart or Walmart just doesn't have the items that discerning shoppers find in Target. "Mainly because I do live next to a shopping mall."

Which is true. In the city, I'm a few blocks away from a couple malls, with no skyscrapers in sight. For the most part suburbia with apartments and malls and chain restaurants around me in Queens.

"I'd love that!" she said.

But even that loses luster after awhile. I suppose there's truth to the Garfield aphorism, "It's not the having, it's the getting." Or maybe Elizabeth Taylor said it. I don't know for sure. I just know I walk by this mall every workday and I have rarely ever made a side trip to shop for something.  Maybe it's my age that means I find comfort in the readily accessible bathroom, nearby bed for sleepy time, and computer calling me to log into Netflix instead of shuffling around JC Penney or Gamestop.

She asked how often I eat out. My wife was quick to point out my employer supplies lunches for free; otherwise I'd probably spend way too much money at the nearby Chipotle, Wendy's or Melt Shop. "I really don't eat out much. It's too damn expensive." Again...very true. A meal at a local restaurant here, for my family, even with alcohol will push maybe $50. We just went to a local Chinese Buffet (I think I mentioned that...) and I, my wife, my son and my parents ate for under $40. In the city the average chain can easily run $70 as a base cost for just three of us. I'd not go to any "known" restaurant (or family sit-down restaurant) with less than $120 in my wallet. If we were careful we could probably get delivery for a decent price; otherwise we'd have to shop around to find a bargain. Cost of eating out once in awhile just isn't a big deal in the small town area for the average family (notice I said once in awhile...eating out every night would without a doubt be a budget buster.)

For me, eating leftovers or microwaving a frozen sandwich from the Costco freezer is just fine.

But I doubt she'd be able to relate to that, as a student. The home town is spread out enough that having a car isn't a luxury; it's a necessity and thus a rite of passage. In the city I ride the subway every day with the exception of days I'm lugging suitcases to the bus station. Having a car in the city means paying between $300 and $500 a month just to park it in one spot; that doesn't count the cost of parking in a garage in the city when I actually commute somewhere (unless I manage to find parking on the street, and and carry with that the associated risk of leaving a car in the open. Crime is on the downturn but I still see quite a few cars sporting The Club antitheft devices on the steering wheel (although those are apparently a bad idea...). That's on top of increased insurance rates, and usually the car only offers the convenience of coming and going when you please rather than depending on the MTA's schedule. Well, that, and you get a personal seat with some environment control from the heater or AC, depending on time of year.

She may "get it" when I say it's expensive, but not truly get it.

I pay one and a half times my mortgage for my apartment per month. My home has two bedrooms, two bathrooms, and a basement on three acres of land. With AC. My apartment is basically a living room and bathroom and a bedroom an hour away from my workplace. And I'm paying one and a half times more for the apartment. Where I don't have control over the neighbors possibly burning candles or bringing used furniture laced with bedbugs.

Oh, the bedbugs! I live in a perpetual fear of that word. Bedbugs. They carry a stigma along with the near impossibility of eradicating without also losing all your belongings (or losing many of them along with a few thousand dollars in exterminator fees to try saving your belongings...but from anecdotes I've heard, you might as well burn your clothes and furniture and start over.)

I'm not a huge fan of the summertime in the city. It gets hot...not summertime in Vegas hot, but 80's and 90's uncomfortably hot. The city has a unique geography that makes the effect worse; it's filled with black pavement, brick and stone facade buildings that concentrate and magnify the sun. But the city is located on the beach; the proximity to the ocean means the city gets the humidity of the ocean but, for most of the people living and working in the city, there are none of the benefits of the beach life. Indeed, it feels strange how little rainfall the city seems to get. The buildings play games with the winds so we don't even get a sea breeze.

Winter does tend to make up for that a little, however. The alleys concentrate the wind to form a kind of pseudo-wind tunnel effect, so the moment you step between buildings you get a sudden punch of pure chill.

There are also little things that I noticed as "different" from back home. Not just the obvious like being able to order a food delivery at two in the morning during a hurricane while my home town shuts down around seven or eight in the evening.

"I think McDonalds is open all night," she said. "Maybe Wal-Mart too."

Yes, that's true, as is the local mini-marts that now sell subs and pizza at late hours. A few of the local super-mini-marts are 24 hours. But it's not quite the same. And it's also recent. I grew up when the town actually shut down. There was even a kind of curfew in effect. It wasn't so much a law as it was a non-surprise when the town cop pulled out behind you at night and selectively pulled you over to find out what you were doing out late while not old enough to have a beard.

The town is slowly being dragged by necessity of expectations from newcomers working in the gas industry, as well as the realization of unclaimed profits, from staying open later. As I previously mentioned the city has shops that specialize in selling luggage or NYC-exclusive kitsch. When there's enough people to support that level of specialization, it's pretty safe to assume there's a longstanding tradition of some store or restaurant being open and probably willing to deliver to your apartment.

What little things are there?

I mentioned the lack of cars. The majority of people use public transit to get around the city. Cars are a huge expense in the city. This means you see a lot more walking. This introduces a new problem; if you don't have a lot of money (enough to afford routine periodic deliveries), how do you get supplies? Especially for families. You're limited to a couple of shopping bags at a time; basically you whatever you can carry with you are your groceries.

But people adapt. This means you see carts. All the time. People usually have one or two collapsible wire pushcarts. They take them shopping, load those up, and roll their groceries home. Back home you never see people with personal carts; groceries fill the trunk, they drive them home, and make trips between the car and front door until stocked up.

Another challenge is the weather. Sure, there's crappy weather back home. Normally the worst weather means a race between the door car. In the city, you may be hiking several blocks in a light rain. Or hazy fog. It's not really common to have rain, but when it does, moving around in wet clothes tends to be more than  little annoying.

It's still surprising to find people with umbrellas in the city. Or as I call them, sidewalk sails. It takes surprisingly little wind to turn umbrellas into inverted cups rather than protective domes. It's not a surprise to see the remnants of umbrellas lying in garbage cans or along the sideswalks.

Other things I've noticed; people in suits spitting like rednecks. New York is a city of social extremes; I work near Wall Street and there are people who don't give a second thought to the fact they are walking past homeless people while wearing $700 shoes. There is a social strata that looks like a parfait, with the wealthiest comprising a thin layer of cream on top of thick layers of the less privileged. Naturally it's a little strange to see someone in a business suit casually spitting as if removing chew from their cheek.

Weird is usually not noticed in the city, which is nice. Being different back home makes you an anomaly, and anomalies are usually not something to be treated with a live and let live mentality. Which I suppose is great for people who want to use that as an opportunity to seek attention. In the city, you ignore the mundane and the unique. I saw a father holding a little girl over a grate as she peed. I see people with animals perched on their heads. I once saw a plane rotating on a pole as an "art exhibit." An actual plane.

Except Times Square. It's hard not to take notice of topless women wearing paint and Iron Man that looks like he's returning from retirement to squeeze into the suit once more to fight evil and collect tips for photographs.

We have lots of events. Street fairs, for example. They'll shut down blocks of roads to set up booths and sell necklaces and hats and shirts and ethnic food. If you walk more than a couple of blocks you'll notice that the booths seem to repeat, though. It's kind of spooky, as if you walked a little too far and reappeared a few blocks backwards in time.

Other events seem more random, like the time I came upon a silent dance party. An area was cleared out and a small DJ stand was set up. They handed out headphones to participants, and the headphones were receivers for whatever music the DJ's were transmitting. It was a large group of random strangers dancing around with headsets on.

And there are other grand spectacles as well. It's like the city thrives on spectacle; maybe it's a distraction from the high rents or slowly decaying public transit system, maybe it's truly an annual celebration. I think it's a mix of both.

Some of the spectacles you're probably aware of, like the forty foot tree in Rockefeller Center. Or the New Year's Eve bash in Times Square. These kinds of things are rather popular on television and nearly impossible for people go to. I once tried going to Rockefeller Center for the tree ceremony; I was there a couple of hours early, but there was no way I was getting within two blocks of the event. People were apparently waiting an absurd amount of hours to get decent standing spots near the performance stage. And if you ever wondered how it is possible for people to get trampled or trapped in a crowd, try going to one of these types of events. You'll quickly learn how that's possible. Also, don't eat or drink for half a day beforehand. You aren't taking a whiz unless it's on the person standing next to you in one of these events, because there's no way you're leaving once you're in the heart of that crowd.

Other spectacles are a little less advertised, like Fleet Week. Want to see fighters and battleships cruising around? That's the time to visit. The New York Marathon passed in front of my first apartment...quite a crowd had gathered. Or you get events that weren't necessarily planned, like Occupy Wall Street. There is a certain rush being near events that end up as breaking news near where you are. Maybe you heard about the time someone climbed the Brooklyn Bridge and removed the American flags, replacing them with white flags? I saw that on Twitter as a news event and just turned to look out my window to see the white flags waving in the breeze. There have been times I see "breaking news" about protesters blocking traffic on the Brooklyn Bridge and I can watch the flashing police lights from my office seat.

It's like a shared experience to see this thing that is not only happening near me, but in the news as well. Maybe it's an extension of a need for people to connect in some way. Maybe it's a way to feed the inner narcissist, to have this "thing" happen that now you can be part of.

An extension of that would be visiting the movie icons. Norma and I had experienced that before I moved to the city when we went to Las Vegas for our honeymoon; every time we saw CSI, we actually knew about the locations they referenced. We had been to many of the casinos and had driven to some of the locations the show visited, and it was eerie to think that we had been standing in spots the cameras were now sweeping their gaze upon.

In New York, I used to work in an office building where we could see the Statue of Liberty in the distance. Looking down I could see the Wall Street Bull. I can't even venture a guess how many movies were shot in Central Park. I used to live in an apartment by which the Roosevelt Island tram would float by, which was featured in a Spider-Man movie. Even the Brooklyn Bridge has been in the Batman movies. Someday I want to pay a visit to the Ghostbusters firehouse. Movies are being shot all the time around the city; it's no longer strange to me to find small notices taped along the street warning you against parking on such-and-such a date due to filming lest you have your car towed. There were scenes shot for a popular TV show outside my apartment while I was at work one day. The CEO of the company for which I work said the TV show "666 Park Avenue" used his apartment building as a set. Remember Stark Tower, later changed to the Avengers Tower in Iron Man and The Avengers? That's the Met Life building, with the top digitally altered. My walks frequently passed the avenue over which the Met Life building towered when I lived in Manhattan. Now I live closer to the giant globe called the Unisphere and seen in movies like Men In Black and Iron Man.

If you live in a place like my hometown, enjoy the space. The city doesn't seem to have a lot of that. Every time I go into a supermarket I'm lucky if I can maneuver a cart around without hitting someone. In fact I rarely use carts, preferring instead to carry a basket with me. I think the city grocery stores are roughly half the size of the average supermarket back home. There's a Costco not far from me, and for a superstore, you can barely get around the aisles; most frustrating are the shoppers that stop for seemingly no reason and stare off into the distance or just block the aisle while looking like they're pondering whether they want 5 pounds of chicken legs or 5 pounds of chicken tenders. It's a special kind of frustrating when you feel clausterphobically squeezed and you just want to get some shopping done.

I guess the last thing I learned living in the city is just how alone you can be, despite being surrounded by millions of people. There's a kind of personal space barrier that is erected because of the number of strangers that you come into close proximity to. Headphones are a must; otherwise you get pegged as a tourist, and the odds of being solicited for money increase significantly. And interacting with people can be dangerous. Most people, admittedly, are not dangerous; but there's always a nonzero chance that you're going to be played for a sucker. People will lie, and people will play on your emotions if it means profiting.

I periodically travel back home on the bus, which means a fun trip to the Port Authority. Sometimes my family was traveling on the bus as well, and one time my wife recognized a guy soliciting "anything you can spare" because his luggage was stolen and he was trying to get a ticket to travel home in Virginia. Or Carolina. Something. I don't recall. The point was this guy had been spotted by us with the same schtick in the Port Authority over at least 6 months.

Generally speaking, if someone wants to talk to you, it usually ends in trying to solicit money. And sometimes those people don't always seem the most...stable. The best thing to do is keep your headphones on and pretend the world doesn't exist; mind your own business, and you should be okay. It's isolating. But safer. I'm sure some people would argue it's better to have the contrary view.

So what's it like to live in New York City? It's exciting. It's busy. It's filled with opportunities. It's expensive as hell, and keeps getting more expensive. It's crowded. It's dirty. It's sad. It's lonely. It's loud. It's iconic. It's hot in the Summertime, and swirls with uncomfortable amounts of humidity. It's diverse. And these things are something that can be acknowledged without truly understanding them until you see a guy holding his toddler daughter over a street grate so she can take a whiz in the middle of the day and no one seems to notice.

Friday, May 15, 2015

Creating a Simple Golang Network Application, Part 7

Parts 1 through 6 chronicled my process in making a relatively simple network-listener. Part 7 is the point where I review some notes as a kind of "post mortem" of lessons learned and potential issues.

I'll start off by saying that technically I don't think anything is "wrong" with this application. It does what it's supposed to do, and it scratched the itch it was supposed to scratch.

What is a potential problem with port_listen?

In a previous post I said that the developer who glanced at the source code said there were two potential issues; one was the passing of errors instead of numerical errors, and the second one I said I'd get into later (but later is now.)

LogEvent() is called from the goroutines monitoring ports. There is nothing serializing these calls, so there is a remote chance that two ports could try calling, and thus writing to, the log file at the same time leading to a race condition.

I had worried that this was a possibility before the developer had pointed it out, so I had tried testing for a race condition on a system that was probed for security vulnerabilities by a remote testing system. In the code if there was an error trying to open the file port_listen should have exited, and it didn't during the test. That doesn't mean the race condition isn't possible, since this was tested on a pretty fast system; if the application were run from a thumbdrive, for example, it's possible that a high load will cause the race condition and exit the program.

The "GoLang Way" to fix the potential race condition is to rewrite the flow of logic so that the goroutines monitoring connections would pass messages via a channel back to a central calling function, and that function would serialize the calls to LogEvent(). It's an ideal use case for channels.

After some testing and thinking about the situation, I decided to keep things as they are. It was working as-is, and if there was evidence of a race condition later I would be able to fix it.

What did I do incorrect with the development process?

I didn't use version control. Initially this was supposed to be a small application and it ended up a little larger than intended...so why bother with version control?

I should have used it anyway. I went through a few stages of altering items where I ended up copying a ".bak" file of the working source code first. If I had used version control, I could have easily rolled changes back. I also documented what I was doing with the notes for the blog post, and zero in-code comments...I could have kept track of items using commit messages to the version control.

I also should have written a functional spec...I sort of mapped out what I wanted the program to do when I started, but I didn't formalize it or model the application formally.

What did I learn from this application?

First, I didn't know that having a program grab a small number of ports in the 700 range would kill IP printing (LPD) on a Mac client. That was highly unexpected.

Second, I used to have a series of downloaded MP3's with lessons on presenting to groups. I copied them to a local directory and created a way to share them to my own machine as an RSS feed, then configured iTunes to read the feed and copy them to my iPhone like any other podcast. Basically I turned my own computer into a podcast server.

I had finished with the podcasts but using this tool I was reminded the iTunes was still trying to pull new files when it connected to the local machine and dumped some header information to the port.

Third, I learned that Flash will try to connect to the local machine to look for some kind of system policy file. I didn't know you could control Flash player with a policy file. But you can, and it will connect to the localhost through a TCP connection to query the existence of said file. Here's what was found in the log:
2015-04-30 08:24:49.174877175 -0400 EDT From port 843:: ::1 is localhost
2015-04-30 08:24:49.175084657 -0400 EDT From port 843: Remote IP ::1:: <policy-file-request/>
2015-04-30 08:24:52.182099935 -0400 EDT From port 843:: Client located at ::1 has disconnected.

What could have been done differently?

It's said that a program is never really done. If I had unlimited time, I think there are plenty of things that could be tweaked; off the top of my head:

Command line flags for specifying port ranges to monitor
Command line flags for specifying port ranges to exclude
Ability to monitor UDP packets
Ability to dump information to the terminal AND the logfile, or just one or the other
Ability to run as a daemon (oh, that would be an interesting sort of pain)
Change the code so it can monitor Linux systems as well as Macs
Ability to package the logs as a zipfile for easy transfer and "clean up" on client systems
A timer that automatically kills the application after X minutes monitoring
Reverse-lookup of connecting IP's to see what DNS thinks is connecting to the machine
A keystroke shows the last couple of log entries
A dot or other character at the command line that appears whenever there's a log entry, just to indicate that something happened

I'm sure there are others I could think of if I wanted to turn this into a full-on application for general use. I'm also sure that I could spend a month tinkering and trying to perfect it rather than moving on to other things...long after having used this for the intended use case.

Perhaps someday I'll revisit the code and add some other features. Nothing says I can't, after all.

This is just one person's process for creating a simple application. Perhaps it'll be useful to someone; if you have suggestions or links to other sites offering a peek behind the scenes of the development process by other developers, leave a comment!

Wednesday, May 13, 2015

Creating a Simple Golang Network Application, Part 6

Uh-oh...

I deployed the application to the test user and soon they reported they couldn't print. "It's set up as an IP printer," she said. "It just gets stuck at 'connecting to printer...'"

I use printers configured as network LPD printers so I tried to reproduce the error and sure enough, it got stuck. After some research (and a question on Apple.stackexchange.com) I discovered that LPD uses the 721-731 range of ports for printing, even if it's not configured as a server.

Weird, but I tried remove those from being monitored and printing worked again!

I changed the ListenToPort call loop to this:


fmt.Println("Port_Listen version 1.01, 4/24/2015")
LogEvent("0", "Port_Listen version 1.01, 4/24/2015")
LogEvent("0", "Application initializing ports to monitor, skipping ports 721-731 (LPD) for printing...")
for i = 1; i < 1025; i++ {
 if (i < 721 || i > 731) {
//   fmt.Println("Listening to " + strconv.FormatInt(int64(i), 10))
  go ListenToPort(i, chanCommLine)
 }
}

I also had to change the line with "if i == 1013 {" under the channels because there are now eleven less ports being monitored. Without this change the application gets stuck in a loop waiting for nonexistent goroutines to reply.

I also added code that noted the version number (since I had kind of "released" it to someone, I thought it apropos to increment by .01) as well as a modified logging message for the application launch demarcation of a fresh launch in the logfile.

The commented out line is there because I wanted to verify, in standard output, that the ports I didn't want listened to had nothing bound to them. It uses the same int-to-string conversion logic used in ListenToPort().

Now for one more tweak...

It seems fairly obvious that the channel in the application was unneeded. Initially I had a vague notion of using it for something more; as the application took shape, it was little more than a wasteful way of saying, "Yup, I'm here!" and counting replies from goroutines as verification that the program was doing what I expected.

As troubleshooting/diagnostics, the channels were kind of helpful but also redundant. In the end they don't really do much.

So I removed the channel code. This involved removing a block in main() and a few references in ListenToPort(). It wouldn't be helpful to describe them in detail here, as they were already explained in earlier parts. I can say that on recompile the application size shrank from 3274088 to 3269992 bytes, a savings of 4kb (hmm...interesting number to have shaved off...)

Here's version 1.02 of the port_listen application:


package main

import (
 "fmt"
 "net"
 "os"
 "os/exec"
 "strconv"
 "strings"
 "syscall"
 "time"
)

func main() {

 var i uint16
 var strUserInput string
 var intEnvironmentReady int

 intEnvironmentReady = SetupEnvironment()
 if intEnvironmentReady != 0 {
  fmt.Println("Something has gone wrong setting up the system environment, error " + strconv.Itoa(intEnvironmentReady))
  os.Exit(1)
 }
 fmt.Println("Port_Listen version 1.02, 4/25/2015")
 LogEvent("0", "Port_Listen version 1.02, 4/25/2015")
 LogEvent("0", "Application initializing ports to monitor, skipping ports 721-731 (LPD) for printing...")
 for i = 1; i < 1025; i++ {
  if i < 721 || i > 731 {
   //fmt.Println("Listening to " + strconv.FormatInt(int64(i), 10))
   go ListenToPort(i)
  }
 }
 for {
  LogEvent("0", "Application started and monitoring")
  fmt.Println("Type 'quit' and hit enter to exit the program...")
  fmt.Scanf("%s", &strUserInput)
  strUserInput = strings.ToLower(strUserInput)
  if strUserInput == "quit" {
   os.Exit(0)
  }
 }
}
func ListenToPort(uint16Port uint16) {

 var strPortToListenOn string
 var strRemoteAddr string
 var strRemoteAddrSansPort string

 strPortToListenOn = strconv.FormatInt(int64(uint16Port), 10)

 netListenFor, err := net.Listen("tcp", ":"+strPortToListenOn)
 if err != nil {
  LogEvent(strPortToListenOn, err.Error())
  return
 }
 defer netListenFor.Close()

 for {
  conn, err := netListenFor.Accept()
  if err != nil {
   LogEvent(strPortToListenOn, err.Error())
  }
  strRemoteAddr = conn.RemoteAddr().String()
  strRemoteAddrSansPort, _, _ = net.SplitHostPort(strRemoteAddr)
  LogEvent(strPortToListenOn, "Connection attempt made from "+strRemoteAddrSansPort)

  go GrabInput(conn, strPortToListenOn, strRemoteAddrSansPort)
 }
}
func GrabInput(conn net.Conn, strFromListenerNumber string, strClientIP string) {

 var strMessage string

 bufIncoming := make([]byte, 1024)

 for {
  bytesRead, err := conn.Read(bufIncoming)
  if err != nil {
   if err.Error() == "EOF" {
    LogEvent(strFromListenerNumber, "Client located at "+strClientIP+" has disconnected.")
   } else {
    LogEvent(strFromListenerNumber+": Remote IP "+strClientIP, err.Error())
   }
   return
  }
  strMessage = string(bufIncoming[0 : bytesRead-1])
  LogEvent(strFromListenerNumber+": Remote IP "+strClientIP, strMessage)
 }
}
func LogEvent(strFrom string, strMessage string) {

 var strLogFile string
 var err error
 var fileLogFile *os.File

 strLogFile, _ = os.Getwd()
 strLogFile = strLogFile + "/port_listen_logs/port_listen.log"
 if _, err = os.Stat(strLogFile); err == nil {
  fileLogFile, err = os.OpenFile(strLogFile, os.O_APPEND|os.O_RDWR, 0644)
  if err != nil {
   fmt.Println("Log file could not be opened! Error " + err.Error())
   os.Exit(1)
  }
 } else {
  fileLogFile, err = os.Create(strLogFile)
  if err != nil {
   fmt.Println("Log file could not be created! Error " + err.Error())
   os.Exit(1)
  }
 }
 defer fileLogFile.Close()
 fileLogFile.WriteString(time.Now().String() + " From port " + strFrom + ":: " + strMessage + "\n")
}
func SetupEnvironment() int {

 var strSudoUserUid string
 var strSudoUserGid string
 var intSudoUserUid int
 var intSudoUserGid int
 var strWorkDir string
 var err error
 var fiDirectory os.FileInfo
 var rLimit syscall.Rlimit

 if os.Getuid() == 0 {
  strSudoUserUid = os.Getenv("SUDO_UID")
  strSudoUserGid = os.Getenv("SUDO_GID")
 } else {
  fmt.Println("Please use sudo to run application (sudo ./port_listen)")
  return 4
 }
 strWorkDir, err = os.Getwd()
 if err != nil {
  fmt.Println("Error getting working directory: " + err.Error())
 }
 strWorkDir = strWorkDir + "/port_listen_logs"
 fiDirectory, err = os.Stat(strWorkDir)
 if err != nil {
  fmt.Println("Logs directory does not exist. Creating directory " + strWorkDir)
  err = os.Mkdir(strWorkDir, 0700)
  if err != nil {
   fmt.Println("Error creating directory; " + err.Error())
   return 5
  }
  intSudoUserUid, _ = strconv.Atoi(strSudoUserUid)
  intSudoUserGid, _ = strconv.Atoi(strSudoUserGid)
  err = os.Chown(strWorkDir, intSudoUserUid, intSudoUserGid)
  if err != nil {
   fmt.Println("Error changing permissions on directory: " + err.Error())
   return 7
  }
 }
 fiDirectory, err = os.Stat(strWorkDir)
 if !fiDirectory.IsDir() {
  fmt.Println(strWorkDir + " is not a directory!")
  return 6
 }

 command := exec.Command("/bin/launchctl", "limit", "maxfiles", "1000000", "1000000")
 _, err = command.Output()
 if err != nil {
  return 1
 }
 err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
 if err != nil {
  return 3
 }
 rLimit.Cur = 1400
 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
 if err != nil {
  return 2
 }
 return 0
}

Despite better judgement I decided to tinker with it a little more.

I had a chance to transfer the executable application to the person that was testing it, and the thought occurred to me that I didn't know if the version I was sending was the most recent compilation (Did I recompile before sending it? Or did I get distracted before "go build?"). I could run the program and view the logfile since it writes the version number there but rebuilding the executable takes only moments, so just to be safe I quickly rebuilt the executable and transferred it.

Then I thought, "Wouldn't it have been simpler if I had a command line flag to pass so it just told me the version? That sort of thing couldn't be all that hard to implement. Just take a look at the arguments passed to the program and if it matches "-v", print a version number and exit. How hard is that?"

First, I decided to centralize the version number into a variable. Change it there, it gets changed wherever else that variable is used.

I declared a variable in main() using "const strVERSION string = "1.03, 4/26/2015"". Yes, I bumped up the version again since this is a new version and I still include the date on which I last saved the source code. Then I changed the lines that separately listed the version to use the variable:

fmt.Println("Port_Listen version " + strVERSION)
LogEvent("0", "Port_Listen version " + strVERSION)

I recompiled the program and re-ran it; the new version appeared on the command line and in the log.

Next, I want to play with the passed command line arguments. It turns out the os package has the ability to read arguments using os.Args[].

Just under the declaration of variables in main(), but above the setup of the environment, I added this block of code:

if len(os.Args) > 1 {
 if os.Args[1] == "-v" {
  fmt.Println("Version " + strVERSION)
  os.Exit(0)
 } else {
  fmt.Println("Invalid command line argument, \"" + os.Args[1] + "\" is not a recognized option.")
  os.Exit(0)
 }
}

The len() checks the number of arguments that are passed. If the length of "Args" is 1, that means it's just the executable. The first element is the executable name. If the number of arguments is more than one, then something else was passed at the command line along with the executable name. If I didn't check this, if I only checked what was in os.Args[1] (remember, Args[0] is the executable name), you can have a crash because you're poking at addresses that are out of bounds. This length check isolates the possibility of that crash.

Next I check for the existence of the "-v" as the argument. If found, it prints the version and exits. If it's anything else, it spits out an error message and exits.

I'll also point out in the "invalid command" part that the output encloses the arguments passed at the command line in quotes; since the string in the code is delimited with quotes, you have to use escape characters to tell the Go compiler that no, you don't want to cut off the string with this particular quotation mark, I want you to print it instead. That's why you see the two \" marks in the quoted string around os.Args in the fmt.Println statement.

If nothing was passed (the number of arguments is just 1), the application continues with the environment setup. This way if the application is run just to get the version, it won't create the subdirectory and logfile.

I'll note there's a separate flags package that can be imported and used to parse command line arguments. Flags is great if I had keywords I wanted to handle, but here I only wanted to handle a simple "-v", so the use of os.Args[] would be more than adequate. If I were to handle things like ranges of port numbers to monitor or create a way to bypass the printed logfile and instead opt to print everything to the console or if I had multiple options that could be specified in any order at the command line, then the flags package would be a worthwhile alternative.

At this point I'm very close to calling it done. I've already gone beyond the original goal...a quick and dirty port monitoring application. But before I released it into the wild (or discussed it on a series of blog posts) I asked an experienced developer to glance at the code and see if there were any glaring mistakes, just to reduce the snark from more experienced people who may see this online and reply just to tell me how stupid I am.

He pointed out two items; one I'll list here, the other I'll list in the next blog post. He said that in the areas returning integers as errors (mostly in the function that sets up the environment) it wasn't necessarily wrong to return a numerical code, but it was considered non-idiomatic of Go.

"Go gives you an error, so why not use it? You can just return that error to the caller."

Once that was pointed out it seemed like a rather obvious difference between Go-think and my previous experience in C++-think. The "GoLang Way" would be to just return the error to the caller. For times when an error wasn't returned by the operating system, there's is a function that crafts an error message for you; fmt.Errorf, as used in the os.Getuid() if block in SetupEnvironment():


if os.Getuid() == 0 {
 strSudoUserUid = os.Getenv("SUDO_UID")
 strSudoUserGid = os.Getenv("SUDO_GID")
} else {
 return fmt.Errorf("Please use sudo to run application (sudo ./port_listen)")
}

I also had to change the caller in main() for SetupEnvironment() to reflect that it'll get back an error, using:


err = SetupEnvironment()

...after having declared a variable in my declaration block of


var err error

...and the SetupEnvironment() needed the definition changed to


func SetupEnvironment() error {

I also bumped up the version from my experimentation and alterations and added a big ol' comment block at the top to give rudimentary instructions on what this application was. Here's the final version...at least as far as I'm planning on taking it for now...of port_listen.go:


/*
 Port_listen is a small application that monitors incoming TCP connections
to your computer and logs what is sent to the connection in a logfile under the
current working directory/port_listen_logs directory.

It must be run using sudo so it can access ports under 1024.

To exit, type "quit" at the command line.

"-v" at the command line invocation gives the application version and build date
*/
package main

import (
 "fmt"
 "net"
 "os"
 "os/exec"
 "strconv"
 "strings"
 "syscall"
 "time"
)

func main() {

 var i uint16
 var strUserInput string
 var err error
 const strVERSION string = "1.06, 4/28/2015"

 if len(os.Args) > 1 {
  if os.Args[1] == "-v" {
   fmt.Println("Version " + strVERSION)
  } else {
   fmt.Println("Invalid command line argument, \"" + os.Args[1] + "\" is not a recognized option.")
  }
  os.Exit(0)
 }
 err = SetupEnvironment()
 if err != nil {
  fmt.Println("Something has gone wrong setting up the system environment, error: " + err.Error())
  os.Exit(1)
 }
 fmt.Println("Port_Listen version " + strVERSION)
 LogEvent("0", "Port_Listen version "+strVERSION)
 LogEvent("0", "Application initializing ports to monitor, skipping ports 721-731 (LPD) for printing...")
 for i = 1; i < 1025; i++ {
  if i < 721 || i > 731 {
   //fmt.Println("Listening to " + strconv.FormatInt(int64(i), 10))
   go ListenToPort(i)
  }
 }
 for {
  LogEvent("0", "Application started and monitoring")
  fmt.Println("Type 'quit' and hit enter to exit the program...")
  fmt.Scanf("%s", &strUserInput)
  strUserInput = strings.ToLower(strUserInput)
  if strUserInput == "quit" {
   os.Exit(0)
  }
 }
}
func ListenToPort(uint16Port uint16) {

 var strPortToListenOn string
 var strRemoteAddr string
 var strRemoteAddrSansPort string

 strPortToListenOn = strconv.FormatInt(int64(uint16Port), 10)

 netListenFor, err := net.Listen("tcp", ":"+strPortToListenOn)
 if err != nil {
  LogEvent(strPortToListenOn, err.Error())
  return
 }
 defer netListenFor.Close()

 for {
  conn, err := netListenFor.Accept()
  if err != nil {
   LogEvent(strPortToListenOn, err.Error())
  }
  strRemoteAddr = conn.RemoteAddr().String()
  strRemoteAddrSansPort, _, _ = net.SplitHostPort(strRemoteAddr)
  LogEvent(strPortToListenOn, "Connection attempt made from "+strRemoteAddrSansPort)

  go GrabInput(conn, strPortToListenOn, strRemoteAddrSansPort)
 }
}
func GrabInput(conn net.Conn, strFromListenerNumber string, strClientIP string) {

 var strMessage string

 bufIncoming := make([]byte, 1024)

 for {
  bytesRead, err := conn.Read(bufIncoming)
  if err != nil {
   if err.Error() == "EOF" {
    LogEvent(strFromListenerNumber, "Client located at "+strClientIP+" has disconnected.")
   } else {
    LogEvent(strFromListenerNumber+": Remote IP "+strClientIP, err.Error())
   }
   return
  }
  strMessage = string(bufIncoming[0 : bytesRead-1])
  LogEvent(strFromListenerNumber+": Remote IP "+strClientIP, strMessage)
 }
}
func LogEvent(strFrom string, strMessage string) {

 var strLogFile string
 var err error
 var fileLogFile *os.File

 strLogFile, _ = os.Getwd()
 strLogFile = strLogFile + "/port_listen_logs/port_listen.log"
 if _, err = os.Stat(strLogFile); err == nil {
  fileLogFile, err = os.OpenFile(strLogFile, os.O_APPEND|os.O_RDWR, 0644)
  if err != nil {
   fmt.Println("Log file could not be opened! Error " + err.Error())
   os.Exit(1)
  }
 } else {
  fileLogFile, err = os.Create(strLogFile)
  if err != nil {
   fmt.Println("Log file could not be created! Error " + err.Error())
   os.Exit(1)
  }
 }
 defer fileLogFile.Close()
 fileLogFile.WriteString(time.Now().String() + " From port " + strFrom + ":: " + strMessage + "\n")
}
func SetupEnvironment() error {

 var strSudoUserUid string
 var strSudoUserGid string
 var intSudoUserUid int
 var intSudoUserGid int
 var strWorkDir string
 var err error
 var fiDirectory os.FileInfo
 var rLimit syscall.Rlimit

 if os.Getuid() == 0 {
  strSudoUserUid = os.Getenv("SUDO_UID")
  strSudoUserGid = os.Getenv("SUDO_GID")
 } else {
  return fmt.Errorf("Please use sudo to run application (sudo ./port_listen)")
 }
 strWorkDir, err = os.Getwd()
 if err != nil {
  return err
 }
 strWorkDir = strWorkDir + "/port_listen_logs"
 fiDirectory, err = os.Stat(strWorkDir)
 if err != nil {
  fmt.Println("Logs directory does not exist. Creating directory " + strWorkDir)
  err = os.Mkdir(strWorkDir, 0700)
  if err != nil {
   return err
  }
  intSudoUserUid, _ = strconv.Atoi(strSudoUserUid)
  intSudoUserGid, _ = strconv.Atoi(strSudoUserGid)
  err = os.Chown(strWorkDir, intSudoUserUid, intSudoUserGid)
  if err != nil {
   return err
  }
 }
 fiDirectory, err = os.Stat(strWorkDir)
 if !fiDirectory.IsDir() {
  return fmt.Errorf(strWorkDir + " is not a directory!")
 }

 command := exec.Command("/bin/launchctl", "limit", "maxfiles", "1000000", "1000000")
 _, err = command.Output()
 if err != nil {
  return err
 }
 err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
 if err != nil {
  return err
 }
 rLimit.Cur = 1400
 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
 if err != nil {
  return err
 }
 return nil
}

Here I'll end part 6. In part 7 I'll go through a wrap-up of lessons learned!