Subversion Repositories SvarDOS

Rev

Rev 1697 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1697 Rev 1801
1
<?php /*
1
<?php /*
2
 
2
 
3
  SvarDOS repo index builder
3
  SvarDOS repo index builder
4
  Copyright (C) Mateusz Viste 2012-2024
4
  Copyright (C) Mateusz Viste 2012-2024
5
 
5
 
6
  buildidx computes an index json file for the SvarDOS repository.
6
  buildidx computes an index json file for the SvarDOS repository.
7
  it must be executed pointing to a directory that stores packages (*.svp)
7
  it must be executed pointing to a directory that stores packages (*.svp)
8
  files. buildidx will generate the index file and save it into the package
8
  files. buildidx will generate the index file and save it into the package
9
  repository.
9
  repository.
10
 
10
 
11
  requires php-zip
11
  requires php-zip
12
 
12
 
-
 
13
  10 mar 2024: support for "url" in LSM files
13
  01 feb 2024: computes the "latest" collection of symlinks
14
  01 feb 2024: computes the "latest" collection of symlinks
14
  24 nov 2023: SVED included in the MS-DOS compat list instead of EDIT + support for "release xyz" versions
15
  24 nov 2023: SVED included in the MS-DOS compat list instead of EDIT + support for "release xyz" versions
15
  25 aug 2023: validation of the hwreq section in LSM files
16
  25 aug 2023: validation of the hwreq section in LSM files
16
  24 aug 2023: load hwreq data from LSM and store them in the json index + skip the '.svn' dir
17
  24 aug 2023: load hwreq data from LSM and store them in the json index + skip the '.svn' dir
17
  30 jun 2023: adapted for new CORE packages location (../packages-core)
18
  30 jun 2023: adapted for new CORE packages location (../packages-core)
18
  28 feb 2022: svarcom allowed to have a COMMAND.COM file without subdirectory
19
  28 feb 2022: svarcom allowed to have a COMMAND.COM file without subdirectory
19
  24 feb 2022: added hardcoded hack to translate version 'x.xx' to '0.44' (NESticle)
20
  24 feb 2022: added hardcoded hack to translate version 'x.xx' to '0.44' (NESticle)
20
  23 feb 2022: basic validation of source archives (not empty + matches an existing svp file)
21
  23 feb 2022: basic validation of source archives (not empty + matches an existing svp file)
21
  21 feb 2022: buildidx collects categories looking at the dir layout of each package + improved version string parsing (replaced version_compare call by dos_version_compare)
22
  21 feb 2022: buildidx collects categories looking at the dir layout of each package + improved version string parsing (replaced version_compare call by dos_version_compare)
22
  17 feb 2022: checking for non-8+3 filenames in packages and duplicates + devload no longer part of CORE
23
  17 feb 2022: checking for non-8+3 filenames in packages and duplicates + devload no longer part of CORE
23
  16 feb 2022: added warning about overlong version strings and wild files location
24
  16 feb 2022: added warning about overlong version strings and wild files location
24
  15 feb 2022: index is generated as json, contains all filenames and alt versions
25
  15 feb 2022: index is generated as json, contains all filenames and alt versions
25
  14 feb 2022: packages are expected to have the *.svp extension
26
  14 feb 2022: packages are expected to have the *.svp extension
26
  12 feb 2022: skip source packages from being processed (*.src.zip)
27
  12 feb 2022: skip source packages from being processed (*.src.zip)
27
  20 jan 2022: rewritten the code from ANSI C to PHP for easier maintenance
28
  20 jan 2022: rewritten the code from ANSI C to PHP for easier maintenance
28
  13 feb 2021: 'title' LSM field is no longer looked after
29
  13 feb 2021: 'title' LSM field is no longer looked after
29
  11 feb 2021: lsm headers are no longer checked, so it is compatible with the simpler lsm format used by SvarDOS
30
  11 feb 2021: lsm headers are no longer checked, so it is compatible with the simpler lsm format used by SvarDOS
30
  13 jan 2021: removed the identification line, changed CRC32 to bsum, not creating the listing.txt file and stopped compressing index
31
  13 jan 2021: removed the identification line, changed CRC32 to bsum, not creating the listing.txt file and stopped compressing index
31
  23 apr 2017: uncompressed index is no longer created, added CRC32 of zib (bin only) files, if present
32
  23 apr 2017: uncompressed index is no longer created, added CRC32 of zib (bin only) files, if present
32
  28 aug 2016: listing.txt is always written inside the repo dir (instead of inside current dir)
33
  28 aug 2016: listing.txt is always written inside the repo dir (instead of inside current dir)
33
  27 aug 2016: accepting full paths to repos (starting with /...)
34
  27 aug 2016: accepting full paths to repos (starting with /...)
34
  07 dec 2013: rewritten buildidx in ANSI C89
35
  07 dec 2013: rewritten buildidx in ANSI C89
35
  19 aug 2013: add a compressed version of the index file to repos (index.gz)
36
  19 aug 2013: add a compressed version of the index file to repos (index.gz)
36
  22 jul 2013: creating a listing.txt file with list of packages
37
  22 jul 2013: creating a listing.txt file with list of packages
37
  18 jul 2013: writing the number of packaged into the first line of the lst file
38
  18 jul 2013: writing the number of packaged into the first line of the lst file
38
  11 jul 2013: added a switch to 7za to make it case insensitive when extracting lsm files
39
  11 jul 2013: added a switch to 7za to make it case insensitive when extracting lsm files
39
  10 jul 2013: changed unzip calls to 7za (to handle cases when appinfo is compressed with lzma)
40
  10 jul 2013: changed unzip calls to 7za (to handle cases when appinfo is compressed with lzma)
40
  04 feb 2013: added CRC32 support
41
  04 feb 2013: added CRC32 support
41
  22 sep 2012: forked 1st version from FDUPDATE builder
42
  22 sep 2012: forked 1st version from FDUPDATE builder
42
*/
43
*/
43
 
44
 
44
$PVER = "20230825";
45
$PVER = "20240310";
45
 
46
 
46
 
47
 
