Subversion Repositories SvarDOS

Rev

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

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