Subversion Repositories SvarDOS

Rev

Rev 1810 | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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