| 1 | #!perl -w
 | 
|---|
| 2 | # mapsymw - mapsym Watcom map files
 | 
|---|
| 3 | 
 | 
|---|
| 4 | # Copyright (c) 2007, 2008 Steven Levine and Associates, Inc.
 | 
|---|
| 5 | # All rights reserved.
 | 
|---|
| 6 | 
 | 
|---|
| 7 | # $TLIB$: $ &(#) %n - Ver %v, %f $
 | 
|---|
| 8 | # TLIB: $ $
 | 
|---|
| 9 | 
 | 
|---|
| 10 | # This program is free software licensed under the terms of the GNU
 | 
|---|
| 11 | # General Public License.  The GPL Software License can be found in
 | 
|---|
| 12 | # gnugpl2.txt or at http://www.gnu.org/licenses/licenses.html#GPL
 | 
|---|
| 13 | 
 | 
|---|
| 14 | # 02 Jul 07 SHL Baseline
 | 
|---|
| 15 | # 02 Jul 07 SHL Adapt from mapsymb.pl
 | 
|---|
| 16 | # 28 Jul 07 SHL Relax module name detect
 | 
|---|
| 17 | # 30 Jul 07 SHL Auto-trim libstdc++ symbols from libc06x maps
 | 
|---|
| 18 | # 09 Aug 07 SHL Generate dummy symbol for interior segments with no symbols
 | 
|---|
| 19 | # 08 Nov 07 SHL Drop leading keywords from function definitions
 | 
|---|
| 20 | # 14 Dec 08 SHL Ensure symbols sorted by value - some apps care
 | 
|---|
| 21 | 
 | 
|---|
| 22 | # mapsym requires each segment to have at least 1 symbol
 | 
|---|
| 23 | # mapsym requires 32 bit segments to have at least 1 symbol with offset > 65K
 | 
|---|
| 24 | # we generate dummy symbols to enforce this
 | 
|---|
| 25 | # mapsym does not understand segment 0
 | 
|---|
| 26 | # we generate Imp flags to support this
 | 
|---|
| 27 | 
 | 
|---|
| 28 | use strict;
 | 
|---|
| 29 | use warnings;
 | 
|---|
| 30 | 
 | 
|---|
| 31 | # use Package::Subpackage Options;
 | 
|---|
| 32 | use POSIX qw(strftime);
 | 
|---|
| 33 | use Getopt::Std;
 | 
|---|
| 34 | use File::Spec;
 | 
|---|
| 35 | use File::Basename;
 | 
|---|
| 36 | 
 | 
|---|
| 37 | our $g_version = '0.3';
 | 
|---|
| 38 | 
 | 
|---|
| 39 | our $g_cmdname;
 | 
|---|
| 40 | our $g_tmpdir;
 | 
|---|
| 41 | our @g_mapfiles;                        # All map files
 | 
|---|
| 42 | our $g_mapfile;                         # Current .map file name
 | 
|---|
| 43 | 
 | 
|---|
| 44 | &initialize;
 | 
|---|
| 45 | 
 | 
|---|
| 46 | our %g_opts;
 | 
|---|
| 47 | 
 | 
|---|
| 48 | &scan_args;
 | 
|---|
| 49 | 
 | 
|---|
| 50 | print "\n";
 | 
|---|
| 51 | 
 | 
|---|
| 52 | foreach $g_mapfile (@g_mapfiles) {
 | 
|---|
| 53 |   &mapsym;
 | 
|---|
| 54 | }
 | 
|---|
| 55 | 
 | 
|---|
| 56 | exit;
 | 
|---|
| 57 | 
 | 
|---|
| 58 | # end main
 | 
|---|
| 59 | 
 | 
|---|
| 60 | #=== initialize() Intialize globals ===
 | 
|---|
| 61 | 
 | 
|---|
| 62 | sub initialize {
 | 
|---|
| 63 | 
 | 
|---|
| 64 |   &set_cmd_name;
 | 
|---|
| 65 |   &get_tmp_dir;
 | 
|---|
| 66 | 
 | 
|---|
| 67 | } # initialize
 | 
|---|
| 68 | 
 | 
|---|
| 69 | #=== mapsym() Generate work file, run mapsym on work file ===
 | 
|---|
| 70 | 
 | 