47
// computes the BSD sum of a file and returns it
48
// computes the BSD sum of a file and returns it
48
function file2bsum($fname) {
49
function file2bsum($fname) {
49
  $result = 0;
50
  $result = 0;
50
 
51
 
51
  $fd = fopen($fname, 'rb');
52
  $fd = fopen($fname, 'rb');
52
  if ($fd === false) return(0);
53
  if ($fd === false) return(0);
53
 
54
 
54
  while (!feof($fd)) {
55
  while (!feof($fd)) {
55
 
56
 
56
    $buff = fread($fd, 1024 * 1024);
57
    $buff = fread($fd, 1024 * 1024);
57
 
58
 
58
    $slen = strlen($buff);
59
    $slen = strlen($buff);
59
    for ($i = 0; $i < $slen; $i++) {
60
    for ($i = 0; $i < $slen; $i++) {
60
      // rotr
61
      // rotr
61
      $result = ($result >> 1) | ($result << 15);
62
      $result = ($result >> 1) | ($result << 15);
62
      // add and truncate to 16 bits
63
      // add and truncate to 16 bits
63
      $result += ord($buff[$i]);
64
      $result += ord($buff[$i]);
64
      $result &= 0xffff;
65
      $result &= 0xffff;
65
    }
66
    }
66
  }
67
  }
67
 
68
 
68
  fclose($fd);
69
  fclose($fd);
69
  return($result);
70
  return($result);
70
}
71
}
71
 
72
 
72
 
73
 
