dilbert

Fiepke.nl

nixcraft

Slashdot

The Daily WTF

XKCD

Toothpaste for Dinner

Ars Tech

Google News

PBFComics

linux voice

SMBC

coindesk

MAGPIE php feed reader

CodeSOD: Shell Out

Duct tape apollo17

Developers sometimes fail to appreciate how difficult a job Operations really is. In companies that don't hold with newfangled DevOps, the division of labor often comes with a division of reputation as well. After all, developers do the hard work of making software. What are Ops guys even for? They don't make software. They don't generate leads or fix your desktop PC. Why bother paying for talented senior Ops professionals?

Spend a few days with the Ops team, however, and you start to see why you should pay them a little more than your average garbageman. The Ops lifecycle is a daily grind of deployments, patching, and sticking fingers in dykes, trying to keep that expensive cesspit the devs call "software" running. Simple tasks such as spinning up new infrastructure in AWS often get pushed to the back burner behind putting out fires and making sure critical maintenance tasks that didn't get done last year don't explode into flames.

Still, companies like to cut corners. Often, Ops folks have very little programming expertise and no training budget, meaning repetitive tasks are automated using cobbled-together bits of shell script found via Google. In the Ops world, a bit of Perl or Python is worth its weight in gold.

Today's snippet, as you can probably guess, is not in Perl or Python. It is instead in a common paradigm: Bash embedded in Perl. Likely, the original script was written by a senior who knows Perl, and this chunk was written by a strapped-for-time medior who didn't:


my $secs = `cut -f1 -d. /proc/uptime`;
$data{lastboottime} = strip(`date -d "$secs seconds ago" '+%Y'-'%m'-'%d'T'%T' 2>/dev/null`);

The point of this snippet is to gather the last time the machine booted; later code sends it to a central inventory system. The bug here is that the last boot time would drift by a second or so between updates?not because the machine had rebooted, but because the code gathering it was imprecise.

For those who spend their day at a higher level of abstraction, let me explain: we start by querying /proc/uptime, which I'll let the manpage explain:

This file contains information detailing how long the system has been on since its last restart. The output of /proc/uptime is quite minimal:

350735.47 234388.90

The first number is the total number of seconds the system has been up. The second number is how much of that time the machine has spent idle, in seconds.

We then use cut to snip the output, using a period as a delimiter and taking only the first field, meaning we take the floor of the uptime in seconds and throw away the rest. We store that in a Perl variable, then feed it back into Bash in the middle of a date format string so that it reads "X seconds ago." We then parse that, rearrange it into year-month-day, throw away any errors, and trim it to put back into Perl for the rest of the script to forward on.

Some days I feel this is the real reason DevOps was invented: a bunch of devs saw the code the Ops guys were writing and cringed so hard, they found themselves volunteering to write "Whatever code you need, man. Just ask me, I'll get it done for you. Please."

[Advertisement] ProGet supports your applications, Docker containers, and third-party packages, allowing you to enforce quality standards across all components. Download and see how!

Error'd: This Movie is Rated S for Safe for SQL

"Clearly the Light Cinema decided to ban unsafe sql characters from the cinema," wrote Simon, "Let's hope no one makes a film called 'Drop Table'."

 

Michael M. wrote, "King Soopers has an amazing algorithm when deciding just what tea to show me from their extensive database."

 

"I didn't know that there was a city named after a Java conversion error in zip code 85034, but if I want to go, hey, Greyhound can take me there," Celti B. writes.

 

"Even estimates.solar's site is weighing in on the current political climate!" wrote Russ S.

 

Derrick M. writes, "For a wireless keyboard to cost this much, it had better be able to reach into space."

 

"According to Microsoft, 13/100 is the new 12%," Roberta wrote.

 

[Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!

CodeSOD: Flip to a Blank Page

You have a web application, written in Spring. Some pages live at endpoints where they?re accessible to the world. Other pages require authentication, and yet others require users belong to specific roles. Fortunately for you, Spring has features and mechanisms to handle all of those details, down to making it extremely easy to return the appropriate HTTP error.

Unfortunately for you, one of the developers on your team is a Rockstar? who is Officially Very Smart and absolutely refuses to use the tools your platform provides. When that Certified Super Genius leaves the organization, you inherit their code.

That?s what happened to Emmer. And that?s how they found this:

List<String> typeList = getTypeList (loginName);
if(CollectionUtils.size(typeList) > 0){
    return viewRepository.findBySubmsnTypeList(typeList, pr);
}
else{
      return viewRepository.findEmptyPage(pr);
}

This doesn?t look too bad, does it? It?s not great- why are roles called ?types?, why are we representing them with strings, why are we checking if a user is logged in by checking which roles they have, and not whether or not they?re logged in? and why on Earth would you send the user an empty page if they?re not authenticated?

The question is: how do you generate an empty page? If ?just return an empty view object? is what you thought you?d do, you?re obviously not a Rockstar?.

	@Query(value = "from viewTable where 1=0", nativeQuery = false)
	public Page<viewTable> findEmptyPage(Pageable pr);

If you want to return an empty page, you run a query against the database which is guaranteed to return absolutely no results. That guarantees that you?ll send a blank page back, because there?s no data to put on the page. Genius! This way, returning nothing requires a hop across the network and a call to the database, instead of just, y?know, returning nothing (or even better, returning an error page).

Suffice to say, when this master programmer gave their two weeks notice, Emmer and the rest of the team suggested that this programmer should spend their last two weeks on vacation.

[Advertisement] Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.

Westward Ho!

Buoy in the ocean

Roman K. once helped to maintain a company website that served a large customer base mainly within the United Kingdom. Each customer was itself a business offering a range of services. The website displayed these businesses on a map so that potential customers could find them. This was done by geocoding the business' addresses to get their longitude and latitude coordinates, then creating points on the map at those locations.

Simple enough?except that over time, some of the businesses began creeping west through the Atlantic Ocean, toward the east coast of North America.

Roman had no idea where to start with troubleshooting. It was only happening with a subset of businesses, and only intermittently. He was certain the initial geocoded coordinates were correct. Those longitude and latitude values were stored in a database table of customer data with strict permissions in place. Even if he wanted to change them himself, he couldn't. Whatever the problem was, it was powerful, and oddly selective: it only ever changed longitude values. Latitude values were never touched.

Were they being hacked by their competitors? Were their customers migrating west en masse? Were goblins messing with the database every night when no one was looking?

Roman dug through reams of code and log files, searching desperately for any whiff of "longitude." He questioned his fellow developers. He blamed his fellow developers. It was all for naught, for the problem was no bug or hack. The problem was a "feature" of the database access layer. Roman discovered that the user class had a simple destructor method that saved all the currently loaded data back to the database:

function __destruct() {
     if ($this->_changed) {
          $this->database->update('customer_table', $this->_user_data, array('customer_id' => $this->_user_data['customer_id']));
     }
}

The legwork was handled by a method called update(). And just what did update() do?

public function update($table, $record, $where = '') {
     // snip
     foreach ($record as $field => $value) {
          if (isset($value[0]) && is_numeric($number) && ($value[0] == '+' || $value[0] == '-')) {
               $set[] = "`$field` = `$field` {$value[0]} ".$number;
          }
     }
}

Each time a customer logged into their account via the website and changed their data: if any of their data began with either a plus or minus sign, those values would have some mysterious value (contained in the variable $number) either added to or subtracted from them. Say a customer's business happened to be located west of the prime meridian. Their longitude would therefore be stored as a negative value, like -3. The next time that customer logged in and changed anything, the update() method would subtract $number from -3, relocating the customer to prime oceanic property. Latitude was never affected because latitude coordinates above the equator are positive. These coordinate values were simply stored as-is, with no sign in front of them.

There was no documentation for the database access layer. The developer responsible for it was long gone. As such, Roman never did learn whether there were some legitimate business reason for this "feature" to exist. He added a flag to the update() method so that customers could disable the behavior upon request. Ever since, the affected companies have remained safely anchored upon UK soil.

[Advertisement] Ensure your software is built only once and then deployed consistently across environments, by packaging your applications and components. Learn how today!

CodeSOD: Switch On Suppression

Krista noticed our article explaining that switches were replacements for ifs. She sent in a version she found in her codebase, around the same idea:

	@SuppressWarnings("incomplete-switch")
	@Transactional
	public void removeAssetFromPackage(Package pkg, Asset assetToRemove) {
		pkg.getAssets().remove(assetToRemove);
		// Delete from DB and asset store.
		removeAsset(pkg, assetToRemove);

		// If we're removing LIVE asset, also delete AsyncJobs.
		switch (assetToRemove.getType()) {
			case LIVE:
				asyncJobService.removeAsyncJobsForPresentation(pkg);
				break;
		}

		// Flush package cache.
		cacheInvalidationService.invalidatePresenationCache(pkg);
	}

Once again, we use a switch instead of an if. Perhaps this was premature flexibility- there are obviously other states the getType method could return. Maybe, someday in the future, they?ll need to do other things inside that switch. Until then, it?s just the sort of thing the compiler will throw warnings about, since there?s no default case.

Oh, except, of course, they suppressed the warning up top.

A quick search in Krista?s codebase for @SuppressWarnings("incomplete-switch") finds dozens of usages of this pattern.

[Advertisement] Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.

CodeSOD: The Secure Cloud API

Melinda's organization has purchased a cloud-based storage system. Like any such system, it has a lovely API which lets you manage quotas and login tokens. It also had a lovely CLI, which was helpful for administrators to modify the cloud environment. Melinda's team built a PHP front-end that could not only manage files, but also allowed administrators to manage those quotas.

Melinda was managing those quotas, and when she clicked the link to view the quotas, she noticed the URL contained ?token=RO-cmV1c2luZyBrZXlzIGlzIFRSV1RG. When she went to modify the quota, the URL parameter became ?token=RW-cmV1c2luZyBrZXlzIGlzIFRSV1RG. That looked like a security key for their cloud API, transmitted in the open. The RW and RO looked like they had something to do with readwrite and readonly, but that wasn't the security model their storage provider used. When Melinda had another co-worker log in, they saw the same tokens. What was going on?

Melinda took a look at the authorization code.

function authorised($token, $verb) { // check if token can do verb // TO DO - turn this in to a database lookup $rights = array( 'RW-cmV1c2luZyBrZXlzIGlzIFRSV1RG' => array('getquota' => 0, 'setquota' => 1), 'RO-cmV1c2luZyBrZXlzIGlzIFRSV1RG' => array('getquota' => 1, 'setquota' => 0) ); return ((isset($rights[$token]) && isset($rights[$token][$verb]) && ($rights[$token][$verb] == 1))); }

The developer behind this wrote their own security model, instead of using the one their storage provider offered. The tokens here were hard-coded secret keys for the API. Essentially this meant no matter who logged in to manage quotas on the application side, the storage system saw them all as a single user- a single user with pretty much unlimited permissions that had root access on every VM in their cloud environment.

"Oh, boy, they released their secret key to essentially a root account, that's bad," you say. It gets worse. The code doesn't, but the logic does.

You see, the culprit here didn't want to learn the API. So they did everything via shell commands. They had one machine set up in the cloud environment with a bunch of machine-based permissions that allowed it to control anything in the cloud. When someone wanted to change quotas, the PHP code would shell out and use SSH to log into that cloud machine as root, and then run the cloud vendor's proprietary CLI tool from within their own environment.

[Advertisement] ProGet supports your applications, Docker containers, and third-party packages, allowing you to enforce quality standards across all components. Download and see how!

Error'd: Stay Away From California

"Deep down, I knew this was one of the most honest labels I've ever seen," wrote Bob E.

 

"Some people struggle to get and keep a high credit score. I, on the other hand, appear to have gotten a high score," writes Pawe?.

 

Steve M. wrote, "I'm trying to register our travel insurance with ROL Cruises, but our travel policy has been rejected because it's under age."

 

"This happens every time my mom tries to place a call in her car," writes Dylan S., "Strangely enough, the call still goes through."

 

"While I appreciate the one year warranty, I don't think I will be using these tea cakes to replace my HP battery," J.R. wrote.

 

Neil D. writes, "So, am I supposed to enter -1 and then I can buy one?"

 

[Advertisement] Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.

Crazy Like a Fox(Pro)

?Database portability? is one of the key things that modern data access frameworks try and ensure for your application. If you?re using an RDBMS, the same data access layer can hopefully work across any RDBMS. Of course, since every RDBMS has its own slightly different idiom of SQL, and since you might depend on stored procedures, triggers, or views, you?re often tied to a specific database vendor, and sometimes a version.

Keulemans Chama fox.png

And really, for your enterprise applications, how often do you really change out your underlying database layer?

Well, for Eion Robb, it?s a pretty common occurrence. Their software, even their SaaS offering of it, allows their customers a great deal of flexibility in choosing a database. As a result, their PHP-based data access layer tries to abstract out the ugly details, they restrict themselves to a subset of SQL, and have a lot of late nights fighting through the surprising bugs.

The databases they support are the big ones- Oracle, SQL Server, MySQL, and FoxPro. Oh, there are others that Eion?s team supports, but it?s FoxPro that?s the big one. Visual FoxPro?s last version was released in 2004, and the last service pack it received was in 2007. Not many vendors support FoxPro, and that?s one of Eion?s company?s selling points to their customers.

The system worked, mostly. Until one day, when it absolutely didn?t. Their hosted SaaS offering crashed hard. So hard that the webserver spinlocked and nothing got logged. Eion had another late night, trying to trace through and figure out: which customer was causing the crash, and what were they doing?

Many hours of debugging and crying later, Eion tracked down the problem to some code which tracked sales or exchanges of product- transactions which might not have a price when they occur.

$query .= odbc_iif("SUM(price) = 0", 0, "SUM(priceact)/SUM(" . odbc_iif("price != 0", 1, 0) . ")") . " AS price_avg ";

odbc_iif was one of their abstractions- an iif function, aka a ternary. In this case, if the SUM(price) isn?t zero, then divide the SUM(priceact) by the number of non-zero prices in the price column. This ensures that there is at least one non-zero price entry. Then they can average out the actual price across all those non-zero price entries, ignoring all the ?free? exchanges.

This line wasn?t failing all the time, which added to Eion?s frustration. It failed when two very specific things were true. The first factor was the database- it only failed in FoxPro. The second factor was the data- it only failed when the first product in the resultset had no entries with a price greater than zero.

Why? Well, we have to think about where FoxPro comes from. FoxPro?s design goal was to be a data-driven programming environment for non-programmers. Like a lot of those environments, it tries its best not to yell at you about types. In fact, if you?re feeding data into a table, you don?t even have to specify the type of the column- it will pick the ?correct? type by looking at the first row.

So, look at the iif again. If the SUM(price) = 0 we output 0 in our resultset. Guess what FoxPro decides the datatype must be? A single digit number. If the second row has an average price of, say, 9.99, that?s not a single digit number, and FoxPro explodes and takes down everything else with it.

Eion needed to fix this in a way that didn?t break their ?database agnostic? code, and thus would continue to work in FoxPro and all the other databases, with at least predictable errors (that don?t crash the whole system). In the moment, suffering through the emergency, Eion changed the code to this:

$query .= "SUM(priceact)/SUM(" . odbc_iif("price != 0", 1, 0) . ")") . " AS price_avg ";

Without the zero check, any products which had no sales would trigger a divide-by-zero error. This was a catchable, trappable error, even in FoxPro. Eion made the change in production, got the system back up and their customers happy, and then actually put the change in source control with a very apologetic commit message.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: Padding Your Time

Today will be a simple one, and it?s arguably low-hanging fruit, because once again, it?s date handling code. But it?s not handling dates where it falls down. It falls down on something much more advanced: conditionals. Supplied by ?_ek1n?.

if ($min == 0) {
    if ($hours == 12) {
        $hours = 12;
        $min   = '00';
    } else {
        $hours = $hours;
        $min   = '00';
    }
}

My favorite part is the type-conversion/padding: turning 0 into '00', especially the fact that the same padding doesn?t happen if $min is 1, or 3, or anything else less than 10. Or, it probably does happen, and it probably happens in a general fashion which would also pad out the 0- or maybe it doesn?t, and TRWTF is that their padding method can?t zero-pad a zero.

The most likely situation, though, is that this is just a brain fart.

[Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!

CodeSOD: Wear a Dunder Cap

In the Python community, one buzzword you?ll find thrown around is whether or not an approach is ?pythonic?. It?s a flexible term, and something you can just throw out in code reviews, even if you?ve never written a line of Python in your life: ?Is that Pythonic??

The general rubric for what truly is ?pythonic? is generally code that is simple and code that operates explicitly. There shouldn?t be any ?magic?. But Python doesn?t force you to write ?pythonic? code, and it provides loads of tools like decorators and metaclasses that let you get as complex and implicit as you like.

One bit of magic is the ?dunder? methods, which are Python?s vague approach to operator overloading. If you want your class to support the [] operator, implement __getitem__. If you want your class to support the + operator, implement __add__. If you want your class to support access to any arbitrary property, implement __getattr__.

Yes, __getattr__ allows you to execute code on any property access, so a simple statement like this?

if self.connected:
  print(self.coffee)

?could involve twenty database calls and invoke a web service connected to a live coffee machine and literally make you a cup of coffee. At no point does the invoker ever see that they?ve called a method, it just happens by ?magic?. And, for extra fun, there?s also a global method getattr, lets you wrap property access with a default, e.g., getattr(some_object, "property", False) will return the value of some_object.property, if it exists, or False if it doesn?t.

Whew, that?s a lot of Python internals, but that brings us to today?s anonymous submission.

Someone wrote some classes which might contain other classes, and wanted them to delegate property access to those, which means there are a number of classes containing this method:

def __getattr__(self, item):
    return self.nested.item

So, for example, you could call some_object.log_level, and this overridden __getattr__ would walk through the whole chain of objects to find where the log_level exists.

That?s fine, if the chain doesn?t contain any loops, where the same object appears in the chain multiple times. If, on the other hand, it does, you get a RecursionError when the loop finally exceeds the system recursion limit.

Our submitter found this a bit of a problem. When the access to log_level failed, it might take a long time before hitting the RecursionError- which, by the way, isn?t triggered by a stack overflow. Python tries to throw a RecursionError before you overflow the stack.

You can, but very much shouldn?t control that value. But our submitter didn?t want to wait for the thousands of calls it might take to get the RecursionError, so they wrapped their accesses to log_level in code that would tweak the recursion limit:

    # Protect against origin having overwritten __getattr__
    old_recursion_limit = sys.getrecursionlimit()
    n = 2
    while True:
        try:
            sys.setrecursionlimit(n)
        except RecursionError as e:
            n += 1
        break
    try:
        log_level = getattr(origin, "log_level", None)
    except RecursionError as e:
        object.__setattr__(origin, "log_level", None)
        log_level = getattr(origin, "log_level", None)
    sys.setrecursionlimit(old_recursion_limit)

So, first, we check the current recursion limit. Then, we try setting the recursion limit to two. If the current recursive depth is greater than two, then when we try to change the recursion limit it throws a RecursionError. So catch that, and then try again with one higher recursion limit.

Once we?ve set the recursion limit to one greater than our current recursion limit, we then try and fetch the log level. If we fail, we?ll just default to None, and finally return back to our original recursion limit.

This is an impressive amount of effort into constructing a footgun. From the use of __getattr__ in the first place, to the misplaced effort of short-circuiting recursion instead of preventing cycles in the first place, and finally to the abuse of setrecursionlimit and error handling to build? this. Absolutely nothing here should have happened. None of it.

Our submitter has confessed their sins. As penance, they?ll say twenty Hail Guidos, and fix this code. Remember, you can?t be absolved of your sins if you just keep on sinning.

[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!

CodeSOD: Legacy Switchout

About a decade ago, I attended a talk. The speaker made the argument that "legacy code" may have many possible interpretations, but the practical view was to simply think of legacy code as "code without unit tests". Thus, the solution to modernizing your legacy code was to simply write unit tests. Refactoring the code to make it testable would have the side effect of modernizing the code base, and writing tests would act as documentation. It's that easy.

Andrew is struggling with some legacy code right now. Worse, they're trying to integrate a large mountain of legacy code into a custom, in-house CI/CD pipeline. This particular pile of legacy code dates back to the mid-2000s, so everything in it is glued together via XML. It was some of that XML code which started failing when Andrew threw some unit tests at it.

It doesn't start that bad:

static bool IsNamespace(XName name) { return name.LocalName == "xmlns" || name.NamespaceName == "http://www.w3.org/2000/xmlns/"; }

Now, this is a silly method, and even .Net 1.0 had an XmlNamespaceManager for handling interacting with namespaces. Without knowing more of the overall context, maybe this method was actually useful? At the very least, this method isn't a WTF.

One thing I want you to note, however, is that this method does the sane thing, and simply returns the result of a boolean comparison. That method was written by the same developer, and checked in on the same day as this method:

static bool IsNewtonsoftNamespace(XAttribute attribute) { switch (attribute.Value) { case "http://james.newtonking.com/projects/json": return true; } switch (attribute.Name.NamespaceName) { case "http://james.newtonking.com/projects/json": return true; } return false; }

Between writing IsNamespace and IsNewtonsoftNamespace, the developer apparently forgot that they could just return boolean expressions. They also apparently forgot- or perhaps never knew- what an if statement is supposed to do.

Andrew at least has the code building in their CI solution, but can look forward to many more weeks of fixing test failures by going through code like this.

[Advertisement] Ensure your software is built only once and then deployed consistently across environments, by packaging your applications and components. Learn how today!

Error'd: An Unfortunate Sign

"Found this in the School of IT. 404: Women not found. Fairly accurate," wrote Maddie J.

 

"In true Disney fashion, even their servers bid us farewell after 'It's a Small World After All' had wrapped up," writes Daniel H.

 

Lorens wrote, "I thought the GNOME bugs in the new 'Bionic Beaver' Ubuntu were going to drain my battery, but turns out there's a lot left."

 

"You know, I never click on ads, and this one certainally doesn't 'speak' to my needs, but I have to know - what is it advertising?" writes Aaron K.

 

John A. wrote, "We got a new super 'secure' expense reporting tool at work."

 

"I guess so long as 'undefined' doesn't get hacked, I'm ok," James writes.

 

[Advertisement] ProGet can centralize your organization's software applications and components to provide uniform access to developers and servers. Check it out!

CodeSOD: Not a Not Bad Approach

In terms of elegance, I think the bitmask has a unique beauty. The compactness of your expression, the simple power of bitwise operators, and the way you can see the underlying implementation of numbers laid bare just speaks to me. Of course, bitmasks can be a bit opaque, and you may have to spend some time thinking about what foo &= 0xFF0000 is actually doing, but there?s also something alluring about it.

Of course, bitmasks are surprisingly hard. For example, let?s look at some code submitted anonymously. This code is meant to run on a line of coin-operated dryers. Depending on the particular install, how many coins a patron puts in, what modes have been enabled by the owner, and so on, different ?extra? features might be disabled or not disabled.

Since bitmasks are hard, we?re not going to use one. Instead, let?s have a pile of constants, like so:

#define DRYER_EXTRA_1    1
#define DRYER_EXTRA_2    2
#define DRYER_EXTRA_3    3
#define DRYER_EXTRA_4    4
#define DRYER_NOT_EXTRA_1    6
#define DRYER_NOT_EXTRA_2    7
#define DRYER_NOT_EXTRA_3    8
#define DRYER_NOT_EXTRA_4    9

Now, how might we use those constants? Why, we?re going to use constructs like this:

if(supportedExtra == DRYER_EXTRA_1)
{
    notSupportedExtrasList[extra1] = DRYER_NOT_EXTRA_1;
}
else
{
    notSupportedExtrasList[extra1] = DRYER_EXTRA_1;
}

A double negative is not a not bad idea. If this dryer is configured to support DRYER_EXTRA_1, then we?ll add DRYER_NOT_EXTRA_1 to the list of unsupported extras. There?s a variation on this code for every possible extra, and they all use the double-negative pattern.

As extras are being selected, however, we need to check against things like if there?s enough money in the machine. Thus, we see patterns like:

if(isEnoughMoney == FALSE)
{
    selectedExtra &= ~DRYER_EXTRA_1;
    //so this is a bit mask now?
}

//...some more lines later
if(selectedExtra == DRYER_EXTRA_1)
{
    //we're back to treating it as integer?
}

//...some more lines later
if((selectedExtra & DRYER_EXTRA_1) == DRYER_EXTRA_1)

Our developer here gets confused about the nature of the constants, swinging from manipulating it as bitmasks (which won?t work, as the constants aren?t powers of two), then back as a regular number (which could work, but maybe isn?t the best way to design this), and back to bitmasks again.

You may be shocked to learn that this entire module doesn?t work, and our submitter has been called in to junk it and rewrite it from scratch. They?ll be using bitmasks.

[Advertisement] ProGet supports your applications, Docker containers, and third-party packages, allowing you to enforce quality standards across all components. Download and see how!

CodeSOD: Caught Up in the Captcha

Gregor needed to download a network driver. Upon clicking the link, a "captcha" appeared, presumably to prevent hotlinking to the driver files. It wasn't a real, image-based captcha, but a simple "here's some characters, type them into the box".

The code which popped up was "S i u x q F b j NaN 4". He hit the "new code" button, and got "T o A 0 J V s L NaN a". In fact, "NaN" showed up in the penultimate position in every code.

Curious, Gregor pulled up the debugger to see how the captcha was generated.

function Captcha(){ var alpha = new Array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', '0','1','2','3','4','5','6','7','8','9'); var i; for (i=0;i<20;i++){ var a = alpha[Math.floor(Math.random() * alpha.length)]; var b = alpha[Math.floor(Math.random() * alpha.length)]; var c = alpha[Math.floor(Math.random() * alpha.length)]; var d = alpha[Math.floor(Math.random() * alpha.length)]; var e = alpha[Math.floor(Math.random() * alpha.length)]; var f = alpha[Math.floor(Math.random() * alpha.length)]; var g = alpha[Math.floor(Math.random() * alpha.length)]; var h = alpha[Math.floor(Math.random() * alpha.length)]; var i = alpha[Math.floor(Math.random() * alpha.length)]; var j = alpha[Math.floor(Math.random() * alpha.length)]; } var code = a + ' ' + b + ' ' + ' ' + c + ' ' + d + ' ' + e + ' '+ f + ' ' + g + ' ' + h + ' ' + i + ' ' + j; document.getElementById("mainCaptcha").innerHTML = code document.getElementById("mainCaptcha").value = code }

The first thing that you might notice is that they wanted to be really random, so they generate the random code inside of a for loop, which claims to execute twenty times. Twenty random numbers are better than one, right?

Well, obviously not. The loop almost always executes only once. The variable i is not simply the loop variable, but also one of the variables holding the captcha characters. If it's set to a letter, i<20 is false, and the loop breaks. If it's set to a number, the loop executes again (meaning that if you're really unlucky, the loop could possibly execute for a very long time).

It's a bit embarrassing to admit, but I had to think for a minute to figure out why i was NaN, and not just whatever letter it got assigned. Then I remembered- a for loop is just a while loop, and the increment step is executed at the end of the loop. Calling ++ on a string assigns NaN to the variable.

In Gregor's case, typing the captcha, NaN and all, into the input box worked just fine, and he was able to download his drivers.

[Advertisement] ProGet supports your applications, Docker containers, and third-party packages, allowing you to enforce quality standards across all components. Download and see how!

SLA-p the Salesman

A Service-Level Agreement (SLA) is meant to ensure customer issues receive the attention they deserve based on severity. It also protects the support company from having customers breathing down their neck for frivolous issues. All of the parameters are agreed upon in writing ahead of time and both sides know the expectations. That is, until a salesman starts to meddle and mess things up, as happened at the place Dominick worked for.

Dominick was a simple remote support tech who fixed things for clients well ahead of the SLA. On the rare occasion there was a priority 1 issue - something stopping anyone in the company from doing work - they had 24 hours to fix it before large monetary penalties would start to rack up. One Friday a priority 4 issue (5 business day SLA) came in from the CFO of a new client. The ticket was assigned to Dominick, who had higher priority work to do for other clients, so he decided it could wait until the following week.

Canon ir2270

Dominick came in Monday morning to find Benjamin, a senior salesman who happened to be a personal friend of the CFO, sitting on his desk with his huge arms crossed. Benjamin glanced at his watch to see it was 7:59 AM. "About time you showed up, Dom. I found out you didn't do the ticket that came in Friday and I want an explanation!"

Still in a pre-coffee Monday morning haze, Dominick had to think for a second to figure out what he was talking about. "Oh... that thing about ordering a new printer? That was only priority 4 and it literally said 'no rush' in it. I have 4 more days to get it done."

Benjamin sprang up off Dom's desk and used his beefy arms to forcefully shove an index finger into his chest. "You don't get it do you, bro?? When I made this deal with them, I assured them anything would be treated with the highest priority!" Ben shouted while spraying an unsanitary amount of saliva droplets. "I don't care what your silly numbering system says, it needs to get done today!

"Ok... well let me sit down and look at it," Dominick said timidly while rubbing the spot on his chest that received a mean poking. Benjamin stormed off to presumably consume another protein shake. He pulled up the ticket about ordering a new printer for the CFO's office. It seemed he'd read about this top of the line printer in some tech magazine and really wanted it. The problem was the printer wasn't even on the market yet - it would be released at the end of the month. Since there was literally nothing Dominick could do to get the printer, he closed the ticket and asked that a new one be submitted when the printer was available.

Later that afternoon, Dominick heard stomping behind him and before he could turn around, Benjamin spun him around in his chair and got in his face. "Hey there, bro. Where is my guy's printer?? He told me you closed his submission without ordering it!"

Dominick stood up to defend himself and weakly poked Ben in the chest. "Listen, bro! He wants a printer that isn't out yet. The best I can do is pre-order it and have it shipped to him in a couple weeks. I closed the ticket so we don't get dinged on the SLA to get this done in 5 days."

Benjamin furrowed his brow and got back within saliva-spraying distance, "You'll have to do better than that, Dom! While you were screwing around not resolving this I made an addendum to their SLA. Any ticket submitted by a CxO level executive will be treated as priority 1 by us. So you better pull whatever techie nerd strings you have to get that printer ordered in the next 24 hours!"

After Benjamin stormed off yet again, the reality of what he had done set in. Since the SLA for the printer was now 24 hours, they would start getting charged penalties by tomorrow. Dominick quickly began crafting an email to senior management to explain the situation and how the request wasn't able to be met. He wasn't sure what sort of "techie nerd" resources Benjamin thought he had, but it wasn't going to happen.

Predictably, the situation didn't end well. The financial penalties started adding up the following day, and the next day, and so on. It became so expensive that it was more cost-effective to pay the client to modify the addendum to the SLA that Benjamin made (they couldn't be compelled to do so otherwise) than to continue to rack up fines.

The end of the month came and the world's most expensive printer finally shipped, which was a relief to everyone. But that also meant the end-of-month financial statements showed the huge deficit caused by it. To compensate, the company decided to lay off 20% of the support staff including Dominick. Benjamin, of course, got to keep his job where he always put customer needs first.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!