Subversion Repositories SvarDOS

Rev

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

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