73
// translates a version string into a array of integer values.
74
// translates a version string into a array of integer values.
74
// Accepted formats follow:
75
// Accepted formats follow:
75
//    300.12.1
76
//    300.12.1
76
//    1
77
//    1
77
//    12.2.34.2-4.5
78
//    12.2.34.2-4.5
78
//    1.2c
79
//    1.2c
79
//    1.01 beta+3
80
//    1.01 beta+3
80
//    2013-12-31
81
//    2013-12-31
81
//    20220222 alpha
82
//    20220222 alpha
82
function vertoarr($verstr) {
83
function vertoarr($verstr) {
83
  $subver = array(0,0,0,0);
84
  $subver = array(0,0,0,0);
84
 
85
 
85
  // switch string to lcase for easier processing and trim any leading or trailing white spaces
86
  // switch string to lcase for easier processing and trim any leading or trailing white spaces
86
  $verstr = strtolower(trim($verstr));
87
  $verstr = strtolower(trim($verstr));
87
 
88
 
88
  // Special hack for E. C. Masloch's lDebug. lDebug's versions are identifying as "releases" and wish to be recognized as such. If the version string starts with "release " I remove this word and continue.
89
  // Special hack for E. C. Masloch's lDebug. lDebug's versions are identifying as "releases" and wish to be recognized as such. If the version string starts with "release " I remove this word and continue.
89
  if (preg_match('/^release /', $verstr)) $verstr = substr($verstr, 8);
90
  if (preg_match('/^release /', $verstr)) $verstr = substr($verstr, 8);
90
 
91
 
91
  // replace all '-' and '/' characters to '.' (uniformization of sub-version parts delimiters)
92
  // replace all '-' and '/' characters to '.' (uniformization of sub-version parts delimiters)
92
  $verstr = strtr($verstr, '-/', '..');
93
  $verstr = strtr($verstr, '-/', '..');
93
 
94
 
94
  // is there a subversion value? (for example "+4" in "1.05+4")
95
  // is there a subversion value? (for example "+4" in "1.05+4")
95
  $i = strrpos($verstr, '+', 1);
96
  $i = strrpos($verstr, '+', 1);
96
  if ($i !== false) {
97
  if ($i !== false) {
97
    // validate the svar-version is a proper integer
98
    // validate the svar-version is a proper integer
98
    $svarver = substr($verstr, $i + 1);
99
    $svarver = substr($verstr, $i + 1);
99
    if (! preg_match('/[1-9][0-9]*/', $svarver)) {
100
    if (! preg_match('/[1-9][0-9]*/', $svarver)) {
100
      return(false);
101
      return(false);
101
    }
102
    }
102
    $subver[3] = intval($svarver); // set the +rev as a very minor item
103
    $subver[3] = intval($svarver); // set the +rev as a very minor item
103
    $verstr = substr($verstr, 0, $i);
104
    $verstr = substr($verstr, 0, $i);
104
  }
105
  }
105
 
106
 
106
  // NESticls hack: version "x.xx" is translated to "0.44"... that sucks but that's how it is.
107
  // NESticls hack: version "x.xx" is translated to "0.44"... that sucks but that's how it is.
107
  // ref: https://web.archive.org/web/20070205074631/http://www.zophar.net/NESticle/
108
  // ref: https://web.archive.org/web/20070205074631/http://www.zophar.net/NESticle/
108
  if ($verstr == 'x.xx') $verstr = '0.44';
109
  if ($verstr == 'x.xx') $verstr = '0.44';
109
 
110
 
110
  // beta reordering: convert "beta 0.95" to "0.95 beta"
111
  // beta reordering: convert "beta 0.95" to "0.95 beta"
111
  if (preg_match('/^beta /', $verstr)) $verstr = substr($verstr, 5) . ' beta';
112
  if (preg_match('/^beta /', $verstr)) $verstr = substr($verstr, 5) . ' beta';
112
 
113
 
113
  // any occurence of alpha,beta,gamma,delta etc preceded by a digit should have a space separator added
114
  // any occurence of alpha,beta,gamma,delta etc preceded by a digit should have a space separator added
114
  // example: "2.6.0pre9" becomes "2.6.0 pre9"
115
  // example: "2.6.0pre9" becomes "2.6.0 pre9"
115
  $verstr = preg_replace('/([0-9])(alpha|beta|gamma|delta|pre|rc|patch)/', '$1 $2', $verstr);
116
  $verstr = preg_replace('/([0-9])(alpha|beta|gamma|delta|pre|rc|patch)/', '$1 $2', $verstr);
116
 
117
 
117
  // same as above, but this time adding a trailing space separator
118
  // same as above, but this time adding a trailing space separator
118
  // example: "2.6.0 pre9" becomes "2.6.0 pre 9"
119
  // example: "2.6.0 pre9" becomes "2.6.0 pre 9"
119
  $verstr = preg_replace('/(alpha|beta|gamma|delta|pre|rc|patch)([0-9])/', '$1 $2', $verstr);
120
  $verstr = preg_replace('/(alpha|beta|gamma|delta|pre|rc|patch)([0-9])/', '$1 $2', $verstr);
120
 
121
 
121
  // is the version ending with ' alpha', 'beta', etc?
122
  // is the version ending with ' alpha', 'beta', etc?
122
  if (preg_match('/ (alpha|beta|gamma|delta|pre|rc|patch)( [0-9]{1,4}){0,1}$/', $verstr)) {
123
  if (preg_match('/ (alpha|beta|gamma|delta|pre|rc|patch)( [0-9]{1,4}){0,1}$/', $verstr)) {
123
    // if there is a trailing beta-number, process it first
124
    // if there is a trailing beta-number, process it first
124
    if (preg_match('/ [0-9]{1,4}$/', $verstr)) {
125
    if (preg_match('/ [0-9]{1,4}$/', $verstr)) {
125
      $i = strrpos($verstr, ' ');
126
      $i = strrpos($verstr, ' ');
126
      $subver[2] = intval(substr($verstr, $i + 1));
127
      $subver[2] = intval(substr($verstr, $i + 1));
127
      $verstr = trim(substr($verstr, 0, $i));
128
      $verstr = trim(substr($verstr, 0, $i));
128
    }
129
    }
129
    $i = strrpos($verstr, ' ');
130
    $i = strrpos($verstr, ' ');
130
    $greek = substr($verstr, $i + 1);
131
    $greek = substr($verstr, $i + 1);
131
    $verstr = trim(substr($verstr, 0, $i));
132
    $verstr = trim(substr($verstr, 0, $i));
132
    if ($greek == 'alpha') {
133
    if ($greek == 'alpha') {
133
      $subver[1] = 1;
134
      $subver[1] = 1;
134
    } else if ($greek == 'beta') {
135
    } else if ($greek == 'beta') {
135
      $subver[1] = 2;
136
      $subver[1] = 2;
136
    } else if ($greek == 'gamma') {
137
    } else if ($greek == 'gamma') {
137
      $subver[1] = 3;
138
      $subver[1] = 3;
138
    } else if ($greek == 'delta') {
139
    } else if ($greek == 'delta') {
139
      $subver[1] = 4;
140
      $subver[1] = 4;
140
    } else if ($greek == 'pre') {
141
    } else if ($greek == 'pre') {
141
      $subver[1] = 5;
142
      $subver[1] = 5;
142
    } else if ($greek == 'rc') {
143
    } else if ($greek == 'rc') {
143
      $subver[1] = 6;
144
      $subver[1] = 6;
144
    } else if ($greek == 'patch') { // this is a POST-release version, as opposed to all above that are PRE-release versions
145
    } else if ($greek == 'patch') { // this is a POST-release version, as opposed to all above that are PRE-release versions
145
      $subver[1] = 99;
146
      $subver[1] = 99;
146
    } else {
147
    } else {
147
      return(false);
148
      return(false);
148
    }
149
    }
149
  } else {
150
  } else {
150
    $subver[1] = 98; // one less than the 'patch' level
151
    $subver[1] = 98; // one less than the 'patch' level
151
  }
152
  }
152
 
153
 
153
  // does the version string have a single-letter subversion? (1.0c)
154
  // does the version string have a single-letter subversion? (1.0c)
154
  if (preg_match('/[a-z]$/', $verstr)) {
155
  if (preg_match('/[a-z]$/', $verstr)) {
155
    $subver[0] = ord(substr($verstr, -1));
156
    $subver[0] = ord(substr($verstr, -1));
156
    $verstr = substr_replace($verstr, '', -1); // remove last character from string
157
    $verstr = substr_replace($verstr, '', -1); // remove last character from string
157
  }
158
  }
158
 
159
 
159
  // convert "30-jan-99", "1999-jan-30" and "30-jan-1999" versions to "30jan99" or "30jan1999"
160
  // convert "30-jan-99", "1999-jan-30" and "30-jan-1999" versions to "30jan99" or "30jan1999"
160
  // note that dashes have already been replaced by dots
161
  // note that dashes have already been replaced by dots
161
  if (preg_match('/^([0-9][0-9]){1,2}\.(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\.([0-9][0-9]){1,2}$/', $verstr)) {
162
  if (preg_match('/^([0-9][0-9]){1,2}\.(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\.([0-9][0-9]){1,2}$/', $verstr)) {
162
    $verstr = str_replace('.', '', $verstr);
163
    $verstr = str_replace('.', '', $verstr);
163
  }
164
  }
164
 
165
 
165
  // convert "2009mar17" versions to "17mar2009"
166
  // convert "2009mar17" versions to "17mar2009"
166
  if (preg_match('/^[0-9]{4}(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[0-9]{2}$/', $verstr)) {
167
  if (preg_match('/^[0-9]{4}(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[0-9]{2}$/', $verstr)) {
167
    $dy = substr($verstr, 7);
168
    $dy = substr($verstr, 7);
168
    $mo = substr($verstr, 4, 3);
169
    $mo = substr($verstr, 4, 3);
169
    $ye = substr($verstr, 0, 4);
170
    $ye = substr($verstr, 0, 4);
170
    $verstr = "{$dy}{$mo}{$ye}";
171
    $verstr = "{$dy}{$mo}{$ye}";
171
  }
172
  }
172
 
173
 
173
  // convert "30jan99" versions to 99.1.30 and "30jan1999" to 1999.1.30
174
  // convert "30jan99" versions to 99.1.30 and "30jan1999" to 1999.1.30
174
  if (preg_match('/^[0-3][0-9](jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)([0-9][0-9]){1,2}$/', $verstr)) {
175
  if (preg_match('/^[0-3][0-9](jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)([0-9][0-9]){1,2}$/', $verstr)) {
175
    $months = array('jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, 'jun' => 6, 'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' => 12);
176
    $months = array('jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, 'jun' => 6, 'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' => 12);
176
    $dy = substr($verstr, 0, 2);
177
    $dy = substr($verstr, 0, 2);
177
    $mo = $months[substr($verstr, 2, 3)];
178
    $mo = $months[substr($verstr, 2, 3)];
178
    $ye = substr($verstr, 5);
179
    $ye = substr($verstr, 5);
179
    $verstr = "{$ye}.{$mo}.{$dy}";
180
    $verstr = "{$ye}.{$mo}.{$dy}";
180
  }
181
  }
181
 
182
 
182
  // validate the format is supported, should be something no more complex than 1.05.3.33
183
  // validate the format is supported, should be something no more complex than 1.05.3.33
183
  if (! preg_match('/^[0-9][0-9.]{0,20}$/', $verstr)) {
184
  if (! preg_match('/^[0-9][0-9.]{0,20}$/', $verstr)) {
184
    return(false);
185
    return(false);
185
  }
186
  }
186
 
187
 
187
  // NOTE: a zero right after a separator and trailed with a digit (as in 1.01)
188
  // NOTE: a zero right after a separator and trailed with a digit (as in 1.01)
188
  //       has a special meaning
189
  //       has a special meaning
189
  $exploded = explode('.', $verstr);
190
  $exploded = explode('.', $verstr);
190
  if (count($exploded) > 16) {
191
  if (count($exploded) > 16) {
191
    return(false);
192
    return(false);
192
  }
193
  }
193
  $exploded[16] = $subver[0]; // a-z (1.0c)
194
  $exploded[16] = $subver[0]; // a-z (1.0c)
194
  $exploded[17] = $subver[1]; // alpha/beta/gamma/delta/rc/pre
195
  $exploded[17] = $subver[1]; // alpha/beta/gamma/delta/rc/pre
195
  $exploded[18] = $subver[2]; // alpha-beta-gamma subversion (eg. "beta 9")
196
  $exploded[18] = $subver[2]; // alpha-beta-gamma subversion (eg. "beta 9")
196
  $exploded[19] = $subver[3]; // svar-ver (1.0+5)
197
  $exploded[19] = $subver[3]; // svar-ver (1.0+5)
197
  for ($i = 0; $i < 20; $i++) if (empty($exploded[$i])) $exploded[$i] = '0';
198
  for ($i = 0; $i < 20; $i++) if (empty($exploded[$i])) $exploded[$i] = '0';
198
 
199
 
199
  ksort($exploded);
200
  ksort($exploded);
200
 
201
 
201
  return($exploded);
202
  return($exploded);
202
}
203
}
203
 
204
 
204
 
205
 
205
function dos_version_compare($v1, $v2) {
206
function dos_version_compare($v1, $v2) {
206
  $v1arr = vertoarr($v1);
207
  $v1arr = vertoarr($v1);
207
  $v2arr = vertoarr($v2);
208
  $v2arr = vertoarr($v2);
208
  for ($i = 0; $i < count($v1arr); $i++) {
209
  for ($i = 0; $i < count($v1arr); $i++) {
209
    if ($v1arr[$i] > $v2arr[$i]) return(1);
210
    if ($v1arr[$i] > $v2arr[$i]) return(1);
210
    if ($v1arr[$i] < $v2arr[$i]) return(-1);
211
    if ($v1arr[$i] < $v2arr[$i]) return(-1);
211
  }
212
  }
212
  return(0);
213
  return(0);
213
}
214
}
214
 
215
 
215
 
216
 
216
// reads file fil from zip archive z and returns its content, or false on error
217
// reads file fil from zip archive z and returns its content, or false on error
217
function read_file_from_zip($z, $fil) {
218
function read_file_from_zip($z, $fil) {
218
  $zip = new ZipArchive;
219
  $zip = new ZipArchive;
219
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
220
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
220
    echo "ERROR: failed to open zip file '{$z}'\n";
221
    echo "ERROR: failed to open zip file '{$z}'\n";
221
    return(false);
222
    return(false);
222
  }
223
  }
223
 
224
 
224
  // load the appinfo/pkgname.lsm file
225
  // load the appinfo/pkgname.lsm file
225
  $res = $zip->getFromName($fil, 8192, ZipArchive::FL_NOCASE);
226
  $res = $zip->getFromName($fil, 8192, ZipArchive::FL_NOCASE);
226
 
227
 
227
  $zip->close();
228
  $zip->close();
228
  return($res);
229
  return($res);
229
}
230
}
230
 
