<?php
//
// This is a set of fixes that must be applied when the software is upgraded.
// If the structure of authorized_keys may change, we must add a fix to
// regenerate the keys.
//
include_once($INC . "/sql.inc.php");
include_once($INC . "/state.inc.php");
include_once($INC . "/util.inc.php");
include_once($INC . "/user.inc.php");
include_once($INC . "/repo.inc.php");
include_once($INC . "/keys.inc.php");
$rg_fixes = array();
$rg_fixes[1] = array(
'functions' => 'rg_fixes_user_index_by_id'
);
$rg_fixes[2] = array(
'functions' => 'rg_fixes_repo_index_by_id'
);
$rg_fixes[3] = array(
'functions' => 'rg_fixes_keys_regen'
);
$rg_fixes[4] = array(
'functions' => 'rg_fixes_repos_last_bug_id'
);
$rg_fixes[5] = array(
'functions' => 'rg_fixes_wh_ver2'
);
$rg_fixes[6] = array(
'functions' => 'rg_fixes_wh_ver3'
);
$rg_fixes[7] = array(
'functions' => 'rg_fixes_wh_ver4'
);
$rg_fixes[8] = array(
'functions' => 'rg_fixes_wh_ver5'
);
$rg_fixes[9] = array(
'functions' => 'rg_fixes_repos_last_mr_id'
);
$rg_fixes[10] = array(
'functions' => 'rg_fixes_drop_if_exists'
);
$rg_fixes[11] = array(
'functions' => 'rg_fixes_fingerprint_sha256'
);
// This must be the last line
$rg_fixes_ver = count($rg_fixes);
/*
* Get rid of bugs_max database
*/
function rg_fixes_repos_last_bug_id($db)
{
rg_log_enter("rg_fixes_repos_last_bug_id");
$ret = FALSE;
while (1) {
$sql = "SELECT * FROM bugs_max";
$res = rg_sql_query($db, $sql);
if (!$res)
break;
$error = FALSE;
while (($row = rg_sql_fetch_array($res))) {
$repo_id = $row['repo_id'];
$last = $row['last_bug_id'];
$params = array("repo_id" => $repo_id, "last" => $last);
$sql = "UPDATE repos SET last_bug_id = @@last@@"
. " WHERE repo_id = @@repo_id@@";
$res2 = rg_sql_query_params($db, $sql, $params);
if (!$res2) {
$error = TRUE;
break;
}
rg_sql_free_result($res2);
}
rg_sql_free_result($res);
if ($error)
break;
$sql = "DROP TABLE bugs_max";
$res = rg_sql_query($db, $sql);
if (!$res)
break;
$ret = TRUE;
break;
}
rg_log_exit();
return $ret;
}
/*
* Just regenerate the keys
*/
function rg_fixes_keys_regen($db)
{
rg_log_enter("fixes_keys_regen");
$ret = rg_keys_regen($db);
if ($ret === FALSE)
rg_log("Could not regenerate keys: " . rg_keys_error() . "!");
rg_log_exit();
return $ret;
}
/*
* Fix one repo (make the names as relative links to ids)
*/
function rg_fixes_repo_index_by_id_one($uid, $repo_id, $repo_name)
{
global $php_errormsg;
rg_log_enter("fixes_repo_index_by_id_one:"
. " uid=$uid repo_id=$repo_id repo_name=$repo_name");
$ret = FALSE;
while (1) {
// we expect to have a folder .../repos/by_id
$by_id = rg_repo_path_by_id($uid, $repo_id);
$p = dirname($by_id);
if (!is_dir($p) && (mkdir($p, 0700, TRUE) === FALSE)) {
rg_log("Cannot create [$p] folder!");
break;
}
// we expect to have a folder .../repos/by_name
$by_name = rg_repo_path_by_name($uid, $repo_name);
$p = dirname($by_name);
if (!is_dir($p) && (mkdir($p, 0700, TRUE) === FALSE)) {
rg_log("Cannot create [$p] folder!");
break;
}
// We already moved it?
$new_path = rg_repo_path_by_id($uid, $repo_id);
if (!is_dir($new_path)) {
$old_path = rg_user_path_by_id($uid) . "/repos/" . $repo_name . ".git";
$r = rename($old_path, $new_path);
if ($r !== TRUE) {
rg_log("Cannot rename $old_path -> $new_path!");
break;
}
}
// Now, make links from by_name
if (!is_link($by_name)) {
$by_id_rel = rg_repo_path_by_id_rel($uid, $repo_id);
$r = symlink($by_id_rel, $by_name);
if ($r !== TRUE) {
rg_log("Cannot symlink $by_id_rel <- $by_name ($php_errormsg)!");
break;
}
}
$ret = TRUE;
break;
}
rg_log_exit();
return $ret;
}
/*
* Reindex repos by id so we can rename repos easier.
* And is more natural to index them by the unique id.
*/
function rg_fixes_repo_index_by_id($db)
{
rg_prof_start("fixes_repo_index_by_id");
rg_log_enter("fixes_repo_index_by_id");
$ret = FALSE;
while (1) {
$sql = "SELECT uid, repo_id, name FROM repos"
. " WHERE git_dir_done > 0";
$res = rg_sql_query($db, $sql);
if ($res === FALSE)
break;
$all_repos_moved = TRUE;
while (($row = rg_sql_fetch_array($res))) {
$r = rg_fixes_repo_index_by_id_one($row['uid'],
$row['repo_id'], $row['name']);
if ($r !== TRUE) {
$all_repos_moved = FALSE;
break;
}
}
rg_sql_free_result($res);
if ($all_repos_moved !== TRUE)
break;
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end("fixes_repo_index_by_id");
return $ret;
}
/*
* Index user by id and make links for names
*/
function rg_fixes_user_index_by_id_one($uid, $username)
{
global $php_errormsg;
global $rg_repos;
rg_log_enter("fixes_user_index_by_id_one: uid=$uid username=$username");
$ret = FALSE;
while (1) {
$user_path_uid = rg_user_path_by_id($uid);
$user_path_name = rg_user_path_by_name($username);
rg_log("user_path=[$user_path_uid][$user_path_name]");
// parend dir exits? if not, create it
$p = dirname($user_path_uid);
if (!is_dir($p) && (mkdir($p, 0700, TRUE) === FALSE)) {
rg_log("cannot create parent dir [$p]");
break;
}
// parend dir exits? if not, create it
$p = dirname($user_path_name);
if (!is_dir($p) && (mkdir($p, 0700, TRUE) === FALSE)) {
rg_log("cannot create parent dir [$p]");
break;
}
// We already moved it?
if (!is_dir($user_path_uid)) {
$x = $username . "_";
$old_path = $rg_repos . "/users/" . $x[0] . "/" . $x[1]
. "/" . $username;
if (!is_dir($old_path)) {
$ret = TRUE;
break;
}
$r = rename($old_path, $user_path_uid);
if ($r !== TRUE) {
rg_log("Cannot rename $old_path -> $user_path_uid!");
break;
}
}
// Now, make links from name to id
if (!is_link($user_path_name)) {
$by_id_rel = rg_user_path_by_id_rel($uid);
$r = symlink($by_id_rel, $user_path_name);
if ($r !== TRUE) {
rg_log("Cannot symlink $by_id_rel <- $user_path_name ($php_errormsg)!");
break;
}
}
$ret = TRUE;
break;
}
rg_log_exit();
return $ret;
}
/*
* Reindex users by id so we can rename users easier.
* And is more natural to index them by the unique id.
*/
function rg_fixes_user_index_by_id($db)
{
global $rg_repos;
rg_prof_start("fixes_user_index_by_id");
rg_log_enter("fixes_user_index_by_id");
$ret = FALSE;
while (1) {
$sql = "SELECT uid, username FROM users";
$res = rg_sql_query($db, $sql);
if ($res === FALSE)
break;
$all_users_moved = TRUE;
while (($row = rg_sql_fetch_array($res))) {
$r = rg_fixes_user_index_by_id_one($row['uid'],
$row['username']);
if ($r !== TRUE) {
$all_users_moved = FALSE;
break;
}
}
rg_sql_free_result($res);
if ($all_users_moved !== TRUE)
break;
// Remove old structure dirs
$r = rg_rmdir($rg_repos . "/users");
if ($r === FALSE) {
rg_log("Cannot remove old structure dirs"
. " (" . rg_util_error() . ").");
break;
}
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end("fixes_user_index_by_id");
return $ret;
}
/*
* Move to the more generic webhooks structure
*/
function rg_fixes_wh_ver2($db)
{
global $rg_repos;
rg_prof_start("fixes_wh_ver2");
rg_log_enter("fixes_wh_ver2");
$ret = FALSE;
while (1) {
$sql = "SELECT * FROM webhooks";
$res = rg_sql_query($db, $sql);
if ($res === FALSE)
break;
$all_fixed = TRUE;
while (($row = rg_sql_fetch_array($res))) {
$idata = array();
$idata['client_cert'] = $row['client_cert'];
$idata['client_ca_cert'] = $row['client_ca_cert'];
$idata['flags'] = $row['flags'];
$idata['itype'] = $row['type'];
$params = array(
'id' => $row['id'],
'idata' => serialize($idata)
);
$sql = 'UPDATE webhooks SET idata = @@idata@@'
. ' WHERE id = @@id@@';
$res2 = rg_sql_query_params($db, $sql, $params);
rg_sql_free_result($res2);
if ($res2 === FALSE) {
$all_fixed = FALSE;
break;
}
}
rg_sql_free_result($res);
if ($all_fixed !== TRUE)
break;
// Remove old fileds
$all_good = TRUE;
$a = array('client_cert', 'client_ca_cert', 'flags', 'type');
foreach ($a as $t) {
$sql = 'ALTER TABLE webhooks DROP ' . $t;
$res = rg_sql_query($db, $sql);
if ($res === FALSE) {
rg_log('Could not drop ' . $t . '!');
$all_good = FALSE;
break;
}
rg_sql_free_result($res);
}
if (!$all_good)
break;
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end("fixes_wh_ver2");
return $ret;
}
/*
* Move to the more generic webhooks structure
*/
function rg_fixes_wh_ver3($db)
{
global $rg_repos;
rg_prof_start("fixes_wh_ver3");
rg_log_enter("fixes_wh_ver3");
$ret = FALSE;
while (1) {
$sql = "SELECT * FROM webhooks";
$res = rg_sql_query($db, $sql);
if ($res === FALSE)
break;
$all_fixed = TRUE;
while (($row = rg_sql_fetch_array($res))) {
if (empty($row['idata']))
$idata = array();
else
$idata = unserialize($row['idata']);
$idata['url'] = $row['url'];
$idata['key'] = $row['key'];
$idata['opaque'] = $row['opaque'];
$idata['events'] = $row['events'];
$params = array(
'id' => $row['id'],
'idata' => serialize($idata)
);
$sql = 'UPDATE webhooks SET idata = @@idata@@'
. ' WHERE id = @@id@@';
$res2 = rg_sql_query_params($db, $sql, $params);
rg_sql_free_result($res2);
if ($res2 === FALSE) {
$all_fixed = FALSE;
break;
}
}
rg_sql_free_result($res);
if ($all_fixed !== TRUE)
break;
// Remove old fileds
$all_good = TRUE;
$a = array('url', 'key', 'opaque', 'events');
foreach ($a as $t) {
$sql = 'ALTER TABLE webhooks DROP ' . $t;
$res = rg_sql_query($db, $sql);
if ($res === FALSE) {
rg_log('Could not drop ' . $t . '!');
$all_good = FALSE;
break;
}
rg_sql_free_result($res);
}
if (!$all_good)
break;
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end("fixes_wh_ver3");
return $ret;
}
/*
* Move to the more generic webhooks structure
*/
function rg_fixes_wh_ver4($db)
{
global $rg_repos;
rg_prof_start("fixes_wh_ver4");
rg_log_enter("fixes_wh_ver4");
$ret = FALSE;
while (1) {
$sql = "SELECT * FROM webhooks";
$res = rg_sql_query($db, $sql);
if ($res === FALSE)
break;
$all_fixed = TRUE;
while (($row = rg_sql_fetch_array($res))) {
if (empty($row['idata']))
$idata = array();
else
$idata = unserialize($row['idata']);
if (isset($idata['flags'])) {
$flags = $idata['flags'];
unset($idata['flags']);
} else {
$flags = '';
}
$params = array(
'id' => $row['id'],
'flags' => $flags,
'idata' => serialize($idata)
);
$sql = 'UPDATE webhooks SET flags = @@flags@@'
. ', idata = @@idata@@'
. ' WHERE id = @@id@@';
$res2 = rg_sql_query_params($db, $sql, $params);
rg_sql_free_result($res2);
if ($res2 === FALSE) {
$all_fixed = FALSE;
break;
}
}
rg_sql_free_result($res);
if ($all_fixed !== TRUE)
break;
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end("fixes_wh_ver4");
return $ret;
}
/*
* Move to the more generic webhooks structure
*/
function rg_fixes_wh_ver5($db)
{
global $rg_repos;
rg_prof_start("fixes_wh_ver5");
rg_log_enter("fixes_wh_ver5");
$ret = FALSE;
while (1) {
$f = rg_sql_fields($db, 'webhooks');
if ($f === FALSE) {
rg_log('Could not load filed names...');
break;
}
// Remove old fields, if present
$all_good = TRUE;
$a = array('url', 'key', 'opaque', 'events');
foreach ($a as $t) {
if (!isset($f[$t]))
continue;
$sql = 'ALTER TABLE webhooks DROP ' . $t;
$res = rg_sql_query($db, $sql);
if ($res === FALSE) {
rg_log('Could not drop ' . $t . '!');
$all_good = FALSE;
break;
}
rg_sql_free_result($res);
}
if (!$all_good)
break;
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end("fixes_wh_ver5");
return $ret;
}
/*
* Allow merge request to have incremental ids
*/
function rg_fixes_repos_last_mr_id($db)
{
global $php_errormsg;
rg_log_enter('rg_fixes_repos_last_mr_id');
$error = FALSE;
$ret = FALSE;
while (1) {
$sql = 'SELECT repo_id, namespace, refname'
. ' FROM merge_requests';
$res = rg_sql_query($db, $sql);
if (!$res)
break;
$error = FALSE;
while (($row = rg_sql_fetch_array($res))) {
$repo_id = $row['repo_id'];
$namespace = $row['namespace'];
rg_log('');
rg_log('repo_id=' . $repo_id);
rg_log('namespace=' . $namespace);
$ri = rg_repo_info($db, $repo_id, 0, '');
if ($ri['exists'] !== 1) {
rg_log('Repo ' . $repo_id . ' does not exists! Ignore it!');
continue;
}
$id = rg_repo_next_id($db, 'mr', $repo_id);
rg_log('id=' . $id);
if ($id === FALSE) {
rg_log('Cannot get next_id!');
$error = TRUE;
break;
}
$params = array(
'repo_id' => $repo_id,
'namespace' => $namespace,
'id' => $id);
$sql = 'UPDATE merge_requests SET id = @@id@@'
. ' WHERE repo_id = @@repo_id@@'
. ' AND namespace = @@namespace@@';
$res2 = rg_sql_query_params($db, $sql, $params);
if ($res2 === FALSE) {
$error = TRUE;
break;
}
rg_sql_free_result($res2);
// Rename mr files
$path = rg_repo_path_by_id($ri['uid'], $repo_id);
$ref = rg_repo_ref_nice($row['refname']);
if (strncmp($namespace, 'rg_', 3) == 0)
$namespace = substr($namespace, 3);
$from = $path . '/refs/mr/' . $ref . '_' . $namespace;
if (file_exists($from)) {
$to = $path . '/refs/mr/' . $id;
$r = @rename($from, $to);
if ($r === FALSE) {
rg_log('cannot rename [' . $from . '] -> [' . $to . ']: ' . $php_errormsg . '!');
$error = TRUE;
break;
}
} else {
rg_log('File [' . $from . '] did not exists. Ignore it.');
}
}
rg_sql_free_result($res);
if ($error)
break;
$ret = TRUE;
break;
}
rg_log_exit();
return $ret;
}
/*
* When started to support PostgreSQL 8, 'IF EXISTS' was not working!
*/
function rg_fixes_drop_if_exists($db)
{
global $php_errormsg;
rg_log_enter('rg_fixes_drop_if_exists');
$ret = TRUE;
$list = array(
'keys' => 'keys_pkey',
'keys' => 'keys_key_key',
'repos' => 'repos_pkey',
'repos' => 'repos_name_key');
foreach ($list as $t => $c) {
if (rg_sql_rel_exists($db, $c) != 1)
continue;
$sql = 'ALTER TABLE ' . $t . ' DROP CONSTRAINT ' . $c;
$res = rg_sql_query($db, $sql);
if ($res === FALSE) {
$ret = FALSE;
break;
}
rg_sql_free_result($res);
}
rg_log_exit();
return $ret;
}
/*
* We need to update the keys.fingerprint_sha256 field
*/
function rg_fixes_fingerprint_sha256($db)
{
global $php_errormsg;
rg_log_enter('rg_fixes_fingerprint_sha256');
$ret = TRUE;
while (1) {
// keys table
$sql = 'SELECT key_id, key FROM keys'
. ' WHERE fingerprint_sha256 = \'\'';
$res = rg_sql_query($db, $sql);
if ($res === FALSE) {
$ret = FALSE;
break;
}
while (($row = rg_sql_fetch_array($res))) {
$ki = rg_keys_info($row['key']);
if ($ki['ok'] !== 1) {
rg_log('Cannot get info about key'
. ' ' . $row['key_id'] . ': '
. rg_keys_error() . '; ignore it');
continue;
}
$params = array(
'key_id' => $row['key_id'],
'fingerprint_sha256' => $ki['fingerprint_sha256']
);
$sql = 'UPDATE keys'
. ' SET fingerprint_sha256 = @@fingerprint_sha256@@'
. ' WHERE key_id = @@key_id@@';
$res2 = rg_sql_query_params($db, $sql, $params);
if ($res2 === FALSE) {
rg_log('Cannot update fingerprint_sha256: '
. rg_sql_error());
break;
}
rg_sql_free_result($res2);
}
rg_sql_free_result($res);
// workers table
$sql = 'SELECT id, ssh_key FROM workers'
. ' WHERE fingerprint_sha256 = \'\'';
$res = rg_sql_query($db, $sql);
if ($res === FALSE) {
$ret = FALSE;
break;
}
while (($row = rg_sql_fetch_array($res))) {
$ki = rg_keys_info($row['ssh_key']);
if ($ki['ok'] !== 1) {
rg_log('Cannot get info about key'
. ' ' . $row['id'] . ': '
. rg_keys_error() . '; ignore it');
continue;
}
$params = array(
'id' => $row['id'],
'fingerprint_sha256' => $ki['fingerprint_sha256']
);
$sql = 'UPDATE workers'
. ' SET fingerprint_sha256 = @@fingerprint_sha256@@'
. ' WHERE id = @@id@@';
$res2 = rg_sql_query_params($db, $sql, $params);
if ($res2 === FALSE) {
rg_log('Cannot update fingerprint_sha256: '
. rg_sql_error());
break;
}
rg_sql_free_result($res2);
}
rg_sql_free_result($res);
break;
}
rg_log_exit();
return $ret;
}
/*
* Apply fixes
*/
function rg_fixes_run($db, $old_ver)
{
global $rg_fixes;
global $rg_fixes_ver;
rg_log("rg_fixes_run: old_ver=$old_ver...");
$ret = TRUE;
for ($i = $old_ver + 1; $i <= $rg_fixes_ver; $i++) {
$res = rg_sql_begin($db);
if (!$res) {
$ret = FALSE;
break;
}
foreach ($rg_fixes[$i] as $function) {
rg_log("Calling function $function...");
$r = $function($db);
if ($r !== TRUE) {
rg_log("Function $function returned error!");
return FALSE;
}
}
$r = rg_state_set($db, "fixes_version", $i);
if ($r !== TRUE) {
rg_log("Cannot set state ver (" . rg_state_error() . ")");
$ret = FALSE;
break;
}
$res = rg_sql_commit($db);
if (!$res) {
$ret = FALSE;
break;
}
}
return $ret;
}
/*
* Tests if fixes are needed.
* Returns FALSE on error, 0 if not needed, 1 if needed.
*/
function rg_fixes_needed($db)
{
global $rg_fixes_ver;
rg_log_enter("fixes_needed");
$ret = FALSE;
while (1) {
$old = rg_state_get($db, "fixes_version");
if ($old === FALSE)
break;
if ($old === "")
$old = 0;
//rg_log("DEBUG: old=$old new=$rg_fixes_ver");
if ($old == $rg_fixes_ver) {
// fixes are up to date
$ret = 0;
break;
}
$ret = 1;
break;
}
rg_log_exit();
return $ret;
}
/*
* Apply fixes if needed
* Returns FALSE in case of error
*/
function rg_fixes_update($db)
{
global $rg_fixes_ver;
rg_log_enter("fixes_update");
$ret = FALSE;
while (1) {
$old = rg_state_get($db, "fixes_version");
if ($old === FALSE)
break;
if ($old === "")
$old = 0;
if ($old == $rg_fixes_ver) {
$ret = TRUE;
break;
}
// If we cannot lock, return error
if (rg_lock("fixes.lock") === FALSE)
break;
$r = rg_fixes_run($db, $old);
if ($r !== TRUE) {
rg_internal_error("Cannot apply fixes.");
break;
}
$ret = TRUE;
break;
}
rg_unlock("fixes.lock");
rg_log_exit();
return $ret;
}
?>