Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • uspace/srv/vfs/vfs_ops.c

    red903174 r6a4e972  
    3838#include "vfs.h"
    3939#include <ipc/ipc.h>
    40 #include <macros.h>
    41 #include <limits.h>
    4240#include <async.h>
    4341#include <errno.h>
     
    5553
    5654/* Forward declarations of static functions. */
    57 static int vfs_truncate_internal(fs_handle_t, dev_handle_t, fs_index_t, aoff64_t);
     55static int vfs_truncate_internal(fs_handle_t, dev_handle_t, fs_index_t, size_t);
    5856
    5957/**
     
    268266       
    269267        /* We want the client to send us the mount point. */
    270         char *mp;
    271         int rc = async_data_write_accept((void **) &mp, true, 0, MAX_PATH_LEN,
    272             0, NULL);
    273         if (rc != EOK) {
    274                 ipc_answer_0(rid, rc);
    275                 return;
    276         }
     268        ipc_callid_t callid;
     269        size_t size;
     270        if (!async_data_write_receive(&callid, &size)) {
     271                ipc_answer_0(callid, EINVAL);
     272                ipc_answer_0(rid, EINVAL);
     273                return;
     274        }
     275       
     276        /* Check whether size is reasonable wrt. the mount point. */
     277        if ((size < 1) || (size > MAX_PATH_LEN)) {
     278                ipc_answer_0(callid, EINVAL);
     279                ipc_answer_0(rid, EINVAL);
     280                return;
     281        }
     282       
     283        /* Allocate buffer for the mount point data being received. */
     284        char *mp = malloc(size + 1);
     285        if (!mp) {
     286                ipc_answer_0(callid, ENOMEM);
     287                ipc_answer_0(rid, ENOMEM);
     288                return;
     289        }
     290       
     291        /* Deliver the mount point. */
     292        ipcarg_t retval = async_data_write_finalize(callid, mp, size);
     293        if (retval != EOK) {
     294                ipc_answer_0(rid, retval);
     295                free(mp);
     296                return;
     297        }
     298        mp[size] = '\0';
    277299       
    278300        /* Now we expect to receive the mount options. */
    279         char *opts;
    280         rc = async_data_write_accept((void **) &opts, true, 0, MAX_MNTOPTS_LEN,
    281             0, NULL);
    282         if (rc != EOK) {
     301        if (!async_data_write_receive(&callid, &size)) {
     302                ipc_answer_0(callid, EINVAL);
     303                ipc_answer_0(rid, EINVAL);
    283304                free(mp);
    284                 ipc_answer_0(rid, rc);
    285                 return;
    286         }
     305                return;
     306        }
     307
     308        /* Check the offered options size. */
     309        if (size > MAX_MNTOPTS_LEN) {
     310                ipc_answer_0(callid, EINVAL);
     311                ipc_answer_0(rid, EINVAL);
     312                free(mp);
     313                return;
     314        }
     315
     316        /* Allocate buffer for the mount options. */
     317        char *opts = (char *) malloc(size + 1);
     318        if (!opts) {
     319                ipc_answer_0(callid, ENOMEM);
     320                ipc_answer_0(rid, ENOMEM);
     321                free(mp);
     322                return;
     323        }
     324
     325        /* Deliver the mount options. */
     326        retval = async_data_write_finalize(callid, opts, size);
     327        if (retval != EOK) {
     328                ipc_answer_0(rid, retval);
     329                free(mp);
     330                free(opts);
     331                return;
     332        }
     333        opts[size] = '\0';
    287334       
    288335        /*
     
    290337         * system.
    291338         */
    292         char *fs_name;
    293         rc = async_data_write_accept((void **) &fs_name, true, 0, FS_NAME_MAXLEN,
    294             0, NULL);
    295         if (rc != EOK) {
     339        if (!async_data_write_receive(&callid, &size)) {
     340                ipc_answer_0(callid, EINVAL);
     341                ipc_answer_0(rid, EINVAL);
    296342                free(mp);
    297343                free(opts);
    298                 ipc_answer_0(rid, rc);
    299                 return;
    300         }
    301        
     344                return;
     345        }
     346       
     347        /*
     348         * Don't receive more than is necessary for storing a full file system
     349         * name.
     350         */
     351        if ((size < 1) || (size > FS_NAME_MAXLEN)) {
     352                ipc_answer_0(callid, EINVAL);
     353                ipc_answer_0(rid, EINVAL);
     354                free(mp);
     355                free(opts);
     356                return;
     357        }
     358       
     359        /*
     360         * Allocate buffer for file system name.
     361         */
     362        char *fs_name = (char *) malloc(size + 1);
     363        if (fs_name == NULL) {
     364                ipc_answer_0(callid, ENOMEM);
     365                ipc_answer_0(rid, ENOMEM);
     366                free(mp);
     367                free(opts);
     368                return;
     369        }
     370       
     371        /* Deliver the file system name. */
     372        retval = async_data_write_finalize(callid, fs_name, size);
     373        if (retval != EOK) {
     374                ipc_answer_0(rid, retval);
     375                free(mp);
     376                free(opts);
     377                free(fs_name);
     378                return;
     379        }
     380        fs_name[size] = '\0';
     381
    302382        /*
    303383         * Wait for IPC_M_PING so that we can return an error if we don't know
     
    305385         */
    306386        ipc_call_t data;
    307         ipc_callid_t callid = async_get_call(&data);
     387        callid = async_get_call(&data);
    308388        if (IPC_GET_METHOD(data) != IPC_M_PING) {
    309389                ipc_answer_0(callid, ENOTSUP);
     
    355435        vfs_lookup_res_t mp_res;
    356436        vfs_lookup_res_t mr_res;
     437        vfs_node_t *mp_node;
    357438        vfs_node_t *mr_node;
    358439        int phone;
     
    361442         * Receive the mount point path.
    362443         */
    363         rc = async_data_write_accept((void **) &mp, true, 0, MAX_PATH_LEN,
    364             0, NULL);
     444        rc = async_data_string_receive(&mp, MAX_PATH_LEN);
    365445        if (rc != EOK)
    366446                ipc_answer_0(rid, rc);
     
    504584        int oflag = IPC_GET_ARG2(*request);
    505585        int mode = IPC_GET_ARG3(*request);
     586        size_t len;
    506587
    507588        /* Ignore mode for now. */
     
    525606                lflag |= L_EXCLUSIVE;
    526607       
    527         char *path;
    528         int rc = async_data_write_accept((void **) &path, true, 0, 0, 0, NULL);
    529         if (rc != EOK) {
    530                 ipc_answer_0(rid, rc);
    531                 return;
    532         }
     608        ipc_callid_t callid;
     609        if (!async_data_write_receive(&callid, &len)) {
     610                ipc_answer_0(callid, EINVAL);
     611                ipc_answer_0(rid, EINVAL);
     612                return;
     613        }
     614       
     615        char *path = malloc(len + 1);
     616        if (!path) {
     617                ipc_answer_0(callid, ENOMEM);
     618                ipc_answer_0(rid, ENOMEM);
     619                return;
     620        }
     621       
     622        int rc;
     623        if ((rc = async_data_write_finalize(callid, path, len))) {
     624                ipc_answer_0(rid, rc);
     625                free(path);
     626                return;
     627        }
     628        path[len] = '\0';
    533629       
    534630        /*
     
    721817}
    722818
    723 int vfs_close_internal(vfs_file_t *file)
     819static int vfs_close_internal(vfs_file_t *file)
    724820{
    725821        /*
     
    798894       
    799895        /*
     896         * Now we need to receive a call with client's
     897         * IPC_M_DATA_READ/IPC_M_DATA_WRITE request.
     898         */
     899        ipc_callid_t callid;
     900        int res;
     901        if (read)
     902                res = async_data_read_receive(&callid, NULL);
     903        else
     904                res = async_data_write_receive(&callid, NULL);
     905        if (!res) {
     906                ipc_answer_0(callid, EINVAL);
     907                ipc_answer_0(rid, EINVAL);
     908                return;
     909        }
     910       
     911        /*
    800912         * Lock the open file structure so that no other thread can manipulate
    801913         * the same open file at a time.
     
    821933        }
    822934       
    823         int fs_phone = vfs_grab_phone(file->node->fs_handle);
    824        
    825         /*
    826          * Make a VFS_READ/VFS_WRITE request at the destination FS server
    827          * and forward the IPC_M_DATA_READ/IPC_M_DATA_WRITE request to the
     935        int fs_phone = vfs_grab_phone(file->node->fs_handle);   
     936       
     937        /* Make a VFS_READ/VFS_WRITE request at the destination FS server. */
     938        aid_t msg;
     939        ipc_call_t answer;
     940        if (!read && file->append)
     941                file->pos = file->node->size;
     942        msg = async_send_3(fs_phone, read ? VFS_OUT_READ : VFS_OUT_WRITE,
     943            file->node->dev_handle, file->node->index, file->pos, &answer);
     944       
     945        /*
     946         * Forward the IPC_M_DATA_READ/IPC_M_DATA_WRITE request to the
    828947         * destination FS server. The call will be routed as if sent by
    829948         * ourselves. Note that call arguments are immutable in this case so we
    830949         * don't have to bother.
    831950         */
     951        ipc_forward_fast(callid, fs_phone, 0, 0, 0, IPC_FF_ROUTE_FROM_ME);
     952
     953        /* Wait for reply from the FS server. */
    832954        ipcarg_t rc;
    833         ipc_call_t answer;
    834         if (read) {
    835                 if (file->append)
    836                         file->pos = file->node->size;
    837                
    838                 rc = async_data_read_forward_3_1(fs_phone, VFS_OUT_READ,
    839                     file->node->dev_handle, file->node->index, file->pos,
    840                     &answer);
    841         } else {
    842                 rc = async_data_write_forward_3_1(fs_phone, VFS_OUT_WRITE,
    843                     file->node->dev_handle, file->node->index, file->pos,
    844                     &answer);
    845         }
     955        async_wait_for(msg, &rc);
    846956       
    847957        vfs_release_phone(fs_phone);
    848958       
    849959        size_t bytes = IPC_GET_ARG1(answer);
    850        
     960
    851961        if (file->node->type == VFS_NODE_DIRECTORY)
    852962                fibril_rwlock_read_unlock(&namespace_rwlock);
     
    887997{
    888998        int fd = (int) IPC_GET_ARG1(*request);
    889         off64_t off =
    890             (off64_t) MERGE_LOUP32(IPC_GET_ARG2(*request), IPC_GET_ARG3(*request));
    891         int whence = (int) IPC_GET_ARG4(*request);
    892        
     999        off_t off = (off_t) IPC_GET_ARG2(*request);
     1000        int whence = (int) IPC_GET_ARG3(*request);
     1001
     1002
    8931003        /* Lookup the file structure corresponding to the file descriptor. */
    8941004        vfs_file_t *file = vfs_file_get(fd);
     
    8971007                return;
    8981008        }
    899        
     1009
     1010        off_t newpos;
    9001011        fibril_mutex_lock(&file->lock);
    901        
    902         off64_t newoff;
    903         switch (whence) {
    904                 case SEEK_SET:
    905                         if (off >= 0) {
    906                                 file->pos = (aoff64_t) off;
    907                                 fibril_mutex_unlock(&file->lock);
    908                                 ipc_answer_1(rid, EOK, off);
    909                                 return;
    910                         }
    911                         break;
    912                 case SEEK_CUR:
    913                         if ((off >= 0) && (file->pos + off < file->pos)) {
    914                                 fibril_mutex_unlock(&file->lock);
    915                                 ipc_answer_0(rid, EOVERFLOW);
    916                                 return;
    917                         }
    918                        
    919                         if ((off < 0) && (file->pos < (aoff64_t) -off)) {
    920                                 fibril_mutex_unlock(&file->lock);
    921                                 ipc_answer_0(rid, EOVERFLOW);
    922                                 return;
    923                         }
    924                        
    925                         file->pos += off;
    926                         newoff = (file->pos > OFF64_MAX) ? OFF64_MAX : file->pos;
    927                        
     1012        if (whence == SEEK_SET) {
     1013                file->pos = off;
     1014                fibril_mutex_unlock(&file->lock);
     1015                ipc_answer_1(rid, EOK, off);
     1016                return;
     1017        }
     1018        if (whence == SEEK_CUR) {
     1019                if (file->pos + off < file->pos) {
    9281020                        fibril_mutex_unlock(&file->lock);
    929                         ipc_answer_2(rid, EOK, LOWER32(newoff), UPPER32(newoff));
     1021                        ipc_answer_0(rid, EOVERFLOW);
    9301022                        return;
    931                 case SEEK_END:
    932                         fibril_rwlock_read_lock(&file->node->contents_rwlock);
    933                         aoff64_t size = file->node->size;
    934                        
    935                         if ((off >= 0) && (size + off < size)) {
    936                                 fibril_rwlock_read_unlock(&file->node->contents_rwlock);
    937                                 fibril_mutex_unlock(&file->lock);
    938                                 ipc_answer_0(rid, EOVERFLOW);
    939                                 return;
    940                         }
    941                        
    942                         if ((off < 0) && (size < (aoff64_t) -off)) {
    943                                 fibril_rwlock_read_unlock(&file->node->contents_rwlock);
    944                                 fibril_mutex_unlock(&file->lock);
    945                                 ipc_answer_0(rid, EOVERFLOW);
    946                                 return;
    947                         }
    948                        
    949                         file->pos = size + off;
    950                         newoff = (file->pos > OFF64_MAX) ? OFF64_MAX : file->pos;
    951                        
    952                         fibril_rwlock_read_unlock(&file->node->contents_rwlock);
     1023                }
     1024                file->pos += off;
     1025                newpos = file->pos;
     1026                fibril_mutex_unlock(&file->lock);
     1027                ipc_answer_1(rid, EOK, newpos);
     1028                return;
     1029        }
     1030        if (whence == SEEK_END) {
     1031                fibril_rwlock_read_lock(&file->node->contents_rwlock);
     1032                size_t size = file->node->size;
     1033                fibril_rwlock_read_unlock(&file->node->contents_rwlock);
     1034                if (size + off < size) {
    9531035                        fibril_mutex_unlock(&file->lock);
    954                         ipc_answer_2(rid, EOK, LOWER32(newoff), UPPER32(newoff));
     1036                        ipc_answer_0(rid, EOVERFLOW);
    9551037                        return;
    956         }
    957        
     1038                }
     1039                newpos = size + off;
     1040                file->pos = newpos;
     1041                fibril_mutex_unlock(&file->lock);
     1042                ipc_answer_1(rid, EOK, newpos);
     1043                return;
     1044        }
    9581045        fibril_mutex_unlock(&file->lock);
    9591046        ipc_answer_0(rid, EINVAL);
    9601047}
    9611048
    962 int vfs_truncate_internal(fs_handle_t fs_handle, dev_handle_t dev_handle,
    963     fs_index_t index, aoff64_t size)
     1049int
     1050vfs_truncate_internal(fs_handle_t fs_handle, dev_handle_t dev_handle,
     1051    fs_index_t index, size_t size)
    9641052{
    9651053        ipcarg_t rc;
     
    9671055       
    9681056        fs_phone = vfs_grab_phone(fs_handle);
    969         rc = async_req_4_0(fs_phone, VFS_OUT_TRUNCATE, (ipcarg_t) dev_handle,
    970             (ipcarg_t) index, LOWER32(size), UPPER32(size));
     1057        rc = async_req_3_0(fs_phone, VFS_OUT_TRUNCATE, (ipcarg_t)dev_handle,
     1058            (ipcarg_t)index, (ipcarg_t)size);
    9711059        vfs_release_phone(fs_phone);
    9721060        return (int)rc;
     
    9761064{
    9771065        int fd = IPC_GET_ARG1(*request);
    978         aoff64_t size =
    979             (aoff64_t) MERGE_LOUP32(IPC_GET_ARG2(*request), IPC_GET_ARG3(*request));
     1066        size_t size = IPC_GET_ARG2(*request);
    9801067        int rc;
    9811068
     
    10331120void vfs_stat(ipc_callid_t rid, ipc_call_t *request)
    10341121{
    1035         char *path;
    1036         int rc = async_data_write_accept((void **) &path, true, 0, 0, 0, NULL);
    1037         if (rc != EOK) {
    1038                 ipc_answer_0(rid, rc);
    1039                 return;
    1040         }
    1041        
     1122        size_t len;
    10421123        ipc_callid_t callid;
     1124
     1125        if (!async_data_write_receive(&callid, &len)) {
     1126                ipc_answer_0(callid, EINVAL);
     1127                ipc_answer_0(rid, EINVAL);
     1128                return;
     1129        }
     1130        char *path = malloc(len + 1);
     1131        if (!path) {
     1132                ipc_answer_0(callid, ENOMEM);
     1133                ipc_answer_0(rid, ENOMEM);
     1134                return;
     1135        }
     1136        int rc;
     1137        if ((rc = async_data_write_finalize(callid, path, len))) {
     1138                ipc_answer_0(rid, rc);
     1139                free(path);
     1140                return;
     1141        }
     1142        path[len] = '\0';
     1143
    10431144        if (!async_data_read_receive(&callid, NULL)) {
    10441145                free(path);
     
    10861187{
    10871188        int mode = IPC_GET_ARG1(*request);
    1088        
    1089         char *path;
    1090         int rc = async_data_write_accept((void **) &path, true, 0, 0, 0, NULL);
    1091         if (rc != EOK) {
    1092                 ipc_answer_0(rid, rc);
    1093                 return;
    1094         }
    1095        
     1189
     1190        size_t len;
     1191        ipc_callid_t callid;
     1192
     1193        if (!async_data_write_receive(&callid, &len)) {
     1194                ipc_answer_0(callid, EINVAL);
     1195                ipc_answer_0(rid, EINVAL);
     1196                return;
     1197        }
     1198        char *path = malloc(len + 1);
     1199        if (!path) {
     1200                ipc_answer_0(callid, ENOMEM);
     1201                ipc_answer_0(rid, ENOMEM);
     1202                return;
     1203        }
     1204        int rc;
     1205        if ((rc = async_data_write_finalize(callid, path, len))) {
     1206                ipc_answer_0(rid, rc);
     1207                free(path);
     1208                return;
     1209        }
     1210        path[len] = '\0';
     1211
    10961212        /* Ignore mode for now. */
    10971213        (void) mode;
     
    11081224{
    11091225        int lflag = IPC_GET_ARG1(*request);
    1110        
    1111         char *path;
    1112         int rc = async_data_write_accept((void **) &path, true, 0, 0, 0, NULL);
    1113         if (rc != EOK) {
    1114                 ipc_answer_0(rid, rc);
    1115                 return;
    1116         }
     1226
     1227        size_t len;
     1228        ipc_callid_t callid;
     1229
     1230        if (!async_data_write_receive(&callid, &len)) {
     1231                ipc_answer_0(callid, EINVAL);
     1232                ipc_answer_0(rid, EINVAL);
     1233                return;
     1234        }
     1235        char *path = malloc(len + 1);
     1236        if (!path) {
     1237                ipc_answer_0(callid, ENOMEM);
     1238                ipc_answer_0(rid, ENOMEM);
     1239                return;
     1240        }
     1241        int rc;
     1242        if ((rc = async_data_write_finalize(callid, path, len))) {
     1243                ipc_answer_0(rid, rc);
     1244                free(path);
     1245                return;
     1246        }
     1247        path[len] = '\0';
    11171248       
    11181249        fibril_rwlock_write_lock(&namespace_rwlock);
     
    11431274void vfs_rename(ipc_callid_t rid, ipc_call_t *request)
    11441275{
     1276        size_t olen, nlen;
     1277        ipc_callid_t callid;
     1278        int rc;
     1279
    11451280        /* Retrieve the old path. */
    1146         char *old;
    1147         int rc = async_data_write_accept((void **) &old, true, 0, 0, 0, NULL);
    1148         if (rc != EOK) {
    1149                 ipc_answer_0(rid, rc);
    1150                 return;
    1151         }
     1281        if (!async_data_write_receive(&callid, &olen)) {
     1282                ipc_answer_0(callid, EINVAL);
     1283                ipc_answer_0(rid, EINVAL);
     1284                return;
     1285        }
     1286        char *old = malloc(olen + 1);
     1287        if (!old) {
     1288                ipc_answer_0(callid, ENOMEM);
     1289                ipc_answer_0(rid, ENOMEM);
     1290                return;
     1291        }
     1292        if ((rc = async_data_write_finalize(callid, old, olen))) {
     1293                ipc_answer_0(rid, rc);
     1294                free(old);
     1295                return;
     1296        }
     1297        old[olen] = '\0';
    11521298       
    11531299        /* Retrieve the new path. */
    1154         char *new;
    1155         rc = async_data_write_accept((void **) &new, true, 0, 0, 0, NULL);
    1156         if (rc != EOK) {
     1300        if (!async_data_write_receive(&callid, &nlen)) {
     1301                ipc_answer_0(callid, EINVAL);
     1302                ipc_answer_0(rid, EINVAL);
    11571303                free(old);
    1158                 ipc_answer_0(rid, rc);
    1159                 return;
    1160         }
    1161        
    1162         size_t olen;
    1163         size_t nlen;
     1304                return;
     1305        }
     1306        char *new = malloc(nlen + 1);
     1307        if (!new) {
     1308                ipc_answer_0(callid, ENOMEM);
     1309                ipc_answer_0(rid, ENOMEM);
     1310                free(old);
     1311                return;
     1312        }
     1313        if ((rc = async_data_write_finalize(callid, new, nlen))) {
     1314                ipc_answer_0(rid, rc);
     1315                free(old);
     1316                free(new);
     1317                return;
     1318        }
     1319        new[nlen] = '\0';
     1320
    11641321        char *oldc = canonify(old, &olen);
    11651322        char *newc = canonify(new, &nlen);
    1166        
    1167         if ((!oldc) || (!newc)) {
     1323        if (!oldc || !newc) {
    11681324                ipc_answer_0(rid, EINVAL);
    11691325                free(old);
     
    11711327                return;
    11721328        }
    1173        
    11741329        oldc[olen] = '\0';
    11751330        newc[nlen] = '\0';
    1176        
    11771331        if ((!str_lcmp(newc, oldc, str_length(oldc))) &&
    11781332            ((newc[str_length(oldc)] == '/') ||
     
    11951349        vfs_lookup_res_t new_par_lr;
    11961350        fibril_rwlock_write_lock(&namespace_rwlock);
    1197        
    11981351        /* Lookup the node belonging to the old file name. */
    11991352        rc = vfs_lookup_internal(oldc, L_NONE, &old_lr, NULL);
     
    12051358                return;
    12061359        }
    1207        
    12081360        vfs_node_t *old_node = vfs_node_get(&old_lr);
    12091361        if (!old_node) {
     
    12141366                return;
    12151367        }
    1216        
    12171368        /* Determine the path to the parent of the node with the new name. */
    12181369        char *parentc = str_dup(newc);
     
    12241375                return;
    12251376        }
    1226        
    12271377        char *lastsl = str_rchr(parentc + 1, '/');
    12281378        if (lastsl)
     
    12301380        else
    12311381                parentc[1] = '\0';
    1232        
    12331382        /* Lookup parent of the new file name. */
    12341383        rc = vfs_lookup_internal(parentc, L_NONE, &new_par_lr, NULL);
     
    12411390                return;
    12421391        }
    1243        
    12441392        /* Check whether linking to the same file system instance. */
    12451393        if ((old_node->fs_handle != new_par_lr.triplet.fs_handle) ||
     
    12511399                return;
    12521400        }
    1253        
    12541401        /* Destroy the old link for the new name. */
    12551402        vfs_node_t *new_node = NULL;
    12561403        rc = vfs_lookup_internal(newc, L_UNLINK, &new_lr, NULL);
    1257        
    12581404        switch (rc) {
    12591405        case ENOENT:
     
    12801426                return;
    12811427        }
    1282        
    12831428        /* Create the new link for the new name. */
    12841429        rc = vfs_lookup_internal(newc, L_LINK, NULL, NULL, old_node->index);
     
    12921437                return;
    12931438        }
    1294        
    12951439        fibril_mutex_lock(&nodes_mutex);
    12961440        old_node->lnkcnt++;
    12971441        fibril_mutex_unlock(&nodes_mutex);
    1298        
    12991442        /* Destroy the link for the old name. */
    13001443        rc = vfs_lookup_internal(oldc, L_UNLINK, NULL, NULL);
     
    13091452                return;
    13101453        }
    1311        
    13121454        fibril_mutex_lock(&nodes_mutex);
    13131455        old_node->lnkcnt--;
     
    13151457        fibril_rwlock_write_unlock(&namespace_rwlock);
    13161458        vfs_node_put(old_node);
    1317        
    13181459        if (new_node)
    13191460                vfs_node_put(new_node);
    1320        
    13211461        free(old);
    13221462        free(new);
Note: See TracChangeset for help on using the changeset viewer.