231
 
231
 
232
 
232
function read_list_of_files_in_zip($z) {
233
function read_list_of_files_in_zip($z) {
233
  $zip = new ZipArchive;
234
  $zip = new ZipArchive;
234
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
235
  if ($zip->open($z, ZipArchive::RDONLY) !== true) {
235
    echo "ERROR: failed to open zip file '{$z}'\n";
236
    echo "ERROR: failed to open zip file '{$z}'\n";
236
    return(false);
237
    return(false);
237
  }
238
  }
238
 
239
 
239
  $res = array();
240
  $res = array();
240
  for ($i = 0; $i < $zip->numFiles; $i++) $res[] = $zip->getNameIndex($i);
241
  for ($i = 0; $i < $zip->numFiles; $i++) $res[] = $zip->getNameIndex($i);
241
 
242
 
242
  $zip->close();
243
  $zip->close();
243
  return($res);
244
  return($res);
244
}
245
}
245
 
246
 
246
 
247
 
247
// reads a LSM string and returns it in the form of an array
248
// reads a LSM string and returns it in the form of an array
248
function parse_lsm($s) {
249
function parse_lsm($s) {
249
  $res = array();
250
  $res = array();
250
  for ($l = strtok($s, "\n"); $l !== false; $l = strtok("\n")) {
251
  for ($l = strtok($s, "\n"); $l !== false; $l = strtok("\n")) {
251
    // the line is "token: value", let's find the colon
252
    // the line is "token: value", let's find the colon
252
    $colpos = strpos($l, ':');
253
    $colpos = strpos($l, ':');
253
    if (($colpos === false) || ($colpos === 0)) continue;
254
    if (($colpos === false) || ($colpos === 0)) continue;
254
    $tok = strtolower(trim(substr($l, 0, $colpos)));
255
    $tok = strtolower(trim(substr($l, 0, $colpos)));
255
    $val = trim(substr($l, $colpos + 1));
256
    $val = trim(substr($l, $colpos + 1));
256
    $res[$tok] = $val;
257
    $res[$tok] = $val;
257
  }
258
  }
258
  return($res);
259
  return($res);
259
}
260
}
260
 
261
 
261
 
262
 
