Thursday Nov 19, 2009

PHP: session.gc_maxlifetime vs. session.cookie_lifetime

PHP and sessions: Very simple to use, but not as simple to understand as we might want to think.


This value (default 1440 seconds) defines how long an unused PHP session will be kept alive. For example: A user logs in, browses through your application or web site, for hours, for days. No problem. As long as the time between his clicks never exceed 1440 seconds. It's a timeout value.

PHP's session garbage collector runs with a probability defined by session.gc_probability divided by session.gc_divisor. By default this is 1/100, which means that above timeout value is checked with a probability of 1 in 100.


This value (default 0, which means until the browser's next restart) defines how long (in seconds) a session cookie will live. Sounds similar to session.gc_maxlifetime, but it's a completely different approach. This value indirectly defines the "absolute" maximum lifetime of a session, whether the user is active or not. If this value is set to 60, every session ends after an hour.

Friday Nov 13, 2009

Little-known PHP commands: scandir()

Always messed around with a combo of opendir(), readdir(), and closedir() if you wanted to read the contents of a directory? Since PHP 5 there is a new sheriff in town: scandir():



    [0] => .
    [1] => ..
    [2] => apache2
    [3] => conf.d

Okay, you still need to traverse an array, but it's much easier to use than the traditional way.

Wednesday Nov 04, 2009

Store PHP sessions in memcached

My last week blog topic was very much marked by Apache load balancing. Well, I promised to leave this topic alone for a while, but there is one related topic that is worth spending a minute on.

The Theory

If your web application is distributed across multiple servers you'll quickly run in sessions problems because each backend server (aka worker) usually stores its session informations locally. Now, if subsequent HTTP requests are handled by different workers, every time a new sessions is created or, even worse, sessions getting mixed up.

To overcome this problem there are two solutions:

  1. Use a session-aware load balancer that binds a user session to the same worker.
  2. Or keep all session data in a central storage.

Both solutions have the similar drawback: if a worker goes down, all session data of this worker are lost. If the central storage goes down, all sessions are lost. But consider the following: you'll probably have tons of workers, and since every computer is supposed to fail after a specific period of time, the probability of a worker failure is much higher than for a single storage server. It depends on what do you want: A system that runs all the time with small failures or a system that fails completely from time to time?

And finally, losing session data sounds worse than it actually is: usually the users only have to login again to restore their session data. That's sad, but it's not the end of the world. Okay, your system may get into trouble if thousands of users try to re-login at the same time, but that's another problem.

The Solution

My favorite solution is the second one: keep all session data in a central place. And in this scenario I'll use Apache/PHP as my "application server" and memcached as central storage for my session data. If you read and still remember the title of this post, you're probably not surprised.


On the left: my load balancer, in the middle my worker farm, and on the right: my single and central memcached server. By the way: You can also have multiple memcached servers, but for this blog post I'll keep it simple.

The Requirements

First, let's check if PHP was build with memcached support:

serverA ~% php -m | egrep memcache

...on each worker node: serverA to serverD.

Second, I check if memcached is running on serverM:

serverM ~% ps -efa | egrep memcached
oswald  1543     1   0 15:21:17 ?         0:00 /home/oswald/webstack1.5/lib/memcached -d ...


The Configuration

Now I need to change the PHP configuration on each worker node: Open php.ini on serverA to serverD and search for these lines:

; Handler used to store/retrieve data.
session.save_handler = files

And change the configuration like this:

; Handler used to store/retrieve data.
session.save_handler = files
session.save_handler = memcache
session.save_path = "tcp://serverM:11211"

Make sure that the settings are the same on all your workers.

That's all. Yes, that's the basic configuration. PHP's sessions will now get stored on the memcached node serverM. No more magic needed.

The Proof

But as we say in Germany: "Prudence is the mother of the china cabinet." Before we can grab the beer, we should make sure everything works as we expect it to.

I put this code in a file named session.php in the document root directory of all my worker nodes:

		echo "Zaphod is ".$_SESSION['zaphod']."!\\n";
		echo "Session ID: ".session_id()."\\n"; 
		echo "Session Name: ".session_name()."\\n";
		echo "Setting 'zaphod' to 'cool'\\n";

From the outside I use lynx to access this file:

% lynx -source 'http://serverA/session.php'
Session ID: df58bc9465f27aa20218c11caba6750f
Session Name: PHPSESSID
Setting 'zaphod' to 'cool'

A new session with the ID df58bc9465f27aa20218c11caba6750f was created and PHP uses the session name PHPSESSID to identify the session parameter. And the session variable zaphod was set to the value cool.

Now I add the session information PHPSESSID=df58bc9465f27aa20218c11caba6750f to my URL and rerun the new lynx command:

% lynx -source 'http://serverA/session.php?PHPSESSID=df58bc9465f27aa20218c11caba6750f'
Zaphod is cool!

Yes, I got the expected output: Zaphod is cool! Proving the session data is available on serverA. But that's not a big surprise, what's about the other nodes? I replace serverA with serverB in my URL:

% lynx -source 'http://serverB/session.php?PHPSESSID=df58bc9465f27aa20218c11caba6750f'
Zaphod is cool!

Bingo, serverB also has the same session data as serverA.

And for serverC? It's also the same:

% lynx -source 'http://serverC/session.php?PHPSESSID=df58bc9465f27aa20218c11caba6750f'
Zaphod is cool!

And so on... for each worker node the session data will be the same.

A dream came true.

Thursday Oct 22, 2009

Typographic headlines with PHP (Part 2)

Yesterday, I started a small tutorial on how to implement typographic headlines with PHP. There were some aspects to be aware of, but in general it was an easy and straight forward process. The final result looked like this:


But there was one big issue I had with my script: It was far to slow (33 requests per second) for use in a production environment. But today, I'll extend my previous script with a simple caching mechanism to make it ready for the real world.

Welcome to the Pleasuredome

Basically that's where I left yesterday:

$font_file="./FFF Tusj.ttf";
$text = "An Example";

$bb = imagettfbbox($font_size,0,$font_file,$text);

$bb_width = $bb[4]-$bb[6];
$bb_height = $bb[3]-$bb[5];

$image  =  imagecreate($bb_width, $bb_height);     

$fillcolor  =  imagecolorallocate($image, 255, 255, 255);    
$fontcolor  =  imagecolorallocate($image, 69, 138, 186);    

imagefill($image, 0, 0, $fillcolor);    

imagettftext($image, $font_size, 0, abs($bb[6]), $bb_height-$bb[3], $fontcolor, 
		$font_file, $text);

header("Content-Type: image/png");

I'll put this in a function - let's say - fancyheadline($text) and will add two lines of code at the beginning of this function and change the last lines a little bit.

The final script will look like this (changes highlighted):

function fancyheadline($text)
		$font_file="./FFF Tusj.ttf";

		$bb = imagettfbbox($font_size,0,$font_file,$text);

		$bb_width = $bb[4]-$bb[6];
		$bb_height = $bb[3]-$bb[5];

		$image  =  imagecreate($bb_width, $bb_height);    

		$fillcolor  =  imagecolorallocate($image, 255, 255, 255);    
		$fontcolor  =  imagecolorallocate($image, 69, 138, 186);    

		imagefill($image, 0, 0, $fillcolor);    

		imagettftext($image, $font_size, 0, abs($bb[6]), $bb_height-$bb[3],
				$fontcolor, $font_file, $text); 

		header("Content-Type: image/png");
	return '<img src="'.$cache.'" alt="'.htmlspecialchars($text).'">';

echo fancyheadline("An Example");

What's happening here is that I first generate a MD5 hash of my headline string $text and check if a cache file containing this hash exists or not. If it doesn't exists, create the file containing the rendered headline image. If it exists, do nothing.

At the end of the script I return a string consisting of an IMG HTML tag which would display the cached image file. (With htmlspecialchars () I convert special characters like ", &, < and > to their corresponding HTML entities, because these chars may break the validity of HTML.)

Before I can run my script, I need to create the cache directory and give it appropriate permissions:

% mkdir cache
% chmod a+rwx cache

On a production server you probably don't want to give write permissions to everyone, but will change the ownership of the directory to www-data or whatever user my web server runs as.

Now it's time to access the script in my trusted browser:


This looks exactly like my original yesterday's example above, but this time the script generates the headline image "in the background" and only outputs some HTML code referring to this image:

<img src="cache/e6cf1c67e6acfa204bb784cd6b25839f.png" alt="An Example">

In other words: I reduced the use and need of PHP as much as possible.

Final words by ApacheBench

First let's check the PHP script itself:

% ab -n 1000 http://demo/headline.php
This is ApacheBench, Version 2.3
Document Path:          /headline.php
Document Length:        71 bytes
Requests per second:    1801.51 [#/sec] (mean)

1800 requests per second. Yes, that's what I wanted to hear. But it's easy to explain: The headline image is generated only once, upon the first request. And for all following requests the script only refers to the already generated image.

And if I benchmark the image itself:

% ab -n 1000 http://demo/cache/e6cf1c67e6acfa204bb784cd6b25839f.png
This is ApacheBench, Version 2.3
Document Path:          /cache/e6cf1c67e6acfa204bb784cd6b25839f.png
Document Length:        7888 bytes
Requests per second:    3308.09 [#/sec] (mean)

Of course, because it's now just static data, and I'll get the most performance possible out of my server.

I started with 33 requests per second and ended somewhere in between of 1800 and 3300 requests per second.

Moving at one million miles an hour... Welcome to the Pleasuredome!

Wednesday Oct 21, 2009

Typographic headlines with PHP (Part 1)

My recent blog post about scaling images with PHP gave me the idea to write something about creating typographic headlines with PHP. At Apache Friends we're using this technique since many years to get rid of the usual boring and everywhere available "web fonts" like Helvetica, Times and Verdana.

For this example I chose the font Tusj by Norwegian graphic designer Magnus Cederholm. Okay, this font will only work for very large headlines, but it's looks cool and it's perfect for this demo's purposes because the TTF file is very huge (1.5 MB) and that makes the processing in PHP quite slow. (Yes, in this case, I want to slow down my PHP script.)

The Basics

First, I define some basic parameter: TTF font file, the font size, and an example text.

$font_file = "./FFF Tusj.ttf";
$font_size = 64;
$text = "An Example";

In the next step I've to find out, how big my image needs to be to take the rendered text. That's not so trivial because it strongly depends on the used characters, the choosen size and of course the font itself. To solve this problem PHP offers the imagettfbbox() function:

$bb = imagettfbbox($font_size, 0, $font_file, $text);
$bb_width = $bb[4]-$bb[6];
$bb_height = $bb[3]-$bb[5];

With imagecreate() I now can create the image using $bb_width and $bb_width for the size:

$image = imagecreate($bb_width, $bb_height);    

Define two colors: one for the background and one for the foreground.

$fillcolor = imagecolorallocate($image, 255, 255, 255);    
$fontcolor = imagecolorallocate($image, 69, 138, 186);    

Fill the background using $fillcolor:

imagefill($image, 0, 0, $fillcolor);    

Render the text to the image:

imagettftext($image, $font_size, 0, abs($bb[6]),$bb_height-$bb[3], $fontcolor,
		$font_file, $text);

I don't want to go into the details of this function, for a detailed explanation of all parameters please refer to the PHP manual.

And finally send it to the browser:

header("Content-Type: image/png");   

Basically that's all you need to do. Here's the output of the above PHP script:


Because I used imagecreate() and imagepng() the file I got is an indexed-colored 8-bit PNG with a file size of 8 KB.

Some Variations

If I use imagecreatetruecolor() and imagepng() I will get a truecolored 24-bit PNG with a file size of 41 KB:


And if I use imagejpeg() instead of imagepng() I will get a truecolored 24-bit JPEG with a quality setting of 100 and a file size of 41 KB:


All three variations look exactly the same, but the indexd-colored 8-bit PNG is the smallest one. So for this purpose an 8-bit PNG seems to be the best choice.

Turning off antialiasing?

By default imagettftext() uses an antialiasing algorithm to smooth the output. Using the negative of a color index turns this feature off:

imagettftext ($image, $font_size, 0, abs($bb[6]),$bb_height-$bb[3], -$font_color, 
		$font_file, $text);

Sometimes (usually in case of small font sizes) this will give you a sharper and better looking result, but in this special case it definitely looks worse:


Welcome to the Real World

As I mentioned in the beginning, I intentionally chose a font based on a very large TTF file, which makes it very expensive for PHP to render a headline. Let's take a look at some quick benchmark results:

% ab -n 1000 http://demo/headline.php
This is ApacheBench, Version 2.3
Benchmarking demo (be patient)
Document Path:          /headline.php
Document Length:        7888 bytes
Requests per second:    33.52 [#/sec] (mean)

Autsch... 33 requests per second is far to slow for a real world scenario. Yes, if I had chosen a smaller font the results would be much better, but probably the script will still be not suitable for use in a production environment. However, a simple caching mechanism should easily solve this issue.

But not today, stay tuned for part 2 of this tutorial. Live long and prosper.

Tuesday Oct 20, 2009

Scaling images in PHP (done right)

Scaling images in PHP is quite easy, but there are some things to consider. (If you're short of time, right at the end you'll find the final script.)

Read the original image with imagecreatefromjpeg()

First of all you'll need to read the original image. If it's a JPEG file the imagecreatefromjpeg() function is the right choice:

	$source_image = imagecreatefromjpeg("osaka.jpg");

If it's a GIF file you'll take imagecreatefromgif(), and if it's a PNG you will prefer imagecreatefrompng().

For this small tutorial I'll use this image from the Osaka Aquarium Kaiyukan.

Get the size of the original image: getimagesize() vs. imagesx() and imagesy()

Reading the image is quite easy, but the first pitfall you'll encounter if you prepare to scale an image, and need to find out the dimensions of the original image.

Most tutorials will propose this way:

	$source_image = imagecreatefromjpeg("osaka.jpg");
	$source_image_size = getimagesize("osaka.jpg");

The problem with the getimagesize() function is that it needs to reopen the file to get the actual image size. This is usually not a big issue if you're reading a local file, but it will get critical if you're reading a file from the network like in:

	$source_image = imagecreatefromjpeg("http://someurl/osaka.jpg");
	$source_image_size = getimagesize("http://someurl/osaka.jpg");

Every time you call getimagesize() the whole image file get transferred over the network, and that quickly became mission-critical. Since you already have the image loaded with imagecreatefromjpeg() there is no need to load it again:

	$source_image = imagecreatefromjpeg("osaka.jpg");
	$source_imagex = imagesx($source_image);
	$source_imagey = imagesy($source_image);

Create the destination image: imagecreate() vs. imagecreatetruecolor()

Now we need to prepare the (scaled) destination image. There are two PHP functions which can be used to create an image: imagecreate() and imagecreatetruecolor(). The first creates a palette based image (with a maximum of 256 different colors), and the second one creates a true color image (with a maximum - as far as I know - of 256\*256\*256 = 16 million colors).

Let's compare the results of both function: On the left imagecreate() and on the right imagecreatetruecolor():


It's obvious: As long as you work with photographic images you'll need more than 256 colors.

So let's decide to use imagecreatetruecolor() and define a target size of 300x200 pixels:

	$dest_imagex = 300;
	$dest_imagey = 200;
	$dest_image = imagecreatetruecolor($dest_imagex, $dest_imagey);

Scale the image: imagecopyresized() vs. imagecopyresampled()

Now it's time to do the actual scaling of the image. Again PHP offers to function for this purpose: imagecopyresized() and imagecopyresampled(). The first one uses a very simple algorithm to scale the image, it's fast but the quality is really poor. The second one uses a better, but slower algorithm, resulting in a very high quality image.

Poor quality, but fast:

	imagecopyresized($dest_image, $source_image, 0, 0, 0, 0, 
				$dest_imagex, $dest_imagey, $source_imagex, $source_imagey);

Best quality, but slow:

	imagecopyresampled($dest_image, $source_image, 0, 0, 0, 0, 
				$dest_imagex, $dest_imagey, $source_imagex, $source_imagey);

I don't want to go into the details of this functions, for a detailed explanation of all parameters please refer to the PHP manual.

Let's compare the results: On the left imagecopyresized() and on the right imagecopyresampled():


Again it's obvious: The quality of imagecopyresampled() is much better. In my opinion there is never a reason to use the faster imagecopyresized(). Why would I ever want a low quality image? Even if it's faster to get?

And finally push the image to the browser: imagejpeg() vs. imagepng()

After scaling the image, we now need to push the image to the user's browser. Probably the most popular image formats in the Internet are currently PNG and JPEG. Both will work great with photographic images but true-colored and loss-less PNG images usually results in larger file sizes than JPEG images.

To send a PNG image (with best compression rate 9) to the browser:

	header("Content-Type: image/png");

Or a JPEG image (with best quality 100):

	header("Content-Type: image/jpeg");

And in comparison, imagepng() on the left vs. imagejpeg() on the right:


Both look absolutely the same, but the JPEG image has a size of 57 KB (using the best quality of 100) and the PNG image is 102 KB big (using the highest available compression rate).

What's the best JPEG quality to choose?

JPEG images are not only smaller but also give you the flexibility to choose the quality and by this indirectly the file size. In PHP you can choose the quality in a range from 0 (worst quality, smaller file) to 100 (best quality, biggest file). Let' take a look.

Quality 100 (57 KB) and quality 80 (16 KB):

scaledjpg-100.jpg scaledjpeg-80.jpg

If you look very carefully at the quality 80 version on the right, you'll see very small artifacts by the JPEG compression.

Quality 60 (12 KB) and quality 40 (8 KB):

scaledjpeg-60.jpg scaledjpeg-40.jpg

The loss of quality gets worse, and in my opinion the quality 40 image on the right looks terrible.

And now the whole script...

Many words end in a small script:

	$source_image = imagecreatefromjpeg("osaka.jpg");
	$source_imagex = imagesx($source_image);
	$source_imagey = imagesy($source_image);

	$dest_imagex = 300;
	$dest_imagey = 200;
	$dest_image = imagecreatetruecolor($dest_imagex, $dest_imagey);

	imagecopyresampled($dest_image, $source_image, 0, 0, 0, 0, $dest_imagex, 
				$dest_imagey, $source_imagex, $source_imagey);

	header("Content-Type: image/jpeg");

In this script I used a quality of 80, that's just my personal preference. You may choose whatever you like. But please, not less than 40.


In many tutorials the PHP script ends with several imagedestroy() function calls. imagedestroy() frees any memory associated with an image. This is a good idea if you sequentially work with different image resources within a single PHP script. But if the imagedestroy() is right at the end of a script, you may omit this function. When the script ends PHP will automatically free any resources.

Wednesday Sep 30, 2009

How PHP handles $variable data?

If you're using PHP you're usually don't care how PHP stores variables internally. But if you start working with references you probably better know what's going on behind the scenes.


(Without) References

Let's assume the following code:


You probably would assume that PHP now keeps the string Zaphod three times in memory. Actually all $a, $b and $c internally(!) reference to the same string Zaphod in memory. See diagram #1.

You, the user, will never know you are actually working with references, because PHP hides this very well. For example: If you change the value of any of this variable the reference just points to the new string:


Now $c internally references to the new value Beeblebrox. See diagram #2.

And after deleting the variable $b:


the variable $a still references to Zaphod. See diagram #3.

With References

If you start using references it's - of course - slightly different. Let's start over:


Now $b and $c are real references to the value of $a. If you output the values you'll see no difference. See diagram #4.

But if you change the value of $b...

$b="Beeblebrox";'ll also change the value of $a and $c because both are pointing the the same value (btw, a value is stored in a so called zval structure). See diagram #5.

Deleting a variable is likely the same as without references:


And the variable $b disappears but the value (as long as referenced by any other variable) stays in memory. Diagram #6.


Even if you don't know, you're probably using references all the time. You don't need to use references to save memory, because PHP already uses references internally, and hides this very well.

Thursday Sep 24, 2009


I love AWK. It is a wonderful tool for data processing on Unix systems. I truly love it. There is certainly no better tool to process and aggregate log files. I remember back when I introduced AWK to my students, there was always an immediately appreciative murmur in the round when the first AWK scripts showed their power.

And it's so stable. Perhaps even more stable than all the new-fashioned stuff nowadays. I have one AWK monster script running since 1992 unchanged in a productive environment, without ever encountering any problems. No need to change anything after countless system upgrades. A dream within a dream. In the same time PHP would have released in ten new major releases and certainly wouldn't be no longer compatible with itself.


In time of HTML entities, URL encoding and XML you quickly run unto the limits of AWK and it's becoming a very hard job to use AWK in this new context. Ever tried to URL decode or encode some string within AWK? Or process an XML file?


Since PHP 5 the command line interface of PHP offers the wonderful options -R with its siblings -B and -E. With this three fellows, you can run PHP in a quasi AWK mode: -R for a line by line processing of the input stream, the option -B as an equivalent for AWK's BEGIN and -E for AWK's END.

A small example

An example as old as the world: counting lines with AWK.

# time awk 'BEGIN {s=0} {s++} END {print s}' access_log
0.148u 0.040s 0:00.17

The same in awkish PHP:

# time php -B '$s=0;' -R '$s++;' -E 'print "$s\\n";' < access_log
1.064u 0.044s 0:01.20

Yes, of course one would use wc in the real world:

# time wc -l access_log
327970 access_log
0.036u 0.048s 0:00.07

The time command in this examples shows the big drawback of PHP: In comparison to AWK it's really slow. But this is understandable, because PHP is far more complex than AWK and PHP was made for web programming and not as a sysadmin's power tool. Always use a tool for the job it was designed to do. On the other hand PHP has all the functions you absolutely need in today's work environments. (Please don't mention XMLgawk or WebAWK now.)

But to be honest: PHP is only a small blink in the history of the Internet. AWK is the A and O, the beginning and the end, the first and the last. Aho, Weinberger and Kernighan. You're my trinity.

Monday Sep 14, 2009

PHP Unconference 2009


Last weekend Germany's most intellectual elite of PHP ubergeeks came together for two days full of interesting and surprisingly entertaining sessions and discussions. Although it was a very informal and student-friendly event (just drop an eye on the picture above), I was pleasantly surprised by the high level and quality of this event. Yes, events like unconferences, barcamps or BoFs become serious competitors for all so-called professional conferences.

I was also very surprised by the high level of professionals and enterprises working with PHP and today I'm more confident than ever that PHP is coming to the enterprise in a big way over the next few years. I can strongly advise IT strategists to start learning about the Personal Home Page tools.

Duke, be aware of the elephant.

Thursday Sep 10, 2009

How to Live Without Magic Quotes

In PHP 5.3 the magic quotes feature has been deprecated and with PHP 6 magic quotes will be removed completely. There are good reasons why magic quotes are bad, but how to live without?

I think the most promising solution is to (finally) start using MySQLi instead of PHP's classic MySQL extension. It's not as easy and straightforward to use as the classic one, but it's easier and probably more secure than to fight SQL injections by ones own hands.

Thursday Sep 03, 2009

PHP Unconference in Hamburg

Yippee, finally found the time to book train and hotel for the upcoming PHP Unconference on September 12th and 13th in Hamburg. Looking forward to meet old friends, talk about bleeding edge technologies and have one or two beers.

Tuesday Sep 01, 2009

Snow Leopard with PHP 5.3

Crazy stuff: The new Mac OS X Snow Leopard 10.6 ships PHP 5.3.

Of course with the usual annoyances:

[sunnyside:~] oswald% echo '<? strftime(0) ?>' | php
Warning: strftime(): It is not safe to rely on the system's timezone settings. You are 
\*required\* to use the date.timezone setting or the date_default_timezone_set() function. 
In case you used any of those methods and you are still getting this warning, you most 
likely misspelled the timezone identifier. We selected 'Europe/Berlin' for 'CEST/2.0/DST' 
instead in /Users/oswald/- on line 1

Looking forward how the Mac community will respond to that.

Thursday Aug 27, 2009

PHP 5.3 and osCommerce

Over the last weeks I got many emails from users complaining about problems running their PHP apps on PHP 5.3. Todays example is osCommerce 2.2:
Deprecated: Function eregi() is deprecated in /opt/lampp/htdocs/oscommerce/catalog/admin/includes/classes/language.php on line 87
The answer in this case is quite simple: the eregi() function as the whole ereg extension got deprecated with PHP 5.3 (see and all PHP apps using this handy functions now need to update their code to use PCRE functions like preg_match().

To temporary work around this issue you may lower the error reporting level in your php.ini.


error_reporting = E_ALL | E_STRICT
error_reporting = E_ALL & ~E_DEPRECATED
With this setting PHP will prevent warnings about deprecated functions. But since in PHP 6 the ereg extension will completely removed from PHP this is only a fix of the symptom. And as Frank N. Furter teached us many years ago, it's always better to:
[So I'll] remove the cause, but not the symptom.
And there is nothing to add.

Kai 'Oswald' Seidler writes about his life as co-founder of Apache Friends, creator of XAMPP, and technology evangelist for web tier products at Sun Microsystems.


« July 2016