Subversion Repositories SvarDOS

Rev

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

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