262
// on PHP 8+ there is str_starts_with(), but not on PHP 7 so I use this
263
// on PHP 8+ there is str_starts_with(), but not on PHP 7 so I use this
263
function str_head_is($haystack, $needle) {
264
function str_head_is($haystack, $needle) {
264
  return strpos($haystack, $needle) === 0;
265
  return strpos($haystack, $needle) === 0;
265
}
266
}
266
 
267
 
267
 
268
 
268
// returns an array that contains CORE packages (populated from the core subdirectory in pkgdir)
269
// returns an array that contains CORE packages (populated from the core subdirectory in pkgdir)
269
function load_core_list($repodir_core) {
270
function load_core_list($repodir_core) {
270
  $res = array();
271
  $res = array();
271
 
272
 
272
  foreach (scandir($repodir_core) as $f) {
273
  foreach (scandir($repodir_core) as $f) {
273
    if (!preg_match('/\.svp$/', $f)) continue;
274
    if (!preg_match('/\.svp$/', $f)) continue;
274
    $res[] = explode('.', $f)[0];
275
    $res[] = explode('.', $f)[0];
275
  }
276
  }
276
  return($res);
277
  return($res);
277
}
278
}
278
 
279
 
279
 
280
 
280
// ***************** MAIN ROUTINE *********************************************
281
// ***************** MAIN ROUTINE *********************************************
281
 
282
 
282
//echo "SvarDOS repository index generator ver {$PVER}\n";
283
//echo "SvarDOS repository index generator ver {$PVER}\n";
283
 
284
 
284
if (($_SERVER['argc'] != 2) || ($_SERVER['argv'][1][0] == '-')) {
285
if (($_SERVER['argc'] != 2) || ($_SERVER['argv'][1][0] == '-')) {
285
  echo "usage: php buildidx.php repodir\n";
286
  echo "usage: php buildidx.php repodir\n";
286
  exit(1);
287
  exit(1);
287
}
288
}
288
 
289
 
289
$repodir = $_SERVER['argv'][1];
290
$repodir = $_SERVER['argv'][1];
290
 
291
 
291
$pkgfiles = scandir($repodir);
292
$pkgfiles = scandir($repodir);
292
$pkgcount = 0;
293
$pkgcount = 0;
293
 
294
 
294
 
295
 
295
// load the list of CORE and MSDOS_COMPAT packages
296
// load the list of CORE and MSDOS_COMPAT packages
296
 
297
 
297
$core_packages_list = load_core_list($repodir . '/../packages-core/');
298
$core_packages_list = load_core_list($repodir . '/../packages-core/');
298
$msdos_compat_list = explode(' ', 'append assign attrib callver chkdsk choice comp cpidos debug defrag deltree diskcomp diskcopy display edlin exe2bin fc fdapm fdisk find format help himemx kernel keyb label localcfg mem mirror mode more move nlsfunc print replace share shsucdx sort svarcom sved swsubst tree undelete unformat xcopy');
299
$msdos_compat_list = explode(' ', 'append assign attrib callver chkdsk choice comp cpidos debug defrag deltree diskcomp diskcopy display edlin exe2bin fc fdapm fdisk find format help himemx kernel keyb label localcfg mem mirror mode more move nlsfunc print replace share shsucdx sort svarcom sved swsubst tree undelete unformat xcopy');
299
 
300
 
300
// do a list of all svp packages with their available versions and descriptions
301
// do a list of all svp packages with their available versions and descriptions
301
 
302
 