|---|
| 71 | sub mapsym {
 | 
|---|
| 72 | 
 | 
|---|
| 73 |   # Isolate map file basename
 | 
|---|
| 74 |   my $mapid = basename($g_mapfile);
 | 
|---|
| 75 |   $mapid =~ s/\.[^.]*$//;               # Strip ext
 | 
|---|
| 76 |   verbose_msg("\nProcessing $mapid");
 | 
|---|
| 77 | 
 | 
|---|
| 78 |   fatal("$g_mapfile does not exist.") if ! -f $g_mapfile;
 | 
|---|
| 79 | 
 | 
|---|
| 80 |   open MAPFILE, $g_mapfile or die "open $g_mapfile $!";
 | 
|---|
| 81 | 
 | 
|---|
| 82 |   my $g_wrkfile = File::Spec->catfile($g_tmpdir, "$mapid.map");
 | 
|---|
| 83 |   unlink $g_wrkfile || die "unlink $g_wrkfile $!" if -f $g_wrkfile;
 | 
|---|
| 84 |   open WRKFILE, ">$g_wrkfile" or die "open $g_wrkfile $!";
 | 
|---|
| 85 | 
 | 
|---|
| 86 |   my $modname;
 | 
|---|
| 87 |   my $state = '';
 | 
|---|
| 88 |   my $segcnt = 0;
 | 
|---|
| 89 |   my $symcnt = 0;
 | 
|---|
| 90 |   my $is32bit;
 | 
|---|
| 91 |   my %segsinfo;
 | 
|---|
| 92 |   my %syms;
 | 
|---|
| 93 |   my $segnum;
 | 
|---|
| 94 |   my $offset;
 | 
|---|
| 95 |   my $segaddr;
 | 
|---|
| 96 | 
 | 
|---|
| 97 |   my $segfmt;
 | 
|---|
| 98 |   my $symfmt;
 | 
|---|
| 99 | 
 | 
|---|
| 100 |   while (<MAPFILE>) {
 | 
|---|
| 101 | 
 | 
|---|
| 102 |     chomp;                      # EOL
 | 
|---|
| 103 | 
 | 
|---|
| 104 |     if (/Executable Image: (\S+)\.\w+$/) {
 | 
|---|
| 105 |       $modname = $1;
 | 
|---|
| 106 |       print WRKFILE "Generated by $g_cmdname from $g_mapfile on ",
 | 
|---|
| 107 |                     strftime('%A, %B %d, %Y at %I:%M %p', localtime), "\n\n";
 | 
|---|
| 108 |       print WRKFILE " $modname\n";
 | 
|---|
| 109 |     }
 | 
|---|
| 110 | 
 | 
|---|
| 111 |     $state = 'segments'
 | 
|---|
| 112 |       if /Segment                Class          Group          Address         Size/;
 | 
|---|
| 113 | 
 | 
|---|
| 114 |     $state = 'addresses' if /Address        Symbol/;
 | 
|---|
| 115 | 
 | 
|---|
| 116 |     # Skip don't cares
 | 
|---|
| 117 |     next if /^=/;
 | 
|---|
| 118 |     next if /^ /;
 | 
|---|
| 119 |     next if /^$/;
 | 
|---|
| 120 | 
 | 
|---|
| 121 |     if ($state eq 'segments') {
 | 
|---|
| 122 |       # In
 | 
|---|
| 123 |       # Segment                Class          Group          Address         Size
 | 
|---|
| 124 |       # _TEXT16                CODE           AUTO           0001:00000000   00000068
 | 
|---|
| 125 |       # Out
 | 
|---|
| 126 |       # 0        1         2         3         4         5         6
 | 
|---|
| 127 |       # 123456789012345678901234567890123456789012345678901234567890
 | 
|---|
| 128 |       #  Start         Length     Name                   Class
 | 
|---|
| 129 |       #  0001:00000000 000000030H _MSGSEG32              CODE 32-bit
 | 
|---|
| 130 | 
 | 
|---|
| 131 |       if (/^(\w+)\s+(\w+)\s+\w+\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:xdigit:]]+)$/) {
 | 
|---|
| 132 |         my $segname = $1;
 | 
|---|
| 133 |         my $class = $2;
 | 
|---|
| 134 |         $segnum = $3;                   # Has leading 0's
 | 
|---|
| 135 |         $offset = $4;
 | 
|---|
| 136 |         my $seglen = $5;
 | 
|---|
| 137 | 
 | 
|---|
| 138 |         $segaddr = "$segnum:$offset";
 | 
|---|
| 139 | 
 | 
|---|
| 140 |         if (!$segcnt) {
 | 
|---|
| 141 |           # First segment - determine address size (16/32 bit)
 | 
|---|
| 142 |           $is32bit = length($offset) == 8;
 | 
|---|
| 143 |           # Output title
 | 
|---|
| 144 |           print WRKFILE "\n";
 | 
|---|
| 145 |           if ($is32bit) {
 | 
|---|
| 146 |             print WRKFILE " Start         Length     Name                   Class\n";
 | 
|---|
| 147 |             $segfmt = " %13s 0%8sH %-22s %s\n";
 | 
|---|
| 148 |             $symfmt = " %13s  %3s  %s\n";
 | 
|---|
| 149 |           } else {
 | 
|---|
| 150 |             print WRKFILE " Start     Length Name                   Class\n";
 | 
|---|
| 151 |             $segfmt = " %9s 0%4sH %-22s %s\n";
 | 
|---|
| 152 |             $symfmt = " %9s  %3s  %s\n";
 | 
|---|
| 153 |           }
 | 
|---|
| 154 |         }
 | 
|---|
| 155 | 
 | 
|---|
| 156 |         $seglen = substr($5, -4) if !$is32bit;
 | 
|---|
| 157 | 
 | 
|---|
| 158 |         printf WRKFILE $segfmt, $segaddr, $seglen, $segname, $class;
 | 
|---|
| 159 |         $segcnt++;
 | 
|---|
| 160 |       }
 | 
