Subversion Repositories SvarDOS

Rev

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

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