302
$pkgdb = array();
303
$pkgdb = array();
303
foreach ($pkgfiles as $fname) {
304
foreach ($pkgfiles as $fname) {
304
 
305
 
305
  // zip files (ie. source archives)
306
  // zip files (ie. source archives)
306
  if (preg_match('/\.zip$/', $fname)) {
307
  if (preg_match('/\.zip$/', $fname)) {
307
    // the zip archive should contain at least one file
308
    // the zip archive should contain at least one file
308
    if (count(read_list_of_files_in_zip($repodir . '/' . $fname)) < 1) echo "WARNING: source archive {$fname} contains no files (either empty or corrupted)\n";
309
    if (count(read_list_of_files_in_zip($repodir . '/' . $fname)) < 1) echo "WARNING: source archive {$fname} contains no files (either empty or corrupted)\n";
309
    // check that the file relates to an existing svp package
310
    // check that the file relates to an existing svp package
310
    $svpfname = preg_replace('/zip$/', 'svp', $fname);
311
    $svpfname = preg_replace('/zip$/', 'svp', $fname);
311
    if (!file_exists($repodir . '/' . $svpfname)) echo "ERROR: orphaned source archive '{$fname}' (no matching svp file, expecting a package named '{$svpfname}')\n";
312
    if (!file_exists($repodir . '/' . $svpfname)) echo "ERROR: orphaned source archive '{$fname}' (no matching svp file, expecting a package named '{$svpfname}')\n";
312
    // that is for zip files
313
    // that is for zip files
313
    continue;
314
    continue;
314
  }
315
  }
315
 
316
 
316
  // silently skip the hidden .svn directory
317
  // silently skip the hidden .svn directory
317
  if ($fname === '.svn') continue;
318
  if ($fname === '.svn') continue;
318
 
319
 
319
  // skip (and warn about) non-svp
320
  // skip (and warn about) non-svp
320
  if (!preg_match('/\.svp$/', $fname)) {
321
  if (!preg_match('/\.svp$/', $fname)) {
321
    $okfiles = array('.', '..', '_cats.json', '_index.json', '_buildidx.log');
322
    $okfiles = array('.', '..', '_cats.json', '_index.json', '_buildidx.log');
322
    if (array_search($fname, $okfiles) !== false) continue;
323
    if (array_search($fname, $okfiles) !== false) continue;
323
    echo "WARNING: wild file '{$fname}' (this is either an useless file that should be removed, or a misnamed package or source archive)'\n";
324
    echo "WARNING: wild file '{$fname}' (this is either an useless file that should be removed, or a misnamed package or source archive)'\n";
324
    continue;
325
    continue;
325
  }
326
  }
326
 
327
 
327
  if (!preg_match('/^[a-zA-Z0-9+. _-]*\.svp$/', $fname)) {
328
  if (!preg_match('/^[a-zA-Z0-9+. _-]*\.svp$/', $fname)) {
328
    echo "ERROR: {$fname} has a very weird name\n";
329
    echo "ERROR: {$fname} has a very weird name\n";
329
    continue;
330
    continue;
330
  }
331
  }
331
 
332
 
332
  $path_parts = pathinfo($fname);
333
  $path_parts = pathinfo($fname);
333
  $pkgnam = explode('-', $path_parts['filename'])[0];
334
  $pkgnam = explode('-', $path_parts['filename'])[0];
334
  $pkgfullpath = realpath($repodir . '/' . $fname);
335
  $pkgfullpath = realpath($repodir . '/' . $fname);
335
 
336
 
336
  $lsm = read_file_from_zip($pkgfullpath, "appinfo/{$pkgnam}.lsm");
337
  $lsm = read_file_from_zip($pkgfullpath, "appinfo/{$pkgnam}.lsm");
337
  if ($lsm == false) {
338
  if ($lsm == false) {
338
    echo "ERROR: {$fname} does not contain an LSM file at the expected location\n";
339
    echo "ERROR: {$fname} does not contain an LSM file at the expected location\n";
339
    continue;
340
    continue;
340
  }
341
  }
341
  $lsmarray = parse_lsm($lsm);
342
  $lsmarray = parse_lsm($lsm);
342
  if (empty($lsmarray['version'])) {
343
  if (empty($lsmarray['version'])) {
343
    echo "ERROR: lsm file in {$fname} does not contain a version\n";
344
    echo "ERROR: lsm file in {$fname} does not contain a version\n";
344
    continue;
345
    continue;
345
  }
346
  }
346
  if (strlen($lsmarray['version']) > 16) {
347
  if (strlen($lsmarray['version']) > 16) {
347
    echo "ERROR: version string in lsm file of {$fname} is too long (16 chars max)\n";
348
    echo "ERROR: version string in lsm file of {$fname} is too long (16 chars max)\n";
348
    continue;
349
    continue;
349
  }
350
  }
350
  if (empty($lsmarray['description'])) {
351
  if (empty($lsmarray['description'])) {
351
    echo "ERROR: lsm file in {$fname} does not contain a description\n";
352
    echo "ERROR: lsm file in {$fname} does not contain a description\n";
352
    continue;
353
    continue;
353
  }
354
  }
354
 
355
 
355
  // validate the files present in the archive
356
  // validate the files present in the archive
356
  $listoffiles = read_list_of_files_in_zip($pkgfullpath);
357
  $listoffiles = read_list_of_files_in_zip($pkgfullpath);
357
  $pkgdir = $pkgnam;
358
  $pkgdir = $pkgnam;
358
 
359
 
359
  // special rule for "parent and children" packages
360
  // special rule for "parent and children" packages
360
  if (str_head_is($pkgnam, 'djgpp_')) $pkgdir = 'djgpp'; // djgpp_* packages put their files in djgpp
361
  if (str_head_is($pkgnam, 'djgpp_')) $pkgdir = 'djgpp'; // djgpp_* packages put their files in djgpp
361
  if ($pkgnam == 'fbc_help') $pkgdir = 'fbc'; // FreeBASIC help goes to the FreeBASIC dir
362
  if ($pkgnam == 'fbc_help') $pkgdir = 'fbc'; // FreeBASIC help goes to the FreeBASIC dir
362
  if ($pkgnam == 'clamdb') $pkgdir = 'clamav'; // data patterns for clamav
363
  if ($pkgnam == 'clamdb') $pkgdir = 'clamav'; // data patterns for clamav
363
 
364
 
364
  // array used to detect duplicated entries after lower-case conversion
365
  // array used to detect duplicated entries after lower-case conversion
365
  $duparr = array();
366
  $duparr = array();
366
 
367
 
367
  // will hold the list of categories that this package belongs to
368
  // will hold the list of categories that this package belongs to
368
  $catlist = array();
369
  $catlist = array();
369
 
370
 
370
  foreach ($listoffiles as $f) {
371
  foreach ($listoffiles as $f) {
371
    $f = strtolower($f);
372
    $f = strtolower($f);
372
    $path_array = explode('/', $f);
373
    $path_array = explode('/', $f);
373
    // emit a warning when non-8+3 filenames are spotted and find duplicates
374
    // emit a warning when non-8+3 filenames are spotted and find duplicates
374
    foreach ($path_array as $item) {
375
    foreach ($path_array as $item) {
375
      if (empty($item)) continue; // skip empty items at end of paths (eg. appinfo/)
376
      if (empty($item)) continue; // skip empty items at end of paths (eg. appinfo/)
376
      if (!preg_match("/[a-z0-9!#$%&'()@^_`{}~-]{1,8}(\.[a-z0-9!#$%&'()@^_`{}~-]{1,3}){0,1}/", $item)) {
377
      if (!preg_match("/[a-z0-9!#$%&'()@^_`{}~-]{1,8}(\.[a-z0-9!#$%&'()@^_`{}~-]{1,3}){0,1}/", $item)) {
377
        echo "WARNING: {$fname} contains a non-8+3 path (or weird char): {$item} (in $f)\n";
378
        echo "WARNING: {$fname} contains a non-8+3 path (or weird char): {$item} (in $f)\n";
378
      }
379
      }
379
    }
380
    }
380
    // look for dups
381
    // look for dups
381
    if (array_search($f, $duparr) !== false) {
382
    if (array_search($f, $duparr) !== false) {
382
      echo "WARNING: {$fname} contains a duplicated entry: '{$f}'\n";
383
      echo "WARNING: {$fname} contains a duplicated entry: '{$f}'\n";
383
    } else {
384
    } else {
384
      $duparr[] = $f;
385
      $duparr[] = $f;
385
    }
386
    }
386
    // LSM file is ok
387
    // LSM file is ok
387
    if ($f === "appinfo/{$pkgnam}.lsm") continue;
388
    if ($f === "appinfo/{$pkgnam}.lsm") continue;
388
    if ($f === "appinfo/") continue;
389
    if ($f === "appinfo/") continue;
389
    // CORE and MSDOS_COMPAT packages are premium citizens and can do a little more
390
    // CORE and MSDOS_COMPAT packages are premium citizens and can do a little more
390
    $core_or_msdoscompat = 0;
391
    $core_or_msdoscompat = 0;
391
    if (array_search($pkgnam, $core_packages_list) !== false) {
392
    if (array_search($pkgnam, $core_packages_list) !== false) {
392
      $catlist[] = 'core';
393
      $catlist[] = 'core';
393
      $core_or_msdoscompat = 1;
394
      $core_or_msdoscompat = 1;
394
    }
395
    }
395
    if (array_search($pkgnam, $msdos_compat_list) !== false) {
396
    if (array_search($pkgnam, $msdos_compat_list) !== false) {
396
      $catlist[] = 'msdos_compat';
397
      $catlist[] = 'msdos_compat';
397
      $core_or_msdoscompat = 1;
398
      $core_or_msdoscompat = 1;
398
    }
399
    }
399
    if ($core_or_msdoscompat == 1) {
400
    if ($core_or_msdoscompat == 1) {
400
      if (str_head_is($f, 'bin/')) continue;
401
      if (str_head_is($f, 'bin/')) continue;
401
      if (str_head_is($f, 'cpi/')) continue;
402
      if (str_head_is($f, 'cpi/')) continue;
402
      if (str_head_is($f, "doc/{$pkgdir}/")) continue;
403
      if (str_head_is($f, "doc/{$pkgdir}/")) continue;
403
      if ($f === 'doc/') continue;
404
      if ($f === 'doc/') continue;
404
      if (str_head_is($f, "nls/{$pkgdir}.")) continue;
405
      if (str_head_is($f, "nls/{$pkgdir}.")) continue;
405
      if ($f === 'nls/') continue;
406
      if ($f === 'nls/') continue;
406
    }
407
    }
407
    // SVARCOM is allowed to have a root-based COMMAND.COM file
408
    // SVARCOM is allowed to have a root-based COMMAND.COM file
408
    if ($pkgnam === 'svarcom') {
409
    if ($pkgnam === 'svarcom') {
409
      if ($f === 'command.com') continue;
410
      if ($f === 'command.com') continue;
410
    }
411
    }
411
    // the help package is allowed to put files in... help
412
    // the help package is allowed to put files in... help
412
    if (($pkgnam == 'help') && (str_head_is($f, 'help/'))) continue;
413
    if (($pkgnam == 'help') && (str_head_is($f, 'help/'))) continue;
413
    // must be category-prefixed file, add it to the list of categories for this package
414
    // must be category-prefixed file, add it to the list of categories for this package
414
    $catlist[] = explode('/', $f)[0];
415
    $catlist[] = explode('/', $f)[0];
415
    // well-known "category" dirs are okay
416
    // well-known "category" dirs are okay
416
    if (str_head_is($f, "progs/{$pkgdir}/")) continue;
417
    if (str_head_is($f, "progs/{$pkgdir}/")) continue;
417
    if ($f === 'progs/') continue;
418
    if ($f === 'progs/') continue;
418
    if (str_head_is($f, "devel/{$pkgdir}/")) continue;
419
    if (str_head_is($f, "devel/{$pkgdir}/")) continue;
419
    if ($f === 'devel/') continue;
420
    if ($f === 'devel/') continue;
420
    if (str_head_is($f, "games/{$pkgdir}/")) continue;
421
    if (str_head_is($f, "games/{$pkgdir}/")) continue;
421
    if ($f === 'games/') continue;
422
    if ($f === 'games/') continue;
422
    if (str_head_is($f, "drivers/{$pkgdir}/")) continue;
423
    if (str_head_is($f, "drivers/{$pkgdir}/")) continue;
423
    if ($f === 'drivers/') continue;
424
    if ($f === 'drivers/') continue;
424
    if (str_head_is($f, "kernels/{$pkgdir}/")) continue;
425
    if (str_head_is($f, "kernels/{$pkgdir}/")) continue;
425
    echo "WARNING: {$fname} contains a file in an illegal location: {$f}\n";
426
    echo "WARNING: {$fname} contains a file in an illegal location: {$f}\n";
426
  }
427
  }
427
 
428
 
428
  // do I understand the version string?
429
  // do I understand the version string?
429
  if (vertoarr($lsmarray['version']) === false) echo "WARNING: {$fname} parsing of version string failed ('{$lsmarray['version']}')\n";
430
  if (vertoarr($lsmarray['version']) === false) echo "WARNING: {$fname} parsing of version string failed ('{$lsmarray['version']}')\n";
430
 
431
 
431
  $meta = array();
432
  $meta = array();
432
  $meta['fname'] = $fname;
433
  $meta['fname'] = $fname;
433
  $meta['desc'] = $lsmarray['description'];
434
  $meta['desc'] = $lsmarray['description'];
434
  $meta['cats'] = array_unique($catlist);
435
  $meta['cats'] = array_unique($catlist);
-
 
436
  $meta['url'] = $lsmarray['url'];
435
 
437
 
436
  if (!empty($lsmarray['hwreq'])) {
438
  if (!empty($lsmarray['hwreq'])) {
437
    $meta['hwreq'] = explode(' ', strtolower($lsmarray['hwreq']));
439
    $meta['hwreq'] = explode(' ', strtolower($lsmarray['hwreq']));
438
    sort($meta['hwreq']);
440
    sort($meta['hwreq']);
439
 
441
 
440
    // validate list of valid hwreq tokens
442
    // validate list of valid hwreq tokens
441
    $validtokens = array('8086', '186', '286', '386', '486', '586', 'fpu', 'mda', 'cga', 'ega', 'vga', 'mcga', 'svga');
443
    $validtokens = array('8086', '186', '286', '386', '486', '586', 'fpu', 'mda', 'cga', 'ega', 'vga', 'mcga', 'svga');
442
    foreach (array_diff($meta['hwreq'], $validtokens) as $tok) echo "WARNING: {$fname} contains an LSM hwreq section with invalid token: {$tok}\n";
444
    foreach (array_diff($meta['hwreq'], $validtokens) as $tok) echo "WARNING: {$fname} contains an LSM hwreq section with invalid token: {$tok}\n";
443
  }
445
  }
444
 
446
 
445
  $pkgdb[$pkgnam][$lsmarray['version']] = $meta;
447
  $pkgdb[$pkgnam][$lsmarray['version']] = $meta;
446
}
448
}
447
 