|---|
| 161 |     } # if segments
 | 
|---|
| 162 | 
 | 
|---|
| 163 |     if ($state eq 'addresses') {
 | 
|---|
| 164 |       # In
 | 
|---|
| 165 |       # Address        Symbol
 | 
|---|
| 166 |       # 0002:0004ae46+ ArcTextProc
 | 
|---|
| 167 |       # Out
 | 
|---|
| 168 |       # 0        1         2         3         4         5         6
 | 
|---|
| 169 |       # 123456789012345678901234567890123456789012345678901234567890
 | 
|---|
| 170 |       #   Address         Publics by Value
 | 
|---|
| 171 |       #  0000:00000000  Imp  WinEmptyClipbrd      (PMWIN.733)
 | 
|---|
| 172 |       #  0002:0001ED40       __towlower_dummy
 | 
|---|
| 173 |       if (/^([[:xdigit:]]+):([[:xdigit:]]+)[+*]?\s+(\w+)$/) {
 | 
|---|
| 174 |         $segnum = $1;
 | 
|---|
| 175 |         $offset = $2;
 | 
|---|
| 176 |         my $sym = $3;
 | 
|---|
| 177 | 
 | 
|---|
| 178 |         my $seginfo;
 | 
|---|
| 179 |         if (defined($segsinfo{$1})) {
 | 
|---|
| 180 |           $seginfo = $segsinfo{$1};
 | 
|---|
| 181 |         }
 | 
|---|
| 182 |         else {
 | 
|---|
| 183 |           $seginfo = {max_offset => 0,
 | 
|---|
| 184 |                       symcnt => 0};
 | 
|---|
| 185 |         }
 | 
|---|
| 186 | 
 | 
|---|
| 187 |         my $n = hex $offset;
 | 
|---|
| 188 |         # Remember max symbol offset
 | 
|---|
| 189 |         $seginfo->{max_offset} = $n if $n > $seginfo->{max_offset};
 | 
|---|
| 190 |         $seginfo->{symcnt}++;
 | 
|---|
| 191 | 
 | 
|---|
| 192 |         $segsinfo{$1} = $seginfo;
 | 
|---|
| 193 | 
 | 
|---|
| 194 |         $segaddr = "$segnum:$offset";
 | 
|---|
| 195 | 
 | 
|---|
| 196 |         # Convert C++ symbols to something mapsym will accept
 | 
|---|
| 197 | 
 | 
|---|
| 198 |         $_ = $sym;
 | 
|---|
| 199 | 
 | 
|---|
| 200 |         # s/\bIdle\b/    /;     # Drop Idle keyword
 | 
|---|
| 201 |         s/\(.*\).*$//;          # Drop (... tails
 | 
|---|
| 202 | 
 | 
|---|
| 203 |         s/::~/_x/;              # Replace ::~ with _x
 | 
|---|
| 204 |         s/::/_/;                # Replace :: with _
 | 
|---|
| 205 | 
 | 
|---|
| 206 |         s/[<,]/_/g;             # Replace < and , with _
 | 
|---|
| 207 |         s/[>]//g;               # Replace > with nothing
 | 
|---|
| 208 |         s/[\[\]]//g;            # Replace [] with nothing
 | 
|---|
| 209 |         s/_*$//;                # Drop trailing _
 | 
|---|
| 210 |         s/\W+\w//;              # Drop leading keywords (including Idle)
 | 
|---|
| 211 | 
 | 
|---|
| 212 |         # Drop leading and trailing _ to match source code
 | 
|---|
| 213 | 
 | 
|---|
| 214 |         s/^_//;                 # Drop leading _ (cdecl)
 | 
|---|
| 215 |         s/_$//;                 # Drop trailing _ (watcall)
 | 
|---|
| 216 | 
 | 
|---|
| 217 |         # Prune some libc symbols to avoid mapsym overflows
 | 
|---|
| 218 |         if ($mapid =~ /libc06/) {
 | 
|---|
| 219 |           # 0001:000b73e0  __ZNSt7codecvtIcc11__mbstate_tEC2Ej
 | 
|---|
| 220 |           # next if / [0-9A-F]{4}:[0-9A-F]{8} {7}S/;
 | 
|---|
| 221 |           next if /\b__Z/;              # Prune libstdc++
 | 
|---|
| 222 |         }
 | 
|---|
| 223 | 
 | 
|---|
| 224 |         if (!$symcnt) {
 | 
|---|
| 225 |           # First symbol - output title
 | 
|---|
| 226 |           print WRKFILE "\n";
 | 
|---|
| 227 |           if ($is32bit) {
 | 
|---|
| 228 |             print WRKFILE "  Address         Publics by Value\n";
 | 
|---|
| 229 |           } else {
 | 
|---|
| 230 |             print WRKFILE "  Address     Publics by Value\n";
 | 
|---|
| 231 |           }
 | 
|---|
| 232 |         }
 | 
|---|
| 233 | 
 | 
|---|
| 234 |         $syms{$segaddr} = $_;
 | 
|---|
| 235 | 
 | 
|---|
| 236 |         $symcnt++;
 | 
|---|
| 237 |       }
 | 
|---|
| 238 |     } # if addresses
 | 