449
 
448
 
450
 
449
$db = array();
451
$db = array();
450
$cats = array();
452
$cats = array();
451
 
453
 
452
// ******** compute the version-sorted list of packages with a single *********
454
// ******** compute the version-sorted list of packages with a single *********
453
// ******** description and category list for each package ********************
455
// ******** description and category list for each package ********************
454
 
456
 
455
// iterate over each svp package
457
// iterate over each svp package
456
foreach ($pkgdb as $pkg => $versions) {
458
foreach ($pkgdb as $pkg => $versions) {
457
 
459
 
458
  // sort filenames by version, highest first
460
  // sort filenames by version, highest first
459
  uksort($versions, "dos_version_compare");
461
  uksort($versions, "dos_version_compare");
460
  $versions = array_reverse($versions, true);
462
  $versions = array_reverse($versions, true);
461
 
463
 
462
  foreach ($versions as $ver => $meta) {
464
  foreach ($versions as $ver => $meta) {
463
    $fname = $meta['fname'];
465
    $fname = $meta['fname'];
464
    $desc = $meta['desc'];
466
    $desc = $meta['desc'];
465
 
467
 
466
    $bsum = file2bsum(realpath($repodir . '/' . $fname));
468
    $bsum = file2bsum(realpath($repodir . '/' . $fname));
467
 
469
 
468
    $meta2 = array();
470
    $meta2 = array();
469
    $meta2['ver'] = strval($ver);
471
    $meta2['ver'] = strval($ver);
470
    $meta2['bsum'] = $bsum;
472
    $meta2['bsum'] = $bsum;
471
    if (!empty($meta['hwreq'])) $meta2['hwreq'] = $meta['hwreq'];
473
    if (!empty($meta['hwreq'])) $meta2['hwreq'] = $meta['hwreq'];
472
 
474
 
473
    if (empty($db[$pkg]['desc'])) $db[$pkg]['desc'] = $desc;
475
    if (empty($db[$pkg]['desc'])) $db[$pkg]['desc'] = $desc;
474
    if (empty($db[$pkg]['cats'])) {
476
    if (empty($db[$pkg]['cats'])) {
475
      $db[$pkg]['cats'] = $meta['cats'];
477
      $db[$pkg]['cats'] = $meta['cats'];
476
      $cats = array_unique(array_merge($cats, $meta['cats']));
478
      $cats = array_unique(array_merge($cats, $meta['cats']));
477
    }
479
    }
478
    $db[$pkg]['versions'][$fname] = $meta2;
480
    $db[$pkg]['versions'][$fname] = $meta2;
479
  }
481
  }
480
 
482
 
481
  $pkgcount++;
483
  $pkgcount++;
482
 
484
 
483
}
485
}
484
 
486
 
485
if ($pkgcount < 100) echo "WARNING: an unexpectedly low number of packages has been found in the repo ({$pkgcount})\n";
487
if ($pkgcount < 100) echo "WARNING: an unexpectedly low number of packages has been found in the repo ({$pkgcount})\n";
486
 
488
 
487
$json_blob = json_encode($db);
489
$json_blob = json_encode($db);
488
if ($json_blob === false) {
490
if ($json_blob === false) {
489
  echo "ERROR: JSON convertion failed! -> ";
491
  echo "ERROR: JSON convertion failed! -> ";
490
  switch (json_last_error()) {
492
  switch (json_last_error()) {
491
    case JSON_ERROR_DEPTH:
493
    case JSON_ERROR_DEPTH:
492
      echo 'maximum stack depth exceeded';
494
      echo 'maximum stack depth exceeded';
493
      break;
495
      break;
494
    case JSON_ERROR_STATE_MISMATCH:
496
    case JSON_ERROR_STATE_MISMATCH:
495
      echo 'underflow of the modes mismatch';
497
      echo 'underflow of the modes mismatch';
496
      break;
498
      break;
497
    case JSON_ERROR_CTRL_CHAR:
499
    case JSON_ERROR_CTRL_CHAR:
498
      echo 'unexpected control character found';
500
      echo 'unexpected control character found';
499
      break;
501
      break;
500
    case JSON_ERROR_UTF8:
502
    case JSON_ERROR_UTF8:
501
      echo 'malformed utf-8 characters';
503
      echo 'malformed utf-8 characters';
502
      break;
504
      break;
503
    default:
505
    default:
504
      echo "unknown error";
506
      echo "unknown error";
505
      break;
507
      break;
506
  }
508
  }
507
  echo "\n";
509
  echo "\n";
508
}
510
}
509
 
511
 
510
file_put_contents($repodir . '/_index.json', $json_blob);
512
file_put_contents($repodir . '/_index.json', $json_blob);
511
 
513
 
512
$cats_json = json_encode($cats);
514
$cats_json = json_encode($cats);
513
file_put_contents($repodir . '/_cats.json', $cats_json);
515
file_put_contents($repodir . '/_cats.json', $cats_json);
514
 
516
 
515
 
517
 
516
// populate the 'latest' dir with symlinks to latest version of every svp
518
// populate the 'latest' dir with symlinks to latest version of every svp
517
mkdir($repodir . '/latest');
519
mkdir($repodir . '/latest');
518
foreach ($db as $pkg => $props) {
520
foreach ($db as $pkg => $props) {
519
  $fname = array_key_first($props['versions']);
521
  $fname = array_key_first($props['versions']);
520
  $cat = array_values($props['cats'])[0];
522
  $cat = array_values($props['cats'])[0];
521
  //echo "pkg = '{$pkg}' ; fname = '{$fname}' ; cat = '{$cat}'\n";
523
  //echo "pkg = '{$pkg}' ; fname = '{$fname}' ; cat = '{$cat}'\n";
522
  if (!file_exists($repodir . '/latest/' . $cat)) mkdir($repodir . '/latest/' . $cat);
524
  if (!file_exists($repodir . '/latest/' . $cat)) mkdir($repodir . '/latest/' . $cat);
523
  symlink('../../' . $fname, $repodir . '/latest/' . $cat . '/' . $pkg . '.svp');
525
  symlink('../../' . $fname, $repodir . '/latest/' . $cat . '/' . $pkg . '.svp');
524
}
526
}
525
 
527
 
526
 
528
 
527
exit(0);
529
exit(0);
528
 
530
 
529
?>
531
?>
530
 
532