|---|
| 239 | 
 | 
|---|
| 240 |   } # while lines
 | 
|---|
| 241 | 
 | 
|---|
| 242 |   close MAPFILE;
 | 
|---|
| 243 | 
 | 
|---|
| 244 |   # Generate dummy symbols as needed
 | 
|---|
| 245 | 
 | 
|---|
| 246 |   my @keys = sort keys %segsinfo;
 | 
|---|
| 247 |   if (@keys) {
 | 
|---|
| 248 |     my $maxseg = pop @keys;
 | 
|---|
| 249 |     @keys = '0000'..$maxseg;
 | 
|---|
| 250 |   }
 | 
|---|
| 251 | 
 | 
|---|
| 252 |   foreach $segnum (@keys) {
 | 
|---|
| 253 |     if ($segnum != 0) {
 | 
|---|
| 254 |       my $seginfo;
 | 
|---|
| 255 |       if (defined($segsinfo{$segnum})) {
 | 
|---|
| 256 |         $seginfo = $segsinfo{$segnum};
 | 
|---|
| 257 |       }
 | 
|---|
| 258 |       else {
 | 
|---|
| 259 |         $seginfo = {max_offset => 0,
 | 
|---|
| 260 |                     symcnt => 0};
 | 
|---|
| 261 |       }
 | 
|---|
| 262 |       if ($seginfo->{symcnt} == 0) {
 | 
|---|
| 263 |         warn "Segment $segnum has no symbols - generating dummy symbol\n";
 | 
|---|
| 264 |         $_ = "Seg${segnum}_dummy";
 | 
|---|
| 265 |         if ($is32bit) {
 | 
|---|
| 266 |           $segaddr = "$segnum:00010000";
 | 
|---|
| 267 |         } else {
 | 
|---|
| 268 |           $segaddr = "$segnum:0000";
 | 
|---|
| 269 |         }
 | 
|---|
| 270 |         $syms{$segaddr} = $_;
 | 
|---|
| 271 |         $symcnt++;
 | 
|---|
| 272 |       } elsif ($is32bit && $seginfo->{max_offset} < 0x10000) {
 | 
|---|
| 273 |         warn "32 bit segment $segnum is smaller than 64K - generating dummy symbol\n";
 | 
|---|
| 274 |         $_ = "Seg${segnum}_dummy";
 | 
|---|
| 275 |         $segaddr = "$segnum:00010000";
 | 
|---|
| 276 |         $syms{$segaddr} = $_;
 | 
|---|
| 277 |         $symcnt++;
 | 
|---|
| 278 |       }
 | 
|---|
| 279 |     }
 | 
|---|
| 280 |   } # foreach
 | 
|---|
| 281 | 
 | 
|---|
| 282 |   @keys = sort keys %syms;
 | 
|---|
| 283 |   foreach $segaddr (@keys) {
 | 
|---|
| 284 |     my $sym = $syms{$segaddr};
 | 
|---|
| 285 |     my $imp = substr($segaddr, 0, 4) eq '0000' ? 'Imp' : '';
 | 
|---|
| 286 |     printf WRKFILE $symfmt, $segaddr, $imp, $sym;
 | 
|---|
| 287 |   }
 | 
|---|
| 288 | 
 | 
|---|
| 289 |   close WRKFILE;
 | 
|---|
| 290 | 
 | 
|---|
| 291 |   die "Can not locate module name.  $g_mapfile is probably not a Watcom map file\n" if !defined($modname);
 | 
|---|
| 292 | 
 | 
|---|
| 293 |   my $symfile = "$mapid.sym";
 | 
|---|
| 294 |   unlink $symfile || die "unlink $symfile $!" if -f $symfile;
 | 
|---|
| 295 | 
 | 
|---|
| 296 |   warn "Processed $segcnt segments and $symcnt symbols for $modname\n";
 | 
|---|
| 297 | 
 | 
|---|
| 298 |   system("mapsym $g_wrkfile");
 | 
|---|
| 299 | 
 | 
|---|
| 300 | } # mapsym
 | 
|---|
| 301 | 
 | 
|---|
| 302 | #=== scan_args(cmdLine) Scan command line ===
 | 
|---|
| 303 | 
 | 
|---|
| 304 | sub scan_args {
 | 
|---|
| 305 | 
 | 
|---|
| 306 |   getopts('dhtvV', \%g_opts) || &usage;
 | 
|---|
| 307 | 
 | 
|---|
| 308 |   &help if $g_opts{h};
 | 
|---|
| 309 | 
 | 
|---|
| 310 |   if ($g_opts{V}) {
 | 
|---|
| 311 |     print "$g_cmdname v$g_version";
 | 
|---|
| 312 |     exit;
 | 
|---|
| 313 |   }
 | 
|---|
| 314 | 
 | 
|---|
| 315 |   my $arg;
 | 
|---|
| 316 | 
 | 
|---|
| 317 |   for $arg (@ARGV) {
 | 
|---|
| 318 |     my @maps = glob($arg);
 | 
|---|
| 319 |     usage("File $arg not found") if @maps == 0;
 | 
|---|
| 320 |     push @g_mapfiles, @maps;
 | 
|---|
| 321 |   } # for arg
 | 
|---|
| 322 | 
 | 
|---|
| 323 | } # scan_args
 | 
|---|
| 324 | 
 | 
|---|
| 325 | #=== help() Display scan_args usage help exit routine ===
 | 
|---|
| 326 | 
 | 
|---|
| 327 | sub help {
 | 
|---|
| 328 | 
 | 
|---|
| 329 |   print <<EOD;
 | 
|---|
| 330 | Generate .sym file for Watcom map files.
 | 
|---|
| 331 | Generates temporary map file reformatted for mapsym and
 | 
|---|
| 332 | invokes mapsym to process this map file.
 | 
|---|
| 333 | 
 | 
|---|
| 334 | Usage: $g_cmdname [-d] [-h] [-v] [-V] mapfile...
 | 
|---|
| 335 |  -d      Display debug messages
 | 
|---|
| 336 |  -h      Display this message
 | 
|---|
| 337 |  -v      Display progress messages
 | 
|---|
| 338 |  -V      Display version
 | 
|---|
| 339 | 
 | 
|---|
| 340 |  mapfile List of map files to process
 | 
|---|
| 341 | EOD
 | 
|---|
| 342 | 
 | 
|---|
| 343 |   exit 255;
 | 
|---|
| 344 | 
 | 
|---|
| 345 | } # help
 | 
|---|
| 346 | 
 | 
|---|
| 347 | #=== usage(message) Report Scanargs usage error exit routine ===
 | 
|---|
| 348 | 
 | 
|---|
| 349 | sub usage {
 | 
|---|
| 350 | 
 | 
|---|
| 351 |   my $msg = shift;
 | 
|---|
| 352 |   print "\n$msg\n" if $msg;
 | 
|---|
| 353 | print <<EOD;
 | 
|---|
| 354 | 
 | 
|---|
| 355 | Usage: $g_cmdname [-d] [-h] [-v] [-V] mapfile...
 | 
|---|
| 356 | EOD
 | 
|---|
| 357 |   exit 255;
 | 
|---|
| 358 | 
 | 
|---|
| 359 | } # usage
 | 
|---|
| 360 | 
 | 
|---|
| 361 | #==========================================================================
 | 
|---|
| 362 | #=== SkelFunc standards - Delete unused - Move modified above this mark ===
 | 
|---|
| 363 | #==========================================================================
 | 
|---|
| 364 | 
 | 
|---|
| 365 | #=== verbose_msg(message) Display message if verbose ===
 | 
|---|
| 366 | 
 | 
|---|
| 367 | sub verbose_msg {
 | 
|---|
| 368 |   if ($g_opts{v}) {
 | 
|---|
| 369 |     my $msg = shift;
 | 
|---|
| 370 |     if (defined $msg) {
 | 
|---|
| 371 |       print STDOUT "$msg\n";
 | 
|---|
| 372 |     } else {
 | 
|---|
| 373 |       print STDOUT "\n";
 | 
|---|
| 374 |     }
 | 
|---|
| 375 |   }
 | 
|---|
| 376 | } # verbose_msg
 | 
|---|
| 377 | 
 | 
|---|
| 378 | #==========================================================================
 | 
|---|
| 379 | #=== SkelPerl standards - Delete unused - Move modified above this mark ===
 | 
|---|
| 380 | #==========================================================================
 | 
|---|
| 381 | 
 | 
|---|
| 382 | #=== fatal(message) Report fatal error and exit ===
 | 
|---|
| 383 | 
 | 
|---|
| 384 | sub fatal {
 | 
|---|
| 385 |   my $msg = shift;
 | 
|---|
| 386 |   print "\n";
 | 
|---|
| 387 |   print STDERR "$g_cmdname: $msg\a\n";
 | 
|---|
| 388 |   exit 254;
 | 
|---|
| 389 | 
 | 
|---|
| 390 | } # fatal
 | 
|---|
| 391 | 
 | 
|---|
| 392 | #=== set_cmd_name() Set $g_cmdname to script name less path and extension ===
 | 
|---|
| 393 | 
 | 
|---|
| 394 | sub set_cmd_name {
 | 
|---|
| 395 |   $g_cmdname = $0;
 | 
|---|
| 396 |   $g_cmdname = basename($g_cmdname);
 | 
|---|
| 397 |   $g_cmdname =~ s/\.[^.]*$//;           # Chop ext
 | 
|---|
| 398 | 
 | 
|---|
| 399 | } # set_cmd_name
 | 
|---|
| 400 | 
 | 
|---|
| 401 | #=== get_tmp_dir() Get TMP dir name with trailing backslash, set Gbl. ===
 | 
|---|
| 402 | 
 | 
|---|
| 403 | sub get_tmp_dir {
 | 
|---|
| 404 | 
 | 
|---|
| 405 |   $g_tmpdir = File::Spec->tmpdir();
 | 
|---|
| 406 |   die "Need to have TMP or TMPDIR or TEMP defined" unless $g_tmpdir;
 | 
|---|
| 407 | 
 | 
|---|
| 408 | } # get_tmp_dir
 | 
|---|
| 409 | 
 | 
|---|
| 410 | # The end
 | 
|---|